evolit 0.1.4 → 0.1.5
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 +10 -1
- package/package.json +1 -1
- package/src/build.js +104 -31
- package/src/client-assets.js +142 -23
- package/src/compiler.js +21 -1
- package/src/deployment-runtime.js +97 -16
package/README.md
CHANGED
|
@@ -60,7 +60,16 @@ Evolit resolves package subpaths with ESM `import` conditions, emits exported CS
|
|
|
60
60
|
route static-asset pipeline, and adds the resulting stylesheet URLs to the rendered document.
|
|
61
61
|
Relative `@import` rules and `url(...)` references inside package CSS are emitted and rewritten
|
|
62
62
|
from their location within `node_modules`. Bare package imports that resolve to JavaScript continue
|
|
63
|
-
to use the shared vendor runtime;
|
|
63
|
+
to use the shared vendor runtime; hydration metadata uses that same canonical package URL so an
|
|
64
|
+
external component is evaluated only once. CSS and other static assets do not enter vendor chunks.
|
|
65
|
+
|
|
66
|
+
Packages declared by the application remain package dependencies even when a workspace manager
|
|
67
|
+
links them to sources elsewhere in a monorepo. Evolit uses the declared package name and its
|
|
68
|
+
browser `exports` subpath as the public identity; the physical symlink or real filesystem path is
|
|
69
|
+
used only to verify package ownership and is never exposed as an `__unmanaged__` client module.
|
|
70
|
+
During `evolit build`, a package reached by the production SSR graph that is declared only in
|
|
71
|
+
`devDependencies` produces a warning. Its package identity is preserved, but applications should
|
|
72
|
+
move it to `dependencies` or keep development dependencies installed in the production runtime.
|
|
64
73
|
|
|
65
74
|
## Commands
|
|
66
75
|
|
package/package.json
CHANGED
package/src/build.js
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
collectTransitiveAssetPreloads,
|
|
12
12
|
collectTransitiveStyleUrls,
|
|
13
|
+
buildSharedVendorRuntime,
|
|
14
|
+
canonicalizePackageModuleId,
|
|
13
15
|
createAssetResolver,
|
|
14
16
|
createHydrationBootstrap,
|
|
15
17
|
createStaticAssetPublicUrlMap,
|
|
@@ -70,6 +72,31 @@ const CONTENT_TYPE_BY_EXTENSION = new Map([
|
|
|
70
72
|
[".js", "text/javascript; charset=utf-8"],
|
|
71
73
|
]);
|
|
72
74
|
|
|
75
|
+
function getBarePackageName(specifier) {
|
|
76
|
+
if (typeof specifier !== "string" || specifier.length === 0) return null;
|
|
77
|
+
const segments = specifier.split("/");
|
|
78
|
+
return specifier.startsWith("@")
|
|
79
|
+
? segments.length >= 2 ? `${segments[0]}/${segments[1]}` : null
|
|
80
|
+
: segments[0];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function collectSsrDevDependencyWarnings(packageJson, packageSpecifiers) {
|
|
84
|
+
const productionDependencies = new Set([
|
|
85
|
+
...Object.keys(packageJson?.dependencies ?? {}),
|
|
86
|
+
...Object.keys(packageJson?.optionalDependencies ?? {}),
|
|
87
|
+
...Object.keys(packageJson?.peerDependencies ?? {}),
|
|
88
|
+
]);
|
|
89
|
+
const devDependencies = new Set(Object.keys(packageJson?.devDependencies ?? {}));
|
|
90
|
+
return [...new Set(packageSpecifiers
|
|
91
|
+
.map(getBarePackageName)
|
|
92
|
+
.filter((packageName) => (
|
|
93
|
+
packageName
|
|
94
|
+
&& devDependencies.has(packageName)
|
|
95
|
+
&& !productionDependencies.has(packageName)
|
|
96
|
+
)))]
|
|
97
|
+
.sort();
|
|
98
|
+
}
|
|
99
|
+
|
|
73
100
|
function getContentTypeForBuildArtifact(filePath) {
|
|
74
101
|
return CONTENT_TYPE_BY_EXTENSION.get(path.extname(filePath)) ?? "application/octet-stream";
|
|
75
102
|
}
|
|
@@ -128,12 +155,15 @@ async function writeDeploymentRuntimeEntry(buildRoot) {
|
|
|
128
155
|
return runtimeEntryPath;
|
|
129
156
|
}
|
|
130
157
|
|
|
131
|
-
export async function buildProject(projectRoot) {
|
|
158
|
+
export async function buildProject(projectRoot, options = {}) {
|
|
132
159
|
const evolitConfig = await loadEvolitConfig(projectRoot);
|
|
133
160
|
const extensions = resolveEvolitExtensions(evolitConfig);
|
|
134
161
|
const extensionClientDescriptors = getExtensionClientDescriptors(extensions);
|
|
162
|
+
const packageClientSpecifiers = new Set(
|
|
163
|
+
extensionClientDescriptors.map((descriptor) => descriptor.module),
|
|
164
|
+
);
|
|
135
165
|
const sharedVendorOptions = {
|
|
136
|
-
additionalEntrySpecifiers:
|
|
166
|
+
additionalEntrySpecifiers: [],
|
|
137
167
|
};
|
|
138
168
|
const routes = await discoverAppRoutes(projectRoot);
|
|
139
169
|
const routeHandlers = await discoverAppRouteHandlers(projectRoot);
|
|
@@ -144,6 +174,19 @@ export async function buildProject(projectRoot) {
|
|
|
144
174
|
const deployHandlers = [];
|
|
145
175
|
const compiledClientBoundaries = new Map();
|
|
146
176
|
const inventoriesBySourceEntry = new Map();
|
|
177
|
+
const ssrPackageImports = new Set();
|
|
178
|
+
|
|
179
|
+
async function compileProductionServerEntry(sourcePath) {
|
|
180
|
+
const result = await compileModuleGraph(sourcePath, {
|
|
181
|
+
projectRoot,
|
|
182
|
+
mode: "production",
|
|
183
|
+
sourceMaps: false,
|
|
184
|
+
ssr: true,
|
|
185
|
+
target: "server",
|
|
186
|
+
});
|
|
187
|
+
result.packageImports.forEach((specifier) => ssrPackageImports.add(specifier));
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
147
190
|
|
|
148
191
|
function getEntryInventory(entryPath) {
|
|
149
192
|
let inventory = inventoriesBySourceEntry.get(entryPath);
|
|
@@ -174,13 +217,7 @@ export async function buildProject(projectRoot) {
|
|
|
174
217
|
await ensureDirectory(buildRoot);
|
|
175
218
|
|
|
176
219
|
for (const routeHandler of routeHandlers) {
|
|
177
|
-
await
|
|
178
|
-
projectRoot,
|
|
179
|
-
mode: "production",
|
|
180
|
-
sourceMaps: false,
|
|
181
|
-
ssr: true,
|
|
182
|
-
target: "server",
|
|
183
|
-
});
|
|
220
|
+
await compileProductionServerEntry(routeHandler.handler);
|
|
184
221
|
|
|
185
222
|
const handlerModule = await importCompiledModule(routeHandler.handler, {
|
|
186
223
|
projectRoot,
|
|
@@ -204,22 +241,10 @@ export async function buildProject(projectRoot) {
|
|
|
204
241
|
}
|
|
205
242
|
|
|
206
243
|
for (const route of routes) {
|
|
207
|
-
await
|
|
208
|
-
projectRoot,
|
|
209
|
-
mode: "production",
|
|
210
|
-
sourceMaps: false,
|
|
211
|
-
ssr: true,
|
|
212
|
-
target: "server",
|
|
213
|
-
});
|
|
244
|
+
await compileProductionServerEntry(route.page);
|
|
214
245
|
|
|
215
246
|
for (const layoutPath of route.layouts) {
|
|
216
|
-
await
|
|
217
|
-
projectRoot,
|
|
218
|
-
mode: "production",
|
|
219
|
-
sourceMaps: false,
|
|
220
|
-
ssr: true,
|
|
221
|
-
target: "server",
|
|
222
|
-
});
|
|
247
|
+
await compileProductionServerEntry(layoutPath);
|
|
223
248
|
|
|
224
249
|
}
|
|
225
250
|
|
|
@@ -228,13 +253,7 @@ export async function buildProject(projectRoot) {
|
|
|
228
253
|
...(route.errorBoundaries ?? []).map((boundary) => boundary.module),
|
|
229
254
|
];
|
|
230
255
|
for (const boundaryPath of new Set(boundaryModules)) {
|
|
231
|
-
await
|
|
232
|
-
projectRoot,
|
|
233
|
-
mode: "production",
|
|
234
|
-
sourceMaps: false,
|
|
235
|
-
ssr: true,
|
|
236
|
-
target: "server",
|
|
237
|
-
});
|
|
256
|
+
await compileProductionServerEntry(boundaryPath);
|
|
238
257
|
}
|
|
239
258
|
|
|
240
259
|
const toProjectRelative = (filePath) => path.relative(projectRoot, filePath).split(path.sep).join("/");
|
|
@@ -247,13 +266,21 @@ export async function buildProject(projectRoot) {
|
|
|
247
266
|
const allAssets = new Set();
|
|
248
267
|
const allClientBoundaries = new Set();
|
|
249
268
|
for (const [entryPath, entryInventory] of inventoriesByEntry) {
|
|
269
|
+
const packageBoundarySources = new Set(
|
|
270
|
+
(entryInventory.packageClientBoundaries ?? []).map((entry) => entry.sourcePath),
|
|
271
|
+
);
|
|
272
|
+
for (const packageBoundary of entryInventory.packageClientBoundaries ?? []) {
|
|
273
|
+
packageClientSpecifiers.add(packageBoundary.specifier);
|
|
274
|
+
}
|
|
250
275
|
serverAssetImportsByEntry[toProjectRelative(entryPath)] = {
|
|
251
276
|
styles: entryInventory.styles.map((filePath) => getClientStaticAssetModule(projectRoot, filePath)),
|
|
252
277
|
assets: entryInventory.assets.map((filePath) => getClientStaticAssetModule(projectRoot, filePath)),
|
|
253
278
|
};
|
|
254
279
|
entryInventory.styles.forEach((filePath) => allStyles.add(filePath));
|
|
255
280
|
entryInventory.assets.forEach((filePath) => allAssets.add(filePath));
|
|
256
|
-
entryInventory.clientBoundaries
|
|
281
|
+
entryInventory.clientBoundaries
|
|
282
|
+
.filter((filePath) => !packageBoundarySources.has(filePath))
|
|
283
|
+
.forEach((filePath) => allClientBoundaries.add(filePath));
|
|
257
284
|
}
|
|
258
285
|
await emitClientStaticAssets([...allStyles, ...allAssets], {
|
|
259
286
|
projectRoot,
|
|
@@ -268,12 +295,23 @@ export async function buildProject(projectRoot) {
|
|
|
268
295
|
}
|
|
269
296
|
for (const [entryPath, entryInventory] of inventoriesByEntry) {
|
|
270
297
|
clientBoundariesByEntry[toProjectRelative(entryPath)] = entryInventory.clientBoundaries
|
|
298
|
+
.filter((clientBoundary) => !(entryInventory.packageClientBoundaries ?? [])
|
|
299
|
+
.some((packageBoundary) => packageBoundary.sourcePath === clientBoundary))
|
|
271
300
|
.map((clientBoundary) => compiledBoundaryModules.get(clientBoundary))
|
|
272
301
|
.filter(Boolean)
|
|
273
302
|
.sort();
|
|
274
303
|
}
|
|
275
304
|
}
|
|
276
305
|
|
|
306
|
+
const projectPackageJson = JSON.parse(await fs.readFile(path.join(projectRoot, "package.json"), "utf8"));
|
|
307
|
+
const warn = options.onWarning ?? console.warn;
|
|
308
|
+
for (const packageName of collectSsrDevDependencyWarnings(projectPackageJson, [...ssrPackageImports])) {
|
|
309
|
+
warn(
|
|
310
|
+
`[evolit] Production SSR imports ${JSON.stringify(packageName)}, but it is declared only in devDependencies. `
|
|
311
|
+
+ "Move it to dependencies or ensure production installations include development dependencies.",
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
277
315
|
const configuredClientBoundaries = evolitConfig.clientBoundaries ?? [];
|
|
278
316
|
if (!Array.isArray(configuredClientBoundaries)) {
|
|
279
317
|
throw new Error("Expected clientBoundaries in evolit.config.js to be an array of module specifiers.");
|
|
@@ -295,8 +333,10 @@ export async function buildProject(projectRoot) {
|
|
|
295
333
|
await compileProductionClientBoundary(sourcePath);
|
|
296
334
|
}
|
|
297
335
|
|
|
336
|
+
sharedVendorOptions.additionalEntrySpecifiers = [...packageClientSpecifiers].sort();
|
|
298
337
|
const clientAssets = await emitBundledClientAssets(projectRoot, {
|
|
299
338
|
entryClientModules,
|
|
339
|
+
additionalVendorSpecifiers: sharedVendorOptions.additionalEntrySpecifiers,
|
|
300
340
|
serverAssetImportsByEntry,
|
|
301
341
|
clientBoundariesByEntry,
|
|
302
342
|
});
|
|
@@ -306,8 +346,12 @@ export async function buildProject(projectRoot) {
|
|
|
306
346
|
const routeResolver = await createRouteResolver(projectRoot, "production", {
|
|
307
347
|
staticAssetPublicUrls,
|
|
308
348
|
});
|
|
349
|
+
const sharedRuntime = await buildSharedVendorRuntime(projectRoot, "production", sharedVendorOptions);
|
|
350
|
+
clientAssets.sharedImports = { ...sharedRuntime.imports };
|
|
351
|
+
const packageImports = sharedRuntime.imports;
|
|
309
352
|
const assetResolver = createAssetResolver(projectRoot, {
|
|
310
353
|
assetManifest: clientAssets,
|
|
354
|
+
packageImports,
|
|
311
355
|
});
|
|
312
356
|
const hydrationModuleUrl = await resolveSharedVendorModuleUrl(
|
|
313
357
|
projectRoot,
|
|
@@ -341,6 +385,33 @@ export async function buildProject(projectRoot) {
|
|
|
341
385
|
].filter((moduleId) => typeof moduleId === "string" && moduleId.length > 0))];
|
|
342
386
|
const unresolved = [];
|
|
343
387
|
for (const moduleId of renderedModules) {
|
|
388
|
+
const packageSpecifier = await canonicalizePackageModuleId(
|
|
389
|
+
projectRoot,
|
|
390
|
+
moduleId,
|
|
391
|
+
packageClientSpecifiers,
|
|
392
|
+
);
|
|
393
|
+
if (packageSpecifier) {
|
|
394
|
+
const publicUrl = packageImports[packageSpecifier] ?? null;
|
|
395
|
+
if (!publicUrl) {
|
|
396
|
+
unresolved.push(moduleId);
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
const previousPublicUrl = assetResolver(moduleId);
|
|
400
|
+
for (const root of result.hydrationData?.roots ?? []) {
|
|
401
|
+
if (root?.moduleId === moduleId) root.moduleId = packageSpecifier;
|
|
402
|
+
}
|
|
403
|
+
if (Array.isArray(result.clientImports)) {
|
|
404
|
+
result.clientImports = result.clientImports.map((value) => (
|
|
405
|
+
value === moduleId || value === previousPublicUrl ? publicUrl : value
|
|
406
|
+
));
|
|
407
|
+
}
|
|
408
|
+
if (Array.isArray(result.hydrationData?.clientImports)) {
|
|
409
|
+
result.hydrationData.clientImports = result.hydrationData.clientImports.map((value) => (
|
|
410
|
+
value === moduleId || value === previousPublicUrl ? publicUrl : value
|
|
411
|
+
));
|
|
412
|
+
}
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
344
415
|
if (assetResolver(moduleId) || clientAssets.byPublicPath?.[moduleId]) continue;
|
|
345
416
|
const importerPath = routeResult.boundaryModule
|
|
346
417
|
?? routeResult.route?.page
|
|
@@ -406,6 +477,7 @@ export async function buildProject(projectRoot) {
|
|
|
406
477
|
result.hydrationData,
|
|
407
478
|
projectRoot,
|
|
408
479
|
resolveHydrationRootClientImports(result.hydrationData, assetResolver),
|
|
480
|
+
assetResolver,
|
|
409
481
|
),
|
|
410
482
|
assetResolver,
|
|
411
483
|
hydrationModuleUrl,
|
|
@@ -420,6 +492,7 @@ export async function buildProject(projectRoot) {
|
|
|
420
492
|
result.hydrationData,
|
|
421
493
|
projectRoot,
|
|
422
494
|
resolveHydrationRootClientImports(result.hydrationData, assetResolver),
|
|
495
|
+
assetResolver,
|
|
423
496
|
),
|
|
424
497
|
);
|
|
425
498
|
},
|
package/src/client-assets.js
CHANGED
|
@@ -172,32 +172,56 @@ export async function resolvePackageRoot(packageName, options = {}) {
|
|
|
172
172
|
resolutionError = error;
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
let currentPath = path.dirname(packageEntryPath);
|
|
175
|
+
if (packageEntryPath) {
|
|
176
|
+
let currentPath = path.dirname(packageEntryPath);
|
|
179
177
|
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
while (true) {
|
|
179
|
+
const packageJsonPath = path.join(currentPath, "package.json");
|
|
182
180
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
try {
|
|
182
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
|
|
183
|
+
if (packageJson?.name === packageName) {
|
|
184
|
+
return { packageRoot: currentPath, packageJson };
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
// Keep walking up until we find the owning package root.
|
|
187
188
|
}
|
|
188
|
-
} catch {
|
|
189
|
-
// Keep walking up until we find the owning package root.
|
|
190
|
-
}
|
|
191
189
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
const parentPath = path.dirname(currentPath);
|
|
191
|
+
if (parentPath === currentPath) {
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
currentPath = parentPath;
|
|
195
196
|
}
|
|
197
|
+
}
|
|
196
198
|
|
|
197
|
-
|
|
199
|
+
const packageSegments = packageName.split("/");
|
|
200
|
+
const validPackageSegments = packageSegments.length === 1
|
|
201
|
+
|| (packageSegments.length === 2 && packageSegments[0].startsWith("@"));
|
|
202
|
+
if (validPackageSegments && packageSegments.every((segment) => segment && segment !== "." && segment !== "..")) {
|
|
203
|
+
let currentPath = resolveFrom;
|
|
204
|
+
while (true) {
|
|
205
|
+
const packageJsonPath = path.join(currentPath, "node_modules", ...packageSegments, "package.json");
|
|
206
|
+
try {
|
|
207
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
|
|
208
|
+
if (packageJson?.name === packageName) {
|
|
209
|
+
return {
|
|
210
|
+
packageRoot: await fs.realpath(path.dirname(packageJsonPath)),
|
|
211
|
+
packageJson,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
} catch {
|
|
215
|
+
// Continue through the normal node_modules ancestor lookup.
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const parentPath = path.dirname(currentPath);
|
|
219
|
+
if (parentPath === currentPath) break;
|
|
220
|
+
currentPath = parentPath;
|
|
221
|
+
}
|
|
198
222
|
}
|
|
199
223
|
|
|
200
|
-
throw new Error(`Unable to resolve package root for ${packageName}`);
|
|
224
|
+
throw resolutionError ?? new Error(`Unable to resolve package root for ${packageName}`);
|
|
201
225
|
})();
|
|
202
226
|
|
|
203
227
|
packageRootCache.set(cacheKey, pendingResolution);
|
|
@@ -322,6 +346,79 @@ export async function resolveBrowserSpecifierFilePath(specifier, options = {}) {
|
|
|
322
346
|
}
|
|
323
347
|
}
|
|
324
348
|
|
|
349
|
+
export async function resolveDeclaredPackageNames(projectRoot) {
|
|
350
|
+
try {
|
|
351
|
+
const packageJson = JSON.parse(await fs.readFile(path.join(projectRoot, "package.json"), "utf8"));
|
|
352
|
+
return new Set([
|
|
353
|
+
...Object.keys(packageJson.dependencies ?? {}),
|
|
354
|
+
...Object.keys(packageJson.optionalDependencies ?? {}),
|
|
355
|
+
...Object.keys(packageJson.peerDependencies ?? {}),
|
|
356
|
+
...Object.keys(packageJson.devDependencies ?? {}),
|
|
357
|
+
]);
|
|
358
|
+
} catch (error) {
|
|
359
|
+
if (error?.code === "ENOENT") return new Set();
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export async function canonicalizePackageModuleId(projectRoot, moduleId, packageSpecifiers = []) {
|
|
365
|
+
const declaredPackageNames = await resolveDeclaredPackageNames(projectRoot);
|
|
366
|
+
const specifiers = [...new Set(packageSpecifiers)]
|
|
367
|
+
.filter((specifier) => {
|
|
368
|
+
const parsed = parsePackageSpecifier(specifier);
|
|
369
|
+
return parsed && declaredPackageNames.has(parsed.packageName);
|
|
370
|
+
});
|
|
371
|
+
if (specifiers.includes(moduleId)) {
|
|
372
|
+
return moduleId;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
let sourcePath = null;
|
|
376
|
+
if (typeof moduleId === "string" && moduleId.startsWith("file:")) {
|
|
377
|
+
try { sourcePath = fileURLToPath(moduleId); } catch { return null; }
|
|
378
|
+
} else if (typeof moduleId === "string" && path.isAbsolute(moduleId)) {
|
|
379
|
+
sourcePath = moduleId;
|
|
380
|
+
}
|
|
381
|
+
if (!sourcePath || sourcePath.startsWith(`${path.sep}_evolit${path.sep}`)) {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
let realSourcePath;
|
|
386
|
+
try {
|
|
387
|
+
realSourcePath = await fs.realpath(sourcePath);
|
|
388
|
+
} catch {
|
|
389
|
+
realSourcePath = path.resolve(sourcePath);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const exactMatches = [];
|
|
393
|
+
const packageMatches = [];
|
|
394
|
+
for (const specifier of specifiers) {
|
|
395
|
+
const parsed = parsePackageSpecifier(specifier);
|
|
396
|
+
try {
|
|
397
|
+
const [{ packageRoot }, entryPath] = await Promise.all([
|
|
398
|
+
resolvePackageRoot(parsed.packageName, { projectRoot }),
|
|
399
|
+
resolveBrowserSpecifierFilePath(specifier, { projectRoot }),
|
|
400
|
+
]);
|
|
401
|
+
const [realPackageRoot, realEntryPath] = await Promise.all([
|
|
402
|
+
fs.realpath(packageRoot),
|
|
403
|
+
fs.realpath(entryPath),
|
|
404
|
+
]);
|
|
405
|
+
const relativePath = path.relative(realPackageRoot, realSourcePath);
|
|
406
|
+
const belongsToPackage = relativePath === ""
|
|
407
|
+
|| (relativePath !== ".."
|
|
408
|
+
&& !relativePath.startsWith(`..${path.sep}`)
|
|
409
|
+
&& !path.isAbsolute(relativePath));
|
|
410
|
+
if (!belongsToPackage) continue;
|
|
411
|
+
packageMatches.push(specifier);
|
|
412
|
+
if (realEntryPath === realSourcePath) exactMatches.push(specifier);
|
|
413
|
+
} catch {
|
|
414
|
+
// An unrelated or unavailable optional package cannot own this module.
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (exactMatches.length === 1) return exactMatches[0];
|
|
419
|
+
return packageMatches.length === 1 ? packageMatches[0] : null;
|
|
420
|
+
}
|
|
421
|
+
|
|
325
422
|
export function getSharedOutputRoot(projectRoot, mode) {
|
|
326
423
|
return path.join(
|
|
327
424
|
projectRoot,
|
|
@@ -1140,6 +1237,10 @@ function toPublicHydrationModuleId(projectRoot, moduleId) {
|
|
|
1140
1237
|
return `/${relativePath}`;
|
|
1141
1238
|
}
|
|
1142
1239
|
|
|
1240
|
+
if (isBareSpecifier(moduleId)) {
|
|
1241
|
+
return moduleId;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1143
1244
|
if (moduleId.startsWith("/")) {
|
|
1144
1245
|
return moduleId;
|
|
1145
1246
|
}
|
|
@@ -1171,12 +1272,19 @@ function getServerOutputRoot(projectRoot) {
|
|
|
1171
1272
|
|
|
1172
1273
|
export function createAssetResolver(projectRoot, options = {}) {
|
|
1173
1274
|
const assetManifest = normalizeClientAssetManifest(options.assetManifest);
|
|
1275
|
+
const packageImports = options.packageImports instanceof Map
|
|
1276
|
+
? options.packageImports
|
|
1277
|
+
: new Map(Object.entries(options.packageImports ?? {}));
|
|
1174
1278
|
|
|
1175
1279
|
return function assetResolver(moduleId) {
|
|
1176
1280
|
if (typeof moduleId !== "string" || moduleId.length === 0) {
|
|
1177
1281
|
return null;
|
|
1178
1282
|
}
|
|
1179
1283
|
|
|
1284
|
+
if (packageImports.has(moduleId)) {
|
|
1285
|
+
return packageImports.get(moduleId);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1180
1288
|
let relativeClientModule = null;
|
|
1181
1289
|
if (path.isAbsolute(moduleId) && !isVirtualClientModuleId(moduleId)) {
|
|
1182
1290
|
relativeClientModule = toClientModuleRelativePath(projectRoot, moduleId);
|
|
@@ -1278,6 +1386,7 @@ export function normalizeHydrationDataForClient(
|
|
|
1278
1386
|
hydrationData,
|
|
1279
1387
|
projectRoot = null,
|
|
1280
1388
|
additionalClientImports = [],
|
|
1389
|
+
assetResolver = null,
|
|
1281
1390
|
) {
|
|
1282
1391
|
if (!hydrationData || typeof hydrationData !== "object") {
|
|
1283
1392
|
return hydrationData ?? null;
|
|
@@ -1314,7 +1423,9 @@ export function normalizeHydrationDataForClient(
|
|
|
1314
1423
|
const clientImports = [...new Set([
|
|
1315
1424
|
...(Array.isArray(hydrationData.clientImports) ? hydrationData.clientImports : []),
|
|
1316
1425
|
...(Array.isArray(additionalClientImports) ? additionalClientImports : []),
|
|
1317
|
-
]
|
|
1426
|
+
]
|
|
1427
|
+
.map((value) => typeof assetResolver === "function" ? assetResolver(value) ?? value : value)
|
|
1428
|
+
.filter((value) => typeof value === "string" && value.length > 0))];
|
|
1318
1429
|
|
|
1319
1430
|
Object.defineProperties(normalizedHydrationData, {
|
|
1320
1431
|
payload: {
|
|
@@ -1844,10 +1955,15 @@ async function bundleClientAssets(projectRoot, options = {}) {
|
|
|
1844
1955
|
const scriptAssetRecords = [];
|
|
1845
1956
|
let nextRollupCache = options.rollupCache ?? null;
|
|
1846
1957
|
if (Object.keys(inputEntries).length > 0) {
|
|
1847
|
-
const sharedVendorSpecifiers =
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1958
|
+
const sharedVendorSpecifiers = [...new Set([
|
|
1959
|
+
...await collectClientVendorSpecifiers(projectRoot, [], {
|
|
1960
|
+
mode,
|
|
1961
|
+
entryClientModules: [...entryClientModules],
|
|
1962
|
+
}),
|
|
1963
|
+
...(Array.isArray(options.additionalVendorSpecifiers)
|
|
1964
|
+
? options.additionalVendorSpecifiers.filter((specifier) => isBareSpecifier(specifier))
|
|
1965
|
+
: []),
|
|
1966
|
+
])].sort();
|
|
1851
1967
|
const sharedRuntime = await buildSharedVendorRuntime(projectRoot, mode, {
|
|
1852
1968
|
additionalEntrySpecifiers: sharedVendorSpecifiers,
|
|
1853
1969
|
});
|
|
@@ -2285,6 +2401,9 @@ export function normalizeClientAssetManifest(manifest) {
|
|
|
2285
2401
|
chunks: Array.isArray(manifest.chunks) ? manifest.chunks : [],
|
|
2286
2402
|
styles: Array.isArray(manifest.styles) ? manifest.styles : [],
|
|
2287
2403
|
resources: Array.isArray(manifest.resources) ? manifest.resources : [],
|
|
2404
|
+
sharedImports: manifest.sharedImports && typeof manifest.sharedImports === "object"
|
|
2405
|
+
? manifest.sharedImports
|
|
2406
|
+
: {},
|
|
2288
2407
|
serverAssetImportsByEntry:
|
|
2289
2408
|
manifest.serverAssetImportsByEntry && typeof manifest.serverAssetImportsByEntry === "object"
|
|
2290
2409
|
? manifest.serverAssetImportsByEntry
|
package/src/compiler.js
CHANGED
|
@@ -840,6 +840,7 @@ async function rewriteRelativeSpecifiers({
|
|
|
840
840
|
staticAssetFiles,
|
|
841
841
|
managedSourceRoots,
|
|
842
842
|
serverExportsByModule,
|
|
843
|
+
packageImports,
|
|
843
844
|
}) {
|
|
844
845
|
const magicSource = new MagicString(code);
|
|
845
846
|
let didRewrite = false;
|
|
@@ -863,6 +864,15 @@ async function rewriteRelativeSpecifiers({
|
|
|
863
864
|
? packageImportPath
|
|
864
865
|
: null;
|
|
865
866
|
|
|
867
|
+
if (
|
|
868
|
+
target === "server"
|
|
869
|
+
&& isBareSpecifier(specifier)
|
|
870
|
+
&& !aliasedImportPath
|
|
871
|
+
&& !packageAssetPath
|
|
872
|
+
) {
|
|
873
|
+
packageImports?.add(specifier);
|
|
874
|
+
}
|
|
875
|
+
|
|
866
876
|
if (
|
|
867
877
|
target === "client"
|
|
868
878
|
&& isBareSpecifier(specifier)
|
|
@@ -1152,6 +1162,7 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
|
|
|
1152
1162
|
const staticAssetFiles = new Set();
|
|
1153
1163
|
const moduleMetadata = new Map();
|
|
1154
1164
|
const serverExportsByModule = new Map();
|
|
1165
|
+
const packageImports = new Set();
|
|
1155
1166
|
const serverImportQuery = target === "server" && mode === "development"
|
|
1156
1167
|
? `t=${Date.now()}`
|
|
1157
1168
|
: null;
|
|
@@ -1221,6 +1232,7 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
|
|
|
1221
1232
|
staticAssetFiles,
|
|
1222
1233
|
managedSourceRoots,
|
|
1223
1234
|
serverExportsByModule,
|
|
1235
|
+
packageImports,
|
|
1224
1236
|
});
|
|
1225
1237
|
|
|
1226
1238
|
await fs.writeFile(outputPath, rewritten.code, "utf8");
|
|
@@ -1250,6 +1262,7 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
|
|
|
1250
1262
|
entrypoint: await compileModule(entryPath),
|
|
1251
1263
|
outputRoot,
|
|
1252
1264
|
sourceFiles: [...new Set([...visited.keys(), ...staticAssetFiles])],
|
|
1265
|
+
packageImports: [...packageImports].sort(),
|
|
1253
1266
|
};
|
|
1254
1267
|
}
|
|
1255
1268
|
|
|
@@ -1314,6 +1327,7 @@ export async function collectClientGraphInventory(entryPaths, options = {}) {
|
|
|
1314
1327
|
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
1315
1328
|
const visited = new Set();
|
|
1316
1329
|
const boundaries = new Set();
|
|
1330
|
+
const packageBoundaries = new Map();
|
|
1317
1331
|
const styles = new Set();
|
|
1318
1332
|
const assets = new Set();
|
|
1319
1333
|
|
|
@@ -1422,7 +1436,10 @@ export async function collectClientGraphInventory(entryPaths, options = {}) {
|
|
|
1422
1436
|
if (!resolved) continue;
|
|
1423
1437
|
if (shouldCompileModule(resolved)) {
|
|
1424
1438
|
if (isBareSpecifier(specifier) && aliasResolved == null) {
|
|
1425
|
-
if (componentImportSpecifiers.has(specifier))
|
|
1439
|
+
if (componentImportSpecifiers.has(specifier)) {
|
|
1440
|
+
boundaries.add(resolved);
|
|
1441
|
+
packageBoundaries.set(specifier, resolved);
|
|
1442
|
+
}
|
|
1426
1443
|
} else {
|
|
1427
1444
|
await visit(resolved, true);
|
|
1428
1445
|
}
|
|
@@ -1438,6 +1455,9 @@ export async function collectClientGraphInventory(entryPaths, options = {}) {
|
|
|
1438
1455
|
for (const entryPath of entryPaths) await visit(entryPath);
|
|
1439
1456
|
return {
|
|
1440
1457
|
clientBoundaries: [...boundaries].sort(),
|
|
1458
|
+
packageClientBoundaries: [...packageBoundaries]
|
|
1459
|
+
.map(([specifier, sourcePath]) => ({ specifier, sourcePath }))
|
|
1460
|
+
.sort((left, right) => left.specifier.localeCompare(right.specifier)),
|
|
1441
1461
|
styles: [...styles].sort(),
|
|
1442
1462
|
assets: [...assets].sort(),
|
|
1443
1463
|
sourceFiles: [...visited].sort(),
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
collectTransitiveAssetPreloads,
|
|
15
15
|
collectTransitiveStyleUrls,
|
|
16
|
+
canonicalizePackageModuleId,
|
|
16
17
|
createAssetResolver,
|
|
17
18
|
createHydrationBootstrap,
|
|
18
19
|
createStaticAssetPublicUrlMap,
|
|
@@ -46,6 +47,20 @@ import {
|
|
|
46
47
|
resolveEvolitExtensions,
|
|
47
48
|
runRequestExtensions,
|
|
48
49
|
} from "./extensions.js";
|
|
50
|
+
|
|
51
|
+
function isBarePackageModuleId(value) {
|
|
52
|
+
return typeof value === "string"
|
|
53
|
+
&& value.length > 0
|
|
54
|
+
&& !value.startsWith(".")
|
|
55
|
+
&& !value.startsWith("/")
|
|
56
|
+
&& !value.startsWith("@/")
|
|
57
|
+
&& !value.startsWith("#")
|
|
58
|
+
&& !value.startsWith("file:")
|
|
59
|
+
&& !value.startsWith("node:")
|
|
60
|
+
&& !value.startsWith("data:")
|
|
61
|
+
&& !value.startsWith("http:")
|
|
62
|
+
&& !value.startsWith("https:");
|
|
63
|
+
}
|
|
49
64
|
const CONTENT_TYPE_BY_EXTENSION = new Map([
|
|
50
65
|
[".css", "text/css; charset=utf-8"],
|
|
51
66
|
[".svg", "image/svg+xml"],
|
|
@@ -240,27 +255,32 @@ export async function createRequestRenderer({
|
|
|
240
255
|
extensions = [],
|
|
241
256
|
}) {
|
|
242
257
|
let currentAssetManifest = normalizeClientAssetManifest(assetManifest);
|
|
258
|
+
const extensionClientDescriptors = getExtensionClientDescriptors(extensions);
|
|
259
|
+
const extensionClientSpecifiers = extensionClientDescriptors.map((descriptor) => descriptor.module);
|
|
260
|
+
const packageClientSpecifiers = new Set(extensionClientSpecifiers);
|
|
261
|
+
const persistedSharedImports = currentAssetManifest?.sharedImports ?? {};
|
|
262
|
+
const packageImports = new Map(Object.entries(persistedSharedImports));
|
|
263
|
+
const packageImportUrls = new Set(packageImports.values());
|
|
243
264
|
let currentAssetResolver = createAssetResolver(projectRoot, {
|
|
244
265
|
assetManifest: currentAssetManifest,
|
|
266
|
+
packageImports,
|
|
245
267
|
});
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
268
|
+
const sharedVendorOptions = { additionalEntrySpecifiers: [...packageClientSpecifiers] };
|
|
269
|
+
const resolveSharedModuleUrl = (specifier, options = sharedVendorOptions) => (
|
|
270
|
+
persistedSharedImports[specifier]
|
|
271
|
+
?? (mode === "development"
|
|
272
|
+
? resolveSharedVendorModuleUrl(projectRoot, mode, specifier, options)
|
|
273
|
+
: null)
|
|
274
|
+
);
|
|
275
|
+
let currentHydrationModuleUrl = await resolveSharedModuleUrl(
|
|
252
276
|
"@litsx/ssr/hydration",
|
|
253
|
-
sharedVendorOptions,
|
|
254
277
|
);
|
|
255
|
-
let currentNavigationModuleUrl = await
|
|
256
|
-
projectRoot,
|
|
257
|
-
mode,
|
|
278
|
+
let currentNavigationModuleUrl = await resolveSharedModuleUrl(
|
|
258
279
|
"evolit/navigation",
|
|
259
|
-
sharedVendorOptions,
|
|
260
280
|
);
|
|
261
281
|
let currentNavigationExtensions = await Promise.all(extensionClientDescriptors.map(async (descriptor) => ({
|
|
262
282
|
...descriptor,
|
|
263
|
-
module: await
|
|
283
|
+
module: await resolveSharedModuleUrl(descriptor.module),
|
|
264
284
|
})));
|
|
265
285
|
const devBundledEntries = new Set();
|
|
266
286
|
const devPreparedClientModules = new Set();
|
|
@@ -359,11 +379,13 @@ export async function createRequestRenderer({
|
|
|
359
379
|
retainOutputPaths: devPreviousAssetOutputPaths,
|
|
360
380
|
serverAssetImportsByEntry: devServerAssetImportsByEntry,
|
|
361
381
|
clientBoundariesByEntry: devClientBoundariesByEntry,
|
|
382
|
+
additionalVendorSpecifiers: [...packageClientSpecifiers],
|
|
362
383
|
});
|
|
363
384
|
currentAssetManifest = bundledClientAssets.manifest;
|
|
364
385
|
devPreviousAssetOutputPaths = new Set();
|
|
365
386
|
currentAssetResolver = createAssetResolver(projectRoot, {
|
|
366
387
|
assetManifest: currentAssetManifest,
|
|
388
|
+
packageImports,
|
|
367
389
|
});
|
|
368
390
|
currentHydrationModuleUrl = await resolveSharedVendorModuleUrl(
|
|
369
391
|
projectRoot,
|
|
@@ -405,6 +427,7 @@ export async function createRequestRenderer({
|
|
|
405
427
|
return (
|
|
406
428
|
typeof currentAssetResolver(moduleId) === "string"
|
|
407
429
|
|| typeof currentAssetManifest?.byPublicPath?.[moduleId] === "string"
|
|
430
|
+
|| packageImportUrls.has(moduleId)
|
|
408
431
|
);
|
|
409
432
|
}
|
|
410
433
|
|
|
@@ -447,6 +470,46 @@ export async function createRequestRenderer({
|
|
|
447
470
|
].filter((moduleId) => typeof moduleId === "string" && moduleId.length > 0))];
|
|
448
471
|
const unresolvedModules = [];
|
|
449
472
|
for (const moduleId of renderedModules) {
|
|
473
|
+
const packageSpecifier = isBarePackageModuleId(moduleId)
|
|
474
|
+
? moduleId
|
|
475
|
+
: await canonicalizePackageModuleId(projectRoot, moduleId, packageClientSpecifiers);
|
|
476
|
+
if (packageSpecifier) {
|
|
477
|
+
packageClientSpecifiers.add(packageSpecifier);
|
|
478
|
+
sharedVendorOptions.additionalEntrySpecifiers = [...packageClientSpecifiers].sort();
|
|
479
|
+
const publicUrl = packageImports.get(packageSpecifier) ?? await resolveSharedModuleUrl(
|
|
480
|
+
packageSpecifier,
|
|
481
|
+
{
|
|
482
|
+
assetManifest: currentAssetManifest,
|
|
483
|
+
entryClientModules: [...devBundledEntries],
|
|
484
|
+
additionalEntrySpecifiers: sharedVendorOptions.additionalEntrySpecifiers,
|
|
485
|
+
},
|
|
486
|
+
);
|
|
487
|
+
if (publicUrl) {
|
|
488
|
+
const previousPublicUrl = currentAssetResolver(moduleId);
|
|
489
|
+
packageImports.set(packageSpecifier, publicUrl);
|
|
490
|
+
packageImportUrls.add(publicUrl);
|
|
491
|
+
currentAssetResolver = createAssetResolver(projectRoot, {
|
|
492
|
+
assetManifest: currentAssetManifest,
|
|
493
|
+
packageImports,
|
|
494
|
+
});
|
|
495
|
+
for (const root of result.hydrationData?.roots ?? []) {
|
|
496
|
+
if (root?.moduleId === moduleId) root.moduleId = packageSpecifier;
|
|
497
|
+
}
|
|
498
|
+
if (Array.isArray(result.clientImports)) {
|
|
499
|
+
result.clientImports = result.clientImports.map((value) => (
|
|
500
|
+
value === moduleId || value === previousPublicUrl ? publicUrl : value
|
|
501
|
+
));
|
|
502
|
+
}
|
|
503
|
+
if (Array.isArray(result.hydrationData?.clientImports)) {
|
|
504
|
+
result.hydrationData.clientImports = result.hydrationData.clientImports.map((value) => (
|
|
505
|
+
value === moduleId || value === previousPublicUrl ? publicUrl : value
|
|
506
|
+
));
|
|
507
|
+
}
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
unresolvedModules.push(moduleId);
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
450
513
|
const sourcePath = await resolveRenderedClientSource(moduleId, routeResult);
|
|
451
514
|
if (sourcePath) devKnownClientBoundarySources.add(path.resolve(sourcePath));
|
|
452
515
|
if (hasClientArtifact(moduleId)) continue;
|
|
@@ -575,6 +638,7 @@ export async function createRequestRenderer({
|
|
|
575
638
|
result.hydrationData,
|
|
576
639
|
projectRoot,
|
|
577
640
|
resolveHydrationRootClientImports(result.hydrationData, currentAssetResolver),
|
|
641
|
+
currentAssetResolver,
|
|
578
642
|
),
|
|
579
643
|
assetResolver(moduleId) {
|
|
580
644
|
return currentAssetResolver(moduleId);
|
|
@@ -591,9 +655,10 @@ export async function createRequestRenderer({
|
|
|
591
655
|
result.hydrationData,
|
|
592
656
|
projectRoot,
|
|
593
657
|
resolveHydrationRootClientImports(
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
658
|
+
result.hydrationData,
|
|
659
|
+
currentAssetResolver,
|
|
660
|
+
),
|
|
661
|
+
currentAssetResolver,
|
|
597
662
|
),
|
|
598
663
|
);
|
|
599
664
|
},
|
|
@@ -678,6 +743,7 @@ export async function createRequestRenderer({
|
|
|
678
743
|
currentAssetManifest = null;
|
|
679
744
|
currentAssetResolver = createAssetResolver(projectRoot, {
|
|
680
745
|
assetManifest: currentAssetManifest,
|
|
746
|
+
packageImports,
|
|
681
747
|
});
|
|
682
748
|
devRollupCache = null;
|
|
683
749
|
for (const clientModule of affectedClientModules) {
|
|
@@ -726,11 +792,24 @@ export async function createRequestRenderer({
|
|
|
726
792
|
])));
|
|
727
793
|
const inventory = {
|
|
728
794
|
clientBoundaries: [...new Set([...inventoriesByEntry.values()].flatMap((entry) => entry.clientBoundaries))].sort(),
|
|
795
|
+
packageClientBoundaries: [...new Map(
|
|
796
|
+
[...inventoriesByEntry.values()]
|
|
797
|
+
.flatMap((entry) => entry.packageClientBoundaries ?? [])
|
|
798
|
+
.map((entry) => [entry.specifier, entry]),
|
|
799
|
+
).values()].sort((left, right) => left.specifier.localeCompare(right.specifier)),
|
|
729
800
|
styles: [...new Set([...inventoriesByEntry.values()].flatMap((entry) => entry.styles))].sort(),
|
|
730
801
|
assets: [...new Set([...inventoriesByEntry.values()].flatMap((entry) => entry.assets))].sort(),
|
|
731
802
|
sourceFiles: [...new Set([...inventoriesByEntry.values()].flatMap((entry) => entry.sourceFiles))].sort(),
|
|
732
803
|
};
|
|
733
|
-
const
|
|
804
|
+
const packageBoundarySources = new Set(
|
|
805
|
+
inventory.packageClientBoundaries.map((entry) => entry.sourcePath),
|
|
806
|
+
);
|
|
807
|
+
for (const packageBoundary of inventory.packageClientBoundaries) {
|
|
808
|
+
packageClientSpecifiers.add(packageBoundary.specifier);
|
|
809
|
+
}
|
|
810
|
+
sharedVendorOptions.additionalEntrySpecifiers = [...packageClientSpecifiers].sort();
|
|
811
|
+
const clientBoundaries = inventory.clientBoundaries
|
|
812
|
+
.filter((clientBoundary) => !packageBoundarySources.has(clientBoundary));
|
|
734
813
|
for (const clientBoundary of clientBoundaries) {
|
|
735
814
|
devKnownClientBoundarySources.add(path.resolve(clientBoundary));
|
|
736
815
|
}
|
|
@@ -747,6 +826,8 @@ export async function createRequestRenderer({
|
|
|
747
826
|
assets: entryInventory.assets.map((filePath) => getClientStaticAssetModule(projectRoot, filePath)),
|
|
748
827
|
};
|
|
749
828
|
devClientBoundariesByEntry[entryKey] = entryInventory.clientBoundaries
|
|
829
|
+
.filter((filePath) => !(entryInventory.packageClientBoundaries ?? [])
|
|
830
|
+
.some((packageBoundary) => packageBoundary.sourcePath === filePath))
|
|
750
831
|
.map((filePath) => getCompiledClientModule(projectRoot, filePath))
|
|
751
832
|
.sort();
|
|
752
833
|
}
|