@solidjs/vite-plugin 3.0.0-next.30 → 3.0.0-next.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -4
- package/dist/cjs/index.cjs +205 -43
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.mjs +207 -45
- package/dist/esm/index.mjs.map +1 -1
- package/dist/types/src/dev-manifest.d.ts +4 -3
- package/dist/types/src/devtools/index.d.ts +5 -0
- package/dist/types/src/environment.d.ts +3 -0
- package/dist/types/src/ssr/index.d.ts +42 -1
- package/package.json +8 -3
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
|
|
@@ -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
|
|
@@ -999,12 +1007,12 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
999
1007
|
const relative = path.relative(root, entry).split(path.sep).join('/');
|
|
1000
1008
|
return relative.startsWith('..') ? '/@fs/' + entry : '/' + relative;
|
|
1001
1009
|
}
|
|
1002
|
-
const
|
|
1010
|
+
const startPlugins = [{
|
|
1003
1011
|
name: 'solid:server-functions/handler',
|
|
1004
1012
|
enforce: 'pre',
|
|
1005
1013
|
resolveId(source, _importer, opts) {
|
|
1006
1014
|
if (source === HANDLER_ID$1) {
|
|
1007
|
-
if (
|
|
1015
|
+
if (getEnvironmentConsumer(this.environment, opts) !== 'server') {
|
|
1008
1016
|
this.error(`${HANDLER_ID$1} is server-only; import it from your server entry (SSR build).`);
|
|
1009
1017
|
}
|
|
1010
1018
|
return {
|
|
@@ -1015,7 +1023,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1015
1023
|
return null;
|
|
1016
1024
|
},
|
|
1017
1025
|
load(id, opts) {
|
|
1018
|
-
if (id === HANDLER_ID$1 && opts
|
|
1026
|
+
if (id === HANDLER_ID$1 && getEnvironmentConsumer(this.environment, opts) === 'server') {
|
|
1019
1027
|
const externalDev = this.environment.mode === 'dev' && (internal.externalDevServer || !isRunnableEnvironment(this.environment));
|
|
1020
1028
|
return handlerModuleCode(isBuild || externalDev);
|
|
1021
1029
|
}
|
|
@@ -1023,7 +1031,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1023
1031
|
}
|
|
1024
1032
|
}];
|
|
1025
1033
|
if (installDevMiddleware) {
|
|
1026
|
-
|
|
1034
|
+
startPlugins.push({
|
|
1027
1035
|
name: 'solid:server-functions/dev-middleware',
|
|
1028
1036
|
apply: 'serve',
|
|
1029
1037
|
configureServer(server) {
|
|
@@ -1135,7 +1143,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1135
1143
|
return null;
|
|
1136
1144
|
},
|
|
1137
1145
|
async load(id, opts) {
|
|
1138
|
-
const mode = opts
|
|
1146
|
+
const mode = getEnvironmentConsumer(this.environment, opts);
|
|
1139
1147
|
if (id === manifestId) {
|
|
1140
1148
|
if (isBuild && mode === 'server') {
|
|
1141
1149
|
// Merge the client build's persisted discoveries at load time,
|
|
@@ -1158,7 +1166,7 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1158
1166
|
name: 'solid:server-functions/compiler',
|
|
1159
1167
|
enforce: 'pre',
|
|
1160
1168
|
async transform(code, fileId, opts) {
|
|
1161
|
-
const mode = opts
|
|
1169
|
+
const mode = getEnvironmentConsumer(this.environment, opts);
|
|
1162
1170
|
const [id] = fileId.split('?');
|
|
1163
1171
|
if (!filter(id)) {
|
|
1164
1172
|
return null;
|
|
@@ -1191,7 +1199,17 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
1191
1199
|
}
|
|
1192
1200
|
return null;
|
|
1193
1201
|
}
|
|
1194
|
-
}, ...
|
|
1202
|
+
}, ...startPlugins];
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
const DEVTOOLS_PACKAGE = '@solidjs/start-devtools';
|
|
1206
|
+
const DEVTOOLS_ID = 'virtual:solid-devtools';
|
|
1207
|
+
const DEVTOOLS_MOUNT_ID = 'virtual:solid-devtools/mount';
|
|
1208
|
+
function devtoolsModuleCode() {
|
|
1209
|
+
return [`import * as serverFunctions from '@solidjs/web/server-functions';`, `import { DevToolbar, pushServerFunctionCall } from '${DEVTOOLS_PACKAGE}';`, `const observe = Reflect.get(serverFunctions, 'observeServerFunctionCalls');`, `if (typeof observe === 'function') observe(pushServerFunctionCall);`, `export { DevToolbar };`].join('\n');
|
|
1210
|
+
}
|
|
1211
|
+
function devtoolsMountModuleCode() {
|
|
1212
|
+
return [`import ${JSON.stringify(DEVTOOLS_ID)};`, `import { mountDevToolbar } from '${DEVTOOLS_PACKAGE}';`, `mountDevToolbar();`].join('\n');
|
|
1195
1213
|
}
|
|
1196
1214
|
|
|
1197
1215
|
// Start-mode serving for plain Vite apps: `solid({ start: {...} })` (or the
|
|
@@ -1276,6 +1294,7 @@ const RESOLVED_DEV_STYLES_ID = '\0' + DEV_STYLES_ID;
|
|
|
1276
1294
|
const ENTRY_SERVER_ID = 'virtual:solid-ssr-entry-server.tsx';
|
|
1277
1295
|
const ENTRY_CLIENT_ID = 'virtual:solid-ssr-entry-client.tsx';
|
|
1278
1296
|
const DOCUMENT_ID = 'virtual:solid-ssr-document.tsx';
|
|
1297
|
+
const ERROR_BOUNDARY_ID = 'virtual:solid-ssr-error-boundary.tsx';
|
|
1279
1298
|
const MANIFEST_ID = 'virtual:solid-manifest';
|
|
1280
1299
|
const SERVER_FUNCTION_HANDLER_ID = 'virtual:solid-server-function-handler';
|
|
1281
1300
|
const STORAGE_SOURCE = '@solidjs/web/storage';
|
|
@@ -1387,6 +1406,12 @@ function startServe(options, internal = {}) {
|
|
|
1387
1406
|
// the server-function handler module either way). Everything is gated
|
|
1388
1407
|
// codegen: with the option off, none of these imports exist anywhere.
|
|
1389
1408
|
const serverComponents = !!internal.serverComponents;
|
|
1409
|
+
const errorBoundary = options.errorBoundary !== false;
|
|
1410
|
+
const styleFilter = internal.styleFilter;
|
|
1411
|
+
let devtools = false;
|
|
1412
|
+
let devtoolsResolution;
|
|
1413
|
+
/** Resolved module id of `@solidjs/start-devtools` once detection succeeds. */
|
|
1414
|
+
let devtoolsId = null;
|
|
1390
1415
|
// `external` is server-mode-only (documented no-op in client mode, so a
|
|
1391
1416
|
// host-integrated config survives the `ssr` boolean flip untouched).
|
|
1392
1417
|
const externalServer = !clientMode && !!options.external;
|
|
@@ -1403,6 +1428,40 @@ function startServe(options, internal = {}) {
|
|
|
1403
1428
|
if (!entries) throw new Error('[@solidjs/vite-plugin] SSR entries not resolved yet');
|
|
1404
1429
|
return entries;
|
|
1405
1430
|
}
|
|
1431
|
+
async function resolveDevtools(resolve, importer) {
|
|
1432
|
+
if (devtools !== undefined) return devtools;
|
|
1433
|
+
// Detect from the app graph first (the documented install location), then
|
|
1434
|
+
// from the plugin's own file: in pnpm-isolated apps a copy that is only a
|
|
1435
|
+
// dependency of the plugin is not reachable from the app's importers. The
|
|
1436
|
+
// resolved id is kept so the virtual modules' imports of the package can
|
|
1437
|
+
// be delegated to it (see resolveId).
|
|
1438
|
+
devtoolsResolution ??= (async () => ((await resolve(DEVTOOLS_PACKAGE, importer)) ?? (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))))))?.id ?? null)();
|
|
1439
|
+
devtoolsId = await devtoolsResolution;
|
|
1440
|
+
devtools = devtoolsId !== null;
|
|
1441
|
+
if (!devtools && options.devtools === true) {
|
|
1442
|
+
throw new Error('[@solidjs/vite-plugin] start.devtools requires @solidjs/start-devtools. ' + 'Install it as a development dependency or set start.devtools to false.');
|
|
1443
|
+
}
|
|
1444
|
+
return devtools;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/**
|
|
1448
|
+
* Cheap root-walk probe mirroring how the optimizer resolves bare
|
|
1449
|
+
* `optimizeDeps.include` entries: is @solidjs/start-devtools reachable from
|
|
1450
|
+
* the Vite root? Detection proper (resolveDevtools) runs later with a real
|
|
1451
|
+
* importer; this only decides whether the toolbar graph can be pre-bundled
|
|
1452
|
+
* at scan time — it hangs off virtual modules the scanner never sees, so
|
|
1453
|
+
* first-request discovery would force a re-optimize + full page reload.
|
|
1454
|
+
*/
|
|
1455
|
+
function devtoolsReachableFromRoot(dir) {
|
|
1456
|
+
for (let current = dir;;) {
|
|
1457
|
+
if (fs.existsSync(path.join(current, 'node_modules', DEVTOOLS_PACKAGE, 'package.json'))) {
|
|
1458
|
+
return true;
|
|
1459
|
+
}
|
|
1460
|
+
const parent = path.dirname(current);
|
|
1461
|
+
if (parent === current) return false;
|
|
1462
|
+
current = parent;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1406
1465
|
|
|
1407
1466
|
/** Import specifier for generated code: absolute for files, id for virtuals. */
|
|
1408
1467
|
function entryServerSpec() {
|
|
@@ -1442,7 +1501,7 @@ function startServe(options, internal = {}) {
|
|
|
1442
1501
|
return generated ? [app, ...(document ? [document] : [])] : [path.resolve(root, entryServer)];
|
|
1443
1502
|
}
|
|
1444
1503
|
async function devStylesModuleCode(environment, watchFile) {
|
|
1445
|
-
const styles = await collectDevStyleSources(environment, styleRoots(), watchFile);
|
|
1504
|
+
const styles = await collectDevStyleSources(environment, styleRoots(), watchFile, styleFilter);
|
|
1446
1505
|
if (!styles.length) return `export default '';`;
|
|
1447
1506
|
const imports = styles.map((style, index) => {
|
|
1448
1507
|
const specifier = style.url.includes('?') ? `${style.url}&inline` : `${style.url}?inline`;
|
|
@@ -1450,19 +1509,26 @@ function startServe(options, internal = {}) {
|
|
|
1450
1509
|
});
|
|
1451
1510
|
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
1511
|
}
|
|
1512
|
+
function errorBoundaryImport() {
|
|
1513
|
+
return isBuild && errorBoundary ? [`import { DefaultErrorBoundary } from ${JSON.stringify(ERROR_BOUNDARY_ID)};`] : [];
|
|
1514
|
+
}
|
|
1515
|
+
function documentTree(root, wrapper) {
|
|
1516
|
+
const content = wrapper ? `<${wrapper}><${root} /></${wrapper}>` : `<${root} />`;
|
|
1517
|
+
return isBuild && errorBoundary ? [` <DefaultErrorBoundary>`, ` <Document>`, ` <DefaultErrorBoundary>`, ` ${content}`, ` </DefaultErrorBoundary>`, ` </Document>`, ` </DefaultErrorBoundary>`] : [` <Document>`, ` ${content}`, ` </Document>`];
|
|
1518
|
+
}
|
|
1453
1519
|
function generatedEntryServerCode() {
|
|
1454
1520
|
if (clientMode) {
|
|
1455
1521
|
// The client-mode shell: the document without the app. Rendered per
|
|
1456
1522
|
// request in dev (any HTML GET gets it — history-fallback semantics)
|
|
1457
1523
|
// and once at build time into dist/client/index.html. The client
|
|
1458
1524
|
// 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
|
|
1525
|
+
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
1526
|
}
|
|
1461
1527
|
const {
|
|
1462
1528
|
app
|
|
1463
1529
|
} = requireEntries();
|
|
1464
1530
|
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 ? [
|
|
1531
|
+
return [`import { renderToStream${setupPath ? ', getRequestEvent' : ''} } from '@solidjs/web';`, ...(serverComponents ? [`import { configureServerFunctionsServer } from '@solidjs/web/server-functions';`, `import { frameTransformDirectResult, ServerComponentPlugin } from '@solidjs/web/frames';`] : []), `import manifest from ${JSON.stringify(MANIFEST_ID)};`, `import Document from ${JSON.stringify(documentSpec())};`, `import App from ${JSON.stringify(app)};`, ...errorBoundaryImport(), ...(setupPath ? [`import setup from ${JSON.stringify(setupPath)};`] : []), ``, ...(setupPath ? [`if (typeof setup !== 'function') {`, ` throw new Error('[@solidjs/vite-plugin] start.setup must default-export a function ' +`, ` '((event, App) => Component | void | Promise<...>): ' + ${JSON.stringify(options.setup)});`, `}`, ``] : []), ...(serverComponents ? [
|
|
1466
1532
|
// Direct (in-process) server-function calls made during document
|
|
1467
1533
|
// SSR must resolve to inline-renderable components; the endpoint
|
|
1468
1534
|
// response transform is installed separately by the
|
|
@@ -1476,9 +1542,9 @@ function startServe(options, internal = {}) {
|
|
|
1476
1542
|
// the *complete* render) and buffers the stream — so it crosses
|
|
1477
1543
|
// boxed under a private key the generated handler unboxes
|
|
1478
1544
|
// (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(() => (`,
|
|
1545
|
+
`export function render(request, context) {`, ` const prepared = setup(getRequestEvent(), App);`, ` if (prepared && typeof prepared.then === 'function') {`, ` return prepared.then((component) => ({ ${STREAM_BOX}: renderApp(component || App) }));`, ` }`, ` return renderApp(prepared || App);`, `}`, ``, `function renderApp(Root) {`, ` return renderToStream(() => (`, ...documentTree('Root'), ` ), ${streamOptions});`, `}`] : [`export function render(request, context) {`, ` return renderToStream(() => (`, ...documentTree('App'), ` ), ${streamOptions});`, `}`])].join('\n');
|
|
1480
1546
|
}
|
|
1481
|
-
function generatedEntryClientCode() {
|
|
1547
|
+
function generatedEntryClientCode(toolbar) {
|
|
1482
1548
|
const {
|
|
1483
1549
|
app
|
|
1484
1550
|
} = requireEntries();
|
|
@@ -1488,13 +1554,13 @@ function startServe(options, internal = {}) {
|
|
|
1488
1554
|
// app cannot claim server DOM anyway. The entry script is injected
|
|
1489
1555
|
// without `async` (plain module = deferred), so document.body is
|
|
1490
1556
|
// complete when this runs.
|
|
1491
|
-
return [`import { render } from '@solidjs/web';`, `import App from ${JSON.stringify(app)};`, ``, `render(() => <App
|
|
1557
|
+
return [`import { render } from '@solidjs/web';`, ...errorBoundaryImport(), ...(toolbar ? [`import { DevToolbar } from ${JSON.stringify(DEVTOOLS_ID)};`] : []), `import App from ${JSON.stringify(app)};`, ``, `render(() => ${isBuild && errorBoundary ? '<DefaultErrorBoundary><App /></DefaultErrorBoundary>' : toolbar ? '<DevToolbar><App /></DevToolbar>' : '<App />'}, document.body);`].join('\n');
|
|
1492
1558
|
}
|
|
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 ? [
|
|
1559
|
+
return [`import { hydrate } from '@solidjs/web';`, ...(toolbar ? [`import { DevToolbar } from ${JSON.stringify(DEVTOOLS_ID)};`] : []), ...(serverComponents ? [`import { installServerComponents } from '@solidjs/web/frames';`] : []), ...errorBoundaryImport(), `import Document from ${JSON.stringify(documentSpec())};`, `import App from ${JSON.stringify(app)};`, ``, ...(serverComponents ? [
|
|
1494
1560
|
// Installs the t=0 document-adoption registry and the transport
|
|
1495
1561
|
// policy (component responses morph their boundary instead of
|
|
1496
1562
|
// decoding as data). Must run before hydrate().
|
|
1497
|
-
`installServerComponents();`, ``] : []), `hydrate(() => (`,
|
|
1563
|
+
`installServerComponents();`, ``] : []), `hydrate(() => (`, ...documentTree('App', toolbar ? 'DevToolbar' : undefined), `), document);`].join('\n');
|
|
1498
1564
|
}
|
|
1499
1565
|
|
|
1500
1566
|
// Built-in document shell: minimal, hydration-ready. The client entry
|
|
@@ -1505,6 +1571,7 @@ function startServe(options, internal = {}) {
|
|
|
1505
1571
|
// HydrationScript is covered too: the handler strips the event-capture
|
|
1506
1572
|
// script from the client-mode shell.)
|
|
1507
1573
|
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');
|
|
1574
|
+
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
1575
|
|
|
1509
1576
|
// The handler module: dev and prod share the render/response plumbing;
|
|
1510
1577
|
// they differ in how the client entry URL is known (baked dev URL vs a
|
|
@@ -1556,7 +1623,7 @@ function startServe(options, internal = {}) {
|
|
|
1556
1623
|
// head-open splice actively broke hydration — a script ahead of the
|
|
1557
1624
|
// authored <head> elements claims as the first walked child and drifts
|
|
1558
1625
|
// every positional claim after it.
|
|
1559
|
-
lines.push(``, `function createHtmlChunkTransform(clientEntry, extraHead) {`, ` let first = true;`, ` let injected = false;`, ` return (chunk) => {`);
|
|
1626
|
+
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
1627
|
if (!generated) {
|
|
1561
1628
|
// Authored entries reference the client entry by its dev path (the
|
|
1562
1629
|
// `<script src="/src/entry-client.tsx">` convention); rewrite it to
|
|
@@ -1586,7 +1653,7 @@ function startServe(options, internal = {}) {
|
|
|
1586
1653
|
// scripts default to deferred execution, which is exactly right for a
|
|
1587
1654
|
// fresh render-into-body mount (hydration, by contrast, wants to
|
|
1588
1655
|
// start as early as possible).
|
|
1589
|
-
headParts.push(`(clientEntry ? '<script type="module" src="' + clientEntry + '"${clientMode ? '' : ' async'}></' + 'script>' : '')`);
|
|
1656
|
+
headParts.push(`(clientEntry ? '<script type="module"' + nonceAttr + ' src="' + clientEntry + '"${clientMode ? '' : ' async'}></' + 'script>' : '')`);
|
|
1590
1657
|
}
|
|
1591
1658
|
if (headParts.length) {
|
|
1592
1659
|
lines.push(` chunk = chunk.replace('</head>', ${headParts.join(' + ')} + '</head>');`);
|
|
@@ -1640,7 +1707,7 @@ function startServe(options, internal = {}) {
|
|
|
1640
1707
|
// The runtime's response-head lifecycle: commit at shell flush,
|
|
1641
1708
|
// pre-flush Location as a real redirect, post-flush Location as the
|
|
1642
1709
|
// 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 = {}) {`,
|
|
1710
|
+
` return createSSRResponse(result, event, {`, ` responseInit: options.responseInit,`, ` nonce: options.nonce,`, ` transformChunk: createHtmlChunkTransform(clientEntry, options.devHead, options.nonce),`, ` });`, `}`, ``, `export async function handleRequest(request, options = {}) {`,
|
|
1644
1711
|
// `options.event` is the public wrapper->event extension seam: extra
|
|
1645
1712
|
// fields (conventionally `nativeEvent`, the platform's raw request
|
|
1646
1713
|
// object) spread over the event's defaults at creation, so hosts and
|
|
@@ -1670,6 +1737,9 @@ function startServe(options, internal = {}) {
|
|
|
1670
1737
|
enforce: 'pre',
|
|
1671
1738
|
config(userConfig, env) {
|
|
1672
1739
|
root = path.resolve(userConfig.root || process.cwd());
|
|
1740
|
+
devtools = env.command === 'serve' && !env.isPreview && options.devtools !== false ? undefined : false;
|
|
1741
|
+
devtoolsResolution = undefined;
|
|
1742
|
+
devtoolsId = null;
|
|
1673
1743
|
entries = resolveEntries(root, options, clientMode);
|
|
1674
1744
|
middlewarePath = options.middleware ? path.resolve(root, normalizeUserPath(root, options.middleware, 'middleware')) : null;
|
|
1675
1745
|
// Server-mode only, like `entryServer`/`external` (a documented
|
|
@@ -1788,7 +1858,15 @@ function startServe(options, internal = {}) {
|
|
|
1788
1858
|
}
|
|
1789
1859
|
} : {}),
|
|
1790
1860
|
optimizeDeps: {
|
|
1791
|
-
entries: scanEntries
|
|
1861
|
+
entries: scanEntries,
|
|
1862
|
+
// Like the refresh runtime in the main plugin: the toolbar
|
|
1863
|
+
// graph is injected behind virtual modules the scanner never
|
|
1864
|
+
// crawls, so pre-bundle it (and the server-functions runtime
|
|
1865
|
+
// the virtual module pulls in) up front — first-request
|
|
1866
|
+
// discovery would re-optimize and full-reload the page.
|
|
1867
|
+
...(devtools === undefined && devtoolsReachableFromRoot(root) ? {
|
|
1868
|
+
include: [DEVTOOLS_PACKAGE, '@solidjs/web/server-functions']
|
|
1869
|
+
} : {})
|
|
1792
1870
|
}
|
|
1793
1871
|
})
|
|
1794
1872
|
};
|
|
@@ -1798,7 +1876,7 @@ function startServe(options, internal = {}) {
|
|
|
1798
1876
|
base = config.base;
|
|
1799
1877
|
isBuild = config.command === 'build';
|
|
1800
1878
|
},
|
|
1801
|
-
resolveId(source) {
|
|
1879
|
+
resolveId(source, importer) {
|
|
1802
1880
|
if (source === HANDLER_ID) {
|
|
1803
1881
|
return {
|
|
1804
1882
|
id: HANDLER_ID,
|
|
@@ -1811,33 +1889,94 @@ function startServe(options, internal = {}) {
|
|
|
1811
1889
|
moduleSideEffects: true
|
|
1812
1890
|
};
|
|
1813
1891
|
}
|
|
1814
|
-
if (source === ENTRY_SERVER_ID || source === ENTRY_CLIENT_ID || source === DOCUMENT_ID) {
|
|
1892
|
+
if (source === ENTRY_SERVER_ID || source === ENTRY_CLIENT_ID || source === DOCUMENT_ID || source === ERROR_BOUNDARY_ID) {
|
|
1815
1893
|
return {
|
|
1816
1894
|
id: source,
|
|
1817
1895
|
moduleSideEffects: source === ENTRY_CLIENT_ID
|
|
1818
1896
|
};
|
|
1819
1897
|
}
|
|
1898
|
+
if (devtools && (source === DEVTOOLS_ID || source === DEVTOOLS_MOUNT_ID)) {
|
|
1899
|
+
return {
|
|
1900
|
+
id: source,
|
|
1901
|
+
moduleSideEffects: true
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
// The virtual devtools modules import the package by its bare name,
|
|
1905
|
+
// but a virtual importer gives Vite no directory to walk, so the
|
|
1906
|
+
// specifier would only resolve from the Vite root — which fails in
|
|
1907
|
+
// pnpm-isolated apps where the package is not a root-level install.
|
|
1908
|
+
// Delegate to the resolution captured at detection time instead.
|
|
1909
|
+
if (devtoolsId && source === DEVTOOLS_PACKAGE && (importer === DEVTOOLS_ID || importer === DEVTOOLS_MOUNT_ID)) {
|
|
1910
|
+
return {
|
|
1911
|
+
id: devtoolsId
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1820
1914
|
return null;
|
|
1821
1915
|
},
|
|
1822
1916
|
async load(id, opts) {
|
|
1917
|
+
const consumer = getEnvironmentConsumer(this.environment, opts);
|
|
1823
1918
|
if (id === HANDLER_ID) {
|
|
1824
|
-
if (
|
|
1919
|
+
if (consumer !== 'server') {
|
|
1825
1920
|
this.error(`${HANDLER_ID} is server-only; import it from server code (SSR build).`);
|
|
1826
1921
|
}
|
|
1827
1922
|
const externalDev = !isBuild && this.environment.mode === 'dev' && (externalServer || !isRunnableEnvironment(this.environment));
|
|
1828
1923
|
return handlerModuleCode(externalDev);
|
|
1829
1924
|
}
|
|
1830
1925
|
if (id === RESOLVED_DEV_STYLES_ID) {
|
|
1831
|
-
if (
|
|
1926
|
+
if (consumer !== 'server' || this.environment.mode !== 'dev') {
|
|
1832
1927
|
this.error(`${DEV_STYLES_ID} is only available to the development server handler.`);
|
|
1833
1928
|
}
|
|
1834
1929
|
return devStylesModuleCode(this.environment, file => this.addWatchFile(file));
|
|
1835
1930
|
}
|
|
1836
1931
|
if (id === ENTRY_SERVER_ID) return generatedEntryServerCode();
|
|
1837
|
-
if (id === ENTRY_CLIENT_ID)
|
|
1932
|
+
if (id === ENTRY_CLIENT_ID) {
|
|
1933
|
+
const toolbar = await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1934
|
+
skipSelf: true
|
|
1935
|
+
}), requireEntries().app);
|
|
1936
|
+
return generatedEntryClientCode(toolbar);
|
|
1937
|
+
}
|
|
1838
1938
|
if (id === DOCUMENT_ID) return documentShellCode;
|
|
1939
|
+
if (id === ERROR_BOUNDARY_ID) return errorBoundaryCode;
|
|
1940
|
+
if (id === DEVTOOLS_ID || id === DEVTOOLS_MOUNT_ID) {
|
|
1941
|
+
// A cold direct request (stale tab reload) can reach the virtual
|
|
1942
|
+
// module before the entry has triggered detection — run it here so
|
|
1943
|
+
// first-touch order doesn't matter.
|
|
1944
|
+
if (!isBuild && consumer === 'client' && devtools === undefined) {
|
|
1945
|
+
const {
|
|
1946
|
+
app,
|
|
1947
|
+
entryClient
|
|
1948
|
+
} = requireEntries();
|
|
1949
|
+
await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1950
|
+
skipSelf: true
|
|
1951
|
+
}), app ?? path.resolve(root, entryClient));
|
|
1952
|
+
}
|
|
1953
|
+
if (isBuild || !devtools || consumer !== 'client') {
|
|
1954
|
+
this.error(`${id} is only available to the development client.`);
|
|
1955
|
+
}
|
|
1956
|
+
return id === DEVTOOLS_ID ? devtoolsModuleCode() : devtoolsMountModuleCode();
|
|
1957
|
+
}
|
|
1839
1958
|
return null;
|
|
1840
1959
|
},
|
|
1960
|
+
async transform(code, id, opts) {
|
|
1961
|
+
if (isBuild || devtools === false) return null;
|
|
1962
|
+
const current = requireEntries();
|
|
1963
|
+
if (current.generated || getEnvironmentConsumer(this.environment, opts) !== 'client') {
|
|
1964
|
+
return null;
|
|
1965
|
+
}
|
|
1966
|
+
// Module ids are always forward-slashed; normalize the path.resolve
|
|
1967
|
+
// side too so the comparison holds on Windows.
|
|
1968
|
+
if (vite.normalizePath(id.split('?')[0]) !== vite.normalizePath(path.resolve(root, current.entryClient))) {
|
|
1969
|
+
return null;
|
|
1970
|
+
}
|
|
1971
|
+
const toolbar = await resolveDevtools((source, importer) => this.resolve(source, importer, {
|
|
1972
|
+
skipSelf: true
|
|
1973
|
+
}), id);
|
|
1974
|
+
if (!toolbar) return null;
|
|
1975
|
+
return {
|
|
1976
|
+
code: `import ${JSON.stringify(DEVTOOLS_MOUNT_ID)};\n${code}`,
|
|
1977
|
+
map: null
|
|
1978
|
+
};
|
|
1979
|
+
},
|
|
1841
1980
|
configurePreviewServer(server) {
|
|
1842
1981
|
// `vite build && vite preview` runs the production artifact as-is:
|
|
1843
1982
|
// Vite's preview statics serve dist/client (see the config hook) and
|
|
@@ -1913,7 +2052,7 @@ function startServe(options, internal = {}) {
|
|
|
1913
2052
|
// Loaded through the SSR environment so the app, the request
|
|
1914
2053
|
// event storage, and the handler share one module registry.
|
|
1915
2054
|
const handler = await server.ssrLoadModule(HANDLER_ID);
|
|
1916
|
-
const styles = pageRequest ? await collectDevStyles(server, styleRoots()) : [];
|
|
2055
|
+
const styles = pageRequest ? await collectDevStyles(server, styleRoots(), styleFilter) : [];
|
|
1917
2056
|
const devHead = styles.map(renderDevStyleTag).join('');
|
|
1918
2057
|
// Post middlewares run after Vite's base middleware stripped
|
|
1919
2058
|
// the configured `base` from req.url; restore it so the app
|
|
@@ -2602,6 +2741,7 @@ const LAZY_PLACEHOLDER_PREFIX = '__SOLID_LAZY_MODULE__:';
|
|
|
2602
2741
|
const REFRESH_RUNTIME_SOURCE = 'solid-js/refresh';
|
|
2603
2742
|
const viteVersionMajor = +vite.version.split('.')[0];
|
|
2604
2743
|
const isVite8 = viteVersionMajor >= 8;
|
|
2744
|
+
const DEFAULT_STYLE_EXCLUDE = /node_modules/;
|
|
2605
2745
|
const VIRTUAL_MANIFEST_ID = 'virtual:solid-manifest';
|
|
2606
2746
|
const RESOLVED_VIRTUAL_MANIFEST_ID = '\0' + VIRTUAL_MANIFEST_ID;
|
|
2607
2747
|
|
|
@@ -2856,10 +2996,30 @@ function solidPlugin(options = {}) {
|
|
|
2856
2996
|
// `start: true` is sugar for the empty options bag — one start mode,
|
|
2857
2997
|
// two spellings — so normalize here and let everything downstream see a
|
|
2858
2998
|
// single shape (`false` behaves exactly like omission).
|
|
2859
|
-
const
|
|
2999
|
+
const startOptions = options.start === true ? {} : options.start || null;
|
|
3000
|
+
const styleFilterOptions = startOptions?.css?.filter;
|
|
3001
|
+
// The CSS crawl walks the module graph from the app's own entries, so a
|
|
3002
|
+
// plain createFilter allowlist can't express the option's purpose (opting
|
|
3003
|
+
// node_modules graphs in): a bare `include` would reject the app sources
|
|
3004
|
+
// the crawl has to traverse to ever reach the included package. Instead
|
|
3005
|
+
// `include` rescues files on top of the baseline (everything except
|
|
3006
|
+
// `exclude`, which defaults to node_modules), while a file matching both
|
|
3007
|
+
// patterns stays excluded — createFilter's own conflict rule.
|
|
3008
|
+
const createStyleFilter = resolve => {
|
|
3009
|
+
const opts = resolve === undefined ? undefined : {
|
|
3010
|
+
resolve
|
|
3011
|
+
};
|
|
3012
|
+
const base = vite.createFilter(undefined, styleFilterOptions?.exclude ?? DEFAULT_STYLE_EXCLUDE, opts);
|
|
3013
|
+
const include = styleFilterOptions?.include;
|
|
3014
|
+
const hasInclude = include != null && (!Array.isArray(include) || include.length > 0);
|
|
3015
|
+
const included = hasInclude ? vite.createFilter(include, styleFilterOptions?.exclude, opts) : null;
|
|
3016
|
+
return id => base(id) || (included ? included(id) : false);
|
|
3017
|
+
};
|
|
3018
|
+
let styleFilter = createStyleFilter();
|
|
3019
|
+
const filterDevStyles = id => styleFilter(id);
|
|
2860
3020
|
// `start.external` only means something when a server side exists to hand
|
|
2861
3021
|
// over (SSR start mode); in client mode it is a documented no-op.
|
|
2862
|
-
const externalDevServer = !!options.ssr && !!
|
|
3022
|
+
const externalDevServer = !!options.ssr && !!startOptions?.external;
|
|
2863
3023
|
let needHmr = false;
|
|
2864
3024
|
let replaceDev = false;
|
|
2865
3025
|
// The live dev server, kept so the dev manifest module can bake the bridge
|
|
@@ -3137,6 +3297,7 @@ function solidPlugin(options = {}) {
|
|
|
3137
3297
|
filter = vite.createFilter(options.include, options.exclude, {
|
|
3138
3298
|
resolve: projectRoot
|
|
3139
3299
|
});
|
|
3300
|
+
styleFilter = createStyleFilter(projectRoot);
|
|
3140
3301
|
if (serverComponents && !(options.start && options.ssr)) {
|
|
3141
3302
|
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
3303
|
}
|
|
@@ -3150,7 +3311,7 @@ function solidPlugin(options = {}) {
|
|
|
3150
3311
|
// that don't share globals with this process, through the HTTP bridge
|
|
3151
3312
|
// endpoint the middleware serves.
|
|
3152
3313
|
if (options.ssr || options.start) {
|
|
3153
|
-
registerDevAssetResolver(server.config.root, createDevAssetResolver(server));
|
|
3314
|
+
registerDevAssetResolver(server.config.root, createDevAssetResolver(server, filterDevStyles));
|
|
3154
3315
|
installDevManifestBridge(server);
|
|
3155
3316
|
}
|
|
3156
3317
|
if (!needHmr) return;
|
|
@@ -3266,7 +3427,7 @@ function solidPlugin(options = {}) {
|
|
|
3266
3427
|
}
|
|
3267
3428
|
},
|
|
3268
3429
|
async transform(source, id, transformOptions) {
|
|
3269
|
-
const isSsr = transformOptions
|
|
3430
|
+
const isSsr = getEnvironmentConsumer(this.environment, transformOptions) === 'server';
|
|
3270
3431
|
const currentFileExtension = getExtension(id);
|
|
3271
3432
|
const extensionsToWatch = options.extensions || [];
|
|
3272
3433
|
const allExtensions = extensionsToWatch.map(extension =>
|
|
@@ -3406,7 +3567,7 @@ function solidPlugin(options = {}) {
|
|
|
3406
3567
|
// With start mode on (either variant), the dev middleware dispatches
|
|
3407
3568
|
// the endpoint through the SSR handler so user middleware and the
|
|
3408
3569
|
// stub-backed request event front it exactly like page SSR.
|
|
3409
|
-
...(
|
|
3570
|
+
...(startOptions ? {
|
|
3410
3571
|
ssrHandler: SSR_HANDLER_ID
|
|
3411
3572
|
} : {})
|
|
3412
3573
|
}), mainPlugin] : [boundaryModules(), mainPlugin];
|
|
@@ -3414,15 +3575,16 @@ function solidPlugin(options = {}) {
|
|
|
3414
3575
|
// The `start` option opts into start-mode serving on top of the transforms;
|
|
3415
3576
|
// the `ssr` boolean picks the mode (a bare `ssr: true` keeps the
|
|
3416
3577
|
// historical transform-only behavior).
|
|
3417
|
-
if (
|
|
3578
|
+
if (startOptions) {
|
|
3418
3579
|
plugins.push(
|
|
3419
3580
|
// Typed env (`start.env`) rides both start modes: config-time
|
|
3420
3581
|
// validation, the virtual:env/{server,client} modules, generated
|
|
3421
3582
|
// types, and the client-bundle leak scan.
|
|
3422
|
-
...startEnv(
|
|
3583
|
+
...startEnv(startOptions.env), ...startServe(startOptions, {
|
|
3423
3584
|
serverFunctions: !!options.serverFunctions,
|
|
3424
3585
|
serverComponents,
|
|
3425
|
-
ssr: !!options.ssr
|
|
3586
|
+
ssr: !!options.ssr,
|
|
3587
|
+
styleFilter: filterDevStyles
|
|
3426
3588
|
}));
|
|
3427
3589
|
}
|
|
3428
3590
|
|