filesystem-routing 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -22
- package/dist/api.d.ts +90 -0
- package/dist/api.js +120 -0
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +17 -10
- package/dist/vite/tree-shake.d.ts +27 -0
- package/dist/vite/tree-shake.js +51 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -13,8 +13,8 @@ a delivery adapter that materializes the manifest into code.
|
|
|
13
13
|
| Scanning, filename convention, neutral route manifest | `filesystem-routing` |
|
|
14
14
|
| Nesting + `(group)` stripping | `filesystem-routing/tree` |
|
|
15
15
|
| Vite delivery (virtual module, HMR, code splitting, build inputs) | `filesystem-routing/vite` |
|
|
16
|
-
| `
|
|
17
|
-
|
|
|
16
|
+
| Request dispatch for `GET`/`POST` routes, as fetch middleware | `filesystem-routing/api` |
|
|
17
|
+
| `RouteDefinition` emission for Solid Router (code splitting, typed paths) | `@solidjs/router/fs` |
|
|
18
18
|
|
|
19
19
|
Nothing here is Solid-specific: the core is bundler-agnostic — it never imports
|
|
20
20
|
Vite — the Vite adapter is router-agnostic, and the conventions are the ones
|
|
@@ -47,9 +47,11 @@ const Router = createRouter({ routes: fileRoutes(pageRoutes) });
|
|
|
47
47
|
export const App = () => <Router>{props => <>{props.children}</>}</Router>;
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
The emission adapter
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
The emission adapter ships with the router — `@solidjs/router/fs` for Solid
|
|
51
|
+
Router; other routers mirror the same shape in their own packages. It never
|
|
52
|
+
imports a virtual module itself — the app does, so the adapter resolves
|
|
53
|
+
without the plugin, custom `moduleId`s work, and nothing needs excluding
|
|
54
|
+
from dependency prebundling.
|
|
53
55
|
|
|
54
56
|
Route modules live in `src/routes` (configurable via `fileRoutes({ dir })`).
|
|
55
57
|
A module is a page when it has a default export, and may export a `route`
|
|
@@ -161,25 +163,29 @@ pages get:
|
|
|
161
163
|
- The recognized set is `HEAD`/`GET`/`POST`/`PUT`/`DELETE`/`PATCH`/`OPTIONS`;
|
|
162
164
|
pass an array instead of `true` to change it.
|
|
163
165
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
Dispatch ships as fetch-style middleware — `filesystem-routing/api` matches
|
|
167
|
+
requests against the manifest's handler refs and composes into any
|
|
168
|
+
`(request, next)` chain, an SSR handler's middleware option included:
|
|
167
169
|
|
|
168
170
|
```ts
|
|
169
|
-
//
|
|
171
|
+
// src/middleware.ts
|
|
170
172
|
import routes from "virtual:file-routes";
|
|
173
|
+
import { createAPIHandler } from "filesystem-routing/api";
|
|
171
174
|
|
|
172
|
-
|
|
173
|
-
const ref = entry[`$${request.method}`];
|
|
174
|
-
if (!ref) return new Response(null, { status: 405 });
|
|
175
|
-
const handlers = await ref.import();
|
|
176
|
-
// a HEAD alias imports the GET handler
|
|
177
|
-
return (handlers[request.method] ?? handlers.GET)({ request, params });
|
|
178
|
-
}
|
|
175
|
+
export default [createAPIHandler(routes)];
|
|
179
176
|
```
|
|
180
177
|
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
Handlers receive the request event of the surrounding scope (the storage
|
|
179
|
+
`provideRequestEvent` establishes — pass `getEvent` to dispatch outside
|
|
180
|
+
one), with the matched `params` written onto it. Unmatched requests (no
|
|
181
|
+
route, or no handler for the method) advance the chain; `HEAD` falls back
|
|
182
|
+
to the `GET` handler; returned strings and JSON values are coerced into
|
|
183
|
+
responses. A `GET` handler may decline by returning `undefined`: a module
|
|
184
|
+
that is also a page falls through to the chain (its component renders
|
|
185
|
+
instead), a handler-only module answers 404. `createAPIMatcher` exposes the
|
|
186
|
+
matching alone for servers with their own dispatch shape.
|
|
187
|
+
|
|
188
|
+
Frameworks typically enable `httpMethods` on the
|
|
183
189
|
server environment's router only (SolidStart pairs it with `components:
|
|
184
190
|
false` in SPA mode, so the server manifest routes requests without shipping
|
|
185
191
|
page modules) — see the per-environment `routers` option below.
|
|
@@ -247,10 +253,10 @@ with `(group)` segments stripped, so emission adapters don't each reimplement
|
|
|
247
253
|
the tree (`buildRouteTree` from `filesystem-routing/tree` is the same
|
|
248
254
|
function, for consumers holding only a flat manifest). An emission adapter is
|
|
249
255
|
a function taking manifest entries and returning the router's shape — see
|
|
250
|
-
`fileRoutes` in `@solidjs/router/fs` for Solid Router's,
|
|
251
|
-
other routers can mirror. Adapters take the manifest as an argument
|
|
252
|
-
than importing the virtual module, so they work standalone and with
|
|
253
|
-
module ids.
|
|
256
|
+
`fileRoutes` in `@solidjs/router/fs` for Solid Router's, an
|
|
257
|
+
adapter other routers can mirror. Adapters take the manifest as an argument
|
|
258
|
+
rather than importing the virtual module, so they work standalone and with
|
|
259
|
+
custom module ids.
|
|
254
260
|
|
|
255
261
|
Add `/// <reference types="filesystem-routing/types" />` to type the import.
|
|
256
262
|
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The API dispatch adapter: matches requests against the flat manifest's
|
|
3
|
+
* handler refs (`$GET`, `$POST`, …) and serves them as fetch-style
|
|
4
|
+
* middleware, so API routes compose into any `(request, next)` chain — an
|
|
5
|
+
* SSR handler's middleware option, a framework's request pipeline — with no
|
|
6
|
+
* bundler or server involvement.
|
|
7
|
+
*
|
|
8
|
+
* Ported from SolidStart's `server/routes.ts` (the radix3 matcher) and the
|
|
9
|
+
* method dispatch in its `server/handler.ts`.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The event a handler receives. Structurally the core request event —
|
|
13
|
+
* `getRequestEvent()` inside the handler sees the same object — with the
|
|
14
|
+
* matched route params written onto it before dispatch.
|
|
15
|
+
*/
|
|
16
|
+
export interface APIEvent {
|
|
17
|
+
request: Request;
|
|
18
|
+
params?: Record<string, string>;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* An API handler: an uppercase method export of a route module. Return a
|
|
23
|
+
* `Response` to answer the request; strings and JSON values are coerced
|
|
24
|
+
* (`new Response(text)` / `Response.json(value)`). A `GET` handler may
|
|
25
|
+
* return `undefined` to decline, falling through to the rest of the chain
|
|
26
|
+
* when the module is also a page.
|
|
27
|
+
*/
|
|
28
|
+
export type APIHandler = (event: APIEvent) => unknown;
|
|
29
|
+
/** A manifest entry as the delivery adapter serves it. */
|
|
30
|
+
export interface FileRouteHandlers {
|
|
31
|
+
path: string;
|
|
32
|
+
page?: boolean;
|
|
33
|
+
$component?: unknown;
|
|
34
|
+
$HEAD?: APIHandlerRef;
|
|
35
|
+
$GET?: APIHandlerRef;
|
|
36
|
+
$POST?: APIHandlerRef;
|
|
37
|
+
$PUT?: APIHandlerRef;
|
|
38
|
+
$PATCH?: APIHandlerRef;
|
|
39
|
+
$DELETE?: APIHandlerRef;
|
|
40
|
+
[key: string]: unknown;
|
|
41
|
+
}
|
|
42
|
+
export interface APIHandlerRef {
|
|
43
|
+
import(): Promise<Record<string, APIHandler>>;
|
|
44
|
+
}
|
|
45
|
+
export interface APIMatch {
|
|
46
|
+
handler: APIHandlerRef;
|
|
47
|
+
params?: Record<string, string>;
|
|
48
|
+
/** `true` when the module also renders a page at this path. */
|
|
49
|
+
isPage: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Builds a matcher over the flat manifest's handler-carrying entries. Route
|
|
53
|
+
* paths translate from the neutral pattern language to the radix tree —
|
|
54
|
+
* `(group)` segments stripped, `*rest` catch-alls, static segments
|
|
55
|
+
* URL-encoded. Optional parameters are rejected (a radix tree cannot
|
|
56
|
+
* represent them) and duplicate paths are a configuration error.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createAPIMatcher(routes: readonly FileRouteHandlers[]): (path: string, method: string) => APIMatch | undefined;
|
|
59
|
+
/** Strips a leading path base: `stripPathBase("/app/x", "/app")` → `/x`. */
|
|
60
|
+
export declare function stripPathBase(path: string, base: string): string;
|
|
61
|
+
export interface APIHandlerOptions {
|
|
62
|
+
/** A path base stripped from the URL before matching. Defaults to `/`. */
|
|
63
|
+
base?: string;
|
|
64
|
+
/**
|
|
65
|
+
* How the adapter reaches the request event it hands to handlers. Defaults
|
|
66
|
+
* to the core request-event scope (the storage `provideRequestEvent`
|
|
67
|
+
* establishes), which is where a middleware chain runs — override it when
|
|
68
|
+
* dispatching outside one, or to hand handlers a richer event.
|
|
69
|
+
*/
|
|
70
|
+
getEvent?: () => APIEvent;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Fetch-style middleware serving the manifest's API routes:
|
|
74
|
+
*
|
|
75
|
+
* ```ts
|
|
76
|
+
* import routes from "virtual:file-routes";
|
|
77
|
+
* import { createAPIHandler } from "filesystem-routing/api";
|
|
78
|
+
*
|
|
79
|
+
* export default [createAPIHandler(routes)];
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* Dispatch preserves SolidStart's semantics: unmatched requests (no route,
|
|
83
|
+
* or no handler for the method) advance the chain; `HEAD` falls back to the
|
|
84
|
+
* `GET` handler; matched params are written to `event.params` before the
|
|
85
|
+
* handler runs. A handler returning `undefined` declines the request —
|
|
86
|
+
* allowed for `GET` only (anything else throws): a page module falls
|
|
87
|
+
* through to the chain (its component renders instead), a handler-only
|
|
88
|
+
* module answers 404.
|
|
89
|
+
*/
|
|
90
|
+
export declare function createAPIHandler(routes: readonly FileRouteHandlers[], options?: APIHandlerOptions): (request: Request, next: (request?: Request) => Response | Promise<Response>) => Promise<Response>;
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createRouter } from "radix3";
|
|
2
|
+
function containsHTTP(route) {
|
|
3
|
+
return route.$HEAD || route.$GET || route.$POST || route.$PUT || route.$PATCH || route.$DELETE;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Builds a matcher over the flat manifest's handler-carrying entries. Route
|
|
7
|
+
* paths translate from the neutral pattern language to the radix tree —
|
|
8
|
+
* `(group)` segments stripped, `*rest` catch-alls, static segments
|
|
9
|
+
* URL-encoded. Optional parameters are rejected (a radix tree cannot
|
|
10
|
+
* represent them) and duplicate paths are a configuration error.
|
|
11
|
+
*/
|
|
12
|
+
export function createAPIMatcher(routes) {
|
|
13
|
+
const router = createRouter({
|
|
14
|
+
routes: routes.reduce((memo, route) => {
|
|
15
|
+
if (!containsHTTP(route))
|
|
16
|
+
return memo;
|
|
17
|
+
const path = route.path
|
|
18
|
+
.replace(/\([^)/]+\)/g, "")
|
|
19
|
+
.replace(/\/+/g, "/")
|
|
20
|
+
.replace(/\*([^/]*)/g, (_, m) => `**:${m}`)
|
|
21
|
+
.split("/")
|
|
22
|
+
.map(s => (s.startsWith(":") || s.startsWith("*") ? s : encodeURIComponent(s)))
|
|
23
|
+
.join("/");
|
|
24
|
+
if (/:[^/]*\?/g.test(path)) {
|
|
25
|
+
throw new Error(`Optional parameters are not supported in API routes: ${path}`);
|
|
26
|
+
}
|
|
27
|
+
if (memo[path]) {
|
|
28
|
+
throw new Error(`Duplicate API routes for "${path}" found at "${memo[path].route.path}" and "${route.path}"`);
|
|
29
|
+
}
|
|
30
|
+
memo[path] = { route };
|
|
31
|
+
return memo;
|
|
32
|
+
}, {})
|
|
33
|
+
});
|
|
34
|
+
return (path, method) => {
|
|
35
|
+
const match = router.lookup(path);
|
|
36
|
+
if (!match || !match.route)
|
|
37
|
+
return;
|
|
38
|
+
const route = match.route;
|
|
39
|
+
// A `GET` handler answers `HEAD` unless the module handles it itself.
|
|
40
|
+
const handler = method === "HEAD" ? route.$HEAD || route.$GET : route[`$${method}`];
|
|
41
|
+
if (handler === undefined)
|
|
42
|
+
return;
|
|
43
|
+
return {
|
|
44
|
+
handler: handler,
|
|
45
|
+
params: match.params,
|
|
46
|
+
isPage: route.page === true && route.$component !== undefined
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Strips a leading path base: `stripPathBase("/app/x", "/app")` → `/x`. */
|
|
51
|
+
export function stripPathBase(path, base) {
|
|
52
|
+
if (!base || base === "/")
|
|
53
|
+
return path;
|
|
54
|
+
const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
55
|
+
if (path === normalizedBase)
|
|
56
|
+
return "/";
|
|
57
|
+
if (path.startsWith(`${normalizedBase}/`))
|
|
58
|
+
return path.slice(normalizedBase.length);
|
|
59
|
+
return path;
|
|
60
|
+
}
|
|
61
|
+
const RequestContext = Symbol.for("solid.RequestContext");
|
|
62
|
+
function getScopedEvent() {
|
|
63
|
+
const event = globalThis[RequestContext]?.getStore();
|
|
64
|
+
if (!event) {
|
|
65
|
+
throw new Error("API route dispatched outside a request-event scope. Compose the handler " +
|
|
66
|
+
"inside `provideRequestEvent` (any `ssr.middleware`-style chain already is), " +
|
|
67
|
+
"or pass `getEvent`.");
|
|
68
|
+
}
|
|
69
|
+
return event;
|
|
70
|
+
}
|
|
71
|
+
function toResponse(result) {
|
|
72
|
+
if (result instanceof Response)
|
|
73
|
+
return result;
|
|
74
|
+
if (typeof result === "string")
|
|
75
|
+
return new Response(result);
|
|
76
|
+
return Response.json(result);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Fetch-style middleware serving the manifest's API routes:
|
|
80
|
+
*
|
|
81
|
+
* ```ts
|
|
82
|
+
* import routes from "virtual:file-routes";
|
|
83
|
+
* import { createAPIHandler } from "filesystem-routing/api";
|
|
84
|
+
*
|
|
85
|
+
* export default [createAPIHandler(routes)];
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* Dispatch preserves SolidStart's semantics: unmatched requests (no route,
|
|
89
|
+
* or no handler for the method) advance the chain; `HEAD` falls back to the
|
|
90
|
+
* `GET` handler; matched params are written to `event.params` before the
|
|
91
|
+
* handler runs. A handler returning `undefined` declines the request —
|
|
92
|
+
* allowed for `GET` only (anything else throws): a page module falls
|
|
93
|
+
* through to the chain (its component renders instead), a handler-only
|
|
94
|
+
* module answers 404.
|
|
95
|
+
*/
|
|
96
|
+
export function createAPIHandler(routes, options = {}) {
|
|
97
|
+
const match = createAPIMatcher(routes);
|
|
98
|
+
const getEvent = options.getEvent ?? getScopedEvent;
|
|
99
|
+
return async (request, next) => {
|
|
100
|
+
const url = new URL(request.url);
|
|
101
|
+
const matched = match(stripPathBase(url.pathname, options.base ?? "/"), request.method);
|
|
102
|
+
if (!matched)
|
|
103
|
+
return next();
|
|
104
|
+
const mod = await matched.handler.import();
|
|
105
|
+
const fn = request.method === "HEAD" ? mod.HEAD || mod.GET : mod[request.method];
|
|
106
|
+
if (typeof fn !== "function")
|
|
107
|
+
return next();
|
|
108
|
+
const event = getEvent();
|
|
109
|
+
event.params = matched.params || {};
|
|
110
|
+
const result = await fn(event);
|
|
111
|
+
if (result !== undefined)
|
|
112
|
+
return toResponse(result);
|
|
113
|
+
if (request.method !== "GET") {
|
|
114
|
+
throw new Error(`API handler for ${request.method} "${request.url}" did not return a response.`);
|
|
115
|
+
}
|
|
116
|
+
if (!matched.isPage)
|
|
117
|
+
return new Response(null, { status: 404 });
|
|
118
|
+
return next();
|
|
119
|
+
};
|
|
120
|
+
}
|
package/dist/vite/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type PageFileSystemRouterConfig } from "../convention.ts";
|
|
|
3
3
|
import { BaseFileSystemRouter } from "../router.ts";
|
|
4
4
|
import { DEFAULT_EXTENSIONS, moduleId } from "./constants.ts";
|
|
5
5
|
export { DEFAULT_EXTENSIONS, moduleId };
|
|
6
|
-
export { treeShake } from "./tree-shake.ts";
|
|
6
|
+
export { sanitizeChunkFileName, toPickId, treeShake } from "./tree-shake.ts";
|
|
7
7
|
export { fileSystemWatcher } from "./fs-watcher.ts";
|
|
8
8
|
export interface FileRoutesOptions extends Pick<PageFileSystemRouterConfig, "components" | "httpMethods" | "toPath" | "toRoute"> {
|
|
9
9
|
/** Route directory, relative to the Vite root. Defaults to `src/routes`. */
|
package/dist/vite/index.js
CHANGED
|
@@ -5,10 +5,10 @@ import { normalizePath } from "../router.js";
|
|
|
5
5
|
import { buildRouteTree } from "../tree.js";
|
|
6
6
|
import { DEFAULT_EXTENSIONS, moduleId } from "./constants.js";
|
|
7
7
|
import { fileSystemWatcher } from "./fs-watcher.js";
|
|
8
|
-
import { treeShake } from "./tree-shake.js";
|
|
8
|
+
import { sanitizeChunkFileName, toPickId, treeShake } from "./tree-shake.js";
|
|
9
9
|
import { serializeTypes } from "./types.js";
|
|
10
10
|
export { DEFAULT_EXTENSIONS, moduleId };
|
|
11
|
-
export { treeShake } from "./tree-shake.js";
|
|
11
|
+
export { sanitizeChunkFileName, toPickId, treeShake } from "./tree-shake.js";
|
|
12
12
|
export { fileSystemWatcher } from "./fs-watcher.js";
|
|
13
13
|
/**
|
|
14
14
|
* The Vite delivery adapter for `filesystem-routing`.
|
|
@@ -61,20 +61,27 @@ export function fileRoutes(options = {}) {
|
|
|
61
61
|
writeFileSync(file, contents);
|
|
62
62
|
}
|
|
63
63
|
/** The id a route module ref is loaded from: its source plus its picks. */
|
|
64
|
-
const toModuleId = (ref) =>
|
|
64
|
+
const toModuleId = (ref) => toPickId(ref.src, ref.pick);
|
|
65
65
|
return [
|
|
66
66
|
{
|
|
67
67
|
name: "filesystem-routing",
|
|
68
68
|
enforce: "pre",
|
|
69
69
|
config() {
|
|
70
|
-
// Packages importing the virtual module (which only this plugin can
|
|
71
|
-
// resolve) must stay out of esbuild prebundling.
|
|
72
|
-
if (!options.optimizeDepsExclude?.length)
|
|
73
|
-
return;
|
|
74
70
|
return {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
build: {
|
|
72
|
+
rollupOptions: {
|
|
73
|
+
output: {
|
|
74
|
+
// Keeps route chunks named after their file rather than after
|
|
75
|
+
// the `?pick=...` id that addresses them. See toPickId.
|
|
76
|
+
sanitizeFileName: sanitizeChunkFileName
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
// Packages importing the virtual module (which only this plugin can
|
|
81
|
+
// resolve) must stay out of esbuild prebundling.
|
|
82
|
+
...(options.optimizeDepsExclude?.length
|
|
83
|
+
? { optimizeDeps: { exclude: options.optimizeDepsExclude } }
|
|
84
|
+
: {})
|
|
78
85
|
};
|
|
79
86
|
},
|
|
80
87
|
configResolved(config) {
|
|
@@ -1,2 +1,29 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
|
+
/**
|
|
3
|
+
* Builds the module id used to import a subset of a route file's exports.
|
|
4
|
+
*
|
|
5
|
+
* The `pick` list has to live in the query so that the same file can be
|
|
6
|
+
* instantiated once per export subset (the client picks `default`/`$css`,
|
|
7
|
+
* the server picks its HTTP handlers). That query would otherwise leave the
|
|
8
|
+
* id ending in `?pick=GET`, which silently excludes route files from any
|
|
9
|
+
* plugin whose filter is anchored on the file extension -- a very common
|
|
10
|
+
* default, e.g. unplugin-macros' `/\.[cm]?[jt]sx?$/`.
|
|
11
|
+
*
|
|
12
|
+
* The trailing `lang.<ext>` marker is the same convention Vue SFCs use
|
|
13
|
+
* (`?vue&type=script&lang.ts`) and puts a real extension back at the end of
|
|
14
|
+
* the id, so those filters match again.
|
|
15
|
+
*
|
|
16
|
+
* @see https://github.com/solidjs/solid-start/issues/1918
|
|
17
|
+
*/
|
|
18
|
+
export declare function toPickId(src: string, pick: string[]): string;
|
|
19
|
+
/**
|
|
20
|
+
* Rollup's default chunk name sanitizer, plus a pass that drops the
|
|
21
|
+
* `?pick=...&lang` tail so route chunks keep the short, stable filenames they
|
|
22
|
+
* had before the `lang` marker was introduced (`index-<hash>.js`, not
|
|
23
|
+
* `index.tsx_pick_default_pick__css_lang-<hash>.js`). Client chunk filenames
|
|
24
|
+
* are public URLs, so this is not purely cosmetic.
|
|
25
|
+
*
|
|
26
|
+
* @see https://github.com/rollup/rollup/blob/master/src/utils/sanitizeFileName.ts
|
|
27
|
+
*/
|
|
28
|
+
export declare function sanitizeChunkFileName(name: string): string;
|
|
2
29
|
export declare function treeShake(): Plugin;
|
package/dist/vite/tree-shake.js
CHANGED
|
@@ -1,7 +1,52 @@
|
|
|
1
1
|
// All credit for this work goes to the amazing Next.js team.
|
|
2
2
|
// https://github.com/vercel/next.js/blob/canary/packages/next/build/babel/plugins/next-ssg-transform.ts
|
|
3
3
|
// This is adapted to work with routeData functions. It can be run in two modes, one which preserves the routeData and the Component in the same file, and one which creates a
|
|
4
|
-
import { basename } from "node:path";
|
|
4
|
+
import { basename, extname } from "node:path";
|
|
5
|
+
const PICKABLE_EXTENSIONS = ["js", "jsx", "ts", "tsx"];
|
|
6
|
+
/**
|
|
7
|
+
* Builds the module id used to import a subset of a route file's exports.
|
|
8
|
+
*
|
|
9
|
+
* The `pick` list has to live in the query so that the same file can be
|
|
10
|
+
* instantiated once per export subset (the client picks `default`/`$css`,
|
|
11
|
+
* the server picks its HTTP handlers). That query would otherwise leave the
|
|
12
|
+
* id ending in `?pick=GET`, which silently excludes route files from any
|
|
13
|
+
* plugin whose filter is anchored on the file extension -- a very common
|
|
14
|
+
* default, e.g. unplugin-macros' `/\.[cm]?[jt]sx?$/`.
|
|
15
|
+
*
|
|
16
|
+
* The trailing `lang.<ext>` marker is the same convention Vue SFCs use
|
|
17
|
+
* (`?vue&type=script&lang.ts`) and puts a real extension back at the end of
|
|
18
|
+
* the id, so those filters match again.
|
|
19
|
+
*
|
|
20
|
+
* @see https://github.com/solidjs/solid-start/issues/1918
|
|
21
|
+
*/
|
|
22
|
+
export function toPickId(src, pick) {
|
|
23
|
+
const query = pick.map(p => `pick=${p}`).join("&");
|
|
24
|
+
const ext = extname(src).slice(1);
|
|
25
|
+
return PICKABLE_EXTENSIONS.includes(ext) ? `${src}?${query}&lang.${ext}` : `${src}?${query}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Matches the tail that {@link toPickId} appends, as it appears in a chunk
|
|
29
|
+
* name. Bundlers derive a chunk name from the module id by taking the
|
|
30
|
+
* basename minus its extension, so `index.tsx?pick=default&pick=$css&lang.tsx`
|
|
31
|
+
* arrives here as `index.tsx?pick=default&pick=$css&lang`.
|
|
32
|
+
*/
|
|
33
|
+
const PICK_CHUNK_NAME_RE = /\.[cm]?[jt]sx?\?pick=[^?]*&lang$/;
|
|
34
|
+
const INVALID_CHAR_RE = /[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g;
|
|
35
|
+
const DRIVE_LETTER_RE = /^[a-z]:/i;
|
|
36
|
+
/**
|
|
37
|
+
* Rollup's default chunk name sanitizer, plus a pass that drops the
|
|
38
|
+
* `?pick=...&lang` tail so route chunks keep the short, stable filenames they
|
|
39
|
+
* had before the `lang` marker was introduced (`index-<hash>.js`, not
|
|
40
|
+
* `index.tsx_pick_default_pick__css_lang-<hash>.js`). Client chunk filenames
|
|
41
|
+
* are public URLs, so this is not purely cosmetic.
|
|
42
|
+
*
|
|
43
|
+
* @see https://github.com/rollup/rollup/blob/master/src/utils/sanitizeFileName.ts
|
|
44
|
+
*/
|
|
45
|
+
export function sanitizeChunkFileName(name) {
|
|
46
|
+
const stripped = name.replace(PICK_CHUNK_NAME_RE, "");
|
|
47
|
+
const driveLetter = DRIVE_LETTER_RE.exec(stripped)?.[0] ?? "";
|
|
48
|
+
return driveLetter + stripped.slice(driveLetter.length).replace(INVALID_CHAR_RE, "_");
|
|
49
|
+
}
|
|
5
50
|
function treeShakeTransform({ types: t }) {
|
|
6
51
|
function getIdentifier(path) {
|
|
7
52
|
const parentPath = path.parentPath;
|
|
@@ -133,6 +178,11 @@ function treeShakeTransform({ types: t }) {
|
|
|
133
178
|
if (!state.opts.pick) {
|
|
134
179
|
return;
|
|
135
180
|
}
|
|
181
|
+
// Only module-level exports are route exports; `export` inside a
|
|
182
|
+
// TS namespace/module block is unrelated to the pick list.
|
|
183
|
+
if (!exportNamedPath.parentPath.isProgram()) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
136
186
|
const specifiers = exportNamedPath.get("specifiers");
|
|
137
187
|
if (specifiers.length) {
|
|
138
188
|
specifiers.forEach(s => {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "filesystem-routing",
|
|
3
3
|
"description": "Router-neutral file-system routing: scans a route directory into a neutral route manifest with pluggable conventions and delivery adapters",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.2.0",
|
|
6
6
|
"author": "Ryan Carniato",
|
|
7
7
|
"homepage": "https://github.com/solidjs/filesystem-routing#readme",
|
|
8
8
|
"repository": {
|
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
"types": "./dist/vite/index.d.ts",
|
|
41
41
|
"default": "./dist/vite/index.js"
|
|
42
42
|
},
|
|
43
|
+
"./api": {
|
|
44
|
+
"types": "./dist/api.d.ts",
|
|
45
|
+
"default": "./dist/api.js"
|
|
46
|
+
},
|
|
43
47
|
"./types": {
|
|
44
48
|
"types": "./types.d.ts"
|
|
45
49
|
}
|
|
@@ -55,7 +59,8 @@
|
|
|
55
59
|
"@types/micromatch": "^4.0.10",
|
|
56
60
|
"fast-glob": "^3.3.3",
|
|
57
61
|
"micromatch": "^4.0.8",
|
|
58
|
-
"oxc-parser": "^0.139.0"
|
|
62
|
+
"oxc-parser": "^0.139.0",
|
|
63
|
+
"radix3": "^1.1.2"
|
|
59
64
|
},
|
|
60
65
|
"peerDependencies": {
|
|
61
66
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|