@solidjs/vite-plugin 3.0.0-next.30 → 3.0.0-next.32
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 +77 -10
- package/dist/cjs/index.cjs +269 -119
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.mjs +269 -119
- 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 +3 -0
- package/dist/types/src/environment.d.ts +5 -1
- package/dist/types/src/ssr/index.d.ts +42 -1
- package/package.json +13 -5
package/dist/cjs/index.cjs
CHANGED
|
@@ -177,6 +177,7 @@ function joinBase(base, pathname) {
|
|
|
177
177
|
* dynamically imported modules register their own styles when they render.
|
|
178
178
|
*/
|
|
179
179
|
|
|
180
|
+
const defaultStyleFilter = id => !id.includes('node_modules');
|
|
180
181
|
// The resolver is created plugin-side (it closes over the dev server) but is
|
|
181
182
|
// called from the SSR module runner, which only shares `globalThis` with the
|
|
182
183
|
// plugin when it runs in-process (the default). The primary channel is a
|
|
@@ -320,13 +321,15 @@ async function getModuleNode(env, file, importer) {
|
|
|
320
321
|
return;
|
|
321
322
|
}
|
|
322
323
|
}
|
|
323
|
-
async function collectModuleDeps(env, file, deps, crawled, onFile, importer) {
|
|
324
|
+
async function collectModuleDeps(env, file, deps, crawled, filter, onFile, importer) {
|
|
324
325
|
crawled.add(file);
|
|
325
326
|
const node = await getModuleNode(env, file, importer);
|
|
326
327
|
if (!node?.id || deps.has(node)) return;
|
|
327
328
|
deps.add(node);
|
|
328
|
-
|
|
329
|
-
if (
|
|
329
|
+
const isCss = cssFileRegExp.test(node.url.split('?')[0]);
|
|
330
|
+
if (!isCss && node.file && !node.id.startsWith('\0') && !filter(node.file)) return;
|
|
331
|
+
if (node.file) onFile?.(node.file);
|
|
332
|
+
if (isCss) return;
|
|
330
333
|
if (!node.transformResult) {
|
|
331
334
|
await env.transformRequest(node.url).catch(() => {});
|
|
332
335
|
}
|
|
@@ -337,7 +340,7 @@ async function collectModuleDeps(env, file, deps, crawled, onFile, importer) {
|
|
|
337
340
|
// from dynamicDeps — dynamic imports load their own styles when rendered.
|
|
338
341
|
for (const dep of directDeps) {
|
|
339
342
|
if (crawled.has(dep)) continue;
|
|
340
|
-
await collectModuleDeps(env, dep, deps, crawled, onFile, node.id);
|
|
343
|
+
await collectModuleDeps(env, dep, deps, crawled, filter, onFile, node.id);
|
|
341
344
|
}
|
|
342
345
|
}
|
|
343
346
|
function injectQuery(url, query) {
|
|
@@ -345,11 +348,11 @@ function injectQuery(url, query) {
|
|
|
345
348
|
}
|
|
346
349
|
|
|
347
350
|
/** Discovers ambient CSS in an entry graph without choosing how it is transported. */
|
|
348
|
-
async function collectDevStyleSources(env, files, onFile) {
|
|
351
|
+
async function collectDevStyleSources(env, files, onFile, filter = defaultStyleFilter) {
|
|
349
352
|
const deps = new Set();
|
|
350
353
|
const crawled = new Set();
|
|
351
354
|
for (const file of files) {
|
|
352
|
-
await collectModuleDeps(env, file, deps, crawled, onFile);
|
|
355
|
+
await collectModuleDeps(env, file, deps, crawled, filter, onFile);
|
|
353
356
|
}
|
|
354
357
|
const css = [];
|
|
355
358
|
const seen = new Set();
|
|
@@ -376,11 +379,11 @@ async function collectDevStyleSources(env, files, onFile) {
|
|
|
376
379
|
* CSS into `<head>` so server-painted content is styled from the first byte
|
|
377
380
|
* (no FOUC while waiting for Vite's client-side style injection).
|
|
378
381
|
*/
|
|
379
|
-
async function collectDevStyles(server, files) {
|
|
382
|
+
async function collectDevStyles(server, files, filter = defaultStyleFilter) {
|
|
380
383
|
const ssrEnv = server.environments?.ssr;
|
|
381
384
|
const clientEnv = server.environments?.client;
|
|
382
385
|
if (!ssrEnv || !clientEnv) return [];
|
|
383
|
-
const sources = await collectDevStyleSources(ssrEnv, files.map(file => path.resolve(server.config.root, file)));
|
|
386
|
+
const sources = await collectDevStyleSources(ssrEnv, files.map(file => path.resolve(server.config.root, file)), undefined, filter);
|
|
384
387
|
const css = [];
|
|
385
388
|
for (const source of sources) {
|
|
386
389
|
// `?direct` yields the compiled stylesheet text (what Vite serves for
|
|
@@ -436,7 +439,7 @@ function devModuleUrl(root, base, key) {
|
|
|
436
439
|
// drive letter on Windows: /@fs/C:/…).
|
|
437
440
|
return joinBase(base, '/@fs/' + absolute.replace(/^\//, '') + query);
|
|
438
441
|
}
|
|
439
|
-
function createDevAssetResolver(server) {
|
|
442
|
+
function createDevAssetResolver(server, filter = defaultStyleFilter) {
|
|
440
443
|
// Server-side lazy() re-requests a module's assets on every retry of a
|
|
441
444
|
// suspended render pass (retries re-create the component). The build
|
|
442
445
|
// manifest answers those repeats synchronously and the pass converges; an
|
|
@@ -470,7 +473,7 @@ function createDevAssetResolver(server) {
|
|
|
470
473
|
// The module's dev URL doubles as its client entry: modulepreload
|
|
471
474
|
// hint and hydration module-map value.
|
|
472
475
|
const js = [devModuleUrl(root, base, key)];
|
|
473
|
-
const css = await collectDevStyles(server, [key]);
|
|
476
|
+
const css = await collectDevStyles(server, [key], filter);
|
|
474
477
|
return {
|
|
475
478
|
js,
|
|
476
479
|
css
|
|
@@ -525,18 +528,18 @@ function boundaryModules() {
|
|
|
525
528
|
// import graph — no directive transforms have run, so it walks
|
|
526
529
|
// straight through 'use server' modules into genuinely server-only
|
|
527
530
|
// code. That graph is legal once transforms split it, so the guard
|
|
528
|
-
// must not fire on the scan pass (`options.scan`,
|
|
529
|
-
// scanner
|
|
530
|
-
// scanner in v8). Still claim the specifier: resolving to the empty
|
|
531
|
+
// must not fire on the scan pass (`options.scan`, set by Rolldown's
|
|
532
|
+
// dependency scanner). Still claim the specifier: resolving to the empty
|
|
531
533
|
// virtual module keeps the scanner from chasing `server-only` /
|
|
532
534
|
// `client-only` as missing bare dependencies, which would abort the
|
|
533
535
|
// scan all the same. Real dev/build module graphs resolve without
|
|
534
536
|
// the flag and stay fully guarded.
|
|
535
537
|
const scan = !!options?.scan;
|
|
538
|
+
const server = this.environment.config.consumer === 'server';
|
|
536
539
|
if (id === 'server-only') {
|
|
537
|
-
if (!
|
|
540
|
+
if (!server && !scan) this.error(`[@solidjs/vite-plugin] Attempt to import 'server-only' in a client module: ${importer}. ` + `Code that uses this module must run only on the server — make sure it is only ` + `imported by server code (e.g. a server entry, a "use server" module, or code ` + `reached exclusively from them).`);
|
|
538
541
|
} else if (id === 'client-only') {
|
|
539
|
-
if (
|
|
542
|
+
if (server && !scan) this.error(`[@solidjs/vite-plugin] Attempt to import 'client-only' in a server module: ${importer}. ` + `Code that uses this module must run only in the browser — make sure it is only ` + `imported by client code (e.g. behind a client-only lazy boundary).`);
|
|
540
543
|
} else {
|
|
541
544
|
return null;
|
|
542
545
|
}
|
|
@@ -563,6 +566,11 @@ function boundaryModules() {
|
|
|
563
566
|
function isRunnableEnvironment(environment) {
|
|
564
567
|
return !!environment && typeof environment === 'object' && 'runner' in environment;
|
|
565
568
|
}
|
|
569
|
+
function getEnvironmentConsumer(environment, options) {
|
|
570
|
+
const consumer = environment?.config?.consumer;
|
|
571
|
+
if (consumer === 'client' || consumer === 'server') return consumer;
|
|
572
|
+
return options?.ssr ? 'server' : 'client';
|
|
573
|
+
}
|
|
566
574
|
|
|
567
575
|
// The `"use server"` directive compiler. This wraps the native
|
|
568
576
|
// `transformDirectives` pass from @dom-expressions/compiler (Rust/Oxc); the
|
|
@@ -834,8 +842,6 @@ function invalidateModule(moduleGraph, path) {
|
|
|
834
842
|
}
|
|
835
843
|
}
|
|
836
844
|
function invalidateModules(server, result, manifest) {
|
|
837
|
-
// `environments` requires Vite 6+; older versions just miss the eager
|
|
838
|
-
// manifest invalidation (the debounced reload still converges).
|
|
839
845
|
if (server?.environments && result.invalidPreload) {
|
|
840
846
|
invalidateModule(server.environments.client.moduleGraph, manifest);
|
|
841
847
|
invalidateModule(server.environments.ssr.moduleGraph, manifest);
|
|
@@ -999,12 +1005,12 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
999
1005
|
const relative = path.relative(root, entry).split(path.sep).join('/');
|
|
1000
1006
|
return relative.startsWith('..') ? '/@fs/' + entry : '/' + relative;
|
|
1001
1007
|
}
|
|
1002
|
-
const
|
|
1008
|
+
const startPlugins = [{
|
|
1003
1009
|
name: 'solid:server-functions/handler',
|
|
1004
1010
|
enforce: 'pre',
|
|
1005
1011
|
resolveId(source, _importer, opts) {
|
|
1006
1012
|
if (source === HANDLER_ID$1) {
|
|
1007
|
-
if (
|
|
1013
|
+
if (getEnvironmentConsumer(this.environment, opts) !== 'server') {
|
|
1008
1014
|
this.error(`${HANDLER_ID$1} is server-only; import it from your server entry (SSR build).`);
|
|
1009
1015
|
}
|
|
1010
1016
|
return {
|
|
@@ -1015,7 +1021,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1015
1021
|
return null;
|
|
1016
1022
|
},
|
|
1017
1023
|
load(id, opts) {
|
|
1018
|
-
if (id === HANDLER_ID$1 && opts
|
|
1024
|
+
if (id === HANDLER_ID$1 && getEnvironmentConsumer(this.environment, opts) === 'server') {
|
|
1019
1025
|
const externalDev = this.environment.mode === 'dev' && (internal.externalDevServer || !isRunnableEnvironment(this.environment));
|
|
1020
1026
|
return handlerModuleCode(isBuild || externalDev);
|
|
1021
1027
|
}
|
|
@@ -1023,12 +1029,12 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1023
1029
|
}
|
|
1024
1030
|
}];
|
|
1025
1031
|
if (installDevMiddleware) {
|
|
1026
|
-
|
|
1032
|
+
startPlugins.push({
|
|
1027
1033
|
name: 'solid:server-functions/dev-middleware',
|
|
1028
1034
|
apply: 'serve',
|
|
1029
1035
|
configureServer(server) {
|
|
1030
1036
|
const ssrEnvironment = server.environments.ssr;
|
|
1031
|
-
if (internal.externalDevServer ||
|
|
1037
|
+
if (internal.externalDevServer || !isRunnableEnvironment(ssrEnvironment)) {
|
|
1032
1038
|
return;
|
|
1033
1039
|
}
|
|
1034
1040
|
server.middlewares.use((req, res, next) => {
|
|
@@ -1051,7 +1057,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1051
1057
|
const functionId = (typeof headerId === 'string' ? headerId.split('#')[0] : undefined) || url.searchParams.get('id');
|
|
1052
1058
|
if (functionId) {
|
|
1053
1059
|
const entry = moduleForFunctionId(functionId);
|
|
1054
|
-
if (entry) await
|
|
1060
|
+
if (entry) await ssrEnvironment.runner.import(moduleDevUrl(entry));
|
|
1055
1061
|
}
|
|
1056
1062
|
// Dispatch through a module evaluated in the SSR environment so
|
|
1057
1063
|
// the handler shares the registry instance with the app modules.
|
|
@@ -1059,7 +1065,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1059
1065
|
// in, and dispatch goes through `handleRequest` instead — one
|
|
1060
1066
|
// middleware chain and one stub-backed request event front the
|
|
1061
1067
|
// endpoint exactly as they front page SSR.
|
|
1062
|
-
const handler = await
|
|
1068
|
+
const handler = await ssrEnvironment.runner.import(internal.ssrHandler ?? HANDLER_ID$1);
|
|
1063
1069
|
// Both dispatch shapes carry the raw Node request on the event
|
|
1064
1070
|
// (the `options.event` seam), matching the SSR dev middleware
|
|
1065
1071
|
// and what a production Node entry passes.
|
|
@@ -1071,7 +1077,6 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1071
1077
|
const response = internal.ssrHandler ? await handler.handleRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions) : await handler.handleServerFunctionRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions);
|
|
1072
1078
|
await sendWebResponse(res, response);
|
|
1073
1079
|
})().catch(error => {
|
|
1074
|
-
if (error instanceof Error) server.ssrFixStacktrace(error);
|
|
1075
1080
|
next(error);
|
|
1076
1081
|
});
|
|
1077
1082
|
});
|
|
@@ -1135,7 +1140,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1135
1140
|
return null;
|
|
1136
1141
|
},
|
|
1137
1142
|
async load(id, opts) {
|
|
1138
|
-
const mode = opts
|
|
1143
|
+
const mode = getEnvironmentConsumer(this.environment, opts);
|
|
1139
1144
|
if (id === manifestId) {
|
|
1140
1145
|
if (isBuild && mode === 'server') {
|
|
1141
1146
|
// Merge the client build's persisted discoveries at load time,
|
|
@@ -1158,7 +1163,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1158
1163
|
name: 'solid:server-functions/compiler',
|
|
1159
1164
|
enforce: 'pre',
|
|
1160
1165
|
async transform(code, fileId, opts) {
|
|
1161
|
-
const mode = opts
|
|
1166
|
+
const mode = getEnvironmentConsumer(this.environment, opts);
|
|
1162
1167
|
const [id] = fileId.split('?');
|
|
1163
1168
|
if (!filter(id)) {
|
|
1164
1169
|
return null;
|
|
@@ -1191,7 +1196,13 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1191
1196
|
}
|
|
1192
1197
|
return null;
|
|
1193
1198
|
}
|
|
1194
|
-
}, ...
|
|
1199
|
+
}, ...startPlugins];
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
const DEVTOOLS_PACKAGE = '@solidjs/start-devtools';
|
|
1203
|
+
const DEVTOOLS_MOUNT_ID = 'virtual:solid-devtools/mount';
|
|
1204
|
+
function devtoolsMountModuleCode() {
|
|
1205
|
+
return [`import { mountDevToolbar } from '${DEVTOOLS_PACKAGE}';`, `mountDevToolbar();`].join('\n');
|
|
1195
1206
|
}
|
|
1196
1207
|
|
|
1197
1208
|
// Start-mode serving for plain Vite apps: `solid({ start: {...} })` (or the
|
|
@@ -1208,7 +1219,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1208
1219
|
// Both paths inject the Vite client, dev style patch, and entry CSS as
|
|
1209
1220
|
// `<style data-vite-dev-id>` tags before the body can paint.
|
|
1210
1221
|
// - Prod: the plugin configures a full-app build (client + server bundles
|
|
1211
|
-
// via the Vite
|
|
1222
|
+
// via the Vite environments/builder API — a single `vite build` builds
|
|
1212
1223
|
// both) whose server entry is `virtual:solid-ssr-handler`: an
|
|
1213
1224
|
// adapter-agnostic named `handleRequest(Request) => Promise<Response>` plus
|
|
1214
1225
|
// a default Fetchable `{ fetch(request) }` export. Both scope each request
|
|
@@ -1276,6 +1287,7 @@ const RESOLVED_DEV_STYLES_ID = '\0' + DEV_STYLES_ID;
|
|
|
1276
1287
|
const ENTRY_SERVER_ID = 'virtual:solid-ssr-entry-server.tsx';
|
|
1277
1288
|
const ENTRY_CLIENT_ID = 'virtual:solid-ssr-entry-client.tsx';
|
|
1278
1289
|
const DOCUMENT_ID = 'virtual:solid-ssr-document.tsx';
|
|
1290
|
+
const ERROR_BOUNDARY_ID = 'virtual:solid-ssr-error-boundary.tsx';
|
|
1279
1291
|
const MANIFEST_ID = 'virtual:solid-manifest';
|
|
1280
1292
|
const SERVER_FUNCTION_HANDLER_ID = 'virtual:solid-server-function-handler';
|
|
1281
1293
|
const STORAGE_SOURCE = '@solidjs/web/storage';
|
|
@@ -1387,6 +1399,11 @@ function startServe(options, internal = {}) {
|
|
|
1387
1399
|
// the server-function handler module either way). Everything is gated
|
|
1388
1400
|
// codegen: with the option off, none of these imports exist anywhere.
|
|
1389
1401
|
const serverComponents = !!internal.serverComponents;
|
|
1402
|
+
const errorBoundary = options.errorBoundary !== false;
|
|
1403
|
+
const styleFilter = internal.styleFilter;
|
|
1404
|
+
let devtoolsEnabled = false;
|
|
1405
|
+
let devtoolsResolutions = {};
|
|
1406
|
+
let devtoolsIds = {};
|
|
1390
1407
|
// `external` is server-mode-only (documented no-op in client mode, so a
|
|
1391
1408
|
// host-integrated config survives the `ssr` boolean flip untouched).
|
|
1392
1409
|
const externalServer = !clientMode && !!options.external;
|
|
@@ -1403,6 +1420,65 @@ function startServe(options, internal = {}) {
|
|
|
1403
1420
|
if (!entries) throw new Error('[@solidjs/vite-plugin] SSR entries not resolved yet');
|
|
1404
1421
|
return entries;
|
|
1405
1422
|
}
|
|
1423
|
+
async function resolveDevtools(resolve, importer, consumer) {
|
|
1424
|
+
if (!devtoolsEnabled) return false;
|
|
1425
|
+
// Detect from the app graph first (the documented install location), then
|
|
1426
|
+
// from the plugin's own file: in pnpm-isolated apps a copy that is only a
|
|
1427
|
+
// dependency of the plugin is not reachable from the app's importers. The
|
|
1428
|
+
// resolved id is kept so imports from generated modules can use it.
|
|
1429
|
+
devtoolsResolutions[consumer] ??= (async () => {
|
|
1430
|
+
// Resolving from the plugin's own file never yields null when the
|
|
1431
|
+
// package is absent: it is declared an optional peer dependency, so
|
|
1432
|
+
// Vite answers with its `__vite-optional-peer-dep:` stub (an empty
|
|
1433
|
+
// module). Treat that stub as "not installed".
|
|
1434
|
+
const realId = resolved => resolved && !resolved.id.startsWith('__vite-optional-peer-dep:') ? resolved.id : null;
|
|
1435
|
+
return realId(await resolve(DEVTOOLS_PACKAGE, importer)) ?? realId(await resolve(DEVTOOLS_PACKAGE, node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))));
|
|
1436
|
+
})();
|
|
1437
|
+
const id = await devtoolsResolutions[consumer];
|
|
1438
|
+
devtoolsIds[consumer] = id;
|
|
1439
|
+
if (!id && options.devtools === true) {
|
|
1440
|
+
throw new Error('[@solidjs/vite-plugin] start.devtools requires @solidjs/start-devtools. ' + 'Install it as a development dependency or set start.devtools to false.');
|
|
1441
|
+
}
|
|
1442
|
+
return id !== null;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
/**
|
|
1446
|
+
* Cheap walk-up probe mirroring how the optimizer resolves bare
|
|
1447
|
+
* `optimizeDeps.include` entries: is @solidjs/start-devtools reachable from
|
|
1448
|
+
* this directory? Detection proper (resolveDevtools) runs later with a real
|
|
1449
|
+
* importer; this only decides whether the toolbar graph can be pre-bundled
|
|
1450
|
+
* at scan time.
|
|
1451
|
+
*/
|
|
1452
|
+
function devtoolsReachableFrom(dir) {
|
|
1453
|
+
for (let current = dir;;) {
|
|
1454
|
+
if (fs.existsSync(path.join(current, 'node_modules', DEVTOOLS_PACKAGE, 'package.json'))) {
|
|
1455
|
+
return true;
|
|
1456
|
+
}
|
|
1457
|
+
const parent = path.dirname(current);
|
|
1458
|
+
if (parent === current) return false;
|
|
1459
|
+
current = parent;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
/**
|
|
1464
|
+
* The `optimizeDeps.include` spec that pre-bundles the toolbar graph, or
|
|
1465
|
+
* null when it cannot be resolved at all. Pre-bundling it is not just a
|
|
1466
|
+
* warm-start nicety: the toolbar hangs off virtual modules the scanner
|
|
1467
|
+
* never crawls, so without an include the optimizer only discovers it on
|
|
1468
|
+
* first request. That re-optimize can pair chunks from different passes
|
|
1469
|
+
* whose shared minified exports disagree, taking down the whole client
|
|
1470
|
+
* entry graph. The spec must therefore cover every install shape
|
|
1471
|
+
* resolveDevtools accepts: bare when the app installs the package, and
|
|
1472
|
+
* Vite's nested-include form (`plugin > dep`) when it is only a dependency
|
|
1473
|
+
* of this plugin (pnpm-isolated installs).
|
|
1474
|
+
*/
|
|
1475
|
+
function devtoolsIncludeSpec(rootDir) {
|
|
1476
|
+
if (devtoolsReachableFrom(rootDir)) return DEVTOOLS_PACKAGE;
|
|
1477
|
+
if (devtoolsReachableFrom(path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))))) {
|
|
1478
|
+
return `@solidjs/vite-plugin > ${DEVTOOLS_PACKAGE}`;
|
|
1479
|
+
}
|
|
1480
|
+
return null;
|
|
1481
|
+
}
|
|
1406
1482
|
|
|
1407
1483
|
/** Import specifier for generated code: absolute for files, id for virtuals. */
|
|
1408
1484
|
function entryServerSpec() {
|
|
@@ -1442,7 +1518,7 @@ function startServe(options, internal = {}) {
|
|
|
1442
1518
|
return generated ? [app, ...(document ? [document] : [])] : [path.resolve(root, entryServer)];
|
|
1443
1519
|
}
|
|
1444
1520
|
async function devStylesModuleCode(environment, watchFile) {
|
|
1445
|
-
const styles = await collectDevStyleSources(environment, styleRoots(), watchFile);
|
|
1521
|
+
const styles = await collectDevStyleSources(environment, styleRoots(), watchFile, styleFilter);
|
|
1446
1522
|
if (!styles.length) return `export default '';`;
|
|
1447
1523
|
const imports = styles.map((style, index) => {
|
|
1448
1524
|
const specifier = style.url.includes('?') ? `${style.url}&inline` : `${style.url}?inline`;
|
|
@@ -1450,19 +1526,26 @@ function startServe(options, internal = {}) {
|
|
|
1450
1526
|
});
|
|
1451
1527
|
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');
|
|
1452
1528
|
}
|
|
1453
|
-
function
|
|
1529
|
+
function errorBoundaryImport() {
|
|
1530
|
+
return isBuild && errorBoundary ? [`import { DefaultErrorBoundary } from ${JSON.stringify(ERROR_BOUNDARY_ID)};`] : [];
|
|
1531
|
+
}
|
|
1532
|
+
function documentTree(root, wrapper) {
|
|
1533
|
+
const content = wrapper ? `<${wrapper}><${root} /></${wrapper}>` : `<${root} />`;
|
|
1534
|
+
return isBuild && errorBoundary ? [` <DefaultErrorBoundary>`, ` <Document>`, ` <DefaultErrorBoundary>`, ` ${content}`, ` </DefaultErrorBoundary>`, ` </Document>`, ` </DefaultErrorBoundary>`] : [` <Document>`, ` ${content}`, ` </Document>`];
|
|
1535
|
+
}
|
|
1536
|
+
function generatedEntryServerCode(toolbar) {
|
|
1454
1537
|
if (clientMode) {
|
|
1455
1538
|
// The client-mode shell: the document without the app. Rendered per
|
|
1456
1539
|
// request in dev (any HTML GET gets it — history-fallback semantics)
|
|
1457
1540
|
// and once at build time into dist/client/index.html. The client
|
|
1458
1541
|
// entry script is injected by the handler, exactly like SSR mode.
|
|
1459
|
-
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
|
|
1542
|
+
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');
|
|
1460
1543
|
}
|
|
1461
1544
|
const {
|
|
1462
1545
|
app
|
|
1463
1546
|
} = requireEntries();
|
|
1464
1547
|
const streamOptions = `{ manifest${serverComponents ? ', plugins: [ServerComponentPlugin]' : ''} }`;
|
|
1465
|
-
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 ? [
|
|
1548
|
+
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)};`, ...(toolbar ? [`import { DevToolbar } from ${JSON.stringify(DEVTOOLS_PACKAGE)};`] : []), ...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 ? [
|
|
1466
1549
|
// Direct (in-process) server-function calls made during document
|
|
1467
1550
|
// SSR must resolve to inline-renderable components; the endpoint
|
|
1468
1551
|
// response transform is installed separately by the
|
|
@@ -1476,9 +1559,9 @@ function startServe(options, internal = {}) {
|
|
|
1476
1559
|
// the *complete* render) and buffers the stream — so it crosses
|
|
1477
1560
|
// boxed under a private key the generated handler unboxes
|
|
1478
1561
|
// (both modules are ours).
|
|
1479
|
-
`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(() => (`,
|
|
1562
|
+
`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', toolbar ? 'DevToolbar' : undefined), ` ), ${streamOptions});`, `}`] : [`export function render(request, context) {`, ` return renderToStream(() => (`, ...documentTree('App', toolbar ? 'DevToolbar' : undefined), ` ), ${streamOptions});`, `}`])].join('\n');
|
|
1480
1563
|
}
|
|
1481
|
-
function generatedEntryClientCode() {
|
|
1564
|
+
function generatedEntryClientCode(toolbar) {
|
|
1482
1565
|
const {
|
|
1483
1566
|
app
|
|
1484
1567
|
} = requireEntries();
|
|
@@ -1488,13 +1571,13 @@ function startServe(options, internal = {}) {
|
|
|
1488
1571
|
// app cannot claim server DOM anyway. The entry script is injected
|
|
1489
1572
|
// without `async` (plain module = deferred), so document.body is
|
|
1490
1573
|
// complete when this runs.
|
|
1491
|
-
return [`import { render } from '@solidjs/web';`, `import App from ${JSON.stringify(app)};`, ``, `render(() => <App
|
|
1574
|
+
return [`import { render } from '@solidjs/web';`, ...errorBoundaryImport(), ...(toolbar ? [`import { DevToolbar } from ${JSON.stringify(DEVTOOLS_PACKAGE)};`] : []), `import App from ${JSON.stringify(app)};`, ``, `render(() => ${isBuild && errorBoundary ? '<DefaultErrorBoundary><App /></DefaultErrorBoundary>' : toolbar ? '<DevToolbar><App /></DevToolbar>' : '<App />'}, document.body);`].join('\n');
|
|
1492
1575
|
}
|
|
1493
|
-
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 ? [
|
|
1576
|
+
return [`import { hydrate } from '@solidjs/web';`, ...(toolbar ? [`import { DevToolbar } from ${JSON.stringify(DEVTOOLS_PACKAGE)};`] : []), ...(serverComponents ? [`import { installServerComponents } from '@solidjs/web/frames';`] : []), ...errorBoundaryImport(), `import Document from ${JSON.stringify(documentSpec())};`, `import App from ${JSON.stringify(app)};`, ``, ...(serverComponents ? [
|
|
1494
1577
|
// Installs the t=0 document-adoption registry and the transport
|
|
1495
1578
|
// policy (component responses morph their boundary instead of
|
|
1496
1579
|
// decoding as data). Must run before hydrate().
|
|
1497
|
-
`installServerComponents();`, ``] : []), `hydrate(() => (`,
|
|
1580
|
+
`installServerComponents();`, ``] : []), `hydrate(() => (`, ...documentTree('App', toolbar ? 'DevToolbar' : undefined), `), document);`].join('\n');
|
|
1498
1581
|
}
|
|
1499
1582
|
|
|
1500
1583
|
// Built-in document shell: minimal, hydration-ready. The client entry
|
|
@@ -1505,6 +1588,7 @@ function startServe(options, internal = {}) {
|
|
|
1505
1588
|
// HydrationScript is covered too: the handler strips the event-capture
|
|
1506
1589
|
// script from the client-mode shell.)
|
|
1507
1590
|
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');
|
|
1591
|
+
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');
|
|
1508
1592
|
|
|
1509
1593
|
// The handler module: dev and prod share the render/response plumbing;
|
|
1510
1594
|
// they differ in how the client entry URL is known (baked dev URL vs a
|
|
@@ -1556,7 +1640,7 @@ function startServe(options, internal = {}) {
|
|
|
1556
1640
|
// head-open splice actively broke hydration — a script ahead of the
|
|
1557
1641
|
// authored <head> elements claims as the first walked child and drifts
|
|
1558
1642
|
// every positional claim after it.
|
|
1559
|
-
lines.push(``, `function createHtmlChunkTransform(clientEntry, extraHead) {`, ` let first = true;`, ` let injected = false;`, ` return (chunk) => {`);
|
|
1643
|
+
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) => {`);
|
|
1560
1644
|
if (!generated) {
|
|
1561
1645
|
// Authored entries reference the client entry by its dev path (the
|
|
1562
1646
|
// `<script src="/src/entry-client.tsx">` convention); rewrite it to
|
|
@@ -1586,7 +1670,7 @@ function startServe(options, internal = {}) {
|
|
|
1586
1670
|
// scripts default to deferred execution, which is exactly right for a
|
|
1587
1671
|
// fresh render-into-body mount (hydration, by contrast, wants to
|
|
1588
1672
|
// start as early as possible).
|
|
1589
|
-
headParts.push(`(clientEntry ? '<script type="module" src="' + clientEntry + '"${clientMode ? '' : ' async'}></' + 'script>' : '')`);
|
|
1673
|
+
headParts.push(`(clientEntry ? '<script type="module"' + nonceAttr + ' src="' + clientEntry + '"${clientMode ? '' : ' async'}></' + 'script>' : '')`);
|
|
1590
1674
|
}
|
|
1591
1675
|
if (headParts.length) {
|
|
1592
1676
|
lines.push(` chunk = chunk.replace('</head>', ${headParts.join(' + ')} + '</head>');`);
|
|
@@ -1640,7 +1724,7 @@ function startServe(options, internal = {}) {
|
|
|
1640
1724
|
// The runtime's response-head lifecycle: commit at shell flush,
|
|
1641
1725
|
// pre-flush Location as a real redirect, post-flush Location as the
|
|
1642
1726
|
// script fallback; the transform injects the doctype/head pieces.
|
|
1643
|
-
` return createSSRResponse(result, event, {`, ` responseInit: options.responseInit,`, ` nonce: options.nonce,`, ` transformChunk: createHtmlChunkTransform(clientEntry, options.devHead),`, ` });`, `}`, ``, `export async function handleRequest(request, options = {}) {`,
|
|
1727
|
+
` return createSSRResponse(result, event, {`, ` responseInit: options.responseInit,`, ` nonce: options.nonce,`, ` transformChunk: createHtmlChunkTransform(clientEntry, options.devHead, options.nonce),`, ` });`, `}`, ``, `export async function handleRequest(request, options = {}) {`,
|
|
1644
1728
|
// `options.event` is the public wrapper->event extension seam: extra
|
|
1645
1729
|
// fields (conventionally `nativeEvent`, the platform's raw request
|
|
1646
1730
|
// object) spread over the event's defaults at creation, so hosts and
|
|
@@ -1670,6 +1754,9 @@ function startServe(options, internal = {}) {
|
|
|
1670
1754
|
enforce: 'pre',
|
|
1671
1755
|
config(userConfig, env) {
|
|
1672
1756
|
root = path.resolve(userConfig.root || process.cwd());
|
|
1757
|
+
devtoolsEnabled = env.command === 'serve' && !env.isPreview && options.devtools !== false;
|
|
1758
|
+
devtoolsResolutions = {};
|
|
1759
|
+
devtoolsIds = {};
|
|
1673
1760
|
entries = resolveEntries(root, options, clientMode);
|
|
1674
1761
|
middlewarePath = options.middleware ? path.resolve(root, normalizeUserPath(root, options.middleware, 'middleware')) : null;
|
|
1675
1762
|
// Server-mode only, like `entryServer`/`external` (a documented
|
|
@@ -1760,7 +1847,7 @@ function startServe(options, internal = {}) {
|
|
|
1760
1847
|
}
|
|
1761
1848
|
},
|
|
1762
1849
|
// Presence of `builder` makes a plain `vite build` build the
|
|
1763
|
-
// whole app (all environments: client then ssr)
|
|
1850
|
+
// whole app (all environments: client then ssr).
|
|
1764
1851
|
// A classic `vite build --ssr` invocation must stay a
|
|
1765
1852
|
// single-environment build, so it doesn't get the flag.
|
|
1766
1853
|
...(env.isSsrBuild ? {} : {
|
|
@@ -1788,17 +1875,34 @@ function startServe(options, internal = {}) {
|
|
|
1788
1875
|
}
|
|
1789
1876
|
} : {}),
|
|
1790
1877
|
optimizeDeps: {
|
|
1791
|
-
entries: scanEntries
|
|
1878
|
+
entries: scanEntries,
|
|
1879
|
+
// Like the refresh runtime in the main plugin: the toolbar
|
|
1880
|
+
// graph is injected behind modules the scanner never crawls,
|
|
1881
|
+
// so pre-bundle it and the server-functions runtime up front.
|
|
1882
|
+
...(() => {
|
|
1883
|
+
const spec = devtoolsEnabled ? devtoolsIncludeSpec(root) : null;
|
|
1884
|
+
return spec ? {
|
|
1885
|
+
include: [spec, '@solidjs/web/server-functions']
|
|
1886
|
+
} : {};
|
|
1887
|
+
})()
|
|
1792
1888
|
}
|
|
1793
1889
|
})
|
|
1794
1890
|
};
|
|
1795
1891
|
},
|
|
1892
|
+
configEnvironment(name, config) {
|
|
1893
|
+
if (name !== 'ssr') return;
|
|
1894
|
+
config.resolve ??= {};
|
|
1895
|
+
const noExternal = config.resolve.noExternal;
|
|
1896
|
+
if (noExternal !== true) {
|
|
1897
|
+
config.resolve.noExternal = [...(Array.isArray(noExternal) ? noExternal : noExternal ? [noExternal] : []), DEVTOOLS_PACKAGE];
|
|
1898
|
+
}
|
|
1899
|
+
},
|
|
1796
1900
|
configResolved(config) {
|
|
1797
1901
|
root = config.root;
|
|
1798
1902
|
base = config.base;
|
|
1799
1903
|
isBuild = config.command === 'build';
|
|
1800
1904
|
},
|
|
1801
|
-
resolveId(source) {
|
|
1905
|
+
resolveId(source, importer, opts) {
|
|
1802
1906
|
if (source === HANDLER_ID) {
|
|
1803
1907
|
return {
|
|
1804
1908
|
id: HANDLER_ID,
|
|
@@ -1811,33 +1915,95 @@ function startServe(options, internal = {}) {
|
|
|
1811
1915
|
moduleSideEffects: true
|
|
1812
1916
|
};
|
|
1813
1917
|
}
|
|
1814
|
-
if (source === ENTRY_SERVER_ID || source === ENTRY_CLIENT_ID || source === DOCUMENT_ID) {
|
|
1918
|
+
if (source === ENTRY_SERVER_ID || source === ENTRY_CLIENT_ID || source === DOCUMENT_ID || source === ERROR_BOUNDARY_ID) {
|
|
1815
1919
|
return {
|
|
1816
1920
|
id: source,
|
|
1817
1921
|
moduleSideEffects: source === ENTRY_CLIENT_ID
|
|
1818
1922
|
};
|
|
1819
1923
|
}
|
|
1924
|
+
if (devtoolsEnabled && source === DEVTOOLS_MOUNT_ID) {
|
|
1925
|
+
return {
|
|
1926
|
+
id: source,
|
|
1927
|
+
moduleSideEffects: true
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
// Generated modules have no directory for bare-package resolution.
|
|
1931
|
+
// Reuse the app-relative id captured during detection.
|
|
1932
|
+
const devtoolsId = devtoolsIds[getEnvironmentConsumer(this.environment, opts)];
|
|
1933
|
+
if (devtoolsId && source === DEVTOOLS_PACKAGE && (importer === ENTRY_SERVER_ID || importer === ENTRY_CLIENT_ID || importer === DEVTOOLS_MOUNT_ID)) {
|
|
1934
|
+
return {
|
|
1935
|
+
id: devtoolsId
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1820
1938
|
return null;
|
|
1821
1939
|
},
|
|
1822
1940
|
async load(id, opts) {
|
|
1941
|
+
const consumer = getEnvironmentConsumer(this.environment, opts);
|
|
1823
1942
|
if (id === HANDLER_ID) {
|
|
1824
|
-
if (
|
|
1943
|
+
if (consumer !== 'server') {
|
|
1825
1944
|
this.error(`${HANDLER_ID} is server-only; import it from server code (SSR build).`);
|
|
1826
1945
|
}
|
|
1827
1946
|
const externalDev = !isBuild && this.environment.mode === 'dev' && (externalServer || !isRunnableEnvironment(this.environment));
|
|
1828
1947
|
return handlerModuleCode(externalDev);
|
|
1829
1948
|
}
|
|
1830
1949
|
if (id === RESOLVED_DEV_STYLES_ID) {
|
|
1831
|
-
if (
|
|
1950
|
+
if (consumer !== 'server' || this.environment.mode !== 'dev') {
|
|
1832
1951
|
this.error(`${DEV_STYLES_ID} is only available to the development server handler.`);
|
|
1833
1952
|
}
|
|
1834
1953
|
return devStylesModuleCode(this.environment, file => this.addWatchFile(file));
|
|
1835
1954
|
}
|
|
1836
|
-
if (id === ENTRY_SERVER_ID)
|
|
1837
|
-
|
|
1955
|
+
if (id === ENTRY_SERVER_ID) {
|
|
1956
|
+
const toolbar = clientMode ? false : await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1957
|
+
skipSelf: true
|
|
1958
|
+
}), requireEntries().app, 'server');
|
|
1959
|
+
return generatedEntryServerCode(toolbar);
|
|
1960
|
+
}
|
|
1961
|
+
if (id === ENTRY_CLIENT_ID) {
|
|
1962
|
+
const toolbar = await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1963
|
+
skipSelf: true
|
|
1964
|
+
}), requireEntries().app, 'client');
|
|
1965
|
+
return generatedEntryClientCode(toolbar);
|
|
1966
|
+
}
|
|
1838
1967
|
if (id === DOCUMENT_ID) return documentShellCode;
|
|
1968
|
+
if (id === ERROR_BOUNDARY_ID) return errorBoundaryCode;
|
|
1969
|
+
if (id === DEVTOOLS_MOUNT_ID) {
|
|
1970
|
+
let enabled = false;
|
|
1971
|
+
if (devtoolsEnabled && consumer === 'client') {
|
|
1972
|
+
const {
|
|
1973
|
+
app,
|
|
1974
|
+
entryClient
|
|
1975
|
+
} = requireEntries();
|
|
1976
|
+
enabled = await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1977
|
+
skipSelf: true
|
|
1978
|
+
}), app ?? path.resolve(root, entryClient), 'client');
|
|
1979
|
+
}
|
|
1980
|
+
if (!enabled) {
|
|
1981
|
+
this.error(`${id} is only available to the development client.`);
|
|
1982
|
+
}
|
|
1983
|
+
return devtoolsMountModuleCode();
|
|
1984
|
+
}
|
|
1839
1985
|
return null;
|
|
1840
1986
|
},
|
|
1987
|
+
async transform(code, id, opts) {
|
|
1988
|
+
if (isBuild || !devtoolsEnabled) return null;
|
|
1989
|
+
const current = requireEntries();
|
|
1990
|
+
if (current.generated || getEnvironmentConsumer(this.environment, opts) !== 'client') {
|
|
1991
|
+
return null;
|
|
1992
|
+
}
|
|
1993
|
+
// Module ids are always forward-slashed; normalize the path.resolve
|
|
1994
|
+
// side too so the comparison holds on Windows.
|
|
1995
|
+
if (vite.normalizePath(id.split('?')[0]) !== vite.normalizePath(path.resolve(root, current.entryClient))) {
|
|
1996
|
+
return null;
|
|
1997
|
+
}
|
|
1998
|
+
const toolbar = await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1999
|
+
skipSelf: true
|
|
2000
|
+
}), id, 'client');
|
|
2001
|
+
if (!toolbar) return null;
|
|
2002
|
+
return {
|
|
2003
|
+
code: `import ${JSON.stringify(DEVTOOLS_MOUNT_ID)};\n${code}`,
|
|
2004
|
+
map: null
|
|
2005
|
+
};
|
|
2006
|
+
},
|
|
1841
2007
|
configurePreviewServer(server) {
|
|
1842
2008
|
// `vite build && vite preview` runs the production artifact as-is:
|
|
1843
2009
|
// Vite's preview statics serve dist/client (see the config hook) and
|
|
@@ -1895,7 +2061,7 @@ function startServe(options, internal = {}) {
|
|
|
1895
2061
|
// that gets the streamed SSR render.
|
|
1896
2062
|
return () => {
|
|
1897
2063
|
const ssrEnvironment = server.environments.ssr;
|
|
1898
|
-
if (externalServer ||
|
|
2064
|
+
if (externalServer || !isRunnableEnvironment(ssrEnvironment)) {
|
|
1899
2065
|
return;
|
|
1900
2066
|
}
|
|
1901
2067
|
server.middlewares.use((req, res, next) => {
|
|
@@ -1912,8 +2078,8 @@ function startServe(options, internal = {}) {
|
|
|
1912
2078
|
(async () => {
|
|
1913
2079
|
// Loaded through the SSR environment so the app, the request
|
|
1914
2080
|
// event storage, and the handler share one module registry.
|
|
1915
|
-
const handler = await
|
|
1916
|
-
const styles = pageRequest ? await collectDevStyles(server, styleRoots()) : [];
|
|
2081
|
+
const handler = await ssrEnvironment.runner.import(HANDLER_ID);
|
|
2082
|
+
const styles = pageRequest ? await collectDevStyles(server, styleRoots(), styleFilter) : [];
|
|
1917
2083
|
const devHead = styles.map(renderDevStyleTag).join('');
|
|
1918
2084
|
// Post middlewares run after Vite's base middleware stripped
|
|
1919
2085
|
// the configured `base` from req.url; restore it so the app
|
|
@@ -1937,7 +2103,6 @@ function startServe(options, internal = {}) {
|
|
|
1937
2103
|
if (response.headers.has(DEV_FALLTHROUGH_HEADER)) return next();
|
|
1938
2104
|
await sendWebResponse(res, response);
|
|
1939
2105
|
})().catch(error => {
|
|
1940
|
-
if (error instanceof Error) server.ssrFixStacktrace(error);
|
|
1941
2106
|
// Vite's error middleware renders the overlay-enabled 500 page.
|
|
1942
2107
|
next(error);
|
|
1943
2108
|
});
|
|
@@ -2027,7 +2192,7 @@ function startServe(options, internal = {}) {
|
|
|
2027
2192
|
// https://github.com/pyyupsk/vite-env), the design-correct prior art. The
|
|
2028
2193
|
// implementation is fresh against this plugin's machinery: Standard Schema
|
|
2029
2194
|
// is the only contract (no zod dependency or zod-specific paths), the
|
|
2030
|
-
// schema file loads through Vite's own `runnerImport
|
|
2195
|
+
// schema file loads through Vite's own `runnerImport`
|
|
2031
2196
|
// (no jiti), server-graph protection keys off the environment *consumer*
|
|
2032
2197
|
// rather than environment-name lists, and the types are inferred from the
|
|
2033
2198
|
// user's schema instead of introspected per-library.
|
|
@@ -2086,37 +2251,18 @@ function formatValidationError(issues, envFile, mode) {
|
|
|
2086
2251
|
return `[@solidjs/vite-plugin] env validation failed (${issues.length} issue${issues.length === 1 ? '' : 's'}) — schema: ${envFile}, mode: ${mode}\n\n` + lines.join('\n') + `\n\nSet the variables in your environment or .env files, or adjust the schema.`;
|
|
2087
2252
|
}
|
|
2088
2253
|
|
|
2089
|
-
/**
|
|
2090
|
-
* Loads the schema module at config time through Vite itself: `runnerImport`
|
|
2091
|
-
* (Vite 6.1+) evaluates TypeScript in-process with project resolution;
|
|
2092
|
-
* older Vite 6 falls back to `loadConfigFromFile`, the exact machinery that
|
|
2093
|
-
* loads vite.config.ts.
|
|
2094
|
-
*/
|
|
2254
|
+
/** Loads the schema module through Vite with project resolution. */
|
|
2095
2255
|
async function importSchemaModule(envFileAbs, root, mode) {
|
|
2096
|
-
const
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
} = await vite.runnerImport(envFileAbs, {
|
|
2102
|
-
root,
|
|
2103
|
-
mode
|
|
2104
|
-
});
|
|
2105
|
-
return {
|
|
2106
|
-
exported: module?.default,
|
|
2107
|
-
dependencies: (dependencies || []).map(dep => path.resolve(root, dep)).filter(dep => fs.existsSync(dep))
|
|
2108
|
-
};
|
|
2109
|
-
}
|
|
2110
|
-
const result = await vite.loadConfigFromFile({
|
|
2111
|
-
command: 'serve',
|
|
2256
|
+
const {
|
|
2257
|
+
module,
|
|
2258
|
+
dependencies
|
|
2259
|
+
} = await vite.runnerImport(envFileAbs, {
|
|
2260
|
+
root,
|
|
2112
2261
|
mode
|
|
2113
|
-
}
|
|
2114
|
-
if (!result) {
|
|
2115
|
-
throw new Error(`[@solidjs/vite-plugin] failed to load env schema from ${envFileAbs}`);
|
|
2116
|
-
}
|
|
2262
|
+
});
|
|
2117
2263
|
return {
|
|
2118
|
-
exported:
|
|
2119
|
-
dependencies:
|
|
2264
|
+
exported: module?.default,
|
|
2265
|
+
dependencies: dependencies.map(dep => path.resolve(root, dep)).filter(dep => fs.existsSync(dep))
|
|
2120
2266
|
};
|
|
2121
2267
|
}
|
|
2122
2268
|
function assertSchemaShape(exported, envFile, envPrefixes) {
|
|
@@ -2600,8 +2746,7 @@ const LAZY_PLACEHOLDER_PREFIX = '__SOLID_LAZY_MODULE__:';
|
|
|
2600
2746
|
* solid-refresh#85 — is no longer used at all).
|
|
2601
2747
|
*/
|
|
2602
2748
|
const REFRESH_RUNTIME_SOURCE = 'solid-js/refresh';
|
|
2603
|
-
const
|
|
2604
|
-
const isVite8 = viteVersionMajor >= 8;
|
|
2749
|
+
const DEFAULT_STYLE_EXCLUDE = /node_modules/;
|
|
2605
2750
|
const VIRTUAL_MANIFEST_ID = 'virtual:solid-manifest';
|
|
2606
2751
|
const RESOLVED_VIRTUAL_MANIFEST_ID = '\0' + VIRTUAL_MANIFEST_ID;
|
|
2607
2752
|
|
|
@@ -2856,10 +3001,30 @@ function solidPlugin(options = {}) {
|
|
|
2856
3001
|
// `start: true` is sugar for the empty options bag — one start mode,
|
|
2857
3002
|
// two spellings — so normalize here and let everything downstream see a
|
|
2858
3003
|
// single shape (`false` behaves exactly like omission).
|
|
2859
|
-
const
|
|
3004
|
+
const startOptions = options.start === true ? {} : options.start || null;
|
|
3005
|
+
const styleFilterOptions = startOptions?.css?.filter;
|
|
3006
|
+
// The CSS crawl walks the module graph from the app's own entries, so a
|
|
3007
|
+
// plain createFilter allowlist can't express the option's purpose (opting
|
|
3008
|
+
// node_modules graphs in): a bare `include` would reject the app sources
|
|
3009
|
+
// the crawl has to traverse to ever reach the included package. Instead
|
|
3010
|
+
// `include` rescues files on top of the baseline (everything except
|
|
3011
|
+
// `exclude`, which defaults to node_modules), while a file matching both
|
|
3012
|
+
// patterns stays excluded — createFilter's own conflict rule.
|
|
3013
|
+
const createStyleFilter = resolve => {
|
|
3014
|
+
const opts = resolve === undefined ? undefined : {
|
|
3015
|
+
resolve
|
|
3016
|
+
};
|
|
3017
|
+
const base = vite.createFilter(undefined, styleFilterOptions?.exclude ?? DEFAULT_STYLE_EXCLUDE, opts);
|
|
3018
|
+
const include = styleFilterOptions?.include;
|
|
3019
|
+
const hasInclude = include != null && (!Array.isArray(include) || include.length > 0);
|
|
3020
|
+
const included = hasInclude ? vite.createFilter(include, styleFilterOptions?.exclude, opts) : null;
|
|
3021
|
+
return id => base(id) || (included ? included(id) : false);
|
|
3022
|
+
};
|
|
3023
|
+
let styleFilter = createStyleFilter();
|
|
3024
|
+
const filterDevStyles = id => styleFilter(id);
|
|
2860
3025
|
// `start.external` only means something when a server side exists to hand
|
|
2861
3026
|
// over (SSR start mode); in client mode it is a documented no-op.
|
|
2862
|
-
const externalDevServer = !!options.ssr && !!
|
|
3027
|
+
const externalDevServer = !!options.ssr && !!startOptions?.external;
|
|
2863
3028
|
let needHmr = false;
|
|
2864
3029
|
let replaceDev = false;
|
|
2865
3030
|
// The live dev server, kept so the dev manifest module can bake the bridge
|
|
@@ -3071,44 +3236,28 @@ function solidPlugin(options = {}) {
|
|
|
3071
3236
|
// server-components runtime installs its response policy there).
|
|
3072
3237
|
...(command === 'serve' && serverComponents ? ['@solidjs/web/frames', '@solidjs/web/server-functions'] : []), ...solidPkgsConfig.optimizeDeps.include],
|
|
3073
3238
|
exclude: solidPkgsConfig.optimizeDeps.exclude,
|
|
3074
|
-
//
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
// (issue #262). The classic runtime is the only scan-safe lowering:
|
|
3080
|
-
// it emits bare `React.createElement` calls without injecting any
|
|
3081
|
-
// import, and the scan output is never executed — it only exists so
|
|
3082
|
-
// rolldown can walk the import graph.
|
|
3083
|
-
...(isVite8 ? {
|
|
3084
|
-
rolldownOptions: {
|
|
3085
|
-
transform: {
|
|
3086
|
-
jsx: {
|
|
3087
|
-
runtime: 'classic'
|
|
3088
|
-
}
|
|
3239
|
+
// Keep Solid TSX from injecting React's automatic runtime during scanning.
|
|
3240
|
+
rolldownOptions: {
|
|
3241
|
+
transform: {
|
|
3242
|
+
jsx: {
|
|
3243
|
+
runtime: 'classic'
|
|
3089
3244
|
}
|
|
3090
3245
|
}
|
|
3091
|
-
}
|
|
3246
|
+
}
|
|
3092
3247
|
},
|
|
3093
3248
|
...(Object.keys(test).length ? {
|
|
3094
3249
|
test
|
|
3095
3250
|
} : {})
|
|
3096
3251
|
};
|
|
3097
3252
|
},
|
|
3098
|
-
|
|
3099
|
-
async configEnvironment(name, config, opts) {
|
|
3253
|
+
configEnvironment(name, config, opts) {
|
|
3100
3254
|
config.resolve ??= {};
|
|
3101
3255
|
// Emulate Vite default fallback for `resolve.conditions` if not set
|
|
3102
3256
|
if (config.resolve.conditions == null) {
|
|
3103
|
-
// @ts-ignore These exports only exist in Vite 6
|
|
3104
|
-
const {
|
|
3105
|
-
defaultClientConditions,
|
|
3106
|
-
defaultServerConditions
|
|
3107
|
-
} = await import('vite');
|
|
3108
3257
|
if (config.consumer === 'client' || name === 'client' || opts.isSsrTargetWebworker) {
|
|
3109
|
-
config.resolve.conditions = [...defaultClientConditions];
|
|
3258
|
+
config.resolve.conditions = [...vite.defaultClientConditions];
|
|
3110
3259
|
} else {
|
|
3111
|
-
config.resolve.conditions = [...defaultServerConditions];
|
|
3260
|
+
config.resolve.conditions = [...vite.defaultServerConditions];
|
|
3112
3261
|
}
|
|
3113
3262
|
}
|
|
3114
3263
|
config.resolve.conditions = ['solid', ...(replaceDev ? ['development'] : []),
|
|
@@ -3137,6 +3286,7 @@ function solidPlugin(options = {}) {
|
|
|
3137
3286
|
filter = vite.createFilter(options.include, options.exclude, {
|
|
3138
3287
|
resolve: projectRoot
|
|
3139
3288
|
});
|
|
3289
|
+
styleFilter = createStyleFilter(projectRoot);
|
|
3140
3290
|
if (serverComponents && !(options.start && options.ssr)) {
|
|
3141
3291
|
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.');
|
|
3142
3292
|
}
|
|
@@ -3150,7 +3300,7 @@ function solidPlugin(options = {}) {
|
|
|
3150
3300
|
// that don't share globals with this process, through the HTTP bridge
|
|
3151
3301
|
// endpoint the middleware serves.
|
|
3152
3302
|
if (options.ssr || options.start) {
|
|
3153
|
-
registerDevAssetResolver(server.config.root, createDevAssetResolver(server));
|
|
3303
|
+
registerDevAssetResolver(server.config.root, createDevAssetResolver(server, filterDevStyles));
|
|
3154
3304
|
installDevManifestBridge(server);
|
|
3155
3305
|
}
|
|
3156
3306
|
if (!needHmr) return;
|
|
@@ -3266,7 +3416,7 @@ function solidPlugin(options = {}) {
|
|
|
3266
3416
|
}
|
|
3267
3417
|
},
|
|
3268
3418
|
async transform(source, id, transformOptions) {
|
|
3269
|
-
const isSsr = transformOptions
|
|
3419
|
+
const isSsr = getEnvironmentConsumer(this.environment, transformOptions) === 'server';
|
|
3270
3420
|
const currentFileExtension = getExtension(id);
|
|
3271
3421
|
const extensionsToWatch = options.extensions || [];
|
|
3272
3422
|
const allExtensions = extensionsToWatch.map(extension =>
|
|
@@ -3406,7 +3556,7 @@ function solidPlugin(options = {}) {
|
|
|
3406
3556
|
// With start mode on (either variant), the dev middleware dispatches
|
|
3407
3557
|
// the endpoint through the SSR handler so user middleware and the
|
|
3408
3558
|
// stub-backed request event front it exactly like page SSR.
|
|
3409
|
-
...(
|
|
3559
|
+
...(startOptions ? {
|
|
3410
3560
|
ssrHandler: SSR_HANDLER_ID
|
|
3411
3561
|
} : {})
|
|
3412
3562
|
}), mainPlugin] : [boundaryModules(), mainPlugin];
|
|
@@ -3414,15 +3564,16 @@ function solidPlugin(options = {}) {
|
|
|
3414
3564
|
// The `start` option opts into start-mode serving on top of the transforms;
|
|
3415
3565
|
// the `ssr` boolean picks the mode (a bare `ssr: true` keeps the
|
|
3416
3566
|
// historical transform-only behavior).
|
|
3417
|
-
if (
|
|
3567
|
+
if (startOptions) {
|
|
3418
3568
|
plugins.push(
|
|
3419
3569
|
// Typed env (`start.env`) rides both start modes: config-time
|
|
3420
3570
|
// validation, the virtual:env/{server,client} modules, generated
|
|
3421
3571
|
// types, and the client-bundle leak scan.
|
|
3422
|
-
...startEnv(
|
|
3572
|
+
...startEnv(startOptions.env), ...startServe(startOptions, {
|
|
3423
3573
|
serverFunctions: !!options.serverFunctions,
|
|
3424
3574
|
serverComponents,
|
|
3425
|
-
ssr: !!options.ssr
|
|
3575
|
+
ssr: !!options.ssr,
|
|
3576
|
+
styleFilter: filterDevStyles
|
|
3426
3577
|
}));
|
|
3427
3578
|
}
|
|
3428
3579
|
|
|
@@ -3437,8 +3588,7 @@ function solidPlugin(options = {}) {
|
|
|
3437
3588
|
// would bake a manifest-less fallback into the server bundle. Every user
|
|
3438
3589
|
// of such a setup had to hand-write this ordering plugin; absorb it.
|
|
3439
3590
|
//
|
|
3440
|
-
// Semantics
|
|
3441
|
-
// these, keeping its build-everything default):
|
|
3591
|
+
// Semantics:
|
|
3442
3592
|
// - The first hook builds the client environment first, but only where
|
|
3443
3593
|
// the ordering matters: a client build that emits a manifest and
|
|
3444
3594
|
// actually has an input. It runs at *normal* order, deliberately not
|