Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions packages/docs/src/pages/api/FunstackStatic.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export default defineConfig({
fsRoutes: {
dir: "./src/pages",
root: "./src/root.tsx",
adapter: "@funstack/static/fs-routes/next-adapter",
},
}),
react(),
Expand Down Expand Up @@ -203,16 +204,16 @@ funstackStatic({
fsRoutes: {
dir: "./src/pages",
root: "./src/root.tsx",
adapter: "./src/my-adapter.ts", // optional
adapter: "@funstack/static/fs-routes/next-adapter",
},
});
```

`FsRoutesConfig` fields:
`FsRoutesConfig` fields (all required — you must be explicit about each):

- **`dir`** (optional, default `"./src/pages"`) — directory scanned for route files, relative to the Vite root.
- **`dir`** (required) — directory scanned for route files, relative to the Vite root. Commonly `"./src/pages"`.
- **`root`** (required) — path to the root (HTML shell) component module.
- **`adapter`** (optional) — path to a module that `export default`s an `FsRoutesAdapter`. Defaults to the built-in Next.js-like adapter (`nextRoutes()` from `@funstack/static/fs-routes`).
- **`adapter`** (required) — a module that `export default`s an `FsRoutesAdapter`, given as a bare module specifier or a path relative to the Vite root. Use the bundled `@funstack/static/fs-routes/next-adapter` for the built-in Next.js-like convention, or point it at your own module for a custom one.

Enable [`ssr`](#ssr-optional) alongside `fsRoutes` — it is required for the dev server to render pages and recommended in general.

Expand Down
9 changes: 6 additions & 3 deletions packages/docs/src/pages/learn/FileSystemRouting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,19 @@ export default defineConfig({
fsRoutes: {
dir: "./src/pages",
root: "./src/root.tsx",
adapter: "@funstack/static/fs-routes/next-adapter",
},
}),
react(),
],
});
```

- `dir` — the directory scanned for route files (default `./src/pages`).
All `fsRoutes` fields are required, so each choice is explicit in your config:

