@solidjs/vite-plugin 3.0.0-next.29 → 3.0.0-next.31
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 +53 -4
- package/dist/cjs/index.cjs +245 -49
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.mjs +247 -51
- package/dist/esm/index.mjs.map +1 -1
- package/dist/types/src/dev-manifest.d.ts +4 -3
- package/dist/types/src/devtools/index.d.ts +5 -0
- package/dist/types/src/environment.d.ts +3 -0
- package/dist/types/src/http.d.ts +8 -1
- package/dist/types/src/ssr/index.d.ts +42 -1
- package/package.json +9 -4
package/dist/esm/index.mjs
CHANGED
|
@@ -6,8 +6,8 @@ import { mergeAndConcat } from 'merge-anything';
|
|
|
6
6
|
import { createRequire } from 'module';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { Readable } from 'node:stream';
|
|
9
|
-
import { createFilter, loadEnv, version } from 'vite';
|
|
10
|
-
import { pathToFileURL } from 'node:url';
|
|
9
|
+
import { createFilter, normalizePath, loadEnv, version } from 'vite';
|
|
10
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
11
11
|
import { crawlFrameworkPkgs } from 'vitefu';
|
|
12
12
|
|
|
13
13
|
// Node <-> web-standard request/response bridging shared by the plugin's dev
|
|
@@ -21,24 +21,54 @@ import { crawlFrameworkPkgs } from 'vitefu';
|
|
|
21
21
|
* different URL than the one node saw — the dev middlewares use it to
|
|
22
22
|
* restore the configured Vite `base` that the dev/preview base middleware
|
|
23
23
|
* stripped, so the handler always sees production-shaped URLs.
|
|
24
|
+
*
|
|
25
|
+
* Handles plain HTTP/1 *and* the HTTP/2 compat API: Vite's dev server uses
|
|
26
|
+
* `http2.createSecureServer({ allowHTTP1: true })` whenever `server.https`
|
|
27
|
+
* is set without a proxy, so under https the middlewares receive
|
|
28
|
+
* `Http2ServerRequest`s. The h2/protocol/abort techniques here are
|
|
29
|
+
* reimplemented from srvx's Node adapter (github.com/h3js/srvx,
|
|
30
|
+
* src/adapters/_node) — reference, not copied code.
|
|
24
31
|
*/
|
|
25
|
-
function webRequestFromNode(req, urlPath) {
|
|
26
|
-
|
|
32
|
+
function webRequestFromNode(req, urlPath, res) {
|
|
33
|
+
// TLS sockets (https and h2) expose `encrypted`; a Request whose url says
|
|
34
|
+
// http: on a TLS connection breaks secure-cookie logic, absolute
|
|
35
|
+
// redirects, and origin checks in application code.
|
|
36
|
+
const protocol = req.socket?.encrypted ? 'https' : 'http';
|
|
37
|
+
// HTTP/2 has no Host header — the authority travels in the `:authority`
|
|
38
|
+
// pseudo-header instead.
|
|
39
|
+
const host = req.headers.host ?? req.headers[':authority'] ?? 'localhost';
|
|
40
|
+
const url = new URL(urlPath ?? req.url ?? '/', `${protocol}://${host}`);
|
|
27
41
|
const headers = new Headers();
|
|
28
42
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
29
43
|
if (value === undefined) continue;
|
|
44
|
+
// HTTP/2 pseudo-headers (:method, :path, :authority, :scheme) are not
|
|
45
|
+
// legal field names — Headers#append throws a TypeError on them.
|
|
46
|
+
if (key[0] === ':') continue;
|
|
30
47
|
if (Array.isArray(value)) {
|
|
31
48
|
for (const item of value) headers.append(key, item);
|
|
32
49
|
} else {
|
|
33
50
|
headers.append(key, value);
|
|
34
51
|
}
|
|
35
52
|
}
|
|
53
|
+
// Surface client disconnects as the request's AbortSignal so handlers can
|
|
54
|
+
// cancel work (streamed SSR renders, in-flight fetches). The response's
|
|
55
|
+
// 'close' fires on normal completion too; `writableEnded` distinguishes a
|
|
56
|
+
// finished response from a client that went away.
|
|
57
|
+
let signal;
|
|
58
|
+
if (res) {
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
res.once('close', () => {
|
|
61
|
+
if (!res.writableEnded) controller.abort();
|
|
62
|
+
});
|
|
63
|
+
signal = controller.signal;
|
|
64
|
+
}
|
|
36
65
|
const method = req.method || 'GET';
|
|
37
66
|
const body = method === 'GET' || method === 'HEAD' ? undefined : Readable.toWeb(req);
|
|
38
67
|
return new Request(url, {
|
|
39
68
|
method,
|
|
40
69
|
headers,
|
|
41
70
|
body,
|
|
71
|
+
signal,
|
|
42
72
|
// undici requires half-duplex for streamed request bodies.
|
|
43
73
|
...(body ? {
|
|
44
74
|
duplex: 'half'
|
|
@@ -53,7 +83,11 @@ async function sendWebResponse(res, response) {
|
|
|
53
83
|
if (key !== 'set-cookie') res.setHeader(key, value);
|
|
54
84
|
});
|
|
55
85
|
if (cookies && cookies.length) res.setHeader('set-cookie', cookies);
|
|
56
|
-
|
|
86
|
+
// HEAD gets the head only — and the body must be *cancelled*, not pumped:
|
|
87
|
+
// node discards HEAD body writes, so streaming a long (or endless) body
|
|
88
|
+
// into the void just burns the render. (Technique from srvx.)
|
|
89
|
+
if (!response.body || res.req?.method === 'HEAD') {
|
|
90
|
+
response.body?.cancel().catch(() => {});
|
|
57
91
|
res.end();
|
|
58
92
|
return;
|
|
59
93
|
}
|
|
@@ -119,6 +153,7 @@ function joinBase(base, pathname) {
|
|
|
119
153
|
* dynamically imported modules register their own styles when they render.
|
|
120
154
|
*/
|
|
121
155
|
|
|
156
|
+
const defaultStyleFilter = id => !id.includes('node_modules');
|
|
122
157
|
// The resolver is created plugin-side (it closes over the dev server) but is
|
|
123
158
|
// called from the SSR module runner, which only shares `globalThis` with the
|
|
124
159
|
// plugin when it runs in-process (the default). The primary channel is a
|
|
@@ -262,13 +297,15 @@ async function getModuleNode(env, file, importer) {
|
|
|
262
297
|
return;
|
|
263
298
|
}
|
|
264
299
|
}
|
|
265
|
-
async function collectModuleDeps(env, file, deps, crawled, onFile, importer) {
|
|
300
|
+
async function collectModuleDeps(env, file, deps, crawled, filter, onFile, importer) {
|
|
266
301
|
crawled.add(file);
|
|
267
302
|
const node = await getModuleNode(env, file, importer);
|
|
268
303
|
if (!node?.id || deps.has(node)) return;
|
|
269
304
|
deps.add(node);
|
|
270
|
-
|
|
271
|
-
if (
|
|
305
|
+
const isCss = cssFileRegExp.test(node.url.split('?')[0]);
|
|
306
|
+
if (!isCss && node.file && !node.id.startsWith('\0') && !filter(node.file)) return;
|
|
307
|
+
if (node.file) onFile?.(node.file);
|
|
308
|
+
if (isCss) return;
|
|
272
309
|
if (!node.transformResult) {
|
|
273
310
|
await env.transformRequest(node.url).catch(() => {});
|
|
274
311
|
}
|
|
@@ -279,7 +316,7 @@ async function collectModuleDeps(env, file, deps, crawled, onFile, importer) {
|
|
|
279
316
|
// from dynamicDeps — dynamic imports load their own styles when rendered.
|
|
280
317
|
for (const dep of directDeps) {
|
|
281
318
|
if (crawled.has(dep)) continue;
|
|
282
|
-
await collectModuleDeps(env, dep, deps, crawled, onFile, node.id);
|
|
319
|
+
await collectModuleDeps(env, dep, deps, crawled, filter, onFile, node.id);
|
|
283
320
|
}
|
|
284
321
|
}
|
|
285
322
|
function injectQuery(url, query) {
|
|
@@ -287,11 +324,11 @@ function injectQuery(url, query) {
|
|
|
287
324
|
}
|
|
288
325
|
|
|
289
326
|
/** Discovers ambient CSS in an entry graph without choosing how it is transported. */
|
|
290
|
-
async function collectDevStyleSources(env, files, onFile) {
|
|
327
|
+
async function collectDevStyleSources(env, files, onFile, filter = defaultStyleFilter) {
|
|
291
328
|
const deps = new Set();
|
|
292
329
|
const crawled = new Set();
|
|
293
330
|
for (const file of files) {
|
|
294
|
-
await collectModuleDeps(env, file, deps, crawled, onFile);
|
|
331
|
+
await collectModuleDeps(env, file, deps, crawled, filter, onFile);
|
|
295
332
|
}
|
|
296
333
|
const css = [];
|
|
297
334
|
const seen = new Set();
|
|
@@ -318,11 +355,11 @@ async function collectDevStyleSources(env, files, onFile) {
|
|
|
318
355
|
* CSS into `<head>` so server-painted content is styled from the first byte
|
|
319
356
|
* (no FOUC while waiting for Vite's client-side style injection).
|
|
320
357
|
*/
|
|
321
|
-
async function collectDevStyles(server, files) {
|
|
358
|
+
async function collectDevStyles(server, files, filter = defaultStyleFilter) {
|
|
322
359
|
const ssrEnv = server.environments?.ssr;
|
|
323
360
|
const clientEnv = server.environments?.client;
|
|
324
361
|
if (!ssrEnv || !clientEnv) return [];
|
|
325
|
-
const sources = await collectDevStyleSources(ssrEnv, files.map(file => path.resolve(server.config.root, file)));
|
|
362
|
+
const sources = await collectDevStyleSources(ssrEnv, files.map(file => path.resolve(server.config.root, file)), undefined, filter);
|
|
326
363
|
const css = [];
|
|
327
364
|
for (const source of sources) {
|
|
328
365
|
// `?direct` yields the compiled stylesheet text (what Vite serves for
|
|
@@ -378,7 +415,7 @@ function devModuleUrl(root, base, key) {
|
|
|
378
415
|
// drive letter on Windows: /@fs/C:/…).
|
|
379
416
|
return joinBase(base, '/@fs/' + absolute.replace(/^\//, '') + query);
|
|
380
417
|
}
|
|
381
|
-
function createDevAssetResolver(server) {
|
|
418
|
+
function createDevAssetResolver(server, filter = defaultStyleFilter) {
|
|
382
419
|
// Server-side lazy() re-requests a module's assets on every retry of a
|
|
383
420
|
// suspended render pass (retries re-create the component). The build
|
|
384
421
|
// manifest answers those repeats synchronously and the pass converges; an
|
|
@@ -412,7 +449,7 @@ function createDevAssetResolver(server) {
|
|
|
412
449
|
// The module's dev URL doubles as its client entry: modulepreload
|
|
413
450
|
// hint and hydration module-map value.
|
|
414
451
|
const js = [devModuleUrl(root, base, key)];
|
|
415
|
-
const css = await collectDevStyles(server, [key]);
|
|
452
|
+
const css = await collectDevStyles(server, [key], filter);
|
|
416
453
|
return {
|
|
417
454
|
js,
|
|
418
455
|
css
|
|
@@ -505,6 +542,11 @@ function boundaryModules() {
|
|
|
505
542
|
function isRunnableEnvironment(environment) {
|
|
506
543
|
return !!environment && typeof environment === 'object' && 'runner' in environment;
|
|
507
544
|
}
|
|
545
|
+
function getEnvironmentConsumer(environment, options) {
|
|
546
|
+
const consumer = environment?.config?.consumer;
|
|
547
|
+
if (consumer === 'client' || consumer === 'server') return consumer;
|
|
548
|
+
return options?.ssr ? 'server' : 'client';
|
|
549
|
+
}
|
|
508
550
|
|
|
509
551
|
// The `"use server"` directive compiler. This wraps the native
|
|
510
552
|
// `transformDirectives` pass from @dom-expressions/compiler (Rust/Oxc); the
|
|
@@ -941,12 +983,12 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
941
983
|
const relative = path.relative(root, entry).split(path.sep).join('/');
|
|
942
984
|
return relative.startsWith('..') ? '/@fs/' + entry : '/' + relative;
|
|
943
985
|
}
|
|
944
|
-
const
|
|
986
|
+
const startPlugins = [{
|
|
945
987
|
name: 'solid:server-functions/handler',
|
|
946
988
|
enforce: 'pre',
|
|
947
989
|
resolveId(source, _importer, opts) {
|
|
948
990
|
if (source === HANDLER_ID$1) {
|
|
949
|
-
if (
|
|
991
|
+
if (getEnvironmentConsumer(this.environment, opts) !== 'server') {
|
|
950
992
|
this.error(`${HANDLER_ID$1} is server-only; import it from your server entry (SSR build).`);
|
|
951
993
|
}
|
|
952
994
|
return {
|
|
@@ -957,7 +999,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
957
999
|
return null;
|
|
958
1000
|
},
|
|
959
1001
|
load(id, opts) {
|
|
960
|
-
if (id === HANDLER_ID$1 && opts
|
|
1002
|
+
if (id === HANDLER_ID$1 && getEnvironmentConsumer(this.environment, opts) === 'server') {
|
|
961
1003
|
const externalDev = this.environment.mode === 'dev' && (internal.externalDevServer || !isRunnableEnvironment(this.environment));
|
|
962
1004
|
return handlerModuleCode(isBuild || externalDev);
|
|
963
1005
|
}
|
|
@@ -965,7 +1007,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
965
1007
|
}
|
|
966
1008
|
}];
|
|
967
1009
|
if (installDevMiddleware) {
|
|
968
|
-
|
|
1010
|
+
startPlugins.push({
|
|
969
1011
|
name: 'solid:server-functions/dev-middleware',
|
|
970
1012
|
apply: 'serve',
|
|
971
1013
|
configureServer(server) {
|
|
@@ -1010,7 +1052,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1010
1052
|
nativeEvent: req
|
|
1011
1053
|
}
|
|
1012
1054
|
};
|
|
1013
|
-
const response = internal.ssrHandler ? await handler.handleRequest(webRequestFromNode(req, dispatchUrl), dispatchOptions) : await handler.handleServerFunctionRequest(webRequestFromNode(req, dispatchUrl), dispatchOptions);
|
|
1055
|
+
const response = internal.ssrHandler ? await handler.handleRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions) : await handler.handleServerFunctionRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions);
|
|
1014
1056
|
await sendWebResponse(res, response);
|
|
1015
1057
|
})().catch(error => {
|
|
1016
1058
|
if (error instanceof Error) server.ssrFixStacktrace(error);
|
|
@@ -1077,7 +1119,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1077
1119
|
return null;
|
|
1078
1120
|
},
|
|
1079
1121
|
async load(id, opts) {
|
|
1080
|
-
const mode = opts
|
|
1122
|
+
const mode = getEnvironmentConsumer(this.environment, opts);
|
|
1081
1123
|
if (id === manifestId) {
|
|
1082
1124
|
if (isBuild && mode === 'server') {
|
|
1083
1125
|
// Merge the client build's persisted discoveries at load time,
|
|
@@ -1100,7 +1142,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1100
1142
|
name: 'solid:server-functions/compiler',
|
|
1101
1143
|
enforce: 'pre',
|
|
1102
1144
|
async transform(code, fileId, opts) {
|
|
1103
|
-
const mode = opts
|
|
1145
|
+
const mode = getEnvironmentConsumer(this.environment, opts);
|
|
1104
1146
|
const [id] = fileId.split('?');
|
|
1105
1147
|
if (!filter(id)) {
|
|
1106
1148
|
return null;
|
|
@@ -1133,7 +1175,17 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1133
1175
|
}
|
|
1134
1176
|
return null;
|
|
1135
1177
|
}
|
|
1136
|
-
}, ...
|
|
1178
|
+
}, ...startPlugins];
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const DEVTOOLS_PACKAGE = '@solidjs/start-devtools';
|
|
1182
|
+
const DEVTOOLS_ID = 'virtual:solid-devtools';
|
|
1183
|
+
const DEVTOOLS_MOUNT_ID = 'virtual:solid-devtools/mount';
|
|
1184
|
+
function devtoolsModuleCode() {
|
|
1185
|
+
return [`import * as serverFunctions from '@solidjs/web/server-functions';`, `import { DevToolbar, pushServerFunctionCall } from '${DEVTOOLS_PACKAGE}';`, `const observe = Reflect.get(serverFunctions, 'observeServerFunctionCalls');`, `if (typeof observe === 'function') observe(pushServerFunctionCall);`, `export { DevToolbar };`].join('\n');
|
|
1186
|
+
}
|
|
1187
|
+
function devtoolsMountModuleCode() {
|
|
1188
|
+
return [`import ${JSON.stringify(DEVTOOLS_ID)};`, `import { mountDevToolbar } from '${DEVTOOLS_PACKAGE}';`, `mountDevToolbar();`].join('\n');
|
|
1137
1189
|
}
|
|
1138
1190
|
|
|
1139
1191
|
// Start-mode serving for plain Vite apps: `solid({ start: {...} })` (or the
|
|
@@ -1218,6 +1270,7 @@ const RESOLVED_DEV_STYLES_ID = '\0' + DEV_STYLES_ID;
|
|
|
1218
1270
|
const ENTRY_SERVER_ID = 'virtual:solid-ssr-entry-server.tsx';
|
|
1219
1271
|
const ENTRY_CLIENT_ID = 'virtual:solid-ssr-entry-client.tsx';
|
|
1220
1272
|
const DOCUMENT_ID = 'virtual:solid-ssr-document.tsx';
|
|
1273
|
+
const ERROR_BOUNDARY_ID = 'virtual:solid-ssr-error-boundary.tsx';
|
|
1221
1274
|
const MANIFEST_ID = 'virtual:solid-manifest';
|
|
1222
1275
|
const SERVER_FUNCTION_HANDLER_ID = 'virtual:solid-server-function-handler';
|
|
1223
1276
|
const STORAGE_SOURCE = '@solidjs/web/storage';
|
|
@@ -1329,6 +1382,12 @@ function startServe(options, internal = {}) {
|
|
|
1329
1382
|
// the server-function handler module either way). Everything is gated
|
|
1330
1383
|
// codegen: with the option off, none of these imports exist anywhere.
|
|
1331
1384
|
const serverComponents = !!internal.serverComponents;
|
|
1385
|
+
const errorBoundary = options.errorBoundary !== false;
|
|
1386
|
+
const styleFilter = internal.styleFilter;
|
|
1387
|
+
let devtools = false;
|
|
1388
|
+
let devtoolsResolution;
|
|
1389
|
+
/** Resolved module id of `@solidjs/start-devtools` once detection succeeds. */
|
|
1390
|
+
let devtoolsId = null;
|
|
1332
1391
|
// `external` is server-mode-only (documented no-op in client mode, so a
|
|
1333
1392
|
// host-integrated config survives the `ssr` boolean flip untouched).
|
|
1334
1393
|
const externalServer = !clientMode && !!options.external;
|
|
@@ -1345,6 +1404,40 @@ function startServe(options, internal = {}) {
|
|
|
1345
1404
|
if (!entries) throw new Error('[@solidjs/vite-plugin] SSR entries not resolved yet');
|
|
1346
1405
|
return entries;
|
|
1347
1406
|
}
|
|
1407
|
+
async function resolveDevtools(resolve, importer) {
|
|
1408
|
+
if (devtools !== undefined) return devtools;
|
|
1409
|
+
// Detect from the app graph first (the documented install location), then
|
|
1410
|
+
// from the plugin's own file: in pnpm-isolated apps a copy that is only a
|
|
1411
|
+
// dependency of the plugin is not reachable from the app's importers. The
|
|
1412
|
+
// resolved id is kept so the virtual modules' imports of the package can
|
|
1413
|
+
// be delegated to it (see resolveId).
|
|
1414
|
+
devtoolsResolution ??= (async () => ((await resolve(DEVTOOLS_PACKAGE, importer)) ?? (await resolve(DEVTOOLS_PACKAGE, fileURLToPath(import.meta.url))))?.id ?? null)();
|
|
1415
|
+
devtoolsId = await devtoolsResolution;
|
|
1416
|
+
devtools = devtoolsId !== null;
|
|
1417
|
+
if (!devtools && options.devtools === true) {
|
|
1418
|
+
throw new Error('[@solidjs/vite-plugin] start.devtools requires @solidjs/start-devtools. ' + 'Install it as a development dependency or set start.devtools to false.');
|
|
1419
|
+
}
|
|
1420
|
+
return devtools;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
/**
|
|
1424
|
+
* Cheap root-walk probe mirroring how the optimizer resolves bare
|
|
1425
|
+
* `optimizeDeps.include` entries: is @solidjs/start-devtools reachable from
|
|
1426
|
+
* the Vite root? Detection proper (resolveDevtools) runs later with a real
|
|
1427
|
+
* importer; this only decides whether the toolbar graph can be pre-bundled
|
|
1428
|
+
* at scan time — it hangs off virtual modules the scanner never sees, so
|
|
1429
|
+
* first-request discovery would force a re-optimize + full page reload.
|
|
1430
|
+
*/
|
|
1431
|
+
function devtoolsReachableFromRoot(dir) {
|
|
1432
|
+
for (let current = dir;;) {
|
|
1433
|
+
if (existsSync(path.join(current, 'node_modules', DEVTOOLS_PACKAGE, 'package.json'))) {
|
|
1434
|
+
return true;
|
|
1435
|
+
}
|
|
1436
|
+
const parent = path.dirname(current);
|
|
1437
|
+
if (parent === current) return false;
|
|
1438
|
+
current = parent;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1348
1441
|
|
|
1349
1442
|
/** Import specifier for generated code: absolute for files, id for virtuals. */
|
|
1350
1443
|
function entryServerSpec() {
|
|
@@ -1384,7 +1477,7 @@ function startServe(options, internal = {}) {
|
|
|
1384
1477
|
return generated ? [app, ...(document ? [document] : [])] : [path.resolve(root, entryServer)];
|
|
1385
1478
|
}
|
|
1386
1479
|
async function devStylesModuleCode(environment, watchFile) {
|
|
1387
|
-
const styles = await collectDevStyleSources(environment, styleRoots(), watchFile);
|
|
1480
|
+
const styles = await collectDevStyleSources(environment, styleRoots(), watchFile, styleFilter);
|
|
1388
1481
|
if (!styles.length) return `export default '';`;
|
|
1389
1482
|
const imports = styles.map((style, index) => {
|
|
1390
1483
|
const specifier = style.url.includes('?') ? `${style.url}&inline` : `${style.url}?inline`;
|
|
@@ -1392,19 +1485,26 @@ function startServe(options, internal = {}) {
|
|
|
1392
1485
|
});
|
|
1393
1486
|
return [...imports, `const ids = ${JSON.stringify(styles.map(style => style.id))};`, `const css = [${styles.map((_, index) => `css${index}`).join(', ')}];`, `const escapeAttr = value => value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');`, `export default css.map((content, index) => {`, ` const id = escapeAttr(ids[index]);`, ` return '<style data-asset="' + id + '" data-vite-dev-id="' + id + '">' +`, ` content.replace(/<\\/(style)/gi, '<\\\\/$1') + '</style>';`, `}).join('');`].join('\n');
|
|
1394
1487
|
}
|
|
1488
|
+
function errorBoundaryImport() {
|
|
1489
|
+
return isBuild && errorBoundary ? [`import { DefaultErrorBoundary } from ${JSON.stringify(ERROR_BOUNDARY_ID)};`] : [];
|
|
1490
|
+
}
|
|
1491
|
+
function documentTree(root, wrapper) {
|
|
1492
|
+
const content = wrapper ? `<${wrapper}><${root} /></${wrapper}>` : `<${root} />`;
|
|
1493
|
+
return isBuild && errorBoundary ? [` <DefaultErrorBoundary>`, ` <Document>`, ` <DefaultErrorBoundary>`, ` ${content}`, ` </DefaultErrorBoundary>`, ` </Document>`, ` </DefaultErrorBoundary>`] : [` <Document>`, ` ${content}`, ` </Document>`];
|
|
1494
|
+
}
|
|
1395
1495
|
function generatedEntryServerCode() {
|
|
1396
1496
|
if (clientMode) {
|
|
1397
1497
|
// The client-mode shell: the document without the app. Rendered per
|
|
1398
1498
|
// request in dev (any HTML GET gets it — history-fallback semantics)
|
|
1399
1499
|
// and once at build time into dist/client/index.html. The client
|
|
1400
1500
|
// entry script is injected by the handler, exactly like SSR mode.
|
|
1401
|
-
return [`import { renderToStream } from '@solidjs/web';`, `import manifest from ${JSON.stringify(MANIFEST_ID)};`, `import Document from ${JSON.stringify(documentSpec())};`, ``, `export function render(request, context) {`, ` return renderToStream(() => <Document
|
|
1501
|
+
return [`import { renderToStream } from '@solidjs/web';`, `import manifest from ${JSON.stringify(MANIFEST_ID)};`, `import Document from ${JSON.stringify(documentSpec())};`, ...errorBoundaryImport(), ``, `export function render(request, context) {`, ` return renderToStream(() => (`, ...(isBuild && errorBoundary ? [` <DefaultErrorBoundary>`, ` <Document />`, ` </DefaultErrorBoundary>`] : [` <Document />`]), ` ), { manifest });`, `}`].join('\n');
|
|
1402
1502
|
}
|
|
1403
1503
|
const {
|
|
1404
1504
|
app
|
|
1405
1505
|
} = requireEntries();
|
|
1406
1506
|
const streamOptions = `{ manifest${serverComponents ? ', plugins: [ServerComponentPlugin]' : ''} }`;
|
|
1407
|
-
return [`import { renderToStream${setupPath ? ', getRequestEvent' : ''} } from '@solidjs/web';`, ...(serverComponents ? [`import { configureServerFunctionsServer } from '@solidjs/web/server-functions';`, `import { frameTransformDirectResult, ServerComponentPlugin } from '@solidjs/web/frames';`] : []), `import manifest from ${JSON.stringify(MANIFEST_ID)};`, `import Document from ${JSON.stringify(documentSpec())};`, `import App from ${JSON.stringify(app)};`, ...(setupPath ? [`import setup from ${JSON.stringify(setupPath)};`] : []), ``, ...(setupPath ? [`if (typeof setup !== 'function') {`, ` throw new Error('[@solidjs/vite-plugin] start.setup must default-export a function ' +`, ` '((event, App) => Component | void | Promise<...>): ' + ${JSON.stringify(options.setup)});`, `}`, ``] : []), ...(serverComponents ? [
|
|
1507
|
+
return [`import { renderToStream${setupPath ? ', getRequestEvent' : ''} } from '@solidjs/web';`, ...(serverComponents ? [`import { configureServerFunctionsServer } from '@solidjs/web/server-functions';`, `import { frameTransformDirectResult, ServerComponentPlugin } from '@solidjs/web/frames';`] : []), `import manifest from ${JSON.stringify(MANIFEST_ID)};`, `import Document from ${JSON.stringify(documentSpec())};`, `import App from ${JSON.stringify(app)};`, ...errorBoundaryImport(), ...(setupPath ? [`import setup from ${JSON.stringify(setupPath)};`] : []), ``, ...(setupPath ? [`if (typeof setup !== 'function') {`, ` throw new Error('[@solidjs/vite-plugin] start.setup must default-export a function ' +`, ` '((event, App) => Component | void | Promise<...>): ' + ${JSON.stringify(options.setup)});`, `}`, ``] : []), ...(serverComponents ? [
|
|
1408
1508
|
// Direct (in-process) server-function calls made during document
|
|
1409
1509
|
// SSR must resolve to inline-renderable components; the endpoint
|
|
1410
1510
|
// response transform is installed separately by the
|
|
@@ -1418,9 +1518,9 @@ function startServe(options, internal = {}) {
|
|
|
1418
1518
|
// the *complete* render) and buffers the stream — so it crosses
|
|
1419
1519
|
// boxed under a private key the generated handler unboxes
|
|
1420
1520
|
// (both modules are ours).
|
|
1421
|
-
`export function render(request, context) {`, ` const prepared = setup(getRequestEvent(), App);`, ` if (prepared && typeof prepared.then === 'function') {`, ` return prepared.then((component) => ({ ${STREAM_BOX}: renderApp(component || App) }));`, ` }`, ` return renderApp(prepared || App);`, `}`, ``, `function renderApp(Root) {`, ` return renderToStream(() => (`,
|
|
1521
|
+
`export function render(request, context) {`, ` const prepared = setup(getRequestEvent(), App);`, ` if (prepared && typeof prepared.then === 'function') {`, ` return prepared.then((component) => ({ ${STREAM_BOX}: renderApp(component || App) }));`, ` }`, ` return renderApp(prepared || App);`, `}`, ``, `function renderApp(Root) {`, ` return renderToStream(() => (`, ...documentTree('Root'), ` ), ${streamOptions});`, `}`] : [`export function render(request, context) {`, ` return renderToStream(() => (`, ...documentTree('App'), ` ), ${streamOptions});`, `}`])].join('\n');
|
|
1422
1522
|
}
|
|
1423
|
-
function generatedEntryClientCode() {
|
|
1523
|
+
function generatedEntryClientCode(toolbar) {
|
|
1424
1524
|
const {
|
|
1425
1525
|
app
|
|
1426
1526
|
} = requireEntries();
|
|
@@ -1430,13 +1530,13 @@ function startServe(options, internal = {}) {
|
|
|
1430
1530
|
// app cannot claim server DOM anyway. The entry script is injected
|
|
1431
1531
|
// without `async` (plain module = deferred), so document.body is
|
|
1432
1532
|
// complete when this runs.
|
|
1433
|
-
return [`import { render } from '@solidjs/web';`, `import App from ${JSON.stringify(app)};`, ``, `render(() => <App
|
|
1533
|
+
return [`import { render } from '@solidjs/web';`, ...errorBoundaryImport(), ...(toolbar ? [`import { DevToolbar } from ${JSON.stringify(DEVTOOLS_ID)};`] : []), `import App from ${JSON.stringify(app)};`, ``, `render(() => ${isBuild && errorBoundary ? '<DefaultErrorBoundary><App /></DefaultErrorBoundary>' : toolbar ? '<DevToolbar><App /></DevToolbar>' : '<App />'}, document.body);`].join('\n');
|
|
1434
1534
|
}
|
|
1435
|
-
return [`import { hydrate } from '@solidjs/web';`, ...(serverComponents ? [`import { installServerComponents } from '@solidjs/web/frames';`] : []), `import Document from ${JSON.stringify(documentSpec())};`, `import App from ${JSON.stringify(app)};`, ``, ...(serverComponents ? [
|
|
1535
|
+
return [`import { hydrate } from '@solidjs/web';`, ...(toolbar ? [`import { DevToolbar } from ${JSON.stringify(DEVTOOLS_ID)};`] : []), ...(serverComponents ? [`import { installServerComponents } from '@solidjs/web/frames';`] : []), ...errorBoundaryImport(), `import Document from ${JSON.stringify(documentSpec())};`, `import App from ${JSON.stringify(app)};`, ``, ...(serverComponents ? [
|
|
1436
1536
|
// Installs the t=0 document-adoption registry and the transport
|
|
1437
1537
|
// policy (component responses morph their boundary instead of
|
|
1438
1538
|
// decoding as data). Must run before hydrate().
|
|
1439
|
-
`installServerComponents();`, ``] : []), `hydrate(() => (`,
|
|
1539
|
+
`installServerComponents();`, ``] : []), `hydrate(() => (`, ...documentTree('App', toolbar ? 'DevToolbar' : undefined), `), document);`].join('\n');
|
|
1440
1540
|
}
|
|
1441
1541
|
|
|
1442
1542
|
// Built-in document shell: minimal, hydration-ready. The client entry
|
|
@@ -1447,6 +1547,7 @@ function startServe(options, internal = {}) {
|
|
|
1447
1547
|
// HydrationScript is covered too: the handler strips the event-capture
|
|
1448
1548
|
// script from the client-mode shell.)
|
|
1449
1549
|
const documentShellCode = [...(clientMode ? [] : [`import { HydrationScript } from '@solidjs/web';`, ``]), `export default function Document(props) {`, ` return (`, ` <html lang="en">`, ` <head>`, ` <meta charset="utf-8" />`, ` <meta name="viewport" content="width=device-width, initial-scale=1.0" />`, ...(clientMode ? [] : [` <HydrationScript />`]), ` </head>`, ` <body>{props.children}</body>`, ` </html>`, ` );`, `}`].join('\n');
|
|
1550
|
+
const errorBoundaryCode = [`import { Errored } from 'solid-js';`, `import { httpStatus, isServer } from '@solidjs/web';`, ``, `function ErrorFallback(props) {`, ` console.error(props.error());`, ` httpStatus(500);`, ` return (`, ` <span style="font-size:1.5em;text-align:center;position:fixed;left:0;bottom:55%;width:100%">`, ` {isServer ? '500 | Internal Server Error' : 'Error | Uncaught Client Exception'}`, ` </span>`, ` );`, `}`, ``, `export function DefaultErrorBoundary(props) {`, ` return (`, ` <Errored fallback={(error) => <ErrorFallback error={error} />}>`, ` {props.children}`, ` </Errored>`, ` );`, `}`].join('\n');
|
|
1450
1551
|
|
|
1451
1552
|
// The handler module: dev and prod share the render/response plumbing;
|
|
1452
1553
|
// they differ in how the client entry URL is known (baked dev URL vs a
|
|
@@ -1498,7 +1599,7 @@ function startServe(options, internal = {}) {
|
|
|
1498
1599
|
// head-open splice actively broke hydration — a script ahead of the
|
|
1499
1600
|
// authored <head> elements claims as the first walked child and drifts
|
|
1500
1601
|
// every positional claim after it.
|
|
1501
|
-
lines.push(``, `function createHtmlChunkTransform(clientEntry, extraHead) {`, ` let first = true;`, ` let injected = false;`, ` return (chunk) => {`);
|
|
1602
|
+
lines.push(``, `function escapeAttribute(value) {`, ` return value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');`, `}`, ``, `function createHtmlChunkTransform(clientEntry, extraHead, nonce) {`, ` const nonceAttr = nonce ? ' nonce="' + escapeAttribute(nonce) + '"' : '';`, ` let first = true;`, ` let injected = false;`, ` return (chunk) => {`);
|
|
1502
1603
|
if (!generated) {
|
|
1503
1604
|
// Authored entries reference the client entry by its dev path (the
|
|
1504
1605
|
// `<script src="/src/entry-client.tsx">` convention); rewrite it to
|
|
@@ -1528,7 +1629,7 @@ function startServe(options, internal = {}) {
|
|
|
1528
1629
|
// scripts default to deferred execution, which is exactly right for a
|
|
1529
1630
|
// fresh render-into-body mount (hydration, by contrast, wants to
|
|
1530
1631
|
// start as early as possible).
|
|
1531
|
-
headParts.push(`(clientEntry ? '<script type="module" src="' + clientEntry + '"${clientMode ? '' : ' async'}></' + 'script>' : '')`);
|
|
1632
|
+
headParts.push(`(clientEntry ? '<script type="module"' + nonceAttr + ' src="' + clientEntry + '"${clientMode ? '' : ' async'}></' + 'script>' : '')`);
|
|
1532
1633
|
}
|
|
1533
1634
|
if (headParts.length) {
|
|
1534
1635
|
lines.push(` chunk = chunk.replace('</head>', ${headParts.join(' + ')} + '</head>');`);
|
|
@@ -1582,7 +1683,7 @@ function startServe(options, internal = {}) {
|
|
|
1582
1683
|
// The runtime's response-head lifecycle: commit at shell flush,
|
|
1583
1684
|
// pre-flush Location as a real redirect, post-flush Location as the
|
|
1584
1685
|
// script fallback; the transform injects the doctype/head pieces.
|
|
1585
|
-
` return createSSRResponse(result, event, {`, ` responseInit: options.responseInit,`, ` nonce: options.nonce,`, ` transformChunk: createHtmlChunkTransform(clientEntry, options.devHead),`, ` });`, `}`, ``, `export async function handleRequest(request, options = {}) {`,
|
|
1686
|
+
` return createSSRResponse(result, event, {`, ` responseInit: options.responseInit,`, ` nonce: options.nonce,`, ` transformChunk: createHtmlChunkTransform(clientEntry, options.devHead, options.nonce),`, ` });`, `}`, ``, `export async function handleRequest(request, options = {}) {`,
|
|
1586
1687
|
// `options.event` is the public wrapper->event extension seam: extra
|
|
1587
1688
|
// fields (conventionally `nativeEvent`, the platform's raw request
|
|
1588
1689
|
// object) spread over the event's defaults at creation, so hosts and
|
|
@@ -1612,6 +1713,9 @@ function startServe(options, internal = {}) {
|
|
|
1612
1713
|
enforce: 'pre',
|
|
1613
1714
|
config(userConfig, env) {
|
|
1614
1715
|
root = path.resolve(userConfig.root || process.cwd());
|
|
1716
|
+
devtools = env.command === 'serve' && !env.isPreview && options.devtools !== false ? undefined : false;
|
|
1717
|
+
devtoolsResolution = undefined;
|
|
1718
|
+
devtoolsId = null;
|
|
1615
1719
|
entries = resolveEntries(root, options, clientMode);
|
|
1616
1720
|
middlewarePath = options.middleware ? path.resolve(root, normalizeUserPath(root, options.middleware, 'middleware')) : null;
|
|
1617
1721
|
// Server-mode only, like `entryServer`/`external` (a documented
|
|
@@ -1730,7 +1834,15 @@ function startServe(options, internal = {}) {
|
|
|
1730
1834
|
}
|
|
1731
1835
|
} : {}),
|
|
1732
1836
|
optimizeDeps: {
|
|
1733
|
-
entries: scanEntries
|
|
1837
|
+
entries: scanEntries,
|
|
1838
|
+
// Like the refresh runtime in the main plugin: the toolbar
|
|
1839
|
+
// graph is injected behind virtual modules the scanner never
|
|
1840
|
+
// crawls, so pre-bundle it (and the server-functions runtime
|
|
1841
|
+
// the virtual module pulls in) up front — first-request
|
|
1842
|
+
// discovery would re-optimize and full-reload the page.
|
|
1843
|
+
...(devtools === undefined && devtoolsReachableFromRoot(root) ? {
|
|
1844
|
+
include: [DEVTOOLS_PACKAGE, '@solidjs/web/server-functions']
|
|
1845
|
+
} : {})
|
|
1734
1846
|
}
|
|
1735
1847
|
})
|
|
1736
1848
|
};
|
|
@@ -1740,7 +1852,7 @@ function startServe(options, internal = {}) {
|
|
|
1740
1852
|
base = config.base;
|
|
1741
1853
|
isBuild = config.command === 'build';
|
|
1742
1854
|
},
|
|
1743
|
-
resolveId(source) {
|
|
1855
|
+
resolveId(source, importer) {
|
|
1744
1856
|
if (source === HANDLER_ID) {
|
|
1745
1857
|
return {
|
|
1746
1858
|
id: HANDLER_ID,
|
|
@@ -1753,33 +1865,94 @@ function startServe(options, internal = {}) {
|
|
|
1753
1865
|
moduleSideEffects: true
|
|
1754
1866
|
};
|
|
1755
1867
|
}
|
|
1756
|
-
if (source === ENTRY_SERVER_ID || source === ENTRY_CLIENT_ID || source === DOCUMENT_ID) {
|
|
1868
|
+
if (source === ENTRY_SERVER_ID || source === ENTRY_CLIENT_ID || source === DOCUMENT_ID || source === ERROR_BOUNDARY_ID) {
|
|
1757
1869
|
return {
|
|
1758
1870
|
id: source,
|
|
1759
1871
|
moduleSideEffects: source === ENTRY_CLIENT_ID
|
|
1760
1872
|
};
|
|
1761
1873
|
}
|
|
1874
|
+
if (devtools && (source === DEVTOOLS_ID || source === DEVTOOLS_MOUNT_ID)) {
|
|
1875
|
+
return {
|
|
1876
|
+
id: source,
|
|
1877
|
+
moduleSideEffects: true
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
// The virtual devtools modules import the package by its bare name,
|
|
1881
|
+
// but a virtual importer gives Vite no directory to walk, so the
|
|
1882
|
+
// specifier would only resolve from the Vite root — which fails in
|
|
1883
|
+
// pnpm-isolated apps where the package is not a root-level install.
|
|
1884
|
+
// Delegate to the resolution captured at detection time instead.
|
|
1885
|
+
if (devtoolsId && source === DEVTOOLS_PACKAGE && (importer === DEVTOOLS_ID || importer === DEVTOOLS_MOUNT_ID)) {
|
|
1886
|
+
return {
|
|
1887
|
+
id: devtoolsId
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1762
1890
|
return null;
|
|
1763
1891
|
},
|
|
1764
1892
|
async load(id, opts) {
|
|
1893
|
+
const consumer = getEnvironmentConsumer(this.environment, opts);
|
|
1765
1894
|
if (id === HANDLER_ID) {
|
|
1766
|
-
if (
|
|
1895
|
+
if (consumer !== 'server') {
|
|
1767
1896
|
this.error(`${HANDLER_ID} is server-only; import it from server code (SSR build).`);
|
|
1768
1897
|
}
|
|
1769
1898
|
const externalDev = !isBuild && this.environment.mode === 'dev' && (externalServer || !isRunnableEnvironment(this.environment));
|
|
1770
1899
|
return handlerModuleCode(externalDev);
|
|
1771
1900
|
}
|
|
1772
1901
|
if (id === RESOLVED_DEV_STYLES_ID) {
|
|
1773
|
-
if (
|
|
1902
|
+
if (consumer !== 'server' || this.environment.mode !== 'dev') {
|
|
1774
1903
|
this.error(`${DEV_STYLES_ID} is only available to the development server handler.`);
|
|
1775
1904
|
}
|
|
1776
1905
|
return devStylesModuleCode(this.environment, file => this.addWatchFile(file));
|
|
1777
1906
|
}
|
|
1778
1907
|
if (id === ENTRY_SERVER_ID) return generatedEntryServerCode();
|
|
1779
|
-
if (id === ENTRY_CLIENT_ID)
|
|
1908
|
+
if (id === ENTRY_CLIENT_ID) {
|
|
1909
|
+
const toolbar = await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1910
|
+
skipSelf: true
|
|
1911
|
+
}), requireEntries().app);
|
|
1912
|
+
return generatedEntryClientCode(toolbar);
|
|
1913
|
+
}
|
|
1780
1914
|
if (id === DOCUMENT_ID) return documentShellCode;
|
|
1915
|
+
if (id === ERROR_BOUNDARY_ID) return errorBoundaryCode;
|
|
1916
|
+
if (id === DEVTOOLS_ID || id === DEVTOOLS_MOUNT_ID) {
|
|
1917
|
+
// A cold direct request (stale tab reload) can reach the virtual
|
|
1918
|
+
// module before the entry has triggered detection — run it here so
|
|
1919
|
+
// first-touch order doesn't matter.
|
|
1920
|
+
if (!isBuild && consumer === 'client' && devtools === undefined) {
|
|
1921
|
+
const {
|
|
1922
|
+
app,
|
|
1923
|
+
entryClient
|
|
1924
|
+
} = requireEntries();
|
|
1925
|
+
await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1926
|
+
skipSelf: true
|
|
1927
|
+
}), app ?? path.resolve(root, entryClient));
|
|
1928
|
+
}
|
|
1929
|
+
if (isBuild || !devtools || consumer !== 'client') {
|
|
1930
|
+
this.error(`${id} is only available to the development client.`);
|
|
1931
|
+
}
|
|
1932
|
+
return id === DEVTOOLS_ID ? devtoolsModuleCode() : devtoolsMountModuleCode();
|
|
1933
|
+
}
|
|
1781
1934
|
return null;
|
|
1782
1935
|
},
|
|
1936
|
+
async transform(code, id, opts) {
|
|
1937
|
+
if (isBuild || devtools === false) return null;
|
|
1938
|
+
const current = requireEntries();
|
|
1939
|
+
if (current.generated || getEnvironmentConsumer(this.environment, opts) !== 'client') {
|
|
1940
|
+
return null;
|
|
1941
|
+
}
|
|
1942
|
+
// Module ids are always forward-slashed; normalize the path.resolve
|
|
1943
|
+
// side too so the comparison holds on Windows.
|
|
1944
|
+
if (normalizePath(id.split('?')[0]) !== normalizePath(path.resolve(root, current.entryClient))) {
|
|
1945
|
+
return null;
|
|
1946
|
+
}
|
|
1947
|
+
const toolbar = await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1948
|
+
skipSelf: true
|
|
1949
|
+
}), id);
|
|
1950
|
+
if (!toolbar) return null;
|
|
1951
|
+
return {
|
|
1952
|
+
code: `import ${JSON.stringify(DEVTOOLS_MOUNT_ID)};\n${code}`,
|
|
1953
|
+
map: null
|
|
1954
|
+
};
|
|
1955
|
+
},
|
|
1783
1956
|
configurePreviewServer(server) {
|
|
1784
1957
|
// `vite build && vite preview` runs the production artifact as-is:
|
|
1785
1958
|
// Vite's preview statics serve dist/client (see the config hook) and
|
|
@@ -1804,7 +1977,7 @@ function startServe(options, internal = {}) {
|
|
|
1804
1977
|
// server-function endpoint) and hands the URL to application
|
|
1805
1978
|
// code, so restore the base — the deployed production handler
|
|
1806
1979
|
// receives base-prefixed URLs and preview must match it.
|
|
1807
|
-
const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/')),
|
|
1980
|
+
const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/'), res),
|
|
1808
1981
|
// Same event extension the dev middleware and a production
|
|
1809
1982
|
// Node entry pass: the raw Node request as `nativeEvent`.
|
|
1810
1983
|
{
|
|
@@ -1855,13 +2028,13 @@ function startServe(options, internal = {}) {
|
|
|
1855
2028
|
// Loaded through the SSR environment so the app, the request
|
|
1856
2029
|
// event storage, and the handler share one module registry.
|
|
1857
2030
|
const handler = await server.ssrLoadModule(HANDLER_ID);
|
|
1858
|
-
const styles = pageRequest ? await collectDevStyles(server, styleRoots()) : [];
|
|
2031
|
+
const styles = pageRequest ? await collectDevStyles(server, styleRoots(), styleFilter) : [];
|
|
1859
2032
|
const devHead = styles.map(renderDevStyleTag).join('');
|
|
1860
2033
|
// Post middlewares run after Vite's base middleware stripped
|
|
1861
2034
|
// the configured `base` from req.url; restore it so the app
|
|
1862
2035
|
// sees the same URLs in dev as in production (where the
|
|
1863
2036
|
// deployed handler receives base-prefixed requests).
|
|
1864
|
-
const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/')), {
|
|
2037
|
+
const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/'), res), {
|
|
1865
2038
|
devHead,
|
|
1866
2039
|
pageRequest,
|
|
1867
2040
|
// The raw Node request on the event, matching what a
|
|
@@ -2544,6 +2717,7 @@ const LAZY_PLACEHOLDER_PREFIX = '__SOLID_LAZY_MODULE__:';
|
|
|
2544
2717
|
const REFRESH_RUNTIME_SOURCE = 'solid-js/refresh';
|
|
2545
2718
|
const viteVersionMajor = +version.split('.')[0];
|
|
2546
2719
|
const isVite8 = viteVersionMajor >= 8;
|
|
2720
|
+
const DEFAULT_STYLE_EXCLUDE = /node_modules/;
|
|
2547
2721
|
const VIRTUAL_MANIFEST_ID = 'virtual:solid-manifest';
|
|
2548
2722
|
const RESOLVED_VIRTUAL_MANIFEST_ID = '\0' + VIRTUAL_MANIFEST_ID;
|
|
2549
2723
|
|
|
@@ -2798,10 +2972,30 @@ function solidPlugin(options = {}) {
|
|
|
2798
2972
|
// `start: true` is sugar for the empty options bag — one start mode,
|
|
2799
2973
|
// two spellings — so normalize here and let everything downstream see a
|
|
2800
2974
|
// single shape (`false` behaves exactly like omission).
|
|
2801
|
-
const
|
|
2975
|
+
const startOptions = options.start === true ? {} : options.start || null;
|
|
2976
|
+
const styleFilterOptions = startOptions?.css?.filter;
|
|
2977
|
+
// The CSS crawl walks the module graph from the app's own entries, so a
|
|
2978
|
+
// plain createFilter allowlist can't express the option's purpose (opting
|
|
2979
|
+
// node_modules graphs in): a bare `include` would reject the app sources
|
|
2980
|
+
// the crawl has to traverse to ever reach the included package. Instead
|
|
2981
|
+
// `include` rescues files on top of the baseline (everything except
|
|
2982
|
+
// `exclude`, which defaults to node_modules), while a file matching both
|
|
2983
|
+
// patterns stays excluded — createFilter's own conflict rule.
|
|
2984
|
+
const createStyleFilter = resolve => {
|
|
2985
|
+
const opts = resolve === undefined ? undefined : {
|
|
2986
|
+
resolve
|
|
2987
|
+
};
|
|
2988
|
+
const base = createFilter(undefined, styleFilterOptions?.exclude ?? DEFAULT_STYLE_EXCLUDE, opts);
|
|
2989
|
+
const include = styleFilterOptions?.include;
|
|
2990
|
+
const hasInclude = include != null && (!Array.isArray(include) || include.length > 0);
|
|
2991
|
+
const included = hasInclude ? createFilter(include, styleFilterOptions?.exclude, opts) : null;
|
|
2992
|
+
return id => base(id) || (included ? included(id) : false);
|
|
2993
|
+
};
|
|
2994
|
+
let styleFilter = createStyleFilter();
|
|
2995
|
+
const filterDevStyles = id => styleFilter(id);
|
|
2802
2996
|
// `start.external` only means something when a server side exists to hand
|
|
2803
2997
|
// over (SSR start mode); in client mode it is a documented no-op.
|
|
2804
|
-
const externalDevServer = !!options.ssr && !!
|
|
2998
|
+
const externalDevServer = !!options.ssr && !!startOptions?.external;
|
|
2805
2999
|
let needHmr = false;
|
|
2806
3000
|
let replaceDev = false;
|
|
2807
3001
|
// The live dev server, kept so the dev manifest module can bake the bridge
|
|
@@ -3079,6 +3273,7 @@ function solidPlugin(options = {}) {
|
|
|
3079
3273
|
filter = createFilter(options.include, options.exclude, {
|
|
3080
3274
|
resolve: projectRoot
|
|
3081
3275
|
});
|
|
3276
|
+
styleFilter = createStyleFilter(projectRoot);
|
|
3082
3277
|
if (serverComponents && !(options.start && options.ssr)) {
|
|
3083
3278
|
config.logger.warn('[@solidjs/vite-plugin] serverFunctions.components is set without SSR start mode (the `start` ' + 'option with `ssr: true`), so the plugin only installs the endpoint response transform ' + '(server functions returning components stream correctly). The document wiring — render ' + 'plugin, bootstrap script, and the client-side installServerComponents() call — is ' + "emitted by SSR start mode's generated entries; without it, server components only mount " + 'from post-boot streams and your client code must call installServerComponents() itself.');
|
|
3084
3279
|
}
|
|
@@ -3092,7 +3287,7 @@ function solidPlugin(options = {}) {
|
|
|
3092
3287
|
// that don't share globals with this process, through the HTTP bridge
|
|
3093
3288
|
// endpoint the middleware serves.
|
|
3094
3289
|
if (options.ssr || options.start) {
|
|
3095
|
-
registerDevAssetResolver(server.config.root, createDevAssetResolver(server));
|
|
3290
|
+
registerDevAssetResolver(server.config.root, createDevAssetResolver(server, filterDevStyles));
|
|
3096
3291
|
installDevManifestBridge(server);
|
|
3097
3292
|
}
|
|
3098
3293
|
if (!needHmr) return;
|
|
@@ -3208,7 +3403,7 @@ function solidPlugin(options = {}) {
|
|
|
3208
3403
|
}
|
|
3209
3404
|
},
|
|
3210
3405
|
async transform(source, id, transformOptions) {
|
|
3211
|
-
const isSsr = transformOptions
|
|
3406
|
+
const isSsr = getEnvironmentConsumer(this.environment, transformOptions) === 'server';
|
|
3212
3407
|
const currentFileExtension = getExtension(id);
|
|
3213
3408
|
const extensionsToWatch = options.extensions || [];
|
|
3214
3409
|
const allExtensions = extensionsToWatch.map(extension =>
|
|
@@ -3348,7 +3543,7 @@ function solidPlugin(options = {}) {
|
|
|
3348
3543
|
// With start mode on (either variant), the dev middleware dispatches
|
|
3349
3544
|
// the endpoint through the SSR handler so user middleware and the
|
|
3350
3545
|
// stub-backed request event front it exactly like page SSR.
|
|
3351
|
-
...(
|
|
3546
|
+
...(startOptions ? {
|
|
3352
3547
|
ssrHandler: SSR_HANDLER_ID
|
|
3353
3548
|
} : {})
|
|
3354
3549
|
}), mainPlugin] : [boundaryModules(), mainPlugin];
|
|
@@ -3356,15 +3551,16 @@ function solidPlugin(options = {}) {
|
|
|
3356
3551
|
// The `start` option opts into start-mode serving on top of the transforms;
|
|
3357
3552
|
// the `ssr` boolean picks the mode (a bare `ssr: true` keeps the
|
|
3358
3553
|
// historical transform-only behavior).
|
|
3359
|
-
if (
|
|
3554
|
+
if (startOptions) {
|
|
3360
3555
|
plugins.push(
|
|
3361
3556
|
// Typed env (`start.env`) rides both start modes: config-time
|
|
3362
3557
|
// validation, the virtual:env/{server,client} modules, generated
|
|
3363
3558
|
// types, and the client-bundle leak scan.
|
|
3364
|
-
...startEnv(
|
|
3559
|
+
...startEnv(startOptions.env), ...startServe(startOptions, {
|
|
3365
3560
|
serverFunctions: !!options.serverFunctions,
|
|
3366
3561
|
serverComponents,
|
|
3367
|
-
ssr: !!options.ssr
|
|
3562
|
+
ssr: !!options.ssr,
|
|
3563
|
+
styleFilter: filterDevStyles
|
|
3368
3564
|
}));
|
|
3369
3565
|
}
|
|
3370
3566
|
|