@solidjs/start 2.0.0-alpha.3 → 2.0.0-beta.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/CHANGELOG.md +16 -0
- package/dist/config/constants.d.ts +2 -2
- package/dist/config/fs-routes/fs-watcher.js +31 -21
- package/dist/config/fs-routes/router.d.ts +8 -3
- package/dist/config/fs-routes/router.js +24 -11
- package/dist/config/fs-routes/tree-shake.js +0 -43
- package/dist/config/index.js +12 -10
- package/dist/config/lazy.js +2 -0
- package/dist/config/manifest.js +4 -3
- package/dist/directives/index.js +1 -0
- package/dist/directives/remove-unused-variables.js +17 -4
- package/dist/server/handler.d.ts +1 -1
- package/dist/server/handler.js +1 -1
- package/dist/utils/debounce.d.ts +4 -0
- package/dist/utils/debounce.js +10 -0
- package/package.json +2 -2
- package/src/config/constants.ts +2 -2
- package/src/config/fs-routes/fs-watcher.ts +36 -40
- package/src/config/fs-routes/router.ts +27 -11
- package/src/config/fs-routes/tree-shake.ts +1 -48
- package/src/config/index.ts +12 -10
- package/src/config/lazy.ts +1 -0
- package/src/config/manifest.ts +8 -3
- package/src/directives/index.ts +1 -0
- package/src/directives/remove-unused-variables.ts +18 -3
- package/src/server/handler.ts +1 -1
- package/src/utils/debounce.ts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @solidjs/start
|
|
2
2
|
|
|
3
|
+
## 2.0.0-beta.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0a9fdc3: fix cloudflare
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 0c95804: Fix URL to path conversion in manifest resolver
|
|
12
|
+
- 6011e5a: Fixed changes in route files resulting in a reload instead of hot module replace. Reloads now only are triggered when adding or removing routes.
|
|
13
|
+
- 8fb81e6: Fixed niche edge cases in the server functions dead code removal (DCE) logic:
|
|
14
|
+
- Server functions only referenced in event handlers (e.g. `onClick`) now aren't considered unused and work properly.
|
|
15
|
+
- Unused variables in server functions no longer lead to compilation errors.
|
|
16
|
+
- 8d12d6a: Fixed tailwind class changes not getting applied via HMR in filesystem routes.
|
|
17
|
+
- ec1b82b: Included Vite 8 in the peer dependency range.
|
|
18
|
+
|
|
3
19
|
## 2.0.0-alpha.3
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { debounce } from "../../utils/debounce.js";
|
|
1
2
|
import { VITE_ENVIRONMENTS } from "../constants.js";
|
|
2
3
|
import { moduleId } from "./index.js";
|
|
3
4
|
function setupWatcher(watcher, routes) {
|
|
@@ -6,37 +7,46 @@ function setupWatcher(watcher, routes) {
|
|
|
6
7
|
watcher.on("change", path => routes.updateRoute(path));
|
|
7
8
|
}
|
|
8
9
|
function createRoutesReloader(server, routes, environment) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
const devEnv = server.environments[environment];
|
|
11
|
+
if (!devEnv?.moduleGraph)
|
|
12
|
+
return;
|
|
13
|
+
/**
|
|
14
|
+
* Debounce catches multiple route changes in a row
|
|
15
|
+
* Short timeout for inexpensive invalidations
|
|
16
|
+
*/
|
|
17
|
+
const invalidateModule = debounce((mod) => {
|
|
18
|
+
devEnv.moduleGraph.invalidateModule(mod);
|
|
19
|
+
}, 0);
|
|
20
|
+
/**
|
|
21
|
+
* Long debounce timeout for expensive reloads
|
|
22
|
+
*/
|
|
23
|
+
const reloadModule = debounce((mod) => {
|
|
24
|
+
devEnv.reloadModule(mod);
|
|
25
|
+
}, 200);
|
|
26
|
+
return routes.on("reload", function handleRoutesReload(evt) {
|
|
16
27
|
const mod = devEnv.moduleGraph.getModuleById(moduleId);
|
|
17
|
-
if (mod) {
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
if (!mod) {
|
|
29
|
+
devEnv.hot.send({ type: "full-reload" });
|
|
30
|
+
return;
|
|
20
31
|
}
|
|
21
|
-
if (environment !== "
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
32
|
+
if (environment === VITE_ENVIRONMENTS.client && evt.detail.type !== "update") {
|
|
33
|
+
// Client has to be reloaded when routes are added or removed
|
|
34
|
+
reloadModule(mod);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
invalidateModule(mod);
|
|
28
38
|
}
|
|
29
|
-
}
|
|
39
|
+
});
|
|
30
40
|
}
|
|
31
41
|
export const fileSystemWatcher = (routers) => {
|
|
32
42
|
const plugin = {
|
|
33
43
|
name: "fs-watcher",
|
|
34
44
|
async configureServer(server) {
|
|
35
|
-
|
|
36
|
-
const router =
|
|
45
|
+
for (const environment of [VITE_ENVIRONMENTS.server, VITE_ENVIRONMENTS.client]) {
|
|
46
|
+
const router = routers[environment];
|
|
37
47
|
setupWatcher(server.watcher, router);
|
|
38
48
|
createRoutesReloader(server, router, environment);
|
|
39
|
-
}
|
|
49
|
+
}
|
|
40
50
|
},
|
|
41
51
|
};
|
|
42
52
|
return plugin;
|
|
@@ -10,6 +10,10 @@ type Route = {
|
|
|
10
10
|
} & Record<string, any>;
|
|
11
11
|
export declare function cleanPath(src: string, config: FileSystemRouterConfig): string;
|
|
12
12
|
export declare function analyzeModule(src: string): readonly [imports: readonly import("es-module-lexer").ImportSpecifier[], exports: readonly import("es-module-lexer").ExportSpecifier[], facade: boolean, hasModuleSyntax: boolean];
|
|
13
|
+
type RouterEvent = CustomEvent<{
|
|
14
|
+
route: string;
|
|
15
|
+
type: "update" | "remove" | "add";
|
|
16
|
+
}>;
|
|
13
17
|
export declare class BaseFileSystemRouter extends EventTarget {
|
|
14
18
|
routes: Route[];
|
|
15
19
|
config: FileSystemRouterConfig;
|
|
@@ -27,11 +31,12 @@ export declare class BaseFileSystemRouter extends EventTarget {
|
|
|
27
31
|
* To be attached by vite plugin to the vite dev server
|
|
28
32
|
*/
|
|
29
33
|
update: undefined;
|
|
30
|
-
_addRoute(route: Route):
|
|
34
|
+
_addRoute(route: Route): boolean;
|
|
31
35
|
addRoute(src: string): Promise<void>;
|
|
32
|
-
reload(route: string): void;
|
|
33
|
-
updateRoute(
|
|
36
|
+
reload(route: string, type: "update" | "remove" | "add"): void;
|
|
37
|
+
updateRoute(src_: string): Promise<void>;
|
|
34
38
|
removeRoute(src: string): void;
|
|
39
|
+
on(type: string, cb: (evt: RouterEvent) => void): () => void;
|
|
35
40
|
buildRoutesPromise?: Promise<any[]>;
|
|
36
41
|
getRoutes(): Promise<Route[]>;
|
|
37
42
|
}
|
|
@@ -71,8 +71,11 @@ export class BaseFileSystemRouter extends EventTarget {
|
|
|
71
71
|
*/
|
|
72
72
|
update = undefined;
|
|
73
73
|
_addRoute(route) {
|
|
74
|
-
|
|
74
|
+
const idx = this.routes.findIndex(r => r.path === route.path);
|
|
75
|
+
if (idx >= 0)
|
|
76
|
+
this.routes.splice(idx, 1);
|
|
75
77
|
this.routes.push(route);
|
|
78
|
+
return idx >= 0;
|
|
76
79
|
}
|
|
77
80
|
async addRoute(src) {
|
|
78
81
|
src = normalizePath(src);
|
|
@@ -81,7 +84,7 @@ export class BaseFileSystemRouter extends EventTarget {
|
|
|
81
84
|
const route = this.toRoute(src);
|
|
82
85
|
if (route) {
|
|
83
86
|
this._addRoute(route);
|
|
84
|
-
this.reload(route.path);
|
|
87
|
+
this.reload(route.path, "add");
|
|
85
88
|
}
|
|
86
89
|
}
|
|
87
90
|
catch (e) {
|
|
@@ -89,22 +92,25 @@ export class BaseFileSystemRouter extends EventTarget {
|
|
|
89
92
|
}
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
|
-
reload(route) {
|
|
93
|
-
this.dispatchEvent(new
|
|
94
|
-
// @ts-ignore
|
|
95
|
+
reload(route, type) {
|
|
96
|
+
this.dispatchEvent(new CustomEvent("reload", {
|
|
95
97
|
detail: {
|
|
96
98
|
route,
|
|
99
|
+
type,
|
|
97
100
|
},
|
|
98
101
|
}));
|
|
99
102
|
}
|
|
100
|
-
async updateRoute(
|
|
101
|
-
src = normalizePath(
|
|
103
|
+
async updateRoute(src_) {
|
|
104
|
+
const src = normalizePath(src_);
|
|
102
105
|
if (this.isRoute(src)) {
|
|
103
106
|
try {
|
|
104
107
|
const route = this.toRoute(src);
|
|
105
108
|
if (route) {
|
|
106
|
-
this._addRoute(route);
|
|
107
|
-
this.reload(route.path);
|
|
109
|
+
const updated = this._addRoute(route);
|
|
110
|
+
this.reload(route.path, updated ? "update" : "add");
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.removeRoute(src_);
|
|
108
114
|
}
|
|
109
115
|
}
|
|
110
116
|
catch (e) {
|
|
@@ -120,10 +126,17 @@ export class BaseFileSystemRouter extends EventTarget {
|
|
|
120
126
|
if (path === undefined) {
|
|
121
127
|
return;
|
|
122
128
|
}
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
const idx = this.routes.findIndex(r => r.path === path);
|
|
130
|
+
if (idx === -1)
|
|
131
|
+
return;
|
|
132
|
+
this.routes.splice(idx, 1);
|
|
133
|
+
this.reload(path, "remove");
|
|
125
134
|
}
|
|
126
135
|
}
|
|
136
|
+
on(type, cb) {
|
|
137
|
+
this.addEventListener(type, cb);
|
|
138
|
+
return () => this.removeEventListener(type, cb);
|
|
139
|
+
}
|
|
127
140
|
buildRoutesPromise;
|
|
128
141
|
async getRoutes() {
|
|
129
142
|
if (!this.buildRoutesPromise) {
|
|
@@ -260,9 +260,6 @@ function treeShakeTransform({ types: t }) {
|
|
|
260
260
|
};
|
|
261
261
|
}
|
|
262
262
|
export function treeShake() {
|
|
263
|
-
let config;
|
|
264
|
-
const cache = {};
|
|
265
|
-
let server;
|
|
266
263
|
async function transform(id, code) {
|
|
267
264
|
const [path, queryString] = id.split("?");
|
|
268
265
|
const query = new URLSearchParams(queryString);
|
|
@@ -286,44 +283,6 @@ export function treeShake() {
|
|
|
286
283
|
return {
|
|
287
284
|
name: "tree-shake",
|
|
288
285
|
enforce: "pre",
|
|
289
|
-
configResolved(resolvedConfig) {
|
|
290
|
-
config = resolvedConfig;
|
|
291
|
-
},
|
|
292
|
-
configureServer(s) {
|
|
293
|
-
server = s;
|
|
294
|
-
},
|
|
295
|
-
async handleHotUpdate(ctx) {
|
|
296
|
-
if (cache[ctx.file]) {
|
|
297
|
-
const mods = [];
|
|
298
|
-
const newCode = await ctx.read();
|
|
299
|
-
for (const [id, code] of Object.entries(cache[ctx.file])) {
|
|
300
|
-
const transformed = await transform(id, newCode);
|
|
301
|
-
if (!transformed)
|
|
302
|
-
continue;
|
|
303
|
-
const { code: transformedCode } = transformed;
|
|
304
|
-
if (transformedCode !== code) {
|
|
305
|
-
const mod = server.moduleGraph.getModuleById(id);
|
|
306
|
-
if (mod)
|
|
307
|
-
mods.push(mod);
|
|
308
|
-
}
|
|
309
|
-
cache[ctx.file] ??= {};
|
|
310
|
-
cache[ctx.file][id] = transformedCode;
|
|
311
|
-
// server.moduleGraph.setModuleSource(id, code);
|
|
312
|
-
}
|
|
313
|
-
return mods;
|
|
314
|
-
}
|
|
315
|
-
// const mods = [];
|
|
316
|
-
// [...server.moduleGraph.urlToModuleMap.entries()].forEach(([url, m]) => {
|
|
317
|
-
// if (m.file === ctx.file && m.id.includes("pick=")) {
|
|
318
|
-
// if (!m.id.includes("pick=loader")) {
|
|
319
|
-
// mods.push(m);
|
|
320
|
-
// }
|
|
321
|
-
// }
|
|
322
|
-
// });
|
|
323
|
-
// return mods;
|
|
324
|
-
// // this.router.updateRoute(ctx.path);
|
|
325
|
-
// }
|
|
326
|
-
},
|
|
327
286
|
async transform(code, id) {
|
|
328
287
|
const [path, queryString] = id.split("?");
|
|
329
288
|
if (!path)
|
|
@@ -336,8 +295,6 @@ export function treeShake() {
|
|
|
336
295
|
const transformed = await transform(id, code);
|
|
337
296
|
if (!transformed?.code)
|
|
338
297
|
return;
|
|
339
|
-
cache[path] ??= {};
|
|
340
|
-
cache[path][id] = transformed.code;
|
|
341
298
|
return {
|
|
342
299
|
code: transformed.code,
|
|
343
300
|
map: transformed.map,
|
package/dist/config/index.js
CHANGED
|
@@ -37,6 +37,18 @@ export function solidStart(options) {
|
|
|
37
37
|
server: `${start.appRoot}/entry-server${entryExtension}`,
|
|
38
38
|
};
|
|
39
39
|
return [
|
|
40
|
+
// TODO (Alexis): check if the comment below is still relevant
|
|
41
|
+
//
|
|
42
|
+
// Must be placed after fsRoutes, as treeShake will remove the
|
|
43
|
+
// server fn exports added in by this plugin
|
|
44
|
+
serverFunctionsPlugin({
|
|
45
|
+
manifest: VIRTUAL_MODULES.serverFnManifest,
|
|
46
|
+
runtime: {
|
|
47
|
+
server: '@solidjs/start/fns/server',
|
|
48
|
+
client: '@solidjs/start/fns/client',
|
|
49
|
+
},
|
|
50
|
+
filter: options?.serverFunctions?.filter,
|
|
51
|
+
}),
|
|
40
52
|
{
|
|
41
53
|
name: "solid-start:config",
|
|
42
54
|
enforce: "pre",
|
|
@@ -166,16 +178,6 @@ export function solidStart(options) {
|
|
|
166
178
|
}),
|
|
167
179
|
lazy(),
|
|
168
180
|
envPlugin(options?.env),
|
|
169
|
-
// Must be placed after fsRoutes, as treeShake will remove the
|
|
170
|
-
// server fn exports added in by this plugin
|
|
171
|
-
serverFunctionsPlugin({
|
|
172
|
-
manifest: VIRTUAL_MODULES.serverFnManifest,
|
|
173
|
-
runtime: {
|
|
174
|
-
server: '@solidjs/start/fns/server',
|
|
175
|
-
client: '@solidjs/start/fns/client',
|
|
176
|
-
},
|
|
177
|
-
filter: options?.serverFunctions?.filter,
|
|
178
|
-
}),
|
|
179
181
|
{
|
|
180
182
|
name: "solid-start:virtual-modules",
|
|
181
183
|
async resolveId(id) {
|
package/dist/config/lazy.js
CHANGED
|
@@ -77,6 +77,8 @@ const lazy = () => {
|
|
|
77
77
|
// Therefore we skip, if the src doesn't have any import
|
|
78
78
|
if (src.indexOf("import") === -1)
|
|
79
79
|
return;
|
|
80
|
+
if (id.includes("entry-server"))
|
|
81
|
+
return;
|
|
80
82
|
const plugins = [];
|
|
81
83
|
const hasDefaultExport = src.indexOf("export default") !== -1;
|
|
82
84
|
if (hasDefaultExport) {
|
package/dist/config/manifest.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { version as viteVersion } from "vite";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { findStylesInModuleGraph } from "../server/collect-styles.js";
|
|
5
6
|
import { VIRTUAL_MODULES } from "./constants.js";
|
|
6
7
|
import { wrapId } from "./vite-utils.js";
|
|
@@ -16,11 +17,11 @@ export function manifest(start) {
|
|
|
16
17
|
if (id === VIRTUAL_MODULES.clientViteManifest)
|
|
17
18
|
return `\0${VIRTUAL_MODULES.clientViteManifest}`;
|
|
18
19
|
if (id === VIRTUAL_MODULES.getClientManifest)
|
|
19
|
-
return this.resolve(new URL("../server/manifest/client-manifest", import.meta.url)
|
|
20
|
+
return this.resolve(fileURLToPath(new URL("../server/manifest/client-manifest", import.meta.url)));
|
|
20
21
|
if (id === VIRTUAL_MODULES.getManifest) {
|
|
21
22
|
return this.environment.config.consumer === "client"
|
|
22
|
-
? this.resolve(new URL("../server/manifest/client-manifest", import.meta.url)
|
|
23
|
-
: this.resolve(new URL("../server/manifest/ssr-manifest", import.meta.url)
|
|
23
|
+
? this.resolve(fileURLToPath(new URL("../server/manifest/client-manifest", import.meta.url)))
|
|
24
|
+
: this.resolve(fileURLToPath(new URL("../server/manifest/ssr-manifest", import.meta.url)));
|
|
24
25
|
}
|
|
25
26
|
if (id === VIRTUAL_MODULES.middleware) {
|
|
26
27
|
if (start.middleware)
|
package/dist/directives/index.js
CHANGED
|
@@ -140,6 +140,7 @@ export function serverFunctionsPlugin(options) {
|
|
|
140
140
|
},
|
|
141
141
|
{
|
|
142
142
|
name: "solid-start:server-functions/compiler",
|
|
143
|
+
enforce: 'pre',
|
|
143
144
|
async transform(code, fileId, opts) {
|
|
144
145
|
const mode = opts?.ssr ? "server" : "client";
|
|
145
146
|
const [id] = fileId.split("?");
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import * as t from "@babel/types";
|
|
2
2
|
import { isPathValid } from "./paths.js";
|
|
3
|
+
function isInvalidForRemoval(path) {
|
|
4
|
+
if (isPathValid(path, t.isCatchClause)) {
|
|
5
|
+
// This case is for `catch (error)` blocks
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
// This one is for destructured variables
|
|
9
|
+
let target = path;
|
|
10
|
+
if (isPathValid(path, t.isVariableDeclarator)) {
|
|
11
|
+
target = path.get('id');
|
|
12
|
+
}
|
|
13
|
+
return isPathValid(target, t.isObjectPattern) || isPathValid(target, t.isArrayPattern);
|
|
14
|
+
}
|
|
3
15
|
export function removeUnusedVariables(program) {
|
|
4
16
|
// TODO(Alexis):
|
|
5
17
|
// This implementation is simple but slow
|
|
@@ -20,19 +32,20 @@ export function removeUnusedVariables(program) {
|
|
|
20
32
|
case "hoisted":
|
|
21
33
|
case "module":
|
|
22
34
|
if (binding.references === 0 && !binding.path.removed) {
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
const parent = binding.path.parentPath;
|
|
36
|
+
if (isPathValid(parent, t.isImportDeclaration)) {
|
|
25
37
|
if (parent.node.specifiers.length === 1) {
|
|
26
38
|
parent.remove();
|
|
27
39
|
}
|
|
28
40
|
else {
|
|
29
41
|
binding.path.remove();
|
|
30
42
|
}
|
|
43
|
+
dirty = true;
|
|
31
44
|
}
|
|
32
|
-
else {
|
|
45
|
+
else if (!(isInvalidForRemoval(binding.path))) {
|
|
33
46
|
binding.path.remove();
|
|
47
|
+
dirty = true;
|
|
34
48
|
}
|
|
35
|
-
dirty = true;
|
|
36
49
|
}
|
|
37
50
|
break;
|
|
38
51
|
case "local":
|
package/dist/server/handler.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type H3Event } from "h3";
|
|
1
|
+
import { type H3Event } from "h3/generic";
|
|
2
2
|
import type { JSX } from "solid-js";
|
|
3
3
|
import type { FetchEvent, HandlerOptions, PageEvent } from "./types.ts";
|
|
4
4
|
export declare function createBaseHandler(createPageEvent: (e: FetchEvent) => Promise<PageEvent>, fn: (context: PageEvent) => JSX.Element, options?: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise<HandlerOptions>)): {
|
package/dist/server/handler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import middleware from "solid-start:middleware";
|
|
2
|
-
import { defineHandler, getCookie, H3, redirect, setCookie } from "h3";
|
|
2
|
+
import { defineHandler, getCookie, H3, redirect, setCookie } from "h3/generic";
|
|
3
3
|
import { sharedConfig } from "solid-js";
|
|
4
4
|
import { getRequestEvent, renderToStream, renderToString } from "solid-js/web";
|
|
5
5
|
import { createRoutes } from "../router.jsx";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidjs/start",
|
|
3
|
-
"version": "2.0.0-
|
|
3
|
+
"version": "2.0.0-beta.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"vitest": "^4.1.0"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"vite": "^7"
|
|
65
|
+
"vite": "^7 || ^8"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"build": "pnpm validate-imports && tsc && node scripts/build.js",
|
package/src/config/constants.ts
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
FSWatcher,
|
|
4
|
-
PluginOption,
|
|
5
|
-
ViteDevServer,
|
|
6
|
-
} from "vite";
|
|
1
|
+
import type { EnvironmentModuleNode, FSWatcher, PluginOption, ViteDevServer } from "vite";
|
|
2
|
+
import { debounce } from "../../utils/debounce.ts";
|
|
7
3
|
import { VITE_ENVIRONMENTS } from "../constants.ts";
|
|
8
4
|
import { moduleId } from "./index.ts";
|
|
9
5
|
import type { BaseFileSystemRouter } from "./router.ts";
|
|
10
6
|
|
|
11
|
-
|
|
12
|
-
removeRoute(path: string): void;
|
|
13
|
-
addRoute(path: string): void;
|
|
14
|
-
updateRoute(path: string): void;
|
|
15
|
-
addEventListener(event: "reload", handler: () => void): void;
|
|
16
|
-
removeEventListener(event: "reload", handler: () => void): void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function setupWatcher(watcher: FSWatcher, routes: CompiledRouter): void {
|
|
7
|
+
function setupWatcher(watcher: FSWatcher, routes: BaseFileSystemRouter): void {
|
|
20
8
|
watcher.on("unlink", path => routes.removeRoute(path));
|
|
21
9
|
watcher.on("add", path => routes.addRoute(path));
|
|
22
10
|
watcher.on("change", path => routes.updateRoute(path));
|
|
@@ -24,33 +12,41 @@ function setupWatcher(watcher: FSWatcher, routes: CompiledRouter): void {
|
|
|
24
12
|
|
|
25
13
|
function createRoutesReloader(
|
|
26
14
|
server: ViteDevServer,
|
|
27
|
-
routes:
|
|
15
|
+
routes: BaseFileSystemRouter,
|
|
28
16
|
environment: "client" | "ssr",
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
17
|
+
) {
|
|
18
|
+
const devEnv = server.environments[environment]!;
|
|
19
|
+
if (!devEnv?.moduleGraph) return;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Debounce catches multiple route changes in a row
|
|
23
|
+
* Short timeout for inexpensive invalidations
|
|
24
|
+
*/
|
|
25
|
+
const invalidateModule = debounce((mod: EnvironmentModuleNode) => {
|
|
26
|
+
devEnv.moduleGraph.invalidateModule(mod);
|
|
27
|
+
}, 0);
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Long debounce timeout for expensive reloads
|
|
31
|
+
*/
|
|
32
|
+
const reloadModule = debounce((mod: EnvironmentModuleNode) => {
|
|
33
|
+
devEnv.reloadModule(mod);
|
|
34
|
+
}, 200);
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (mod) {
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
return routes.on("reload", function handleRoutesReload(evt): void {
|
|
37
|
+
const mod = devEnv.moduleGraph.getModuleById(moduleId)!;
|
|
38
|
+
if (!mod) {
|
|
39
|
+
devEnv.hot.send({ type: "full-reload" });
|
|
40
|
+
return;
|
|
44
41
|
}
|
|
45
42
|
|
|
46
|
-
if (environment !== "
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
43
|
+
if (environment === VITE_ENVIRONMENTS.client && evt.detail.type !== "update") {
|
|
44
|
+
// Client has to be reloaded when routes are added or removed
|
|
45
|
+
reloadModule(mod);
|
|
46
|
+
} else {
|
|
47
|
+
invalidateModule(mod);
|
|
52
48
|
}
|
|
53
|
-
}
|
|
49
|
+
});
|
|
54
50
|
}
|
|
55
51
|
|
|
56
52
|
export const fileSystemWatcher = (
|
|
@@ -59,11 +55,11 @@ export const fileSystemWatcher = (
|
|
|
59
55
|
const plugin: PluginOption = {
|
|
60
56
|
name: "fs-watcher",
|
|
61
57
|
async configureServer(server: ViteDevServer) {
|
|
62
|
-
|
|
63
|
-
const router =
|
|
58
|
+
for (const environment of [VITE_ENVIRONMENTS.server, VITE_ENVIRONMENTS.client]) {
|
|
59
|
+
const router = routers[environment];
|
|
64
60
|
setupWatcher(server.watcher, router);
|
|
65
|
-
createRoutesReloader(server, router, environment
|
|
66
|
-
}
|
|
61
|
+
createRoutesReloader(server, router, environment);
|
|
62
|
+
}
|
|
67
63
|
},
|
|
68
64
|
};
|
|
69
65
|
return plugin;
|
|
@@ -32,6 +32,8 @@ export function analyzeModule(src: string) {
|
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
type RouterEvent = CustomEvent<{ route: string; type: "update" | "remove" | "add" }>;
|
|
36
|
+
|
|
35
37
|
export class BaseFileSystemRouter extends EventTarget {
|
|
36
38
|
routes: Route[];
|
|
37
39
|
|
|
@@ -98,8 +100,11 @@ export class BaseFileSystemRouter extends EventTarget {
|
|
|
98
100
|
update = undefined;
|
|
99
101
|
|
|
100
102
|
_addRoute(route: Route) {
|
|
101
|
-
|
|
103
|
+
const idx = this.routes.findIndex(r => r.path === route.path);
|
|
104
|
+
if (idx >= 0) this.routes.splice(idx, 1);
|
|
102
105
|
this.routes.push(route);
|
|
106
|
+
|
|
107
|
+
return idx >= 0;
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
async addRoute(src: string) {
|
|
@@ -109,7 +114,7 @@ export class BaseFileSystemRouter extends EventTarget {
|
|
|
109
114
|
const route = this.toRoute(src);
|
|
110
115
|
if (route) {
|
|
111
116
|
this._addRoute(route);
|
|
112
|
-
this.reload(route.path);
|
|
117
|
+
this.reload(route.path, "add");
|
|
113
118
|
}
|
|
114
119
|
} catch (e) {
|
|
115
120
|
console.error(e);
|
|
@@ -117,25 +122,27 @@ export class BaseFileSystemRouter extends EventTarget {
|
|
|
117
122
|
}
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
reload(route: string) {
|
|
125
|
+
reload(route: string, type: "update" | "remove" | "add") {
|
|
121
126
|
this.dispatchEvent(
|
|
122
|
-
new
|
|
123
|
-
// @ts-ignore
|
|
127
|
+
new CustomEvent("reload", {
|
|
124
128
|
detail: {
|
|
125
129
|
route,
|
|
130
|
+
type,
|
|
126
131
|
},
|
|
127
132
|
}),
|
|
128
133
|
);
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
async updateRoute(
|
|
132
|
-
src = normalizePath(
|
|
136
|
+
async updateRoute(src_: string) {
|
|
137
|
+
const src = normalizePath(src_);
|
|
133
138
|
if (this.isRoute(src)) {
|
|
134
139
|
try {
|
|
135
140
|
const route = this.toRoute(src);
|
|
136
141
|
if (route) {
|
|
137
|
-
this._addRoute(route);
|
|
138
|
-
this.reload(route.path);
|
|
142
|
+
const updated = this._addRoute(route);
|
|
143
|
+
this.reload(route.path, updated ? "update" : "add");
|
|
144
|
+
} else {
|
|
145
|
+
this.removeRoute(src_);
|
|
139
146
|
}
|
|
140
147
|
} catch (e) {
|
|
141
148
|
console.error(e);
|
|
@@ -151,11 +158,20 @@ export class BaseFileSystemRouter extends EventTarget {
|
|
|
151
158
|
if (path === undefined) {
|
|
152
159
|
return;
|
|
153
160
|
}
|
|
154
|
-
|
|
155
|
-
this.
|
|
161
|
+
|
|
162
|
+
const idx = this.routes.findIndex(r => r.path === path);
|
|
163
|
+
if (idx === -1) return;
|
|
164
|
+
|
|
165
|
+
this.routes.splice(idx, 1);
|
|
166
|
+
this.reload(path, "remove");
|
|
156
167
|
}
|
|
157
168
|
}
|
|
158
169
|
|
|
170
|
+
on(type: string, cb: (evt: RouterEvent) => void) {
|
|
171
|
+
this.addEventListener(type, cb as any);
|
|
172
|
+
return () => this.removeEventListener(type, cb as any);
|
|
173
|
+
}
|
|
174
|
+
|
|
159
175
|
buildRoutesPromise?: Promise<any[]>;
|
|
160
176
|
|
|
161
177
|
async getRoutes() {
|
|
@@ -6,7 +6,7 @@ import type * as Babel from "@babel/core";
|
|
|
6
6
|
import type { NodePath, PluginObj, PluginPass } from "@babel/core";
|
|
7
7
|
import type { Binding } from "@babel/traverse";
|
|
8
8
|
import { basename } from "pathe";
|
|
9
|
-
import type { Plugin
|
|
9
|
+
import type { Plugin } from "vite";
|
|
10
10
|
|
|
11
11
|
type State = Omit<PluginPass, "opts"> & {
|
|
12
12
|
opts: { pick: string[] };
|
|
@@ -280,10 +280,6 @@ function treeShakeTransform({ types: t }: typeof Babel): PluginObj<State> {
|
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
export function treeShake(): Plugin {
|
|
283
|
-
let config: ResolvedConfig;
|
|
284
|
-
const cache: Record<string, any> = {};
|
|
285
|
-
let server: ViteDevServer;
|
|
286
|
-
|
|
287
283
|
async function transform(id: string, code: string) {
|
|
288
284
|
const [path, queryString] = id.split("?");
|
|
289
285
|
const query = new URLSearchParams(queryString);
|
|
@@ -308,46 +304,6 @@ export function treeShake(): Plugin {
|
|
|
308
304
|
return {
|
|
309
305
|
name: "tree-shake",
|
|
310
306
|
enforce: "pre",
|
|
311
|
-
configResolved(resolvedConfig) {
|
|
312
|
-
config = resolvedConfig;
|
|
313
|
-
},
|
|
314
|
-
configureServer(s) {
|
|
315
|
-
server = s;
|
|
316
|
-
},
|
|
317
|
-
async handleHotUpdate(ctx) {
|
|
318
|
-
if (cache[ctx.file]) {
|
|
319
|
-
const mods = [];
|
|
320
|
-
const newCode = await ctx.read();
|
|
321
|
-
for (const [id, code] of Object.entries(cache[ctx.file])) {
|
|
322
|
-
const transformed = await transform(id, newCode);
|
|
323
|
-
if (!transformed) continue;
|
|
324
|
-
|
|
325
|
-
const { code: transformedCode } = transformed;
|
|
326
|
-
|
|
327
|
-
if (transformedCode !== code) {
|
|
328
|
-
const mod = server.moduleGraph.getModuleById(id);
|
|
329
|
-
if (mod) mods.push(mod);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
cache[ctx.file] ??= {};
|
|
333
|
-
cache[ctx.file][id] = transformedCode;
|
|
334
|
-
// server.moduleGraph.setModuleSource(id, code);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return mods;
|
|
338
|
-
}
|
|
339
|
-
// const mods = [];
|
|
340
|
-
// [...server.moduleGraph.urlToModuleMap.entries()].forEach(([url, m]) => {
|
|
341
|
-
// if (m.file === ctx.file && m.id.includes("pick=")) {
|
|
342
|
-
// if (!m.id.includes("pick=loader")) {
|
|
343
|
-
// mods.push(m);
|
|
344
|
-
// }
|
|
345
|
-
// }
|
|
346
|
-
// });
|
|
347
|
-
// return mods;
|
|
348
|
-
// // this.router.updateRoute(ctx.path);
|
|
349
|
-
// }
|
|
350
|
-
},
|
|
351
307
|
async transform(code, id) {
|
|
352
308
|
const [path, queryString] = id.split("?");
|
|
353
309
|
if (!path) return;
|
|
@@ -358,9 +314,6 @@ export function treeShake(): Plugin {
|
|
|
358
314
|
const transformed = await transform(id, code);
|
|
359
315
|
if (!transformed?.code) return;
|
|
360
316
|
|
|
361
|
-
cache[path] ??= {};
|
|
362
|
-
cache[path][id] = transformed.code;
|
|
363
|
-
|
|
364
317
|
return {
|
|
365
318
|
code: transformed.code,
|
|
366
319
|
map: transformed.map,
|
package/src/config/index.ts
CHANGED
|
@@ -63,6 +63,18 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
|
|
|
63
63
|
server: `${start.appRoot}/entry-server${entryExtension}`,
|
|
64
64
|
};
|
|
65
65
|
return [
|
|
66
|
+
// TODO (Alexis): check if the comment below is still relevant
|
|
67
|
+
//
|
|
68
|
+
// Must be placed after fsRoutes, as treeShake will remove the
|
|
69
|
+
// server fn exports added in by this plugin
|
|
70
|
+
serverFunctionsPlugin({
|
|
71
|
+
manifest: VIRTUAL_MODULES.serverFnManifest,
|
|
72
|
+
runtime: {
|
|
73
|
+
server: '@solidjs/start/fns/server',
|
|
74
|
+
client: '@solidjs/start/fns/client',
|
|
75
|
+
},
|
|
76
|
+
filter: options?.serverFunctions?.filter,
|
|
77
|
+
}),
|
|
66
78
|
{
|
|
67
79
|
name: "solid-start:config",
|
|
68
80
|
enforce: "pre",
|
|
@@ -190,16 +202,6 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
|
|
|
190
202
|
}),
|
|
191
203
|
lazy(),
|
|
192
204
|
envPlugin(options?.env),
|
|
193
|
-
// Must be placed after fsRoutes, as treeShake will remove the
|
|
194
|
-
// server fn exports added in by this plugin
|
|
195
|
-
serverFunctionsPlugin({
|
|
196
|
-
manifest: VIRTUAL_MODULES.serverFnManifest,
|
|
197
|
-
runtime: {
|
|
198
|
-
server: '@solidjs/start/fns/server',
|
|
199
|
-
client: '@solidjs/start/fns/client',
|
|
200
|
-
},
|
|
201
|
-
filter: options?.serverFunctions?.filter,
|
|
202
|
-
}),
|
|
203
205
|
{
|
|
204
206
|
name: "solid-start:virtual-modules",
|
|
205
207
|
async resolveId(id) {
|
package/src/config/lazy.ts
CHANGED
|
@@ -90,6 +90,7 @@ const lazy = (): PluginOption => {
|
|
|
90
90
|
// The transformed files either import "lazy" or css files
|
|
91
91
|
// Therefore we skip, if the src doesn't have any import
|
|
92
92
|
if (src.indexOf("import") === -1) return;
|
|
93
|
+
if (id.includes("entry-server")) return;
|
|
93
94
|
|
|
94
95
|
const plugins: PluginItem[] = [];
|
|
95
96
|
|
package/src/config/manifest.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { type PluginOption, type ViteDevServer, version as viteVersion } from "vite";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
|
|
5
6
|
import { findStylesInModuleGraph } from "../server/collect-styles.ts";
|
|
6
7
|
import { VIRTUAL_MODULES } from "./constants.ts";
|
|
@@ -20,12 +21,16 @@ export function manifest(start: SolidStartOptions): PluginOption {
|
|
|
20
21
|
return `\0${VIRTUAL_MODULES.clientViteManifest}`;
|
|
21
22
|
if (id === VIRTUAL_MODULES.getClientManifest)
|
|
22
23
|
return this.resolve(
|
|
23
|
-
new URL("../server/manifest/client-manifest", import.meta.url)
|
|
24
|
+
fileURLToPath(new URL("../server/manifest/client-manifest", import.meta.url)),
|
|
24
25
|
);
|
|
25
26
|
if (id === VIRTUAL_MODULES.getManifest) {
|
|
26
27
|
return this.environment.config.consumer === "client"
|
|
27
|
-
? this.resolve(
|
|
28
|
-
|
|
28
|
+
? this.resolve(
|
|
29
|
+
fileURLToPath(new URL("../server/manifest/client-manifest", import.meta.url)),
|
|
30
|
+
)
|
|
31
|
+
: this.resolve(
|
|
32
|
+
fileURLToPath(new URL("../server/manifest/ssr-manifest", import.meta.url)),
|
|
33
|
+
);
|
|
29
34
|
}
|
|
30
35
|
if (id === VIRTUAL_MODULES.middleware) {
|
|
31
36
|
if (start.middleware) return await this.resolve(start.middleware);
|
package/src/directives/index.ts
CHANGED
|
@@ -196,6 +196,7 @@ export function serverFunctionsPlugin(options: ServerFunctionsOptions): Plugin[]
|
|
|
196
196
|
},
|
|
197
197
|
{
|
|
198
198
|
name: "solid-start:server-functions/compiler",
|
|
199
|
+
enforce: 'pre',
|
|
199
200
|
async transform(code, fileId, opts) {
|
|
200
201
|
const mode = opts?.ssr ? "server" : "client";
|
|
201
202
|
const [id] = fileId.split("?");
|
|
@@ -2,6 +2,20 @@ import type * as babel from "@babel/core";
|
|
|
2
2
|
import * as t from "@babel/types";
|
|
3
3
|
import { isPathValid } from "./paths.ts";
|
|
4
4
|
|
|
5
|
+
function isInvalidForRemoval(path: babel.NodePath) {
|
|
6
|
+
if (isPathValid(path, t.isCatchClause)) {
|
|
7
|
+
// This case is for `catch (error)` blocks
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// This one is for destructured variables
|
|
12
|
+
let target = path;
|
|
13
|
+
if (isPathValid(path, t.isVariableDeclarator)) {
|
|
14
|
+
target = path.get('id');
|
|
15
|
+
}
|
|
16
|
+
return isPathValid(target, t.isObjectPattern) || isPathValid(target, t.isArrayPattern);
|
|
17
|
+
}
|
|
18
|
+
|
|
5
19
|
export function removeUnusedVariables(program: babel.NodePath<t.Program>) {
|
|
6
20
|
// TODO(Alexis):
|
|
7
21
|
// This implementation is simple but slow
|
|
@@ -24,17 +38,18 @@ export function removeUnusedVariables(program: babel.NodePath<t.Program>) {
|
|
|
24
38
|
case "hoisted":
|
|
25
39
|
case "module":
|
|
26
40
|
if (binding.references === 0 && !binding.path.removed) {
|
|
27
|
-
if (isPathValid(binding.path.parentPath, t.isImportDeclaration)) {
|
|
28
41
|
const parent = binding.path.parentPath;
|
|
42
|
+
if (isPathValid(parent, t.isImportDeclaration)) {
|
|
29
43
|
if (parent.node.specifiers.length === 1) {
|
|
30
44
|
parent.remove();
|
|
31
45
|
} else {
|
|
32
46
|
binding.path.remove();
|
|
33
47
|
}
|
|
34
|
-
|
|
48
|
+
dirty = true;
|
|
49
|
+
} else if (!(isInvalidForRemoval(binding.path))) {
|
|
35
50
|
binding.path.remove();
|
|
51
|
+
dirty = true;
|
|
36
52
|
}
|
|
37
|
-
dirty = true;
|
|
38
53
|
}
|
|
39
54
|
break;
|
|
40
55
|
case "local":
|
package/src/server/handler.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import middleware from "solid-start:middleware";
|
|
2
|
-
import { defineHandler, getCookie, H3, type H3Event, redirect, setCookie } from "h3";
|
|
2
|
+
import { defineHandler, getCookie, H3, type H3Event, redirect, setCookie } from "h3/generic";
|
|
3
3
|
import { join } from "pathe";
|
|
4
4
|
import type { JSX } from "solid-js";
|
|
5
5
|
import { sharedConfig } from "solid-js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a debounced function that delays the invocation of a function
|
|
3
|
+
*/
|
|
4
|
+
export const debounce = <T extends (...args: any[]) => void>(cb: T, debounceMs: number) => {
|
|
5
|
+
let timeout: NodeJS.Timeout | undefined;
|
|
6
|
+
return (...args: Parameters<T>) => {
|
|
7
|
+
clearTimeout(timeout);
|
|
8
|
+
timeout = setTimeout(() => cb(...args), debounceMs)
|
|
9
|
+
}
|
|
10
|
+
}
|