@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/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,
|
|
10
|
-
import { pathToFileURL } from 'node:url';
|
|
9
|
+
import { createFilter, normalizePath, loadEnv, runnerImport, defaultClientConditions, defaultServerConditions } 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
|
|
@@ -153,6 +153,7 @@ function joinBase(base, pathname) {
|
|
|
153
153
|
* dynamically imported modules register their own styles when they render.
|
|
154
154
|
*/
|
|
155
155
|
|
|
156
|
+
const defaultStyleFilter = id => !id.includes('node_modules');
|
|
156
157
|
// The resolver is created plugin-side (it closes over the dev server) but is
|
|
157
158
|
// called from the SSR module runner, which only shares `globalThis` with the
|
|
158
159
|
// plugin when it runs in-process (the default). The primary channel is a
|
|
@@ -296,13 +297,15 @@ async function getModuleNode(env, file, importer) {
|
|
|
296
297
|
return;
|
|
297
298
|
}
|
|
298
299
|
}
|
|
299
|
-
async function collectModuleDeps(env, file, deps, crawled, onFile, importer) {
|
|
300
|
+
async function collectModuleDeps(env, file, deps, crawled, filter, onFile, importer) {
|
|
300
301
|
crawled.add(file);
|
|
301
302
|
const node = await getModuleNode(env, file, importer);
|
|
302
303
|
if (!node?.id || deps.has(node)) return;
|
|
303
304
|
deps.add(node);
|
|
304
|
-
|
|
305
|
-
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;
|
|
306
309
|
if (!node.transformResult) {
|
|
307
310
|
await env.transformRequest(node.url).catch(() => {});
|
|
308
311
|
}
|
|
@@ -313,7 +316,7 @@ async function collectModuleDeps(env, file, deps, crawled, onFile, importer) {
|
|
|
313
316
|
// from dynamicDeps — dynamic imports load their own styles when rendered.
|
|
314
317
|
for (const dep of directDeps) {
|
|
315
318
|
if (crawled.has(dep)) continue;
|
|
316
|
-
await collectModuleDeps(env, dep, deps, crawled, onFile, node.id);
|
|
319
|
+
await collectModuleDeps(env, dep, deps, crawled, filter, onFile, node.id);
|
|
317
320
|
}
|
|
318
321
|
}
|
|
319
322
|
function injectQuery(url, query) {
|
|
@@ -321,11 +324,11 @@ function injectQuery(url, query) {
|
|
|
321
324
|
}
|
|
322
325
|
|
|
323
326
|
/** Discovers ambient CSS in an entry graph without choosing how it is transported. */
|
|
324
|
-
async function collectDevStyleSources(env, files, onFile) {
|
|
327
|
+
async function collectDevStyleSources(env, files, onFile, filter = defaultStyleFilter) {
|
|
325
328
|
const deps = new Set();
|
|
326
329
|
const crawled = new Set();
|
|
327
330
|
for (const file of files) {
|
|
328
|
-
await collectModuleDeps(env, file, deps, crawled, onFile);
|
|
331
|
+
await collectModuleDeps(env, file, deps, crawled, filter, onFile);
|
|
329
332
|
}
|
|
330
333
|
const css = [];
|
|
331
334
|
const seen = new Set();
|
|
@@ -352,11 +355,11 @@ async function collectDevStyleSources(env, files, onFile) {
|
|
|
352
355
|
* CSS into `<head>` so server-painted content is styled from the first byte
|
|
353
356
|
* (no FOUC while waiting for Vite's client-side style injection).
|
|
354
357
|
*/
|
|
355
|
-
async function collectDevStyles(server, files) {
|
|
358
|
+
async function collectDevStyles(server, files, filter = defaultStyleFilter) {
|
|
356
359
|
const ssrEnv = server.environments?.ssr;
|
|
357
360
|
const clientEnv = server.environments?.client;
|
|
358
361
|
if (!ssrEnv || !clientEnv) return [];
|
|
359
|
-
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);
|
|
360
363
|
const css = [];
|
|
361
364
|
for (const source of sources) {
|
|
362
365
|
// `?direct` yields the compiled stylesheet text (what Vite serves for
|
|
@@ -412,7 +415,7 @@ function devModuleUrl(root, base, key) {
|
|
|
412
415
|
// drive letter on Windows: /@fs/C:/…).
|
|
413
416
|
return joinBase(base, '/@fs/' + absolute.replace(/^\//, '') + query);
|
|
414
417
|
}
|
|
415
|
-
function createDevAssetResolver(server) {
|
|
418
|
+
function createDevAssetResolver(server, filter = defaultStyleFilter) {
|
|
416
419
|
// Server-side lazy() re-requests a module's assets on every retry of a
|
|
417
420
|
// suspended render pass (retries re-create the component). The build
|
|
418
421
|
// manifest answers those repeats synchronously and the pass converges; an
|
|
@@ -446,7 +449,7 @@ function createDevAssetResolver(server) {
|
|
|
446
449
|
// The module's dev URL doubles as its client entry: modulepreload
|
|
447
450
|
// hint and hydration module-map value.
|
|
448
451
|
const js = [devModuleUrl(root, base, key)];
|
|
449
|
-
const css = await collectDevStyles(server, [key]);
|
|
452
|
+
const css = await collectDevStyles(server, [key], filter);
|
|
450
453
|
return {
|
|
451
454
|
js,
|
|
452
455
|
css
|
|
@@ -501,18 +504,18 @@ function boundaryModules() {
|
|
|
501
504
|
// import graph — no directive transforms have run, so it walks
|
|
502
505
|
// straight through 'use server' modules into genuinely server-only
|
|
503
506
|
// code. That graph is legal once transforms split it, so the guard
|
|
504
|
-
// must not fire on the scan pass (`options.scan`,
|
|
505
|
-
// scanner
|
|
506
|
-
// scanner in v8). Still claim the specifier: resolving to the empty
|
|
507
|
+
// must not fire on the scan pass (`options.scan`, set by Rolldown's
|
|
508
|
+
// dependency scanner). Still claim the specifier: resolving to the empty
|
|
507
509
|
// virtual module keeps the scanner from chasing `server-only` /
|
|
508
510
|
// `client-only` as missing bare dependencies, which would abort the
|
|
509
511
|
// scan all the same. Real dev/build module graphs resolve without
|
|
510
512
|
// the flag and stay fully guarded.
|
|
511
513
|
const scan = !!options?.scan;
|
|
514
|
+
const server = this.environment.config.consumer === 'server';
|
|
512
515
|
if (id === 'server-only') {
|
|
513
|
-
if (!
|
|
516
|
+
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).`);
|
|
514
517
|
} else if (id === 'client-only') {
|
|
515
|
-
if (
|
|
518
|
+
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).`);
|
|
516
519
|
} else {
|
|
517
520
|
return null;
|
|
518
521
|
}
|
|
@@ -539,6 +542,11 @@ function boundaryModules() {
|
|
|
539
542
|
function isRunnableEnvironment(environment) {
|
|
540
543
|
return !!environment && typeof environment === 'object' && 'runner' in environment;
|
|
541
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
|
+
}
|
|
542
550
|
|
|
543
551
|
// The `"use server"` directive compiler. This wraps the native
|
|
544
552
|
// `transformDirectives` pass from @dom-expressions/compiler (Rust/Oxc); the
|
|
@@ -810,8 +818,6 @@ function invalidateModule(moduleGraph, path) {
|
|
|
810
818
|
}
|
|
811
819
|
}
|
|
812
820
|
function invalidateModules(server, result, manifest) {
|
|
813
|
-
// `environments` requires Vite 6+; older versions just miss the eager
|
|
814
|
-
// manifest invalidation (the debounced reload still converges).
|
|
815
821
|
if (server?.environments && result.invalidPreload) {
|
|
816
822
|
invalidateModule(server.environments.client.moduleGraph, manifest);
|
|
817
823
|
invalidateModule(server.environments.ssr.moduleGraph, manifest);
|
|
@@ -975,12 +981,12 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
975
981
|
const relative = path.relative(root, entry).split(path.sep).join('/');
|
|
976
982
|
return relative.startsWith('..') ? '/@fs/' + entry : '/' + relative;
|
|
977
983
|
}
|
|
978
|
-
const
|
|
984
|
+
const startPlugins = [{
|
|
979
985
|
name: 'solid:server-functions/handler',
|
|
980
986
|
enforce: 'pre',
|
|
981
987
|
resolveId(source, _importer, opts) {
|
|
982
988
|
if (source === HANDLER_ID$1) {
|
|
983
|
-
if (
|
|
989
|
+
if (getEnvironmentConsumer(this.environment, opts) !== 'server') {
|
|
984
990
|
this.error(`${HANDLER_ID$1} is server-only; import it from your server entry (SSR build).`);
|
|
985
991
|
}
|
|
986
992
|
return {
|
|
@@ -991,7 +997,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
991
997
|
return null;
|
|
992
998
|
},
|
|
993
999
|
load(id, opts) {
|
|
994
|
-
if (id === HANDLER_ID$1 && opts
|
|
1000
|
+
if (id === HANDLER_ID$1 && getEnvironmentConsumer(this.environment, opts) === 'server') {
|
|
995
1001
|
const externalDev = this.environment.mode === 'dev' && (internal.externalDevServer || !isRunnableEnvironment(this.environment));
|
|
996
1002
|
return handlerModuleCode(isBuild || externalDev);
|
|
997
1003
|
}
|
|
@@ -999,12 +1005,12 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
999
1005
|
}
|
|
1000
1006
|
}];
|
|
1001
1007
|
if (installDevMiddleware) {
|
|
1002
|
-
|
|
1008
|
+
startPlugins.push({
|
|
1003
1009
|
name: 'solid:server-functions/dev-middleware',
|
|
1004
1010
|
apply: 'serve',
|
|
1005
1011
|
configureServer(server) {
|
|
1006
1012
|
const ssrEnvironment = server.environments.ssr;
|
|
1007
|
-
if (internal.externalDevServer ||
|
|
1013
|
+
if (internal.externalDevServer || !isRunnableEnvironment(ssrEnvironment)) {
|
|
1008
1014
|
return;
|
|
1009
1015
|
}
|
|
1010
1016
|
server.middlewares.use((req, res, next) => {
|
|
@@ -1027,7 +1033,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1027
1033
|
const functionId = (typeof headerId === 'string' ? headerId.split('#')[0] : undefined) || url.searchParams.get('id');
|
|
1028
1034
|
if (functionId) {
|
|
1029
1035
|
const entry = moduleForFunctionId(functionId);
|
|
1030
|
-
if (entry) await
|
|
1036
|
+
if (entry) await ssrEnvironment.runner.import(moduleDevUrl(entry));
|
|
1031
1037
|
}
|
|
1032
1038
|
// Dispatch through a module evaluated in the SSR environment so
|
|
1033
1039
|
// the handler shares the registry instance with the app modules.
|
|
@@ -1035,7 +1041,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1035
1041
|
// in, and dispatch goes through `handleRequest` instead — one
|
|
1036
1042
|
// middleware chain and one stub-backed request event front the
|
|
1037
1043
|
// endpoint exactly as they front page SSR.
|
|
1038
|
-
const handler = await
|
|
1044
|
+
const handler = await ssrEnvironment.runner.import(internal.ssrHandler ?? HANDLER_ID$1);
|
|
1039
1045
|
// Both dispatch shapes carry the raw Node request on the event
|
|
1040
1046
|
// (the `options.event` seam), matching the SSR dev middleware
|
|
1041
1047
|
// and what a production Node entry passes.
|
|
@@ -1047,7 +1053,6 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1047
1053
|
const response = internal.ssrHandler ? await handler.handleRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions) : await handler.handleServerFunctionRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions);
|
|
1048
1054
|
await sendWebResponse(res, response);
|
|
1049
1055
|
})().catch(error => {
|
|
1050
|
-
if (error instanceof Error) server.ssrFixStacktrace(error);
|
|
1051
1056
|
next(error);
|
|
1052
1057
|
});
|
|
1053
1058
|
});
|
|
@@ -1111,7 +1116,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1111
1116
|
return null;
|
|
1112
1117
|
},
|
|
1113
1118
|
async load(id, opts) {
|
|
1114
|
-
const mode = opts
|
|
1119
|
+
const mode = getEnvironmentConsumer(this.environment, opts);
|
|
1115
1120
|
if (id === manifestId) {
|
|
1116
1121
|
if (isBuild && mode === 'server') {
|
|
1117
1122
|
// Merge the client build's persisted discoveries at load time,
|
|
@@ -1134,7 +1139,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1134
1139
|
name: 'solid:server-functions/compiler',
|
|
1135
1140
|
enforce: 'pre',
|
|
1136
1141
|
async transform(code, fileId, opts) {
|
|
1137
|
-
const mode = opts
|
|
1142
|
+
const mode = getEnvironmentConsumer(this.environment, opts);
|
|
1138
1143
|
const [id] = fileId.split('?');
|
|
1139
1144
|
if (!filter(id)) {
|
|
1140
1145
|
return null;
|
|
@@ -1167,7 +1172,13 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1167
1172
|
}
|
|
1168
1173
|
return null;
|
|
1169
1174
|
}
|
|
1170
|
-
}, ...
|
|
1175
|
+
}, ...startPlugins];
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
const DEVTOOLS_PACKAGE = '@solidjs/start-devtools';
|
|
1179
|
+
const DEVTOOLS_MOUNT_ID = 'virtual:solid-devtools/mount';
|
|
1180
|
+
function devtoolsMountModuleCode() {
|
|
1181
|
+
return [`import { mountDevToolbar } from '${DEVTOOLS_PACKAGE}';`, `mountDevToolbar();`].join('\n');
|
|
1171
1182
|
}
|
|
1172
1183
|
|
|
1173
1184
|
// Start-mode serving for plain Vite apps: `solid({ start: {...} })` (or the
|
|
@@ -1184,7 +1195,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1184
1195
|
// Both paths inject the Vite client, dev style patch, and entry CSS as
|
|
1185
1196
|
// `<style data-vite-dev-id>` tags before the body can paint.
|
|
1186
1197
|
// - Prod: the plugin configures a full-app build (client + server bundles
|
|
1187
|
-
// via the Vite
|
|
1198
|
+
// via the Vite environments/builder API — a single `vite build` builds
|
|
1188
1199
|
// both) whose server entry is `virtual:solid-ssr-handler`: an
|
|
1189
1200
|
// adapter-agnostic named `handleRequest(Request) => Promise<Response>` plus
|
|
1190
1201
|
// a default Fetchable `{ fetch(request) }` export. Both scope each request
|
|
@@ -1252,6 +1263,7 @@ const RESOLVED_DEV_STYLES_ID = '\0' + DEV_STYLES_ID;
|
|
|
1252
1263
|
const ENTRY_SERVER_ID = 'virtual:solid-ssr-entry-server.tsx';
|
|
1253
1264
|
const ENTRY_CLIENT_ID = 'virtual:solid-ssr-entry-client.tsx';
|
|
1254
1265
|
const DOCUMENT_ID = 'virtual:solid-ssr-document.tsx';
|
|
1266
|
+
const ERROR_BOUNDARY_ID = 'virtual:solid-ssr-error-boundary.tsx';
|
|
1255
1267
|
const MANIFEST_ID = 'virtual:solid-manifest';
|
|
1256
1268
|
const SERVER_FUNCTION_HANDLER_ID = 'virtual:solid-server-function-handler';
|
|
1257
1269
|
const STORAGE_SOURCE = '@solidjs/web/storage';
|
|
@@ -1363,6 +1375,11 @@ function startServe(options, internal = {}) {
|
|
|
1363
1375
|
// the server-function handler module either way). Everything is gated
|
|
1364
1376
|
// codegen: with the option off, none of these imports exist anywhere.
|
|
1365
1377
|
const serverComponents = !!internal.serverComponents;
|
|
1378
|
+
const errorBoundary = options.errorBoundary !== false;
|
|
1379
|
+
const styleFilter = internal.styleFilter;
|
|
1380
|
+
let devtoolsEnabled = false;
|
|
1381
|
+
let devtoolsResolutions = {};
|
|
1382
|
+
let devtoolsIds = {};
|
|
1366
1383
|
// `external` is server-mode-only (documented no-op in client mode, so a
|
|
1367
1384
|
// host-integrated config survives the `ssr` boolean flip untouched).
|
|
1368
1385
|
const externalServer = !clientMode && !!options.external;
|
|
@@ -1379,6 +1396,65 @@ function startServe(options, internal = {}) {
|
|
|
1379
1396
|
if (!entries) throw new Error('[@solidjs/vite-plugin] SSR entries not resolved yet');
|
|
1380
1397
|
return entries;
|
|
1381
1398
|
}
|
|
1399
|
+
async function resolveDevtools(resolve, importer, consumer) {
|
|
1400
|
+
if (!devtoolsEnabled) return false;
|
|
1401
|
+
// Detect from the app graph first (the documented install location), then
|
|
1402
|
+
// from the plugin's own file: in pnpm-isolated apps a copy that is only a
|
|
1403
|
+
// dependency of the plugin is not reachable from the app's importers. The
|
|
1404
|
+
// resolved id is kept so imports from generated modules can use it.
|
|
1405
|
+
devtoolsResolutions[consumer] ??= (async () => {
|
|
1406
|
+
// Resolving from the plugin's own file never yields null when the
|
|
1407
|
+
// package is absent: it is declared an optional peer dependency, so
|
|
1408
|
+
// Vite answers with its `__vite-optional-peer-dep:` stub (an empty
|
|
1409
|
+
// module). Treat that stub as "not installed".
|
|
1410
|
+
const realId = resolved => resolved && !resolved.id.startsWith('__vite-optional-peer-dep:') ? resolved.id : null;
|
|
1411
|
+
return realId(await resolve(DEVTOOLS_PACKAGE, importer)) ?? realId(await resolve(DEVTOOLS_PACKAGE, fileURLToPath(import.meta.url)));
|
|
1412
|
+
})();
|
|
1413
|
+
const id = await devtoolsResolutions[consumer];
|
|
1414
|
+
devtoolsIds[consumer] = id;
|
|
1415
|
+
if (!id && options.devtools === true) {
|
|
1416
|
+
throw new Error('[@solidjs/vite-plugin] start.devtools requires @solidjs/start-devtools. ' + 'Install it as a development dependency or set start.devtools to false.');
|
|
1417
|
+
}
|
|
1418
|
+
return id !== null;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
/**
|
|
1422
|
+
* Cheap walk-up probe mirroring how the optimizer resolves bare
|
|
1423
|
+
* `optimizeDeps.include` entries: is @solidjs/start-devtools reachable from
|
|
1424
|
+
* this directory? Detection proper (resolveDevtools) runs later with a real
|
|
1425
|
+
* importer; this only decides whether the toolbar graph can be pre-bundled
|
|
1426
|
+
* at scan time.
|
|
1427
|
+
*/
|
|
1428
|
+
function devtoolsReachableFrom(dir) {
|
|
1429
|
+
for (let current = dir;;) {
|
|
1430
|
+
if (existsSync(path.join(current, 'node_modules', DEVTOOLS_PACKAGE, 'package.json'))) {
|
|
1431
|
+
return true;
|
|
1432
|
+
}
|
|
1433
|
+
const parent = path.dirname(current);
|
|
1434
|
+
if (parent === current) return false;
|
|
1435
|
+
current = parent;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* The `optimizeDeps.include` spec that pre-bundles the toolbar graph, or
|
|
1441
|
+
* null when it cannot be resolved at all. Pre-bundling it is not just a
|
|
1442
|
+
* warm-start nicety: the toolbar hangs off virtual modules the scanner
|
|
1443
|
+
* never crawls, so without an include the optimizer only discovers it on
|
|
1444
|
+
* first request. That re-optimize can pair chunks from different passes
|
|
1445
|
+
* whose shared minified exports disagree, taking down the whole client
|
|
1446
|
+
* entry graph. The spec must therefore cover every install shape
|
|
1447
|
+
* resolveDevtools accepts: bare when the app installs the package, and
|
|
1448
|
+
* Vite's nested-include form (`plugin > dep`) when it is only a dependency
|
|
1449
|
+
* of this plugin (pnpm-isolated installs).
|
|
1450
|
+
*/
|
|
1451
|
+
function devtoolsIncludeSpec(rootDir) {
|
|
1452
|
+
if (devtoolsReachableFrom(rootDir)) return DEVTOOLS_PACKAGE;
|
|
1453
|
+
if (devtoolsReachableFrom(path.dirname(fileURLToPath(import.meta.url)))) {
|
|
1454
|
+
return `@solidjs/vite-plugin > ${DEVTOOLS_PACKAGE}`;
|
|
1455
|
+
}
|
|
1456
|
+
return null;
|
|
1457
|
+
}
|
|
1382
1458
|
|
|
1383
1459
|
/** Import specifier for generated code: absolute for files, id for virtuals. */
|
|
1384
1460
|
function entryServerSpec() {
|
|
@@ -1418,7 +1494,7 @@ function startServe(options, internal = {}) {
|
|
|
1418
1494
|
return generated ? [app, ...(document ? [document] : [])] : [path.resolve(root, entryServer)];
|
|
1419
1495
|
}
|
|
1420
1496
|
async function devStylesModuleCode(environment, watchFile) {
|
|
1421
|
-
const styles = await collectDevStyleSources(environment, styleRoots(), watchFile);
|
|
1497
|
+
const styles = await collectDevStyleSources(environment, styleRoots(), watchFile, styleFilter);
|
|
1422
1498
|
if (!styles.length) return `export default '';`;
|
|
1423
1499
|
const imports = styles.map((style, index) => {
|
|
1424
1500
|
const specifier = style.url.includes('?') ? `${style.url}&inline` : `${style.url}?inline`;
|
|
@@ -1426,19 +1502,26 @@ function startServe(options, internal = {}) {
|
|
|
1426
1502
|
});
|
|
1427
1503
|
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');
|
|
1428
1504
|
}
|
|
1429
|
-
function
|
|
1505
|
+
function errorBoundaryImport() {
|
|
1506
|
+
return isBuild && errorBoundary ? [`import { DefaultErrorBoundary } from ${JSON.stringify(ERROR_BOUNDARY_ID)};`] : [];
|
|
1507
|
+
}
|
|
1508
|
+
function documentTree(root, wrapper) {
|
|
1509
|
+
const content = wrapper ? `<${wrapper}><${root} /></${wrapper}>` : `<${root} />`;
|
|
1510
|
+
return isBuild && errorBoundary ? [` <DefaultErrorBoundary>`, ` <Document>`, ` <DefaultErrorBoundary>`, ` ${content}`, ` </DefaultErrorBoundary>`, ` </Document>`, ` </DefaultErrorBoundary>`] : [` <Document>`, ` ${content}`, ` </Document>`];
|
|
1511
|
+
}
|
|
1512
|
+
function generatedEntryServerCode(toolbar) {
|
|
1430
1513
|
if (clientMode) {
|
|
1431
1514
|
// The client-mode shell: the document without the app. Rendered per
|
|
1432
1515
|
// request in dev (any HTML GET gets it — history-fallback semantics)
|
|
1433
1516
|
// and once at build time into dist/client/index.html. The client
|
|
1434
1517
|
// entry script is injected by the handler, exactly like SSR mode.
|
|
1435
|
-
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
|
|
1518
|
+
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');
|
|
1436
1519
|
}
|
|
1437
1520
|
const {
|
|
1438
1521
|
app
|
|
1439
1522
|
} = requireEntries();
|
|
1440
1523
|
const streamOptions = `{ manifest${serverComponents ? ', plugins: [ServerComponentPlugin]' : ''} }`;
|
|
1441
|
-
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 ? [
|
|
1524
|
+
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 ? [
|
|
1442
1525
|
// Direct (in-process) server-function calls made during document
|
|
1443
1526
|
// SSR must resolve to inline-renderable components; the endpoint
|
|
1444
1527
|
// response transform is installed separately by the
|
|
@@ -1452,9 +1535,9 @@ function startServe(options, internal = {}) {
|
|
|
1452
1535
|
// the *complete* render) and buffers the stream — so it crosses
|
|
1453
1536
|
// boxed under a private key the generated handler unboxes
|
|
1454
1537
|
// (both modules are ours).
|
|
1455
|
-
`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(() => (`,
|
|
1538
|
+
`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');
|
|
1456
1539
|
}
|
|
1457
|
-
function generatedEntryClientCode() {
|
|
1540
|
+
function generatedEntryClientCode(toolbar) {
|
|
1458
1541
|
const {
|
|
1459
1542
|
app
|
|
1460
1543
|
} = requireEntries();
|
|
@@ -1464,13 +1547,13 @@ function startServe(options, internal = {}) {
|
|
|
1464
1547
|
// app cannot claim server DOM anyway. The entry script is injected
|
|
1465
1548
|
// without `async` (plain module = deferred), so document.body is
|
|
1466
1549
|
// complete when this runs.
|
|
1467
|
-
return [`import { render } from '@solidjs/web';`, `import App from ${JSON.stringify(app)};`, ``, `render(() => <App
|
|
1550
|
+
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');
|
|
1468
1551
|
}
|
|
1469
|
-
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 ? [
|
|
1552
|
+
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 ? [
|
|
1470
1553
|
// Installs the t=0 document-adoption registry and the transport
|
|
1471
1554
|
// policy (component responses morph their boundary instead of
|
|
1472
1555
|
// decoding as data). Must run before hydrate().
|
|
1473
|
-
`installServerComponents();`, ``] : []), `hydrate(() => (`,
|
|
1556
|
+
`installServerComponents();`, ``] : []), `hydrate(() => (`, ...documentTree('App', toolbar ? 'DevToolbar' : undefined), `), document);`].join('\n');
|
|
1474
1557
|
}
|
|
1475
1558
|
|
|
1476
1559
|
// Built-in document shell: minimal, hydration-ready. The client entry
|
|
@@ -1481,6 +1564,7 @@ function startServe(options, internal = {}) {
|
|
|
1481
1564
|
// HydrationScript is covered too: the handler strips the event-capture
|
|
1482
1565
|
// script from the client-mode shell.)
|
|
1483
1566
|
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');
|
|
1567
|
+
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');
|
|
1484
1568
|
|
|
1485
1569
|
// The handler module: dev and prod share the render/response plumbing;
|
|
1486
1570
|
// they differ in how the client entry URL is known (baked dev URL vs a
|
|
@@ -1532,7 +1616,7 @@ function startServe(options, internal = {}) {
|
|
|
1532
1616
|
// head-open splice actively broke hydration — a script ahead of the
|
|
1533
1617
|
// authored <head> elements claims as the first walked child and drifts
|
|
1534
1618
|
// every positional claim after it.
|
|
1535
|
-
lines.push(``, `function createHtmlChunkTransform(clientEntry, extraHead) {`, ` let first = true;`, ` let injected = false;`, ` return (chunk) => {`);
|
|
1619
|
+
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) => {`);
|
|
1536
1620
|
if (!generated) {
|
|
1537
1621
|
// Authored entries reference the client entry by its dev path (the
|
|
1538
1622
|
// `<script src="/src/entry-client.tsx">` convention); rewrite it to
|
|
@@ -1562,7 +1646,7 @@ function startServe(options, internal = {}) {
|
|
|
1562
1646
|
// scripts default to deferred execution, which is exactly right for a
|
|
1563
1647
|
// fresh render-into-body mount (hydration, by contrast, wants to
|
|
1564
1648
|
// start as early as possible).
|
|
1565
|
-
headParts.push(`(clientEntry ? '<script type="module" src="' + clientEntry + '"${clientMode ? '' : ' async'}></' + 'script>' : '')`);
|
|
1649
|
+
headParts.push(`(clientEntry ? '<script type="module"' + nonceAttr + ' src="' + clientEntry + '"${clientMode ? '' : ' async'}></' + 'script>' : '')`);
|
|
1566
1650
|
}
|
|
1567
1651
|
if (headParts.length) {
|
|
1568
1652
|
lines.push(` chunk = chunk.replace('</head>', ${headParts.join(' + ')} + '</head>');`);
|
|
@@ -1616,7 +1700,7 @@ function startServe(options, internal = {}) {
|
|
|
1616
1700
|
// The runtime's response-head lifecycle: commit at shell flush,
|
|
1617
1701
|
// pre-flush Location as a real redirect, post-flush Location as the
|
|
1618
1702
|
// script fallback; the transform injects the doctype/head pieces.
|
|
1619
|
-
` return createSSRResponse(result, event, {`, ` responseInit: options.responseInit,`, ` nonce: options.nonce,`, ` transformChunk: createHtmlChunkTransform(clientEntry, options.devHead),`, ` });`, `}`, ``, `export async function handleRequest(request, options = {}) {`,
|
|
1703
|
+
` return createSSRResponse(result, event, {`, ` responseInit: options.responseInit,`, ` nonce: options.nonce,`, ` transformChunk: createHtmlChunkTransform(clientEntry, options.devHead, options.nonce),`, ` });`, `}`, ``, `export async function handleRequest(request, options = {}) {`,
|
|
1620
1704
|
// `options.event` is the public wrapper->event extension seam: extra
|
|
1621
1705
|
// fields (conventionally `nativeEvent`, the platform's raw request
|
|
1622
1706
|
// object) spread over the event's defaults at creation, so hosts and
|
|
@@ -1646,6 +1730,9 @@ function startServe(options, internal = {}) {
|
|
|
1646
1730
|
enforce: 'pre',
|
|
1647
1731
|
config(userConfig, env) {
|
|
1648
1732
|
root = path.resolve(userConfig.root || process.cwd());
|
|
1733
|
+
devtoolsEnabled = env.command === 'serve' && !env.isPreview && options.devtools !== false;
|
|
1734
|
+
devtoolsResolutions = {};
|
|
1735
|
+
devtoolsIds = {};
|
|
1649
1736
|
entries = resolveEntries(root, options, clientMode);
|
|
1650
1737
|
middlewarePath = options.middleware ? path.resolve(root, normalizeUserPath(root, options.middleware, 'middleware')) : null;
|
|
1651
1738
|
// Server-mode only, like `entryServer`/`external` (a documented
|
|
@@ -1736,7 +1823,7 @@ function startServe(options, internal = {}) {
|
|
|
1736
1823
|
}
|
|
1737
1824
|
},
|
|
1738
1825
|
// Presence of `builder` makes a plain `vite build` build the
|
|
1739
|
-
// whole app (all environments: client then ssr)
|
|
1826
|
+
// whole app (all environments: client then ssr).
|
|
1740
1827
|
// A classic `vite build --ssr` invocation must stay a
|
|
1741
1828
|
// single-environment build, so it doesn't get the flag.
|
|
1742
1829
|
...(env.isSsrBuild ? {} : {
|
|
@@ -1764,17 +1851,34 @@ function startServe(options, internal = {}) {
|
|
|
1764
1851
|
}
|
|
1765
1852
|
} : {}),
|
|
1766
1853
|
optimizeDeps: {
|
|
1767
|
-
entries: scanEntries
|
|
1854
|
+
entries: scanEntries,
|
|
1855
|
+
// Like the refresh runtime in the main plugin: the toolbar
|
|
1856
|
+
// graph is injected behind modules the scanner never crawls,
|
|
1857
|
+
// so pre-bundle it and the server-functions runtime up front.
|
|
1858
|
+
...(() => {
|
|
1859
|
+
const spec = devtoolsEnabled ? devtoolsIncludeSpec(root) : null;
|
|
1860
|
+
return spec ? {
|
|
1861
|
+
include: [spec, '@solidjs/web/server-functions']
|
|
1862
|
+
} : {};
|
|
1863
|
+
})()
|
|
1768
1864
|
}
|
|
1769
1865
|
})
|
|
1770
1866
|
};
|
|
1771
1867
|
},
|
|
1868
|
+
configEnvironment(name, config) {
|
|
1869
|
+
if (name !== 'ssr') return;
|
|
1870
|
+
config.resolve ??= {};
|
|
1871
|
+
const noExternal = config.resolve.noExternal;
|
|
1872
|
+
if (noExternal !== true) {
|
|
1873
|
+
config.resolve.noExternal = [...(Array.isArray(noExternal) ? noExternal : noExternal ? [noExternal] : []), DEVTOOLS_PACKAGE];
|
|
1874
|
+
}
|
|
1875
|
+
},
|
|
1772
1876
|
configResolved(config) {
|
|
1773
1877
|
root = config.root;
|
|
1774
1878
|
base = config.base;
|
|
1775
1879
|
isBuild = config.command === 'build';
|
|
1776
1880
|
},
|
|
1777
|
-
resolveId(source) {
|
|
1881
|
+
resolveId(source, importer, opts) {
|
|
1778
1882
|
if (source === HANDLER_ID) {
|
|
1779
1883
|
return {
|
|
1780
1884
|
id: HANDLER_ID,
|
|
@@ -1787,33 +1891,95 @@ function startServe(options, internal = {}) {
|
|
|
1787
1891
|
moduleSideEffects: true
|
|
1788
1892
|
};
|
|
1789
1893
|
}
|
|
1790
|
-
if (source === ENTRY_SERVER_ID || source === ENTRY_CLIENT_ID || source === DOCUMENT_ID) {
|
|
1894
|
+
if (source === ENTRY_SERVER_ID || source === ENTRY_CLIENT_ID || source === DOCUMENT_ID || source === ERROR_BOUNDARY_ID) {
|
|
1791
1895
|
return {
|
|
1792
1896
|
id: source,
|
|
1793
1897
|
moduleSideEffects: source === ENTRY_CLIENT_ID
|
|
1794
1898
|
};
|
|
1795
1899
|
}
|
|
1900
|
+
if (devtoolsEnabled && source === DEVTOOLS_MOUNT_ID) {
|
|
1901
|
+
return {
|
|
1902
|
+
id: source,
|
|
1903
|
+
moduleSideEffects: true
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
// Generated modules have no directory for bare-package resolution.
|
|
1907
|
+
// Reuse the app-relative id captured during detection.
|
|
1908
|
+
const devtoolsId = devtoolsIds[getEnvironmentConsumer(this.environment, opts)];
|
|
1909
|
+
if (devtoolsId && source === DEVTOOLS_PACKAGE && (importer === ENTRY_SERVER_ID || importer === ENTRY_CLIENT_ID || importer === DEVTOOLS_MOUNT_ID)) {
|
|
1910
|
+
return {
|
|
1911
|
+
id: devtoolsId
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1796
1914
|
return null;
|
|
1797
1915
|
},
|
|
1798
1916
|
async load(id, opts) {
|
|
1917
|
+
const consumer = getEnvironmentConsumer(this.environment, opts);
|
|
1799
1918
|
if (id === HANDLER_ID) {
|
|
1800
|
-
if (
|
|
1919
|
+
if (consumer !== 'server') {
|
|
1801
1920
|
this.error(`${HANDLER_ID} is server-only; import it from server code (SSR build).`);
|
|
1802
1921
|
}
|
|
1803
1922
|
const externalDev = !isBuild && this.environment.mode === 'dev' && (externalServer || !isRunnableEnvironment(this.environment));
|
|
1804
1923
|
return handlerModuleCode(externalDev);
|
|
1805
1924
|
}
|
|
1806
1925
|
if (id === RESOLVED_DEV_STYLES_ID) {
|
|
1807
|
-
if (
|
|
1926
|
+
if (consumer !== 'server' || this.environment.mode !== 'dev') {
|
|
1808
1927
|
this.error(`${DEV_STYLES_ID} is only available to the development server handler.`);
|
|
1809
1928
|
}
|
|
1810
1929
|
return devStylesModuleCode(this.environment, file => this.addWatchFile(file));
|
|
1811
1930
|
}
|
|
1812
|
-
if (id === ENTRY_SERVER_ID)
|
|
1813
|
-
|
|
1931
|
+
if (id === ENTRY_SERVER_ID) {
|
|
1932
|
+
const toolbar = clientMode ? false : await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1933
|
+
skipSelf: true
|
|
1934
|
+
}), requireEntries().app, 'server');
|
|
1935
|
+
return generatedEntryServerCode(toolbar);
|
|
1936
|
+
}
|
|
1937
|
+
if (id === ENTRY_CLIENT_ID) {
|
|
1938
|
+
const toolbar = await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1939
|
+
skipSelf: true
|
|
1940
|
+
}), requireEntries().app, 'client');
|
|
1941
|
+
return generatedEntryClientCode(toolbar);
|
|
1942
|
+
}
|
|
1814
1943
|
if (id === DOCUMENT_ID) return documentShellCode;
|
|
1944
|
+
if (id === ERROR_BOUNDARY_ID) return errorBoundaryCode;
|
|
1945
|
+
if (id === DEVTOOLS_MOUNT_ID) {
|
|
1946
|
+
let enabled = false;
|
|
1947
|
+
if (devtoolsEnabled && consumer === 'client') {
|
|
1948
|
+
const {
|
|
1949
|
+
app,
|
|
1950
|
+
entryClient
|
|
1951
|
+
} = requireEntries();
|
|
1952
|
+
enabled = await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1953
|
+
skipSelf: true
|
|
1954
|
+
}), app ?? path.resolve(root, entryClient), 'client');
|
|
1955
|
+
}
|
|
1956
|
+
if (!enabled) {
|
|
1957
|
+
this.error(`${id} is only available to the development client.`);
|
|
1958
|
+
}
|
|
1959
|
+
return devtoolsMountModuleCode();
|
|
1960
|
+
}
|
|
1815
1961
|
return null;
|
|
1816
1962
|
},
|
|
1963
|
+
async transform(code, id, opts) {
|
|
1964
|
+
if (isBuild || !devtoolsEnabled) return null;
|
|
1965
|
+
const current = requireEntries();
|
|
1966
|
+
if (current.generated || getEnvironmentConsumer(this.environment, opts) !== 'client') {
|
|
1967
|
+
return null;
|
|
1968
|
+
}
|
|
1969
|
+
// Module ids are always forward-slashed; normalize the path.resolve
|
|
1970
|
+
// side too so the comparison holds on Windows.
|
|
1971
|
+
if (normalizePath(id.split('?')[0]) !== normalizePath(path.resolve(root, current.entryClient))) {
|
|
1972
|
+
return null;
|
|
1973
|
+
}
|
|
1974
|
+
const toolbar = await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1975
|
+
skipSelf: true
|
|
1976
|
+
}), id, 'client');
|
|
1977
|
+
if (!toolbar) return null;
|
|
1978
|
+
return {
|
|
1979
|
+
code: `import ${JSON.stringify(DEVTOOLS_MOUNT_ID)};\n${code}`,
|
|
1980
|
+
map: null
|
|
1981
|
+
};
|
|
1982
|
+
},
|
|
1817
1983
|
configurePreviewServer(server) {
|
|
1818
1984
|
// `vite build && vite preview` runs the production artifact as-is:
|
|
1819
1985
|
// Vite's preview statics serve dist/client (see the config hook) and
|
|
@@ -1871,7 +2037,7 @@ function startServe(options, internal = {}) {
|
|
|
1871
2037
|
// that gets the streamed SSR render.
|
|
1872
2038
|
return () => {
|
|
1873
2039
|
const ssrEnvironment = server.environments.ssr;
|
|
1874
|
-
if (externalServer ||
|
|
2040
|
+
if (externalServer || !isRunnableEnvironment(ssrEnvironment)) {
|
|
1875
2041
|
return;
|
|
1876
2042
|
}
|
|
1877
2043
|
server.middlewares.use((req, res, next) => {
|
|
@@ -1888,8 +2054,8 @@ function startServe(options, internal = {}) {
|
|
|
1888
2054
|
(async () => {
|
|
1889
2055
|
// Loaded through the SSR environment so the app, the request
|
|
1890
2056
|
// event storage, and the handler share one module registry.
|
|
1891
|
-
const handler = await
|
|
1892
|
-
const styles = pageRequest ? await collectDevStyles(server, styleRoots()) : [];
|
|
2057
|
+
const handler = await ssrEnvironment.runner.import(HANDLER_ID);
|
|
2058
|
+
const styles = pageRequest ? await collectDevStyles(server, styleRoots(), styleFilter) : [];
|
|
1893
2059
|
const devHead = styles.map(renderDevStyleTag).join('');
|
|
1894
2060
|
// Post middlewares run after Vite's base middleware stripped
|
|
1895
2061
|
// the configured `base` from req.url; restore it so the app
|
|
@@ -1913,7 +2079,6 @@ function startServe(options, internal = {}) {
|
|
|
1913
2079
|
if (response.headers.has(DEV_FALLTHROUGH_HEADER)) return next();
|
|
1914
2080
|
await sendWebResponse(res, response);
|
|
1915
2081
|
})().catch(error => {
|
|
1916
|
-
if (error instanceof Error) server.ssrFixStacktrace(error);
|
|
1917
2082
|
// Vite's error middleware renders the overlay-enabled 500 page.
|
|
1918
2083
|
next(error);
|
|
1919
2084
|
});
|
|
@@ -2003,7 +2168,7 @@ function startServe(options, internal = {}) {
|
|
|
2003
2168
|
// https://github.com/pyyupsk/vite-env), the design-correct prior art. The
|
|
2004
2169
|
// implementation is fresh against this plugin's machinery: Standard Schema
|
|
2005
2170
|
// is the only contract (no zod dependency or zod-specific paths), the
|
|
2006
|
-
// schema file loads through Vite's own `runnerImport
|
|
2171
|
+
// schema file loads through Vite's own `runnerImport`
|
|
2007
2172
|
// (no jiti), server-graph protection keys off the environment *consumer*
|
|
2008
2173
|
// rather than environment-name lists, and the types are inferred from the
|
|
2009
2174
|
// user's schema instead of introspected per-library.
|
|
@@ -2062,37 +2227,18 @@ function formatValidationError(issues, envFile, mode) {
|
|
|
2062
2227
|
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.`;
|
|
2063
2228
|
}
|
|
2064
2229
|
|
|
2065
|
-
/**
|
|
2066
|
-
* Loads the schema module at config time through Vite itself: `runnerImport`
|
|
2067
|
-
* (Vite 6.1+) evaluates TypeScript in-process with project resolution;
|
|
2068
|
-
* older Vite 6 falls back to `loadConfigFromFile`, the exact machinery that
|
|
2069
|
-
* loads vite.config.ts.
|
|
2070
|
-
*/
|
|
2230
|
+
/** Loads the schema module through Vite with project resolution. */
|
|
2071
2231
|
async function importSchemaModule(envFileAbs, root, mode) {
|
|
2072
|
-
const
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
} = await vite.runnerImport(envFileAbs, {
|
|
2078
|
-
root,
|
|
2079
|
-
mode
|
|
2080
|
-
});
|
|
2081
|
-
return {
|
|
2082
|
-
exported: module?.default,
|
|
2083
|
-
dependencies: (dependencies || []).map(dep => path.resolve(root, dep)).filter(dep => existsSync(dep))
|
|
2084
|
-
};
|
|
2085
|
-
}
|
|
2086
|
-
const result = await vite.loadConfigFromFile({
|
|
2087
|
-
command: 'serve',
|
|
2232
|
+
const {
|
|
2233
|
+
module,
|
|
2234
|
+
dependencies
|
|
2235
|
+
} = await runnerImport(envFileAbs, {
|
|
2236
|
+
root,
|
|
2088
2237
|
mode
|
|
2089
|
-
}
|
|
2090
|
-
if (!result) {
|
|
2091
|
-
throw new Error(`[@solidjs/vite-plugin] failed to load env schema from ${envFileAbs}`);
|
|
2092
|
-
}
|
|
2238
|
+
});
|
|
2093
2239
|
return {
|
|
2094
|
-
exported:
|
|
2095
|
-
dependencies:
|
|
2240
|
+
exported: module?.default,
|
|
2241
|
+
dependencies: dependencies.map(dep => path.resolve(root, dep)).filter(dep => existsSync(dep))
|
|
2096
2242
|
};
|
|
2097
2243
|
}
|
|
2098
2244
|
function assertSchemaShape(exported, envFile, envPrefixes) {
|
|
@@ -2576,8 +2722,7 @@ const LAZY_PLACEHOLDER_PREFIX = '__SOLID_LAZY_MODULE__:';
|
|
|
2576
2722
|
* solid-refresh#85 — is no longer used at all).
|
|
2577
2723
|
*/
|
|
2578
2724
|
const REFRESH_RUNTIME_SOURCE = 'solid-js/refresh';
|
|
2579
|
-
const
|
|
2580
|
-
const isVite8 = viteVersionMajor >= 8;
|
|
2725
|
+
const DEFAULT_STYLE_EXCLUDE = /node_modules/;
|
|
2581
2726
|
const VIRTUAL_MANIFEST_ID = 'virtual:solid-manifest';
|
|
2582
2727
|
const RESOLVED_VIRTUAL_MANIFEST_ID = '\0' + VIRTUAL_MANIFEST_ID;
|
|
2583
2728
|
|
|
@@ -2832,10 +2977,30 @@ function solidPlugin(options = {}) {
|
|
|
2832
2977
|
// `start: true` is sugar for the empty options bag — one start mode,
|
|
2833
2978
|
// two spellings — so normalize here and let everything downstream see a
|
|
2834
2979
|
// single shape (`false` behaves exactly like omission).
|
|
2835
|
-
const
|
|
2980
|
+
const startOptions = options.start === true ? {} : options.start || null;
|
|
2981
|
+
const styleFilterOptions = startOptions?.css?.filter;
|
|
2982
|
+
// The CSS crawl walks the module graph from the app's own entries, so a
|
|
2983
|
+
// plain createFilter allowlist can't express the option's purpose (opting
|
|
2984
|
+
// node_modules graphs in): a bare `include` would reject the app sources
|
|
2985
|
+
// the crawl has to traverse to ever reach the included package. Instead
|
|
2986
|
+
// `include` rescues files on top of the baseline (everything except
|
|
2987
|
+
// `exclude`, which defaults to node_modules), while a file matching both
|
|
2988
|
+
// patterns stays excluded — createFilter's own conflict rule.
|
|
2989
|
+
const createStyleFilter = resolve => {
|
|
2990
|
+
const opts = resolve === undefined ? undefined : {
|
|
2991
|
+
resolve
|
|
2992
|
+
};
|
|
2993
|
+
const base = createFilter(undefined, styleFilterOptions?.exclude ?? DEFAULT_STYLE_EXCLUDE, opts);
|
|
2994
|
+
const include = styleFilterOptions?.include;
|
|
2995
|
+
const hasInclude = include != null && (!Array.isArray(include) || include.length > 0);
|
|
2996
|
+
const included = hasInclude ? createFilter(include, styleFilterOptions?.exclude, opts) : null;
|
|
2997
|
+
return id => base(id) || (included ? included(id) : false);
|
|
2998
|
+
};
|
|
2999
|
+
let styleFilter = createStyleFilter();
|
|
3000
|
+
const filterDevStyles = id => styleFilter(id);
|
|
2836
3001
|
// `start.external` only means something when a server side exists to hand
|
|
2837
3002
|
// over (SSR start mode); in client mode it is a documented no-op.
|
|
2838
|
-
const externalDevServer = !!options.ssr && !!
|
|
3003
|
+
const externalDevServer = !!options.ssr && !!startOptions?.external;
|
|
2839
3004
|
let needHmr = false;
|
|
2840
3005
|
let replaceDev = false;
|
|
2841
3006
|
// The live dev server, kept so the dev manifest module can bake the bridge
|
|
@@ -3047,40 +3212,24 @@ function solidPlugin(options = {}) {
|
|
|
3047
3212
|
// server-components runtime installs its response policy there).
|
|
3048
3213
|
...(command === 'serve' && serverComponents ? ['@solidjs/web/frames', '@solidjs/web/server-functions'] : []), ...solidPkgsConfig.optimizeDeps.include],
|
|
3049
3214
|
exclude: solidPkgsConfig.optimizeDeps.exclude,
|
|
3050
|
-
//
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
// (issue #262). The classic runtime is the only scan-safe lowering:
|
|
3056
|
-
// it emits bare `React.createElement` calls without injecting any
|
|
3057
|
-
// import, and the scan output is never executed — it only exists so
|
|
3058
|
-
// rolldown can walk the import graph.
|
|
3059
|
-
...(isVite8 ? {
|
|
3060
|
-
rolldownOptions: {
|
|
3061
|
-
transform: {
|
|
3062
|
-
jsx: {
|
|
3063
|
-
runtime: 'classic'
|
|
3064
|
-
}
|
|
3215
|
+
// Keep Solid TSX from injecting React's automatic runtime during scanning.
|
|
3216
|
+
rolldownOptions: {
|
|
3217
|
+
transform: {
|
|
3218
|
+
jsx: {
|
|
3219
|
+
runtime: 'classic'
|
|
3065
3220
|
}
|
|
3066
3221
|
}
|
|
3067
|
-
}
|
|
3222
|
+
}
|
|
3068
3223
|
},
|
|
3069
3224
|
...(Object.keys(test).length ? {
|
|
3070
3225
|
test
|
|
3071
3226
|
} : {})
|
|
3072
3227
|
};
|
|
3073
3228
|
},
|
|
3074
|
-
|
|
3075
|
-
async configEnvironment(name, config, opts) {
|
|
3229
|
+
configEnvironment(name, config, opts) {
|
|
3076
3230
|
config.resolve ??= {};
|
|
3077
3231
|
// Emulate Vite default fallback for `resolve.conditions` if not set
|
|
3078
3232
|
if (config.resolve.conditions == null) {
|
|
3079
|
-
// @ts-ignore These exports only exist in Vite 6
|
|
3080
|
-
const {
|
|
3081
|
-
defaultClientConditions,
|
|
3082
|
-
defaultServerConditions
|
|
3083
|
-
} = await import('vite');
|
|
3084
3233
|
if (config.consumer === 'client' || name === 'client' || opts.isSsrTargetWebworker) {
|
|
3085
3234
|
config.resolve.conditions = [...defaultClientConditions];
|
|
3086
3235
|
} else {
|
|
@@ -3113,6 +3262,7 @@ function solidPlugin(options = {}) {
|
|
|
3113
3262
|
filter = createFilter(options.include, options.exclude, {
|
|
3114
3263
|
resolve: projectRoot
|
|
3115
3264
|
});
|
|
3265
|
+
styleFilter = createStyleFilter(projectRoot);
|
|
3116
3266
|
if (serverComponents && !(options.start && options.ssr)) {
|
|
3117
3267
|
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.');
|
|
3118
3268
|
}
|
|
@@ -3126,7 +3276,7 @@ function solidPlugin(options = {}) {
|
|
|
3126
3276
|
// that don't share globals with this process, through the HTTP bridge
|
|
3127
3277
|
// endpoint the middleware serves.
|
|
3128
3278
|
if (options.ssr || options.start) {
|
|
3129
|
-
registerDevAssetResolver(server.config.root, createDevAssetResolver(server));
|
|
3279
|
+
registerDevAssetResolver(server.config.root, createDevAssetResolver(server, filterDevStyles));
|
|
3130
3280
|
installDevManifestBridge(server);
|
|
3131
3281
|
}
|
|
3132
3282
|
if (!needHmr) return;
|
|
@@ -3242,7 +3392,7 @@ function solidPlugin(options = {}) {
|
|
|
3242
3392
|
}
|
|
3243
3393
|
},
|
|
3244
3394
|
async transform(source, id, transformOptions) {
|
|
3245
|
-
const isSsr = transformOptions
|
|
3395
|
+
const isSsr = getEnvironmentConsumer(this.environment, transformOptions) === 'server';
|
|
3246
3396
|
const currentFileExtension = getExtension(id);
|
|
3247
3397
|
const extensionsToWatch = options.extensions || [];
|
|
3248
3398
|
const allExtensions = extensionsToWatch.map(extension =>
|
|
@@ -3382,7 +3532,7 @@ function solidPlugin(options = {}) {
|
|
|
3382
3532
|
// With start mode on (either variant), the dev middleware dispatches
|
|
3383
3533
|
// the endpoint through the SSR handler so user middleware and the
|
|
3384
3534
|
// stub-backed request event front it exactly like page SSR.
|
|
3385
|
-
...(
|
|
3535
|
+
...(startOptions ? {
|
|
3386
3536
|
ssrHandler: SSR_HANDLER_ID
|
|
3387
3537
|
} : {})
|
|
3388
3538
|
}), mainPlugin] : [boundaryModules(), mainPlugin];
|
|
@@ -3390,15 +3540,16 @@ function solidPlugin(options = {}) {
|
|
|
3390
3540
|
// The `start` option opts into start-mode serving on top of the transforms;
|
|
3391
3541
|
// the `ssr` boolean picks the mode (a bare `ssr: true` keeps the
|
|
3392
3542
|
// historical transform-only behavior).
|
|
3393
|
-
if (
|
|
3543
|
+
if (startOptions) {
|
|
3394
3544
|
plugins.push(
|
|
3395
3545
|
// Typed env (`start.env`) rides both start modes: config-time
|
|
3396
3546
|
// validation, the virtual:env/{server,client} modules, generated
|
|
3397
3547
|
// types, and the client-bundle leak scan.
|
|
3398
|
-
...startEnv(
|
|
3548
|
+
...startEnv(startOptions.env), ...startServe(startOptions, {
|
|
3399
3549
|
serverFunctions: !!options.serverFunctions,
|
|
3400
3550
|
serverComponents,
|
|
3401
|
-
ssr: !!options.ssr
|
|
3551
|
+
ssr: !!options.ssr,
|
|
3552
|
+
styleFilter: filterDevStyles
|
|
3402
3553
|
}));
|
|
3403
3554
|
}
|
|
3404
3555
|
|
|
@@ -3413,8 +3564,7 @@ function solidPlugin(options = {}) {
|
|
|
3413
3564
|
// would bake a manifest-less fallback into the server bundle. Every user
|
|
3414
3565
|
// of such a setup had to hand-write this ordering plugin; absorb it.
|
|
3415
3566
|
//
|
|
3416
|
-
// Semantics
|
|
3417
|
-
// these, keeping its build-everything default):
|
|
3567
|
+
// Semantics:
|
|
3418
3568
|
// - The first hook builds the client environment first, but only where
|
|
3419
3569
|
// the ordering matters: a client build that emits a manifest and
|
|
3420
3570
|
// actually has an input. It runs at *normal* order, deliberately not
|