- `dir` — the directory scanned for route files (commonly `./src/pages`).
- `root` — the HTML shell component (`<html>…<body>{children}</body></html>`).
- `adapter` — optional path to a custom adapter module (defaults to the built-in Next.js-like adapter).
- `adapter` — a module that `export default`s the adapter defining the directory / file-name convention. The bundled `@funstack/static/fs-routes/next-adapter` provides the built-in Next.js-like convention with default options; see [Custom Conventions](#custom-conventions-adapters) to configure it or write your own.

`fsRoutes` is mutually exclusive with the `root` + `app` (single-entry) and `entries` (multiple entries) modes.

Expand Down Expand Up @@ -108,7 +111,7 @@ A dynamic route without `generateStaticParams` is **not** pre-rendered (a warnin

## Custom Conventions (Adapters)

The convention is defined by an **adapter** implementing `FsRoutesAdapter`. Point `adapter` at a module that `export default`s an adapter to use a different convention:
The convention is defined by an **adapter** implementing `FsRoutesAdapter`. The built-in `@funstack/static/fs-routes/next-adapter` is the Next.js-like adapter with default options; to use a different convention, point `adapter` at your own module that `export default`s an adapter:

```typescript
// vite.config.ts
Expand Down
1 change: 1 addition & 0 deletions packages/example-fs-routing/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default defineConfig({
fsRoutes: {
dir: "./src/pages",
root: "./src/root.tsx",
adapter: "@funstack/static/fs-routes/next-adapter",
},
}),
react(),
Expand Down
1 change: 1 addition & 0 deletions packages/static/e2e/fixture-fs-routing/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default defineConfig({
fsRoutes: {
dir: "./src/pages",
root: "./src/root.tsx",
adapter: "@funstack/static/fs-routes/next-adapter",
},
}),
react(),
Expand Down
4 changes: 4 additions & 0 deletions packages/static/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
"types": "./dist/fs-routes/index.d.mts",
"import": "./dist/fs-routes/index.mjs"
},
"./fs-routes/next-adapter": {
"types": "./dist/fs-routes/next-adapter.d.mts",
"import": "./dist/fs-routes/next-adapter.mjs"
},
"./entries/*": "./dist/entries/*.mjs"
},
"imports": {
Expand Down
31 changes: 31 additions & 0 deletions packages/static/src/fs-routes/next-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Convenience adapter module for the built-in Next.js-like file-system routing
* convention, configured with default options.
*
* Point the `fsRoutes.adapter` plugin option at this module to use it without
* writing your own adapter file:
*
* ```ts
* funstackStatic({
* fsRoutes: {
* dir: "./src/pages",
* root: "./src/root.tsx",
* adapter: "@funstack/static/fs-routes/next-adapter",
* },
* });
* ```
*
* To customize the convention, call `nextRoutes(options)` from
* `@funstack/static/fs-routes` in your own adapter module instead.
*
* @experimental File-system routing is experimental and not yet subject to
* semantic versioning.
*
* @packageDocumentation
*/
import { nextRoutes } from "./nextAdapter";
import type { FsRoutesAdapter } from "./types";

const adapter: FsRoutesAdapter = nextRoutes();

export default adapter;
52 changes: 32 additions & 20 deletions packages/static/src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,23 +96,26 @@ export interface FsRoutesConfig {
/**
* Directory containing route files, relative to the Vite root.
*
* @default "./src/pages"
* Commonly `"./src/pages"`.
*/
dir?: string;
dir: string;
/**
* Path to the root (HTML shell) component module.
* The file should `export default` a React component that renders the whole
* page (`<html>...</html>`) and places `children` inside `<body>`.
*/
root: string;
/**
* Path to a module that `export default`s an `FsRoutesAdapter`, which defines
* the directory / file-name convention.
* Module that `export default`s an `FsRoutesAdapter`, which defines the
* directory / file-name convention. Either a bare module specifier (a package
* import) or a path to a local module, relative to the Vite root.
*
* Defaults to the built-in Next.js-like adapter (`nextRoutes()` from
* `@funstack/static/fs-routes`).
* To use the built-in Next.js-like convention with default options, point
* this at the bundled module `@funstack/static/fs-routes/next-adapter`.
* For custom options, `export default nextRoutes(options)` (from
* `@funstack/static/fs-routes`) in your own module and point this at it.
*/
adapter?: string;
adapter: string;
}

interface FsRoutesOptions {
Expand All @@ -131,6 +134,15 @@ interface FsRoutesOptions {
export type FunstackStaticOptions = FunstackStaticBaseOptions &
(SingleEntryOptions | MultipleEntriesOptions | FsRoutesOptions);

/**
* Whether `id` is a bare module specifier (a package import) rather than a
* relative or absolute file path. Bare specifiers are passed through to the
* generated import as-is; paths are resolved against the Vite root.
*/
function isBareSpecifier(id: string): boolean {
return !id.startsWith(".") && !path.isAbsolute(id);
}

export default function funstackStatic(
options: FunstackStaticOptions,
): (Plugin | Plugin[])[] {
Expand Down Expand Up @@ -159,7 +171,7 @@ export default function funstackStatic(
let resolvedBuildEntry: string | undefined;
// Resolved configuration for file-system routing (fsRoutes mode).
let resolvedFsRoutes:
| { root: string; adapter: string | undefined; globBase: string }
| { root: string; adapter: string; globBase: string }
| undefined;

// Determine which entry mode the user selected.
Expand Down Expand Up @@ -198,20 +210,24 @@ export default function funstackStatic(
configResolved(config) {
if (isFsRoutes) {
const fsRoutes = options.fsRoutes;
const dir = fsRoutes.dir ?? "./src/pages";
const resolvedRoot = normalizePath(
path.resolve(config.root, fsRoutes.root),
);
const resolvedDir = normalizePath(path.resolve(config.root, dir));
const resolvedDir = normalizePath(
path.resolve(config.root, fsRoutes.dir),
);
// Glob patterns in generated virtual modules must be root-relative
// (a virtual module has no real path to resolve "./" against).
const relativeDir = normalizePath(
path.relative(config.root, resolvedDir),
);
const globBase = `/${relativeDir.replace(/^\.?\/?/, "").replace(/\/$/, "")}`;
const adapter = fsRoutes.adapter
? normalizePath(path.resolve(config.root, fsRoutes.adapter))
: undefined;
// The adapter may be a bare module specifier (e.g. the built-in
// `@funstack/static/fs-routes/next-adapter`) or a path to a local module.
// Resolve only the latter against the Vite root.
const adapter = isBareSpecifier(fsRoutes.adapter)
? fsRoutes.adapter
: normalizePath(path.resolve(config.root, fsRoutes.adapter));
resolvedFsRoutes = { root: resolvedRoot, adapter, globBase };
} else if (isMultiEntry) {
resolvedEntriesModule = normalizePath(
Expand Down Expand Up @@ -294,19 +310,15 @@ export default function funstackStatic(
const globPattern = `${globBase}/**/*.{tsx,jsx}`;
const lines = [
`import Root from "${root}";`,
`import adapter from "${adapter}";`,
`import { createFsRoutesEntries } from "@funstack/static/fs-routes";`,
];
if (adapter) {
lines.push(`import adapter from "${adapter}";`);
}
lines.push(
`const modules = import.meta.glob(${JSON.stringify(globPattern)}, { eager: true });`,
`export default createFsRoutesEntries({`,
` modules,`,
` root: Root,`,
...(adapter ? [` adapter,`] : []),
` adapter,`,
`});`,
);
];
return lines.join("\n");
}
if (isMultiEntry) {
Expand Down
1 change: 1 addition & 0 deletions packages/static/tsdown.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default defineConfig({
"src/entries/*.ts",
"src/bin/*.ts",
"src/fs-routes/index.ts",
"src/fs-routes/next-adapter.ts",
],
// Vite virtual modules & subpath imports
external: [/^virtual:/, /^#/],
Expand Down