evolit 0.1.3 → 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 +25 -0
- package/package.json +1 -1
- package/src/build.js +104 -31
- package/src/client-assets.js +209 -32
- package/src/compiler.js +88 -15
- package/src/deployment-runtime.js +97 -16
package/README.md
CHANGED
|
@@ -46,6 +46,31 @@ 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
|
+
### Package CSS and static assets
|
|
50
|
+
|
|
51
|
+
Applications can import stylesheets exposed through package `exports` in the same way as local
|
|
52
|
+
stylesheets:
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import "@scope/design-system/tokens.css";
|
|
56
|
+
import "@scope/design-system/theme.css";
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Evolit resolves package subpaths with ESM `import` conditions, emits exported CSS through the
|
|
60
|
+
route static-asset pipeline, and adds the resulting stylesheet URLs to the rendered document.
|
|
61
|
+
Relative `@import` rules and `url(...)` references inside package CSS are emitted and rewritten
|
|
62
|
+
from their location within `node_modules`. Bare package imports that resolve to JavaScript continue
|
|
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.
|
|
73
|
+
|
|
49
74
|
## Commands
|
|
50
75
|
|
|
51
76
|
```sh
|
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
|
@@ -144,35 +144,84 @@ export async function resolvePackageRoot(packageName, options = {}) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
const pendingResolution = (async () => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
packageEntryPath = requireFromHere.resolve(packageName);
|
|
152
|
-
}
|
|
153
|
-
let currentPath = path.dirname(packageEntryPath);
|
|
154
|
-
|
|
155
|
-
while (true) {
|
|
156
|
-
const packageJsonPath = path.join(currentPath, "package.json");
|
|
147
|
+
const resolvers = [
|
|
148
|
+
createRequire(path.join(resolveFrom, "package.json")),
|
|
149
|
+
requireFromHere,
|
|
150
|
+
];
|
|
157
151
|
|
|
152
|
+
for (const resolver of resolvers) {
|
|
158
153
|
try {
|
|
154
|
+
const packageJsonPath = resolver.resolve(`${packageName}/package.json`);
|
|
159
155
|
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
|
|
160
156
|
if (packageJson?.name === packageName) {
|
|
161
|
-
return { packageRoot:
|
|
157
|
+
return { packageRoot: path.dirname(packageJsonPath), packageJson };
|
|
162
158
|
}
|
|
163
159
|
} catch {
|
|
164
|
-
//
|
|
160
|
+
// Some packages intentionally do not export package.json. Resolve the
|
|
161
|
+
// entrypoint below and walk back to its owning manifest instead.
|
|
165
162
|
}
|
|
163
|
+
}
|
|
166
164
|
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
let packageEntryPath = null;
|
|
166
|
+
let resolutionError = null;
|
|
167
|
+
for (const resolver of resolvers) {
|
|
168
|
+
try {
|
|
169
|
+
packageEntryPath = resolver.resolve(packageName);
|
|
169
170
|
break;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
resolutionError = error;
|
|
170
173
|
}
|
|
174
|
+
}
|
|
175
|
+
if (packageEntryPath) {
|
|
176
|
+
let currentPath = path.dirname(packageEntryPath);
|
|
177
|
+
|
|
178
|
+
while (true) {
|
|
179
|
+
const packageJsonPath = path.join(currentPath, "package.json");
|
|
171
180
|
|
|
172
|
-
|
|
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.
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const parentPath = path.dirname(currentPath);
|
|
191
|
+
if (parentPath === currentPath) {
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
currentPath = parentPath;
|
|
196
|
+
}
|
|
173
197
|
}
|
|
174
198
|
|
|
175
|
-
|
|
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
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
throw resolutionError ?? new Error(`Unable to resolve package root for ${packageName}`);
|
|
176
225
|
})();
|
|
177
226
|
|
|
178
227
|
packageRootCache.set(cacheKey, pendingResolution);
|
|
@@ -190,6 +239,14 @@ function pickBrowserExportTarget(target) {
|
|
|
190
239
|
return target;
|
|
191
240
|
}
|
|
192
241
|
|
|
242
|
+
if (Array.isArray(target)) {
|
|
243
|
+
for (const candidate of target) {
|
|
244
|
+
const resolved = pickBrowserExportTarget(candidate);
|
|
245
|
+
if (resolved) return resolved;
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
193
250
|
if (!target || typeof target !== "object") {
|
|
194
251
|
return null;
|
|
195
252
|
}
|
|
@@ -202,6 +259,33 @@ function pickBrowserExportTarget(target) {
|
|
|
202
259
|
?? null;
|
|
203
260
|
}
|
|
204
261
|
|
|
262
|
+
function resolveBrowserPackageExport(exportsField, exportKey) {
|
|
263
|
+
if (typeof exportsField === "string" || Array.isArray(exportsField)) {
|
|
264
|
+
return exportKey === "." ? pickBrowserExportTarget(exportsField) : null;
|
|
265
|
+
}
|
|
266
|
+
if (!exportsField || typeof exportsField !== "object") {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
if (!Object.keys(exportsField).some((key) => key.startsWith("."))) {
|
|
270
|
+
return exportKey === "." ? pickBrowserExportTarget(exportsField) : null;
|
|
271
|
+
}
|
|
272
|
+
if (exportsField[exportKey] != null) {
|
|
273
|
+
return pickBrowserExportTarget(exportsField[exportKey]);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
for (const [pattern, target] of Object.entries(exportsField)) {
|
|
277
|
+
const wildcardIndex = pattern.indexOf("*");
|
|
278
|
+
if (wildcardIndex < 0) continue;
|
|
279
|
+
const prefix = pattern.slice(0, wildcardIndex);
|
|
280
|
+
const suffix = pattern.slice(wildcardIndex + 1);
|
|
281
|
+
if (!exportKey.startsWith(prefix) || !exportKey.endsWith(suffix)) continue;
|
|
282
|
+
const wildcard = exportKey.slice(prefix.length, exportKey.length - suffix.length);
|
|
283
|
+
const resolved = pickBrowserExportTarget(target);
|
|
284
|
+
if (resolved) return resolved.replace("*", wildcard);
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
205
289
|
export async function resolveBrowserSpecifierFilePath(specifier, options = {}) {
|
|
206
290
|
if (!isBareSpecifier(specifier)) {
|
|
207
291
|
return null;
|
|
@@ -222,17 +306,15 @@ export async function resolveBrowserSpecifierFilePath(specifier, options = {}) {
|
|
|
222
306
|
const { packageName, subpath } = parsedSpecifier;
|
|
223
307
|
const { packageRoot, packageJson } = await resolvePackageRoot(packageName, { projectRoot: resolveFrom });
|
|
224
308
|
const exportKey = subpath.length > 0 ? `./${subpath}` : ".";
|
|
225
|
-
const
|
|
226
|
-
const exportTarget =
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
? packageJson.browser
|
|
235
|
-
: packageJson.module ?? packageJson.main ?? null;
|
|
309
|
+
const hasExports = packageJson.exports != null;
|
|
310
|
+
const exportTarget = resolveBrowserPackageExport(packageJson.exports, exportKey);
|
|
311
|
+
const fallbackTarget = hasExports
|
|
312
|
+
? null
|
|
313
|
+
: subpath.length > 0
|
|
314
|
+
? `./${subpath}`
|
|
315
|
+
: typeof packageJson.browser === "string"
|
|
316
|
+
? packageJson.browser
|
|
317
|
+
: packageJson.module ?? packageJson.main ?? null;
|
|
236
318
|
let resolvedTarget = exportTarget ?? fallbackTarget;
|
|
237
319
|
if (resolvedTarget && packageJson.browser && typeof packageJson.browser === "object") {
|
|
238
320
|
const normalizedTarget = resolvedTarget.startsWith("./")
|
|
@@ -264,6 +346,79 @@ export async function resolveBrowserSpecifierFilePath(specifier, options = {}) {
|
|
|
264
346
|
}
|
|
265
347
|
}
|
|
266
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
|
+
|
|
267
422
|
export function getSharedOutputRoot(projectRoot, mode) {
|
|
268
423
|
return path.join(
|
|
269
424
|
projectRoot,
|
|
@@ -1082,6 +1237,10 @@ function toPublicHydrationModuleId(projectRoot, moduleId) {
|
|
|
1082
1237
|
return `/${relativePath}`;
|
|
1083
1238
|
}
|
|
1084
1239
|
|
|
1240
|
+
if (isBareSpecifier(moduleId)) {
|
|
1241
|
+
return moduleId;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1085
1244
|
if (moduleId.startsWith("/")) {
|
|
1086
1245
|
return moduleId;
|
|
1087
1246
|
}
|
|
@@ -1113,12 +1272,19 @@ function getServerOutputRoot(projectRoot) {
|
|
|
1113
1272
|
|
|
1114
1273
|
export function createAssetResolver(projectRoot, options = {}) {
|
|
1115
1274
|
const assetManifest = normalizeClientAssetManifest(options.assetManifest);
|
|
1275
|
+
const packageImports = options.packageImports instanceof Map
|
|
1276
|
+
? options.packageImports
|
|
1277
|
+
: new Map(Object.entries(options.packageImports ?? {}));
|
|
1116
1278
|
|
|
1117
1279
|
return function assetResolver(moduleId) {
|
|
1118
1280
|
if (typeof moduleId !== "string" || moduleId.length === 0) {
|
|
1119
1281
|
return null;
|
|
1120
1282
|
}
|
|
1121
1283
|
|
|
1284
|
+
if (packageImports.has(moduleId)) {
|
|
1285
|
+
return packageImports.get(moduleId);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1122
1288
|
let relativeClientModule = null;
|
|
1123
1289
|
if (path.isAbsolute(moduleId) && !isVirtualClientModuleId(moduleId)) {
|
|
1124
1290
|
relativeClientModule = toClientModuleRelativePath(projectRoot, moduleId);
|
|
@@ -1220,6 +1386,7 @@ export function normalizeHydrationDataForClient(
|
|
|
1220
1386
|
hydrationData,
|
|
1221
1387
|
projectRoot = null,
|
|
1222
1388
|
additionalClientImports = [],
|
|
1389
|
+
assetResolver = null,
|
|
1223
1390
|
) {
|
|
1224
1391
|
if (!hydrationData || typeof hydrationData !== "object") {
|
|
1225
1392
|
return hydrationData ?? null;
|
|
@@ -1256,7 +1423,9 @@ export function normalizeHydrationDataForClient(
|
|
|
1256
1423
|
const clientImports = [...new Set([
|
|
1257
1424
|
...(Array.isArray(hydrationData.clientImports) ? hydrationData.clientImports : []),
|
|
1258
1425
|
...(Array.isArray(additionalClientImports) ? additionalClientImports : []),
|
|
1259
|
-
]
|
|
1426
|
+
]
|
|
1427
|
+
.map((value) => typeof assetResolver === "function" ? assetResolver(value) ?? value : value)
|
|
1428
|
+
.filter((value) => typeof value === "string" && value.length > 0))];
|
|
1260
1429
|
|
|
1261
1430
|
Object.defineProperties(normalizedHydrationData, {
|
|
1262
1431
|
payload: {
|
|
@@ -1786,10 +1955,15 @@ async function bundleClientAssets(projectRoot, options = {}) {
|
|
|
1786
1955
|
const scriptAssetRecords = [];
|
|
1787
1956
|
let nextRollupCache = options.rollupCache ?? null;
|
|
1788
1957
|
if (Object.keys(inputEntries).length > 0) {
|
|
1789
|
-
const sharedVendorSpecifiers =
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
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();
|
|
1793
1967
|
const sharedRuntime = await buildSharedVendorRuntime(projectRoot, mode, {
|
|
1794
1968
|
additionalEntrySpecifiers: sharedVendorSpecifiers,
|
|
1795
1969
|
});
|
|
@@ -2227,6 +2401,9 @@ export function normalizeClientAssetManifest(manifest) {
|
|
|
2227
2401
|
chunks: Array.isArray(manifest.chunks) ? manifest.chunks : [],
|
|
2228
2402
|
styles: Array.isArray(manifest.styles) ? manifest.styles : [],
|
|
2229
2403
|
resources: Array.isArray(manifest.resources) ? manifest.resources : [],
|
|
2404
|
+
sharedImports: manifest.sharedImports && typeof manifest.sharedImports === "object"
|
|
2405
|
+
? manifest.sharedImports
|
|
2406
|
+
: {},
|
|
2230
2407
|
serverAssetImportsByEntry:
|
|
2231
2408
|
manifest.serverAssetImportsByEntry && typeof manifest.serverAssetImportsByEntry === "object"
|
|
2232
2409
|
? manifest.serverAssetImportsByEntry
|
package/src/compiler.js
CHANGED
|
@@ -19,6 +19,7 @@ import { ensureDirectory } from "./fs-utils.js";
|
|
|
19
19
|
|
|
20
20
|
const MODULE_SPECIFIER_PATTERN =
|
|
21
21
|
/\b(?:import|export)\s+(?:[^"']*?\s+from\s+)?["']([^"']+)["']|\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
22
|
+
const CSS_DEPENDENCY_PATTERN = /(?:@import\s+(?:url\(\s*)?["']([^"']+)["']\s*\)?|url\(\s*["']?([^"')]+)["']?\s*\))/g;
|
|
22
23
|
const RESOLVABLE_IMPORT_EXTENSIONS = [
|
|
23
24
|
...MODULE_EXTENSIONS,
|
|
24
25
|
...STATIC_ASSET_EXTENSIONS,
|
|
@@ -497,7 +498,8 @@ function createStaticAssetStubSource(relativeAssetPath, mode, target = "server",
|
|
|
497
498
|
}
|
|
498
499
|
|
|
499
500
|
async function resolveImportPath(importerPath, specifier) {
|
|
500
|
-
const
|
|
501
|
+
const cleanSpecifier = String(specifier).split("?")[0].split("#")[0];
|
|
502
|
+
const basePath = path.resolve(path.dirname(importerPath), cleanSpecifier);
|
|
501
503
|
const candidates = [basePath];
|
|
502
504
|
|
|
503
505
|
if (!hasResolvableImportExtension(basePath)) {
|
|
@@ -521,6 +523,29 @@ async function resolveImportPath(importerPath, specifier) {
|
|
|
521
523
|
return null;
|
|
522
524
|
}
|
|
523
525
|
|
|
526
|
+
async function collectStaticAssetGraph(entryPath, collected = new Set()) {
|
|
527
|
+
const normalizedEntryPath = path.resolve(entryPath);
|
|
528
|
+
if (collected.has(normalizedEntryPath) || !isStaticAssetPath(normalizedEntryPath)) {
|
|
529
|
+
return collected;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
collected.add(normalizedEntryPath);
|
|
533
|
+
if (!isStyleAssetPath(normalizedEntryPath)) {
|
|
534
|
+
return collected;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const source = await fs.readFile(normalizedEntryPath, "utf8");
|
|
538
|
+
for (const match of source.matchAll(CSS_DEPENDENCY_PATTERN)) {
|
|
539
|
+
const specifier = match[1] ?? match[2];
|
|
540
|
+
if (!specifier || !isRelativeSpecifier(specifier)) continue;
|
|
541
|
+
const resolvedPath = await resolveImportPath(normalizedEntryPath, specifier);
|
|
542
|
+
if (resolvedPath && isStaticAssetPath(resolvedPath)) {
|
|
543
|
+
await collectStaticAssetGraph(resolvedPath, collected);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return collected;
|
|
547
|
+
}
|
|
548
|
+
|
|
524
549
|
async function loadProjectPathAliases(projectRoot) {
|
|
525
550
|
if (projectPathAliasesCache.has(projectRoot)) return projectPathAliasesCache.get(projectRoot);
|
|
526
551
|
const pending = (async () => {
|
|
@@ -660,12 +685,15 @@ async function resolveProjectPackageImport(projectRoot, importerPath, specifier)
|
|
|
660
685
|
try {
|
|
661
686
|
const packageJson = JSON.parse(await fs.readFile(path.join(packageRoot, "package.json"), "utf8"));
|
|
662
687
|
const exportKey = parsed.subpath ? `./${parsed.subpath}` : ".";
|
|
688
|
+
const hasExports = packageJson.exports != null;
|
|
663
689
|
let target = resolvePackageExports(packageJson.exports, exportKey)
|
|
664
|
-
?? (
|
|
665
|
-
?
|
|
666
|
-
:
|
|
667
|
-
?
|
|
668
|
-
: packageJson.
|
|
690
|
+
?? (hasExports
|
|
691
|
+
? null
|
|
692
|
+
: parsed.subpath
|
|
693
|
+
? `./${parsed.subpath}`
|
|
694
|
+
: typeof packageJson.browser === "string"
|
|
695
|
+
? packageJson.browser
|
|
696
|
+
: packageJson.module ?? packageJson.main);
|
|
669
697
|
if (target && packageJson.browser && typeof packageJson.browser === "object") {
|
|
670
698
|
const normalizedTarget = target.startsWith("./") ? target : `./${target}`;
|
|
671
699
|
const browserTarget = packageJson.browser[normalizedTarget]
|
|
@@ -812,6 +840,7 @@ async function rewriteRelativeSpecifiers({
|
|
|
812
840
|
staticAssetFiles,
|
|
813
841
|
managedSourceRoots,
|
|
814
842
|
serverExportsByModule,
|
|
843
|
+
packageImports,
|
|
815
844
|
}) {
|
|
816
845
|
const magicSource = new MagicString(code);
|
|
817
846
|
let didRewrite = false;
|
|
@@ -828,8 +857,28 @@ async function rewriteRelativeSpecifiers({
|
|
|
828
857
|
const aliasedImportPath = isBareSpecifier(specifier)
|
|
829
858
|
? await resolveProjectMappedImport(projectRoot, sourcePath, specifier)
|
|
830
859
|
: null;
|
|
860
|
+
const packageImportPath = isBareSpecifier(specifier) && !aliasedImportPath
|
|
861
|
+
? await resolveProjectPackageImport(projectRoot, sourcePath, specifier)
|
|
862
|
+
: null;
|
|
863
|
+
const packageAssetPath = packageImportPath && isStaticAssetPath(packageImportPath)
|
|
864
|
+
? packageImportPath
|
|
865
|
+
: null;
|
|
831
866
|
|
|
832
|
-
if (
|
|
867
|
+
if (
|
|
868
|
+
target === "server"
|
|
869
|
+
&& isBareSpecifier(specifier)
|
|
870
|
+
&& !aliasedImportPath
|
|
871
|
+
&& !packageAssetPath
|
|
872
|
+
) {
|
|
873
|
+
packageImports?.add(specifier);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (
|
|
877
|
+
target === "client"
|
|
878
|
+
&& isBareSpecifier(specifier)
|
|
879
|
+
&& !aliasedImportPath
|
|
880
|
+
&& !packageAssetPath
|
|
881
|
+
) {
|
|
833
882
|
const sourceMetadata = moduleMetadata.get(sourcePath) ?? {
|
|
834
883
|
moduleImports: new Set(),
|
|
835
884
|
vendorImports: new Set(),
|
|
@@ -841,11 +890,13 @@ async function rewriteRelativeSpecifiers({
|
|
|
841
890
|
continue;
|
|
842
891
|
}
|
|
843
892
|
|
|
844
|
-
if (!isRelativeSpecifier(specifier) && !aliasedImportPath) {
|
|
893
|
+
if (!isRelativeSpecifier(specifier) && !aliasedImportPath && !packageAssetPath) {
|
|
845
894
|
continue;
|
|
846
895
|
}
|
|
847
896
|
|
|
848
|
-
const resolvedImportPath =
|
|
897
|
+
const resolvedImportPath = packageAssetPath
|
|
898
|
+
?? aliasedImportPath
|
|
899
|
+
?? await resolveImportPath(sourcePath, specifier);
|
|
849
900
|
if (!resolvedImportPath) {
|
|
850
901
|
continue;
|
|
851
902
|
}
|
|
@@ -875,7 +926,10 @@ async function rewriteRelativeSpecifiers({
|
|
|
875
926
|
);
|
|
876
927
|
}
|
|
877
928
|
} else if (isStaticAssetPath(resolvedImportPath)) {
|
|
878
|
-
|
|
929
|
+
const staticAssetGraph = await collectStaticAssetGraph(resolvedImportPath);
|
|
930
|
+
for (const staticAssetPath of staticAssetGraph) {
|
|
931
|
+
staticAssetFiles?.add(staticAssetPath);
|
|
932
|
+
}
|
|
879
933
|
const relativeAssetPath = toOutputRelativePath(projectRoot, resolvedImportPath);
|
|
880
934
|
const assetOutputPath = path.join(outputRoot, relativeAssetPath);
|
|
881
935
|
const stubOutputPath = `${assetOutputPath}.mjs`;
|
|
@@ -888,8 +942,14 @@ async function rewriteRelativeSpecifiers({
|
|
|
888
942
|
);
|
|
889
943
|
|
|
890
944
|
if (target === "client") {
|
|
891
|
-
|
|
892
|
-
|
|
945
|
+
for (const staticAssetPath of staticAssetGraph) {
|
|
946
|
+
const staticAssetOutputPath = path.join(
|
|
947
|
+
outputRoot,
|
|
948
|
+
toOutputRelativePath(projectRoot, staticAssetPath),
|
|
949
|
+
);
|
|
950
|
+
await ensureDirectory(path.dirname(staticAssetOutputPath));
|
|
951
|
+
await fs.copyFile(staticAssetPath, staticAssetOutputPath);
|
|
952
|
+
}
|
|
893
953
|
const sourceMetadata = moduleMetadata.get(sourcePath) ?? {
|
|
894
954
|
moduleImports: new Set(),
|
|
895
955
|
vendorImports: new Set(),
|
|
@@ -1102,6 +1162,7 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
|
|
|
1102
1162
|
const staticAssetFiles = new Set();
|
|
1103
1163
|
const moduleMetadata = new Map();
|
|
1104
1164
|
const serverExportsByModule = new Map();
|
|
1165
|
+
const packageImports = new Set();
|
|
1105
1166
|
const serverImportQuery = target === "server" && mode === "development"
|
|
1106
1167
|
? `t=${Date.now()}`
|
|
1107
1168
|
: null;
|
|
@@ -1171,6 +1232,7 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
|
|
|
1171
1232
|
staticAssetFiles,
|
|
1172
1233
|
managedSourceRoots,
|
|
1173
1234
|
serverExportsByModule,
|
|
1235
|
+
packageImports,
|
|
1174
1236
|
});
|
|
1175
1237
|
|
|
1176
1238
|
await fs.writeFile(outputPath, rewritten.code, "utf8");
|
|
@@ -1200,6 +1262,7 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
|
|
|
1200
1262
|
entrypoint: await compileModule(entryPath),
|
|
1201
1263
|
outputRoot,
|
|
1202
1264
|
sourceFiles: [...new Set([...visited.keys(), ...staticAssetFiles])],
|
|
1265
|
+
packageImports: [...packageImports].sort(),
|
|
1203
1266
|
};
|
|
1204
1267
|
}
|
|
1205
1268
|
|
|
@@ -1239,8 +1302,6 @@ export async function compileModuleGraph(entryPath, options = {}) {
|
|
|
1239
1302
|
}
|
|
1240
1303
|
}
|
|
1241
1304
|
|
|
1242
|
-
const CSS_DEPENDENCY_PATTERN = /(?:@import\s+(?:url\(\s*)?["']([^"']+)["']\s*\)?|url\(\s*["']?([^"')]+)["']?\s*\))/g;
|
|
1243
|
-
|
|
1244
1305
|
function normalizeProjectRelativePath(projectRoot, filePath) {
|
|
1245
1306
|
return toOutputRelativePath(projectRoot, filePath).split(path.sep).join("/");
|
|
1246
1307
|
}
|
|
@@ -1266,6 +1327,7 @@ export async function collectClientGraphInventory(entryPaths, options = {}) {
|
|
|
1266
1327
|
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
1267
1328
|
const visited = new Set();
|
|
1268
1329
|
const boundaries = new Set();
|
|
1330
|
+
const packageBoundaries = new Map();
|
|
1269
1331
|
const styles = new Set();
|
|
1270
1332
|
const assets = new Set();
|
|
1271
1333
|
|
|
@@ -1374,7 +1436,10 @@ export async function collectClientGraphInventory(entryPaths, options = {}) {
|
|
|
1374
1436
|
if (!resolved) continue;
|
|
1375
1437
|
if (shouldCompileModule(resolved)) {
|
|
1376
1438
|
if (isBareSpecifier(specifier) && aliasResolved == null) {
|
|
1377
|
-
if (componentImportSpecifiers.has(specifier))
|
|
1439
|
+
if (componentImportSpecifiers.has(specifier)) {
|
|
1440
|
+
boundaries.add(resolved);
|
|
1441
|
+
packageBoundaries.set(specifier, resolved);
|
|
1442
|
+
}
|
|
1378
1443
|
} else {
|
|
1379
1444
|
await visit(resolved, true);
|
|
1380
1445
|
}
|
|
@@ -1390,6 +1455,9 @@ export async function collectClientGraphInventory(entryPaths, options = {}) {
|
|
|
1390
1455
|
for (const entryPath of entryPaths) await visit(entryPath);
|
|
1391
1456
|
return {
|
|
1392
1457
|
clientBoundaries: [...boundaries].sort(),
|
|
1458
|
+
packageClientBoundaries: [...packageBoundaries]
|
|
1459
|
+
.map(([specifier, sourcePath]) => ({ specifier, sourcePath }))
|
|
1460
|
+
.sort((left, right) => left.specifier.localeCompare(right.specifier)),
|
|
1393
1461
|
styles: [...styles].sort(),
|
|
1394
1462
|
assets: [...assets].sort(),
|
|
1395
1463
|
sourceFiles: [...visited].sort(),
|
|
@@ -1406,8 +1474,13 @@ export async function emitClientStaticAssets(assetPaths, options = {}) {
|
|
|
1406
1474
|
const outputRoot = getTypedOutputRoot(projectRoot, mode, "client");
|
|
1407
1475
|
const emitted = [];
|
|
1408
1476
|
|
|
1477
|
+
const expandedAssetPaths = new Set();
|
|
1409
1478
|
for (const assetPath of new Set(assetPaths ?? [])) {
|
|
1410
1479
|
if (!isStaticAssetPath(assetPath)) continue;
|
|
1480
|
+
await collectStaticAssetGraph(assetPath, expandedAssetPaths);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
for (const assetPath of expandedAssetPaths) {
|
|
1411
1484
|
const relativePath = normalizeProjectRelativePath(projectRoot, assetPath);
|
|
1412
1485
|
const outputPath = path.join(outputRoot, relativePath.split("/").join(path.sep));
|
|
1413
1486
|
await ensureDirectory(path.dirname(outputPath));
|
|
@@ -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
|
}
|