@ecopages/core 0.2.0-alpha.47 → 0.2.0-alpha.49
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/package.json +2 -2
- package/src/build/build-adapter.js +6 -0
- package/src/build/runtime-specifier-aliases.js +1 -1
- package/src/route-renderer/orchestration/integration-renderer.js +9 -4
- package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +23 -1
- package/src/route-renderer/orchestration/route-render-orchestrator.js +140 -5
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +1 -0
- package/src/services/module-loading/node-bootstrap-plugin.d.ts +5 -9
- package/src/services/module-loading/node-bootstrap-plugin.js +23 -178
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/core",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.49",
|
|
4
4
|
"description": "Core package for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"directory": "packages/core"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@ecopages/file-system": "0.2.0-alpha.
|
|
20
|
+
"@ecopages/file-system": "0.2.0-alpha.49",
|
|
21
21
|
"@ecopages/logger": "^0.2.3",
|
|
22
22
|
"@ecopages/scripts-injector": "^0.1.5",
|
|
23
23
|
"@worker-tools/html-rewriter": "0.1.0-pre.19",
|
|
@@ -227,6 +227,12 @@ class BunBuildAdapter {
|
|
|
227
227
|
if (fs.existsSync(outputPath)) {
|
|
228
228
|
return outputPath;
|
|
229
229
|
}
|
|
230
|
+
for (const extension of [".js", ".mjs", ".cjs"]) {
|
|
231
|
+
const outputPathWithExtension = `${outputPath}${extension}`;
|
|
232
|
+
if (fs.existsSync(outputPathWithExtension)) {
|
|
233
|
+
return outputPathWithExtension;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
230
236
|
if (!outputPath.includes("[hash]")) {
|
|
231
237
|
return outputPath;
|
|
232
238
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseSync } from "oxc-parser";
|
|
2
|
-
const RUNTIME_SPECIFIER_ALIAS_MAP = /* @__PURE__ */ Symbol("ecopages.runtimeSpecifierAliasMap");
|
|
2
|
+
const RUNTIME_SPECIFIER_ALIAS_MAP = /* @__PURE__ */ Symbol.for("ecopages.runtimeSpecifierAliasMap");
|
|
3
3
|
function attachRuntimeSpecifierAliasMap(plugin, specifierMap) {
|
|
4
4
|
plugin[RUNTIME_SPECIFIER_ALIAS_MAP] = specifierMap;
|
|
5
5
|
return plugin;
|
|
@@ -184,9 +184,9 @@ class IntegrationRenderer {
|
|
|
184
184
|
return await this.routeRenderOrchestrator.resolveDeclaredPageBrowserGraph({
|
|
185
185
|
routeFile: filePath,
|
|
186
186
|
integrationName: this.name,
|
|
187
|
-
collectContribution: async () => {
|
|
188
|
-
const pageModule = await this.importPageFile(
|
|
189
|
-
return await this.collectPageBrowserGraphContribution({ file:
|
|
187
|
+
collectContribution: async (routeFile) => {
|
|
188
|
+
const pageModule = await this.importPageFile(routeFile);
|
|
189
|
+
return await this.collectPageBrowserGraphContribution({ file: routeFile, pageModule });
|
|
190
190
|
}
|
|
191
191
|
});
|
|
192
192
|
}
|
|
@@ -241,7 +241,12 @@ class IntegrationRenderer {
|
|
|
241
241
|
...this.htmlTransformer.getProcessedDependencies(),
|
|
242
242
|
...nextDependencies
|
|
243
243
|
]);
|
|
244
|
-
this.htmlTransformer.
|
|
244
|
+
const currentPageBrowserGraph = this.htmlTransformer.getPagePackage()?.pageBrowserGraph;
|
|
245
|
+
this.htmlTransformer.setPagePackage(
|
|
246
|
+
createPagePackage(mergedDependencies, {
|
|
247
|
+
pageBrowserGraph: currentPageBrowserGraph
|
|
248
|
+
})
|
|
249
|
+
);
|
|
245
250
|
return nextDependencies;
|
|
246
251
|
}
|
|
247
252
|
/**
|
|
@@ -94,6 +94,7 @@ export declare class RouteRenderOrchestrator {
|
|
|
94
94
|
private readonly ownershipPlanningService;
|
|
95
95
|
private readonly ownershipValidationService;
|
|
96
96
|
private readonly pageBrowserGraphCache;
|
|
97
|
+
private readonly groupedPageBrowserGraphCache;
|
|
97
98
|
constructor(appConfig: EcoPagesAppConfig, assetProcessingService: AssetProcessingService, dependencies?: RouteRenderOrchestratorDependencies);
|
|
98
99
|
/**
|
|
99
100
|
* Builds normalized route render options before the integration render runs.
|
|
@@ -106,11 +107,32 @@ export declare class RouteRenderOrchestrator {
|
|
|
106
107
|
resolveDeclaredPageBrowserGraph(input: {
|
|
107
108
|
routeFile: string;
|
|
108
109
|
integrationName: string;
|
|
109
|
-
collectContribution: () => Promise<PageBrowserGraphContribution | undefined>;
|
|
110
|
+
collectContribution: (routeFile: string) => Promise<PageBrowserGraphContribution | undefined>;
|
|
110
111
|
}): Promise<PageBrowserGraphResult | undefined>;
|
|
111
112
|
private resolvePageBrowserGraph;
|
|
112
113
|
private isHmrEnabled;
|
|
113
114
|
private buildPageBrowserGraph;
|
|
115
|
+
/**
|
|
116
|
+
* Resolves grouped page-browser assets for all routes owned by one integration.
|
|
117
|
+
*
|
|
118
|
+
* @remarks
|
|
119
|
+
* This keeps one shared browser-build result available across sibling routes so
|
|
120
|
+
* navigation can reuse the same emitted client graph instead of rebuilding one
|
|
121
|
+
* page entry at a time. HMR bypasses this cache because the grouped build must
|
|
122
|
+
* reflect the latest source on every request.
|
|
123
|
+
*/
|
|
124
|
+
private resolveGroupedPageBrowserAssets;
|
|
125
|
+
/**
|
|
126
|
+
* Builds the shared grouped page-browser asset map for one integration.
|
|
127
|
+
*
|
|
128
|
+
* @remarks
|
|
129
|
+
* Each route can declare grouped content-script dependencies that should be
|
|
130
|
+
* emitted together. This method collects those declarations across the owning
|
|
131
|
+
* integration, runs the grouped processor once, and then maps the emitted
|
|
132
|
+
* assets back onto the routes that reference them.
|
|
133
|
+
*/
|
|
134
|
+
private buildGroupedPageBrowserAssets;
|
|
135
|
+
private listIntegrationRouteFiles;
|
|
114
136
|
private partitionPageBrowserGraphAssets;
|
|
115
137
|
/**
|
|
116
138
|
* Captures one route render body as HTML while preserving a replayable body value.
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { fileSystem } from "@ecopages/file-system";
|
|
3
4
|
import {
|
|
4
5
|
AssetFactory,
|
|
5
6
|
createPagePackage
|
|
6
7
|
} from "../../services/assets/asset-processing-service/index.js";
|
|
7
8
|
import { buildGlobalInjectorBootstrapContent, buildGlobalInjectorMapScript } from "../../eco/global-injector-map.js";
|
|
8
9
|
import { LocalsAccessError } from "../../errors/locals-access-error.js";
|
|
10
|
+
import { appLogger } from "../../global/app-logger.js";
|
|
9
11
|
import { inspectUnresolvedMarkerArtifactHtml } from "./render-output.utils.js";
|
|
10
12
|
import { OwnershipValidationService } from "./ownership-validation.service.js";
|
|
11
13
|
import { OwnershipPlanningService } from "./ownership-planning.service.js";
|
|
@@ -39,12 +41,19 @@ function createPageLocalsProxy(filePath) {
|
|
|
39
41
|
}
|
|
40
42
|
);
|
|
41
43
|
}
|
|
44
|
+
function isGroupedContentScriptAsset(asset) {
|
|
45
|
+
return asset.kind === "script" && asset.source === "content" && Boolean(asset.groupedBundle?.id);
|
|
46
|
+
}
|
|
47
|
+
function getGroupedBundleAssetKey(groupedBundle) {
|
|
48
|
+
return `${groupedBundle.id}:${groupedBundle.entryName}`;
|
|
49
|
+
}
|
|
42
50
|
class RouteRenderOrchestrator {
|
|
43
51
|
appConfig;
|
|
44
52
|
assetProcessingService;
|
|
45
53
|
ownershipPlanningService;
|
|
46
54
|
ownershipValidationService;
|
|
47
55
|
pageBrowserGraphCache = /* @__PURE__ */ new Map();
|
|
56
|
+
groupedPageBrowserGraphCache = /* @__PURE__ */ new Map();
|
|
48
57
|
constructor(appConfig, assetProcessingService, dependencies = {}) {
|
|
49
58
|
this.appConfig = appConfig;
|
|
50
59
|
this.assetProcessingService = assetProcessingService;
|
|
@@ -84,7 +93,7 @@ class RouteRenderOrchestrator {
|
|
|
84
93
|
const pageBrowserGraph = await this.resolvePageBrowserGraph({
|
|
85
94
|
routeFile: routeOptions.file,
|
|
86
95
|
integrationName: adapter.name,
|
|
87
|
-
collectContribution: async () => await adapter.collectPageBrowserGraphContribution(
|
|
96
|
+
collectContribution: async (routeFile) => await adapter.collectPageBrowserGraphContribution(routeFile)
|
|
88
97
|
});
|
|
89
98
|
const usedIntegrationDependencies = this.collectUsedIntegrationDependencies(componentsToResolve, adapter.name);
|
|
90
99
|
const allDependencies = [...resolvedDependencies, ...usedIntegrationDependencies];
|
|
@@ -165,17 +174,143 @@ class RouteRenderOrchestrator {
|
|
|
165
174
|
return typeof this.assetProcessingService.getHmrManager === "function" && this.assetProcessingService.getHmrManager()?.isEnabled() === true;
|
|
166
175
|
}
|
|
167
176
|
async buildPageBrowserGraph(input) {
|
|
168
|
-
const contribution = await input.collectContribution();
|
|
177
|
+
const contribution = await input.collectContribution(input.routeFile);
|
|
169
178
|
if (!contribution) {
|
|
170
179
|
return void 0;
|
|
171
180
|
}
|
|
172
|
-
const
|
|
173
|
-
|
|
181
|
+
const groupedDependencies = (contribution.dependencies ?? []).filter((dep) => isGroupedContentScriptAsset(dep));
|
|
182
|
+
const ungroupedDependencies = (contribution.dependencies ?? []).filter(
|
|
183
|
+
(dep) => !isGroupedContentScriptAsset(dep)
|
|
184
|
+
);
|
|
185
|
+
const groupedAssets = groupedDependencies.length ? (await this.resolveGroupedPageBrowserAssets(input, contribution)).get(input.routeFile) ?? [] : [];
|
|
186
|
+
const processedDependencies = ungroupedDependencies.length ? await this.assetProcessingService.processDependencies(
|
|
187
|
+
ungroupedDependencies,
|
|
174
188
|
`${input.integrationName}:${input.routeFile}`
|
|
175
189
|
) : [];
|
|
176
|
-
const resolvedAssets = [...processedDependencies, ...contribution.assets ?? []];
|
|
190
|
+
const resolvedAssets = [...processedDependencies, ...groupedAssets, ...contribution.assets ?? []];
|
|
177
191
|
return this.partitionPageBrowserGraphAssets(resolvedAssets);
|
|
178
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Resolves grouped page-browser assets for all routes owned by one integration.
|
|
195
|
+
*
|
|
196
|
+
* @remarks
|
|
197
|
+
* This keeps one shared browser-build result available across sibling routes so
|
|
198
|
+
* navigation can reuse the same emitted client graph instead of rebuilding one
|
|
199
|
+
* page entry at a time. HMR bypasses this cache because the grouped build must
|
|
200
|
+
* reflect the latest source on every request.
|
|
201
|
+
*/
|
|
202
|
+
async resolveGroupedPageBrowserAssets(input, currentContribution) {
|
|
203
|
+
if (this.isHmrEnabled()) {
|
|
204
|
+
const result = await this.buildGroupedPageBrowserAssets(input, currentContribution);
|
|
205
|
+
return result.assetsByRoute;
|
|
206
|
+
}
|
|
207
|
+
const cacheKey = input.integrationName;
|
|
208
|
+
const cached = this.groupedPageBrowserGraphCache.get(cacheKey);
|
|
209
|
+
if (cached) {
|
|
210
|
+
return await cached;
|
|
211
|
+
}
|
|
212
|
+
const pendingGroupedAssets = this.buildGroupedPageBrowserAssets(input, currentContribution).then((result) => {
|
|
213
|
+
if (result.hasCollectionFailures) {
|
|
214
|
+
this.groupedPageBrowserGraphCache.delete(cacheKey);
|
|
215
|
+
}
|
|
216
|
+
return result.assetsByRoute;
|
|
217
|
+
}).catch((error) => {
|
|
218
|
+
this.groupedPageBrowserGraphCache.delete(cacheKey);
|
|
219
|
+
throw error;
|
|
220
|
+
});
|
|
221
|
+
this.groupedPageBrowserGraphCache.set(cacheKey, pendingGroupedAssets);
|
|
222
|
+
return await pendingGroupedAssets;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Builds the shared grouped page-browser asset map for one integration.
|
|
226
|
+
*
|
|
227
|
+
* @remarks
|
|
228
|
+
* Each route can declare grouped content-script dependencies that should be
|
|
229
|
+
* emitted together. This method collects those declarations across the owning
|
|
230
|
+
* integration, runs the grouped processor once, and then maps the emitted
|
|
231
|
+
* assets back onto the routes that reference them.
|
|
232
|
+
*/
|
|
233
|
+
async buildGroupedPageBrowserAssets(input, currentContribution) {
|
|
234
|
+
const routeFiles = await this.listIntegrationRouteFiles(input.integrationName);
|
|
235
|
+
const currentRouteGroupedDependencies = (currentContribution.dependencies ?? []).filter(
|
|
236
|
+
(dep) => isGroupedContentScriptAsset(dep)
|
|
237
|
+
);
|
|
238
|
+
const groupedDependencies = [...currentRouteGroupedDependencies];
|
|
239
|
+
const groupedAssetKeysByRoute = /* @__PURE__ */ new Map();
|
|
240
|
+
let hasCollectionFailures = false;
|
|
241
|
+
if (currentRouteGroupedDependencies.length > 0) {
|
|
242
|
+
groupedAssetKeysByRoute.set(
|
|
243
|
+
input.routeFile,
|
|
244
|
+
new Set(currentRouteGroupedDependencies.map((dep) => getGroupedBundleAssetKey(dep.groupedBundle)))
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
for (const routeFile of routeFiles) {
|
|
248
|
+
if (routeFile === input.routeFile) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
let contribution;
|
|
252
|
+
try {
|
|
253
|
+
contribution = await input.collectContribution(routeFile);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
hasCollectionFailures = true;
|
|
256
|
+
appLogger.warn(
|
|
257
|
+
`Skipping grouped page-browser contribution for ${routeFile}: ${error instanceof Error ? error.message : String(error)}`
|
|
258
|
+
);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (!contribution?.dependencies?.length) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const routeGroupedDependencies = contribution.dependencies.filter(
|
|
265
|
+
(dep) => isGroupedContentScriptAsset(dep)
|
|
266
|
+
);
|
|
267
|
+
if (routeGroupedDependencies.length === 0) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
groupedDependencies.push(...routeGroupedDependencies);
|
|
271
|
+
groupedAssetKeysByRoute.set(
|
|
272
|
+
routeFile,
|
|
273
|
+
new Set(routeGroupedDependencies.map((dep) => getGroupedBundleAssetKey(dep.groupedBundle)))
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
if (groupedDependencies.length === 0) {
|
|
277
|
+
return {
|
|
278
|
+
assetsByRoute: /* @__PURE__ */ new Map(),
|
|
279
|
+
hasCollectionFailures
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const processedGroupedDependencies = await this.assetProcessingService.processDependencies(
|
|
283
|
+
groupedDependencies,
|
|
284
|
+
`${input.integrationName}:grouped-page-browser-graph`
|
|
285
|
+
);
|
|
286
|
+
const groupedAssetsByRoute = /* @__PURE__ */ new Map();
|
|
287
|
+
for (const [routeFile, groupedAssetKeys] of groupedAssetKeysByRoute) {
|
|
288
|
+
groupedAssetsByRoute.set(
|
|
289
|
+
routeFile,
|
|
290
|
+
processedGroupedDependencies.filter((asset) => {
|
|
291
|
+
if (!asset.groupedBundle) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
return groupedAssetKeys.has(getGroupedBundleAssetKey(asset.groupedBundle));
|
|
295
|
+
})
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
assetsByRoute: groupedAssetsByRoute,
|
|
300
|
+
hasCollectionFailures
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
async listIntegrationRouteFiles(integrationName) {
|
|
304
|
+
const integration = this.appConfig.integrations.find((plugin) => plugin.name === integrationName);
|
|
305
|
+
if (!integration) {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
const scannedFiles = await fileSystem.glob(
|
|
309
|
+
integration.extensions.map((extension) => `**/*${extension}`),
|
|
310
|
+
{ cwd: this.appConfig.absolutePaths.pagesDir }
|
|
311
|
+
);
|
|
312
|
+
return scannedFiles.filter((file) => !file.includes(".ecopages-node.")).map((file) => path.join(this.appConfig.absolutePaths.pagesDir, file)).sort((left, right) => left.localeCompare(right));
|
|
313
|
+
}
|
|
179
314
|
partitionPageBrowserGraphAssets(assets) {
|
|
180
315
|
const entryAssets = [];
|
|
181
316
|
const chunkAssets = [];
|
package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js
CHANGED
|
@@ -75,6 +75,7 @@ class ContentScriptProcessor extends BaseScriptProcessor {
|
|
|
75
75
|
inline: dep.inline,
|
|
76
76
|
excludeFromHtml: dep.excludeFromHtml,
|
|
77
77
|
packageRole: dep.packageRole,
|
|
78
|
+
groupedBundle: dep.groupedBundle,
|
|
78
79
|
bundledSourceFilepaths: dep.bundledSourceFilepaths
|
|
79
80
|
};
|
|
80
81
|
this.writeCacheFile(filename, unbundledProcessedAsset);
|
|
@@ -12,8 +12,8 @@ export interface NodeBootstrapResolutionOptions {
|
|
|
12
12
|
*/
|
|
13
13
|
projectDir: string;
|
|
14
14
|
/**
|
|
15
|
-
* Runtime-local node_modules directory
|
|
16
|
-
*
|
|
15
|
+
* Runtime-local node_modules directory retained for backwards-compatible
|
|
16
|
+
* option shape. Third-party packages now resolve through native Node lookup.
|
|
17
17
|
*/
|
|
18
18
|
runtimeNodeModulesDir: string;
|
|
19
19
|
}
|
|
@@ -26,17 +26,13 @@ export declare function resolveNodeBootstrapDependency(args: Pick<EcoBuildOnReso
|
|
|
26
26
|
/**
|
|
27
27
|
* Creates the Node bootstrap plugin used by app-owned server module loads.
|
|
28
28
|
*
|
|
29
|
-
* The resolver
|
|
30
|
-
*
|
|
31
|
-
* node_modules directory. That keeps transpiled Node execution aligned with the
|
|
32
|
-
* package graph each source file was authored against.
|
|
29
|
+
* The resolver keeps third-party imports external so native Node package
|
|
30
|
+
* semantics decide exports, subpaths, and CommonJS interop at runtime.
|
|
33
31
|
*/
|
|
34
32
|
export declare function createNodeBootstrapPlugin(options: NodeBootstrapResolutionOptions): EcoBuildPlugin;
|
|
35
33
|
/**
|
|
36
34
|
* Creates the default Node bootstrap plugin for one Ecopages app runtime.
|
|
37
35
|
*
|
|
38
|
-
* This binds the shared resolution policy to
|
|
39
|
-
* directory so transpiled server modules can externalize packages into one
|
|
40
|
-
* stable runtime node_modules graph.
|
|
36
|
+
* This binds the shared resolution policy to one Ecopages app runtime.
|
|
41
37
|
*/
|
|
42
38
|
export declare function createAppNodeBootstrapPlugin(appConfig: Pick<EcoPagesAppConfig, 'rootDir' | 'workDir' | 'absolutePaths'>): EcoBuildPlugin;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { existsSync,
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { resolveInternalExecutionDir } from "../../utils/resolve-work-dir.js";
|
|
@@ -13,139 +13,6 @@ function getPackageNameFromSpecifier(specifier) {
|
|
|
13
13
|
}
|
|
14
14
|
return specifier.split("/")[0] ?? specifier;
|
|
15
15
|
}
|
|
16
|
-
function findPackageRoot(resolvedPath) {
|
|
17
|
-
let currentPath = path.dirname(resolvedPath);
|
|
18
|
-
while (true) {
|
|
19
|
-
const packageJsonPath = path.join(currentPath, "package.json");
|
|
20
|
-
if (existsSync(packageJsonPath)) {
|
|
21
|
-
return currentPath;
|
|
22
|
-
}
|
|
23
|
-
const parentPath = path.dirname(currentPath);
|
|
24
|
-
if (parentPath === currentPath) {
|
|
25
|
-
throw new Error(`Could not find package root for resolved dependency path: ${resolvedPath}`);
|
|
26
|
-
}
|
|
27
|
-
currentPath = parentPath;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function pathEntryExists(filePath) {
|
|
31
|
-
try {
|
|
32
|
-
lstatSync(filePath);
|
|
33
|
-
return true;
|
|
34
|
-
} catch {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
function linkPointsToPackage(linkPath, packageRoot) {
|
|
39
|
-
try {
|
|
40
|
-
return realpathSync(linkPath) === realpathSync(packageRoot);
|
|
41
|
-
} catch {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
function resolveRuntimePackageRoot(specifier, resolvedPath, parentPath) {
|
|
46
|
-
const packageName = getPackageNameFromSpecifier(specifier);
|
|
47
|
-
return findInstalledPackageDir(packageName, parentPath) ?? findPackageRoot(resolvedPath);
|
|
48
|
-
}
|
|
49
|
-
function isPackageExportedSubpath(specifier, resolvedPath, parentPath) {
|
|
50
|
-
const packageName = getPackageNameFromSpecifier(specifier);
|
|
51
|
-
if (specifier === packageName) {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
const packageRoot = resolveRuntimePackageRoot(specifier, resolvedPath, parentPath);
|
|
55
|
-
const manifest = readPackageManifest(packageRoot);
|
|
56
|
-
if (!manifest?.exports || typeof manifest.exports !== "object" || Array.isArray(manifest.exports)) {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
const subpath = `.${specifier.slice(packageName.length)}`;
|
|
60
|
-
return subpath in manifest.exports;
|
|
61
|
-
}
|
|
62
|
-
function getNodeExternalSpecifier(specifier, resolvedPath, parentPath) {
|
|
63
|
-
const packageName = getPackageNameFromSpecifier(specifier);
|
|
64
|
-
if (specifier === packageName) {
|
|
65
|
-
return specifier;
|
|
66
|
-
}
|
|
67
|
-
if (path.extname(specifier)) {
|
|
68
|
-
return specifier;
|
|
69
|
-
}
|
|
70
|
-
if (isPackageExportedSubpath(specifier, resolvedPath, parentPath)) {
|
|
71
|
-
return specifier;
|
|
72
|
-
}
|
|
73
|
-
for (const extension of [".js", ".mjs", ".cjs", ".json"]) {
|
|
74
|
-
const candidateSpecifier = `${specifier}${extension}`;
|
|
75
|
-
try {
|
|
76
|
-
const candidateResolvedPath = resolveSpecifier(candidateSpecifier, parentPath);
|
|
77
|
-
if (existsSync(candidateResolvedPath)) {
|
|
78
|
-
return candidateSpecifier;
|
|
79
|
-
}
|
|
80
|
-
} catch {
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
for (const candidatePath of [
|
|
84
|
-
resolvedPath,
|
|
85
|
-
...[".js", ".mjs", ".cjs", ".json"].map((extension) => `${specifier}${extension}`)
|
|
86
|
-
]) {
|
|
87
|
-
const candidateResolvedPath = candidatePath === resolvedPath ? resolvedPath : (() => {
|
|
88
|
-
try {
|
|
89
|
-
return resolveSpecifier(candidatePath, parentPath);
|
|
90
|
-
} catch {
|
|
91
|
-
return void 0;
|
|
92
|
-
}
|
|
93
|
-
})();
|
|
94
|
-
if (!candidateResolvedPath) {
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
if (!existsSync(candidateResolvedPath)) {
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
const resolvedExtension = path.extname(candidateResolvedPath);
|
|
101
|
-
if (![".js", ".mjs", ".cjs", ".json"].includes(resolvedExtension)) {
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
const packageRoot = resolveRuntimePackageRoot(specifier, candidateResolvedPath, parentPath);
|
|
105
|
-
const requestedSubpath = specifier.slice(packageName.length + 1);
|
|
106
|
-
const resolvedSubpath = path.relative(packageRoot, candidateResolvedPath);
|
|
107
|
-
if (resolvedSubpath === `${requestedSubpath}${resolvedExtension}`) {
|
|
108
|
-
return `${specifier}${resolvedExtension}`;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return specifier;
|
|
112
|
-
}
|
|
113
|
-
function ensureRuntimePackageLink(nodeModulesDir, specifier, resolvedPath, parentPath) {
|
|
114
|
-
const packageName = getPackageNameFromSpecifier(specifier);
|
|
115
|
-
const packageRoot = resolveRuntimePackageRoot(specifier, resolvedPath, parentPath);
|
|
116
|
-
const linkPath = path.join(nodeModulesDir, packageName);
|
|
117
|
-
mkdirSync(path.dirname(linkPath), { recursive: true });
|
|
118
|
-
if (pathEntryExists(linkPath)) {
|
|
119
|
-
if (linkPointsToPackage(linkPath, packageRoot)) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
removeRuntimePackageLink(linkPath);
|
|
123
|
-
}
|
|
124
|
-
try {
|
|
125
|
-
symlinkSync(packageRoot, linkPath, "dir");
|
|
126
|
-
} catch (error) {
|
|
127
|
-
if (error.code !== "EEXIST") {
|
|
128
|
-
throw error;
|
|
129
|
-
}
|
|
130
|
-
if (linkPointsToPackage(linkPath, packageRoot)) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
removeRuntimePackageLink(linkPath);
|
|
134
|
-
symlinkSync(packageRoot, linkPath, "dir");
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
function removeRuntimePackageLink(linkPath) {
|
|
138
|
-
try {
|
|
139
|
-
const stats = lstatSync(linkPath);
|
|
140
|
-
if (stats.isSymbolicLink()) {
|
|
141
|
-
unlinkSync(linkPath);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
} catch {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
rmSync(linkPath, { recursive: true, force: true });
|
|
148
|
-
}
|
|
149
16
|
function getNodeUnsupportedBuiltinError(specifier, importer) {
|
|
150
17
|
return `Node bootstrap transpilation does not support Bun builtin specifier ${JSON.stringify(specifier)}${importer ? ` imported from ${importer}` : ""}.`;
|
|
151
18
|
}
|
|
@@ -204,6 +71,18 @@ function resolvePackageExportTarget(packageDir, target) {
|
|
|
204
71
|
const record = target;
|
|
205
72
|
return resolvePackageExportTarget(packageDir, record.import) ?? resolvePackageExportTarget(packageDir, record.default) ?? resolvePackageExportTarget(packageDir, record.require);
|
|
206
73
|
}
|
|
74
|
+
function shouldRewriteProjectImportMeta(filePath, projectDir) {
|
|
75
|
+
const normalizedFilePath = path.normalize(filePath);
|
|
76
|
+
const normalizedProjectDir = path.normalize(projectDir);
|
|
77
|
+
return (normalizedFilePath === normalizedProjectDir || normalizedFilePath.startsWith(`${normalizedProjectDir}${path.sep}`)) && !normalizedFilePath.includes(`${path.sep}node_modules${path.sep}`);
|
|
78
|
+
}
|
|
79
|
+
function getLoaderForPath(filePath) {
|
|
80
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
81
|
+
if (extension === ".tsx") return "tsx";
|
|
82
|
+
if (extension === ".ts") return "ts";
|
|
83
|
+
if (extension === ".jsx") return "jsx";
|
|
84
|
+
return "js";
|
|
85
|
+
}
|
|
207
86
|
function resolveInstalledPackageTarget(specifier, parentPath) {
|
|
208
87
|
const packageName = getPackageNameFromSpecifier(specifier);
|
|
209
88
|
const packageDir = findInstalledPackageDir(packageName, parentPath);
|
|
@@ -248,27 +127,6 @@ function findResolutionParent(importer, projectDir) {
|
|
|
248
127
|
currentPath = parentPath;
|
|
249
128
|
}
|
|
250
129
|
}
|
|
251
|
-
function getBootstrapBuildLoaderForPath(filePath) {
|
|
252
|
-
switch (path.extname(filePath).toLowerCase()) {
|
|
253
|
-
case ".ts":
|
|
254
|
-
case ".mts":
|
|
255
|
-
case ".cts":
|
|
256
|
-
return "ts";
|
|
257
|
-
case ".tsx":
|
|
258
|
-
return "tsx";
|
|
259
|
-
case ".jsx":
|
|
260
|
-
return "jsx";
|
|
261
|
-
case ".json":
|
|
262
|
-
return "json";
|
|
263
|
-
default:
|
|
264
|
-
return "js";
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
function shouldRewriteBootstrapSource(filePath, projectDir) {
|
|
268
|
-
const normalizedPath = path.resolve(filePath);
|
|
269
|
-
const normalizedProjectDir = path.resolve(projectDir);
|
|
270
|
-
return normalizedPath.startsWith(`${normalizedProjectDir}${path.sep}`) && !normalizedPath.includes(`${path.sep}node_modules${path.sep}`);
|
|
271
|
-
}
|
|
272
130
|
function resolveNodeBootstrapDependency(args, options) {
|
|
273
131
|
if (args.path.startsWith("./") || args.path.startsWith("../") || args.path.startsWith("@/") || args.path.startsWith("/") || args.path.startsWith("node:")) {
|
|
274
132
|
return void 0;
|
|
@@ -285,57 +143,45 @@ function resolveNodeBootstrapDependency(args, options) {
|
|
|
285
143
|
const resolvedSubpath = resolveSpecifier(args.path, resolveParent);
|
|
286
144
|
return { path: resolvedSubpath };
|
|
287
145
|
}
|
|
288
|
-
let
|
|
146
|
+
let resolvedPath;
|
|
289
147
|
try {
|
|
290
|
-
|
|
148
|
+
resolvedPath = resolveFromCore(args.path);
|
|
291
149
|
} catch {
|
|
292
150
|
try {
|
|
293
|
-
|
|
151
|
+
resolvedPath = resolveSpecifier(args.path, resolveParent);
|
|
294
152
|
} catch {
|
|
295
153
|
const candidatePath = path.join(options.projectDir, "node_modules", packageName);
|
|
296
154
|
const candidatePackageJson = path.join(candidatePath, "package.json");
|
|
297
155
|
if (existsSync(candidatePackageJson)) {
|
|
298
|
-
ensureRuntimePackageLink(
|
|
299
|
-
options.runtimeNodeModulesDir,
|
|
300
|
-
args.path,
|
|
301
|
-
candidatePackageJson,
|
|
302
|
-
resolveParent
|
|
303
|
-
);
|
|
304
156
|
return { path: args.path, external: true };
|
|
305
157
|
}
|
|
306
158
|
}
|
|
307
159
|
}
|
|
308
|
-
if (!
|
|
160
|
+
if (!resolvedPath) {
|
|
309
161
|
return void 0;
|
|
310
162
|
}
|
|
311
|
-
if (
|
|
312
|
-
ensureRuntimePackageLink(options.runtimeNodeModulesDir, args.path, resolvedPath2, resolveParent);
|
|
163
|
+
if (resolvedPath.includes(`${path.sep}node_modules${path.sep}`)) {
|
|
313
164
|
return {
|
|
314
165
|
path: args.path,
|
|
315
166
|
external: true
|
|
316
167
|
};
|
|
317
168
|
}
|
|
318
|
-
return { path:
|
|
169
|
+
return { path: resolvedPath };
|
|
319
170
|
}
|
|
320
|
-
const resolvedPath = resolveSpecifier(args.path, resolveParent);
|
|
321
|
-
ensureRuntimePackageLink(options.runtimeNodeModulesDir, args.path, resolvedPath, resolveParent);
|
|
322
171
|
return {
|
|
323
|
-
path:
|
|
172
|
+
path: args.path,
|
|
324
173
|
external: true
|
|
325
174
|
};
|
|
326
175
|
}
|
|
327
176
|
function createNodeBootstrapPlugin(options) {
|
|
328
|
-
const projectDir = path.resolve(options.projectDir);
|
|
329
177
|
return {
|
|
330
178
|
name: "node-bootstrap-plugin",
|
|
331
179
|
setup(build) {
|
|
332
180
|
build.onResolve({ filter: /^bun:/ }, (args) => {
|
|
333
181
|
throw new Error(getNodeUnsupportedBuiltinError(args.path, args.importer));
|
|
334
182
|
});
|
|
335
|
-
build.onLoad({ filter: /\.[cm]?[jt]sx?$/ },
|
|
336
|
-
|
|
337
|
-
const shouldRewriteImportMeta = shouldRewriteBootstrapSource(absolutePath, projectDir);
|
|
338
|
-
if (!shouldRewriteImportMeta) {
|
|
183
|
+
build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, (args) => {
|
|
184
|
+
if (!shouldRewriteProjectImportMeta(args.path, options.projectDir)) {
|
|
339
185
|
return void 0;
|
|
340
186
|
}
|
|
341
187
|
const originalContents = readFileSync(args.path, "utf8");
|
|
@@ -345,8 +191,7 @@ function createNodeBootstrapPlugin(options) {
|
|
|
345
191
|
}
|
|
346
192
|
return {
|
|
347
193
|
contents,
|
|
348
|
-
loader:
|
|
349
|
-
resolveDir: path.dirname(args.path)
|
|
194
|
+
loader: getLoaderForPath(args.path)
|
|
350
195
|
};
|
|
351
196
|
});
|
|
352
197
|
build.onResolve({ filter: /^[@A-Za-z0-9][^:]*$/ }, (args) => {
|