evolit 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -0
- package/package.json +8 -2
- package/src/build.js +88 -11
- package/src/client-assets.js +1 -0
- package/src/compiler.js +301 -34
- package/src/deployment-runtime.js +77 -8
- package/src/index.js +3 -0
- package/src/litsx-pipeline.d.ts +137 -0
- package/src/litsx-pipeline.js +705 -0
- package/src/render.js +1 -0
- package/src/server-setup.js +65 -0
- package/src/server.js +41 -1
- package/src/urql-ssr.js +67 -2
package/README.md
CHANGED
|
@@ -46,6 +46,26 @@ Generated applications declare `"@/*": ["./*"]` in `jsconfig.json`, so editors a
|
|
|
46
46
|
use the same convention. An explicit `@/*` mapping in `jsconfig.json` or `tsconfig.json` takes
|
|
47
47
|
priority when an application needs a different source root.
|
|
48
48
|
|
|
49
|
+
### Interpolated dynamic imports
|
|
50
|
+
|
|
51
|
+
Relative dynamic imports may use a template literal when every possible module can be discovered
|
|
52
|
+
from the literal path at build time:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
const module = await import(`./templates/${locale}/${name}.tsx`);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Evolit treats each interpolation as one wildcard that matches a single path segment. The example
|
|
59
|
+
above discovers `./templates/*/*.tsx`, compiles every matching module, and emits a finite runtime
|
|
60
|
+
dispatcher whose cases preserve the original source specifiers. Interpolations never cross a `/`,
|
|
61
|
+
and there is no framework-imposed candidate limit.
|
|
62
|
+
|
|
63
|
+
The template must be relative, end in a supported authored module extension, and match at least one
|
|
64
|
+
module during compilation. A runtime value that was not among the discovered candidates rejects the
|
|
65
|
+
import. Development watches the matched directory tree, so adding a new candidate invalidates the
|
|
66
|
+
affected graph. Arbitrary computed specifiers that do not use this template-literal form remain
|
|
67
|
+
outside the statically discoverable graph.
|
|
68
|
+
|
|
49
69
|
### Package CSS and static assets
|
|
50
70
|
|
|
51
71
|
Applications can import stylesheets exposed through package `exports` in the same way as local
|
|
@@ -301,6 +321,94 @@ server components and helpers that do not receive route props directly. It retur
|
|
|
301
321
|
Reading `getRouteState().url` has the same dynamic-rendering semantics as `requestUrl()`; reading
|
|
302
322
|
`params` and `searchParams` participates in the normal segment-cache key tracking.
|
|
303
323
|
|
|
324
|
+
## LitSX compiler integrations
|
|
325
|
+
|
|
326
|
+
`evolit.config.js` can configure the native LitSX compiler and register build-tool-neutral LitSX
|
|
327
|
+
integrations. The same declaration is used by development, SSR, hydration, production builds and
|
|
328
|
+
the standalone runtime:
|
|
329
|
+
|
|
330
|
+
```js
|
|
331
|
+
import { litsxUnoCss } from "@litsx/unocss";
|
|
332
|
+
import { defineEvolitConfig } from "evolit/litsx";
|
|
333
|
+
|
|
334
|
+
export default defineEvolitConfig({
|
|
335
|
+
litsx: {
|
|
336
|
+
compiler: {
|
|
337
|
+
sourceMaps: true,
|
|
338
|
+
},
|
|
339
|
+
integrations: [litsxUnoCss()],
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
This is the complete Evolit + UnoCSS setup. The application does not coordinate compiler plugins,
|
|
345
|
+
virtual modules, generation, finalization or global CSS links, and it does not need Vite or
|
|
346
|
+
PostCSS. `@litsx/unocss` remains the owner of candidate extraction and CSS generation; Evolit only
|
|
347
|
+
executes its neutral LitSX lifecycle.
|
|
348
|
+
|
|
349
|
+
`compiler` accepts the public `TransformLitsxOptions` surface except the values Evolit must own for
|
|
350
|
+
correct server and browser graphs. Evolit always supplies the current `filename`, selects `ssr` per
|
|
351
|
+
target and forces native lowering with `reactCompat: false`; `compiler.sourceMaps` can override the
|
|
352
|
+
existing per-pipeline default without changing applications that omit `litsx`.
|
|
353
|
+
Authoring and output plugins contributed by the application and integrations are composed in
|
|
354
|
+
declaration order rather than replacing framework invariants.
|
|
355
|
+
|
|
356
|
+
An integration is a descriptor with a unique `name` and `create(context)` method. Each call to
|
|
357
|
+
`create` belongs to exactly one development server, build or standalone runtime, so mutable caches
|
|
358
|
+
must live on the returned instance, never on the descriptor. An instance can contribute:
|
|
359
|
+
|
|
360
|
+
- `compiler`: native compiler options, including authoring and output plugins.
|
|
361
|
+
- `resolveModule`: sources for integration-owned virtual modules in both server and browser graphs.
|
|
362
|
+
- `processModule`: post-processing, observable dependencies and preliminary outputs for a compiled
|
|
363
|
+
module.
|
|
364
|
+
- `finalize`: graph-wide assets, styles or modules after every reachable module is known.
|
|
365
|
+
- `invalidate` and `forget`: configuration/dependency refresh and removal of stale module state.
|
|
366
|
+
- `dispose`: idempotent cleanup when the owning server, build or runtime closes.
|
|
367
|
+
|
|
368
|
+
Declared outputs use stable IDs unique within the pipeline and one of `asset`, `module` or `style`. A module
|
|
369
|
+
may expose a safe `virtual:` specifier; a style with `document: true` is content-hashed, included in
|
|
370
|
+
the client asset manifest and linked once in the document. Evolit publishes a generation only after
|
|
371
|
+
every integration finalizes successfully, so a failed generation cannot expose a partial mix of
|
|
372
|
+
old and new assets.
|
|
373
|
+
|
|
374
|
+
In development, dependencies reported by compiler plugins and lifecycle hooks are watched together
|
|
375
|
+
with application modules. Invalidating a dependency advances the integration generation, evicts
|
|
376
|
+
affected compiled modules and calls `forget` for modules that leave the graph. Configuration reload
|
|
377
|
+
therefore removes obsolete results instead of accumulating them. Hook failures identify the
|
|
378
|
+
integration, lifecycle phase and relevant module in development; production errors keep the same
|
|
379
|
+
context while redacting project-local absolute paths.
|
|
380
|
+
|
|
381
|
+
For native Shadow DOM, `@litsx/unocss` serializes component preflight inside each Declarative Shadow
|
|
382
|
+
Root, preserves authored `Component.styles` before generated utilities, and emits the document
|
|
383
|
+
theme/custom-property layers once. Those document variables inherit through Shadow Roots and the
|
|
384
|
+
browser hydrates the server result without switching to `react-compat`.
|
|
385
|
+
|
|
386
|
+
## Server Setup
|
|
387
|
+
|
|
388
|
+
Server integrations that need one-time application setup can be registered with
|
|
389
|
+
`server.setup`. A module specifier is compiled as part of the server graph, so the
|
|
390
|
+
setup may be authored in JavaScript or TypeScript:
|
|
391
|
+
|
|
392
|
+
```js
|
|
393
|
+
// evolit.config.js
|
|
394
|
+
export default {
|
|
395
|
+
server: {
|
|
396
|
+
setup: "./src/server/setup.ts",
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
The module exports `setup` (or a default function). Evolit invokes it once per
|
|
402
|
+
runtime and once before build-time prerendering with `{ projectRoot, mode }`.
|
|
403
|
+
It may return a cleanup function or `{ dispose() }`; runtime cleanup runs from
|
|
404
|
+
`runtime.close()`.
|
|
405
|
+
|
|
406
|
+
When `@litsx/urql` is installed, Evolit opens its SSR resource around the whole
|
|
407
|
+
route render and supplies `{ request, responseHeaders }`. Request-derived URQL
|
|
408
|
+
configuration therefore stays isolated per render, extracted SSR data is read
|
|
409
|
+
before the scope closes, and integration response headers are included before
|
|
410
|
+
the response is cached.
|
|
411
|
+
|
|
304
412
|
## Extensions
|
|
305
413
|
|
|
306
414
|
Optional integrations are configured explicitly in `evolit.config.js`. Core only coordinates their
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evolit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A convention-driven application framework for LitSX and web components.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "yarn@4.10.3",
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
"browser": "./src/extensions-client.js",
|
|
35
35
|
"default": "./src/extensions.js"
|
|
36
36
|
},
|
|
37
|
+
"./litsx": {
|
|
38
|
+
"types": "./src/litsx-pipeline.d.ts",
|
|
39
|
+
"default": "./src/litsx-pipeline.js"
|
|
40
|
+
},
|
|
37
41
|
"./internal/development-hot": "./src/development-hot-client.js"
|
|
38
42
|
},
|
|
39
43
|
"scripts": {
|
|
@@ -71,7 +75,7 @@
|
|
|
71
75
|
"ws": "^8.18.3"
|
|
72
76
|
},
|
|
73
77
|
"peerDependencies": {
|
|
74
|
-
"@litsx/urql": "^0.
|
|
78
|
+
"@litsx/urql": "^0.4.0"
|
|
75
79
|
},
|
|
76
80
|
"peerDependenciesMeta": {
|
|
77
81
|
"@litsx/urql": {
|
|
@@ -79,8 +83,10 @@
|
|
|
79
83
|
}
|
|
80
84
|
},
|
|
81
85
|
"devDependencies": {
|
|
86
|
+
"@litsx/unocss": "1.0.0-next.8",
|
|
82
87
|
"@playwright/test": "^1.62.0",
|
|
83
88
|
"@webcomponents/scoped-custom-element-registry": "^0.0.10",
|
|
89
|
+
"unocss": "66.8.1",
|
|
84
90
|
"vite": "^8.1.5"
|
|
85
91
|
}
|
|
86
92
|
}
|
package/src/build.js
CHANGED
|
@@ -51,7 +51,14 @@ import {
|
|
|
51
51
|
import { serializeRouteCachePolicy } from "./route-config.js";
|
|
52
52
|
import { createSsrAdapter, renderRouteTreeWithAdapter } from "./ssr-adapter.js";
|
|
53
53
|
import { ensureDirectory, writeJson } from "./fs-utils.js";
|
|
54
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
appendSsrUrqlData,
|
|
56
|
+
applySsrUrqlRequestContext,
|
|
57
|
+
createSsrUrqlRequestContext,
|
|
58
|
+
runWithOptionalSsrUrqlScope,
|
|
59
|
+
} from "./urql-ssr.js";
|
|
60
|
+
import { runServerSetup } from "./server-setup.js";
|
|
61
|
+
import { createLitsxPipeline, mergeLitsxAssetsIntoManifest } from "./litsx-pipeline.js";
|
|
55
62
|
|
|
56
63
|
const CONTENT_TYPE_BY_EXTENSION = new Map([
|
|
57
64
|
[".css", "text/css; charset=utf-8"],
|
|
@@ -157,6 +164,20 @@ async function writeDeploymentRuntimeEntry(buildRoot) {
|
|
|
157
164
|
|
|
158
165
|
export async function buildProject(projectRoot, options = {}) {
|
|
159
166
|
const evolitConfig = await loadEvolitConfig(projectRoot);
|
|
167
|
+
const litsxPipeline = await createLitsxPipeline({
|
|
168
|
+
projectRoot,
|
|
169
|
+
mode: "production",
|
|
170
|
+
config: evolitConfig,
|
|
171
|
+
});
|
|
172
|
+
let serverSetupCleanup = null;
|
|
173
|
+
let buildError = null;
|
|
174
|
+
try {
|
|
175
|
+
serverSetupCleanup = await runServerSetup({
|
|
176
|
+
projectRoot,
|
|
177
|
+
mode: "production",
|
|
178
|
+
evolitConfig,
|
|
179
|
+
litsxPipeline,
|
|
180
|
+
});
|
|
160
181
|
const extensions = resolveEvolitExtensions(evolitConfig);
|
|
161
182
|
const extensionClientDescriptors = getExtensionClientDescriptors(extensions);
|
|
162
183
|
const packageClientSpecifiers = new Set(
|
|
@@ -183,6 +204,7 @@ export async function buildProject(projectRoot, options = {}) {
|
|
|
183
204
|
sourceMaps: false,
|
|
184
205
|
ssr: true,
|
|
185
206
|
target: "server",
|
|
207
|
+
litsxPipeline,
|
|
186
208
|
});
|
|
187
209
|
result.packageImports.forEach((specifier) => ssrPackageImports.add(specifier));
|
|
188
210
|
return result;
|
|
@@ -191,7 +213,7 @@ export async function buildProject(projectRoot, options = {}) {
|
|
|
191
213
|
function getEntryInventory(entryPath) {
|
|
192
214
|
let inventory = inventoriesBySourceEntry.get(entryPath);
|
|
193
215
|
if (!inventory) {
|
|
194
|
-
inventory = collectClientGraphInventory([entryPath], { projectRoot });
|
|
216
|
+
inventory = collectClientGraphInventory([entryPath], { projectRoot, litsxPipeline });
|
|
195
217
|
inventoriesBySourceEntry.set(entryPath, inventory);
|
|
196
218
|
}
|
|
197
219
|
return inventory;
|
|
@@ -205,6 +227,7 @@ export async function buildProject(projectRoot, options = {}) {
|
|
|
205
227
|
mode: "production",
|
|
206
228
|
sourceMaps: true,
|
|
207
229
|
target: "client",
|
|
230
|
+
litsxPipeline,
|
|
208
231
|
});
|
|
209
232
|
clientModule = path.relative(clientBuild.outputRoot, clientBuild.entrypoint)
|
|
210
233
|
.split(path.sep)
|
|
@@ -224,6 +247,7 @@ export async function buildProject(projectRoot, options = {}) {
|
|
|
224
247
|
mode: "production",
|
|
225
248
|
ssr: true,
|
|
226
249
|
target: "server",
|
|
250
|
+
litsxPipeline,
|
|
227
251
|
});
|
|
228
252
|
const methods = Object.keys(handlerModule)
|
|
229
253
|
.filter((name) => /^[A-Z]+$/.test(name) && typeof handlerModule[name] === "function")
|
|
@@ -334,18 +358,20 @@ export async function buildProject(projectRoot, options = {}) {
|
|
|
334
358
|
}
|
|
335
359
|
|
|
336
360
|
sharedVendorOptions.additionalEntrySpecifiers = [...packageClientSpecifiers].sort();
|
|
337
|
-
|
|
361
|
+
let clientAssets = await emitBundledClientAssets(projectRoot, {
|
|
338
362
|
entryClientModules,
|
|
339
363
|
additionalVendorSpecifiers: sharedVendorOptions.additionalEntrySpecifiers,
|
|
340
364
|
serverAssetImportsByEntry,
|
|
341
365
|
clientBoundariesByEntry,
|
|
342
366
|
});
|
|
367
|
+
const integrationOutputs = await litsxPipeline?.finalize({
|
|
368
|
+
routes,
|
|
369
|
+
routeHandlers,
|
|
370
|
+
});
|
|
371
|
+
clientAssets = mergeLitsxAssetsIntoManifest(clientAssets, integrationOutputs);
|
|
343
372
|
const staticAssetPublicUrls = createStaticAssetPublicUrlMap(clientAssets);
|
|
344
373
|
await rewriteServerAssetPlaceholders(projectRoot, clientAssets);
|
|
345
374
|
|
|
346
|
-
const routeResolver = await createRouteResolver(projectRoot, "production", {
|
|
347
|
-
staticAssetPublicUrls,
|
|
348
|
-
});
|
|
349
375
|
const sharedRuntime = await buildSharedVendorRuntime(projectRoot, "production", sharedVendorOptions);
|
|
350
376
|
clientAssets.sharedImports = { ...sharedRuntime.imports };
|
|
351
377
|
const packageImports = sharedRuntime.imports;
|
|
@@ -353,6 +379,11 @@ export async function buildProject(projectRoot, options = {}) {
|
|
|
353
379
|
assetManifest: clientAssets,
|
|
354
380
|
packageImports,
|
|
355
381
|
});
|
|
382
|
+
const routeResolver = await createRouteResolver(projectRoot, "production", {
|
|
383
|
+
staticAssetPublicUrls,
|
|
384
|
+
assetResolver,
|
|
385
|
+
litsxPipeline,
|
|
386
|
+
});
|
|
356
387
|
const hydrationModuleUrl = await resolveSharedVendorModuleUrl(
|
|
357
388
|
projectRoot,
|
|
358
389
|
"production",
|
|
@@ -412,7 +443,21 @@ export async function buildProject(projectRoot, options = {}) {
|
|
|
412
443
|
}
|
|
413
444
|
continue;
|
|
414
445
|
}
|
|
415
|
-
|
|
446
|
+
const directPublicUrl = assetResolver(moduleId)
|
|
447
|
+
?? (clientAssets.byPublicPath?.[moduleId] ? moduleId : null);
|
|
448
|
+
if (directPublicUrl) {
|
|
449
|
+
if (Array.isArray(result.clientImports)) {
|
|
450
|
+
result.clientImports = result.clientImports.map((value) => (
|
|
451
|
+
value === moduleId ? directPublicUrl : value
|
|
452
|
+
));
|
|
453
|
+
}
|
|
454
|
+
if (Array.isArray(result.hydrationData?.clientImports)) {
|
|
455
|
+
result.hydrationData.clientImports = result.hydrationData.clientImports.map((value) => (
|
|
456
|
+
value === moduleId ? directPublicUrl : value
|
|
457
|
+
));
|
|
458
|
+
}
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
416
461
|
const importerPath = routeResult.boundaryModule
|
|
417
462
|
?? routeResult.route?.page
|
|
418
463
|
?? path.join(projectRoot, "app", "page.jsx");
|
|
@@ -462,6 +507,7 @@ export async function buildProject(projectRoot, options = {}) {
|
|
|
462
507
|
])]
|
|
463
508
|
: [];
|
|
464
509
|
const styleUrls = [...new Set([
|
|
510
|
+
...(clientAssets.documentStyles ?? []),
|
|
465
511
|
...collectTransitiveStyleUrls(clientImports, clientAssets),
|
|
466
512
|
...resolveServerStyleUrls(routeResult, projectRoot, clientAssets),
|
|
467
513
|
])];
|
|
@@ -541,23 +587,32 @@ export async function buildProject(projectRoot, options = {}) {
|
|
|
541
587
|
seenPrerenderTargets.add(targetPathname);
|
|
542
588
|
|
|
543
589
|
const targetRequest = new Request(`http://evolit.local${targetPathname}`);
|
|
544
|
-
const
|
|
590
|
+
const urqlRequestContext = createSsrUrqlRequestContext(targetRequest);
|
|
591
|
+
const { routeResult, response } = await runWithOptionalSsrUrqlScope(urqlRequestContext, async (urqlAdapter) => {
|
|
545
592
|
const resolvedRouteResult = await routeResolver.resolveRequest(targetRequest);
|
|
546
593
|
if (resolvedRouteResult.type !== "route" || resolvedRouteResult.cachePolicy.mode === "dynamic") {
|
|
547
594
|
return { routeResult: resolvedRouteResult, response: null };
|
|
548
595
|
}
|
|
549
596
|
|
|
550
597
|
const renderedResponse = await renderRouteTreeWithAdapter(resolvedRouteResult, ssrAdapter);
|
|
598
|
+
const responseWithData = urqlAdapter
|
|
599
|
+
? appendSsrUrqlData(renderedResponse, await urqlAdapter.getUrqlSsrData())
|
|
600
|
+
: renderedResponse;
|
|
551
601
|
return {
|
|
552
602
|
routeResult: resolvedRouteResult,
|
|
553
|
-
response:
|
|
554
|
-
|
|
555
|
-
|
|
603
|
+
response: applySsrUrqlRequestContext(
|
|
604
|
+
resolvedRouteResult,
|
|
605
|
+
responseWithData,
|
|
606
|
+
urqlRequestContext,
|
|
607
|
+
),
|
|
556
608
|
};
|
|
557
609
|
});
|
|
558
610
|
if (!response) {
|
|
559
611
|
continue;
|
|
560
612
|
}
|
|
613
|
+
if (routeResult.cachePolicy.mode === "dynamic") {
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
561
616
|
if (response.status !== 200) {
|
|
562
617
|
continue;
|
|
563
618
|
}
|
|
@@ -630,6 +685,11 @@ export async function buildProject(projectRoot, options = {}) {
|
|
|
630
685
|
cache: sortedRouteCache.find((entry) => entry.pathname === artifact.routePathname)?.cache
|
|
631
686
|
?? "static",
|
|
632
687
|
})),
|
|
688
|
+
...(integrationOutputs?.assets ?? []).map((asset) => ({
|
|
689
|
+
kind: asset.kind,
|
|
690
|
+
outputPath: toBuildRelativePath(projectRoot, asset.outputPath),
|
|
691
|
+
integration: asset.integration,
|
|
692
|
+
})),
|
|
633
693
|
],
|
|
634
694
|
};
|
|
635
695
|
const manifestPath = path.join(buildRoot, MANIFEST_FILENAME);
|
|
@@ -657,4 +717,21 @@ export async function buildProject(projectRoot, options = {}) {
|
|
|
657
717
|
await writeJson(path.join(buildRoot, DEPLOY_SERVER_MANIFEST_FILENAME), deployServer);
|
|
658
718
|
|
|
659
719
|
return manifestPath;
|
|
720
|
+
} catch (error) {
|
|
721
|
+
buildError = error;
|
|
722
|
+
throw error;
|
|
723
|
+
} finally {
|
|
724
|
+
let cleanupError = null;
|
|
725
|
+
try {
|
|
726
|
+
await serverSetupCleanup?.();
|
|
727
|
+
} catch (error) {
|
|
728
|
+
cleanupError = error;
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
await litsxPipeline?.dispose();
|
|
732
|
+
} catch (error) {
|
|
733
|
+
cleanupError ??= error;
|
|
734
|
+
}
|
|
735
|
+
if (!buildError && cleanupError) throw cleanupError;
|
|
736
|
+
}
|
|
660
737
|
}
|
package/src/client-assets.js
CHANGED
|
@@ -2483,6 +2483,7 @@ export function normalizeClientAssetManifest(manifest) {
|
|
|
2483
2483
|
manifest.clientBoundariesByEntry && typeof manifest.clientBoundariesByEntry === "object"
|
|
2484
2484
|
? manifest.clientBoundariesByEntry
|
|
2485
2485
|
: {},
|
|
2486
|
+
documentStyles: Array.isArray(manifest.documentStyles) ? manifest.documentStyles : [],
|
|
2486
2487
|
assets: manifest.assets,
|
|
2487
2488
|
};
|
|
2488
2489
|
}
|