@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/core",
3
- "version": "0.2.0-alpha.47",
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.47",
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(filePath);
189
- return await this.collectPageBrowserGraphContribution({ file: filePath, pageModule });
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.setPagePackage(createPagePackage(mergedDependencies));
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(routeOptions.file)
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 processedDependencies = contribution.dependencies?.length ? await this.assetProcessingService.processDependencies(
173
- contribution.dependencies,
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 = [];
@@ -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 that receives symlinks to resolved
16
- * package roots so transpiled Node imports share one package graph.
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 anchors third-party imports to the nearest package boundary for
30
- * the importing file, then mirrors the resolved package root into the runtime
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 the app's internal execution
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, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, unlinkSync } from "node:fs";
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 resolvedPath2;
146
+ let resolvedPath;
289
147
  try {
290
- resolvedPath2 = resolveFromCore(args.path);
148
+ resolvedPath = resolveFromCore(args.path);
291
149
  } catch {
292
150
  try {
293
- resolvedPath2 = resolveSpecifier(args.path, resolveParent);
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 (!resolvedPath2) {
160
+ if (!resolvedPath) {
309
161
  return void 0;
310
162
  }
311
- if (resolvedPath2.includes(`${path.sep}node_modules${path.sep}`)) {
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: resolvedPath2 };
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: getNodeExternalSpecifier(args.path, resolvedPath, resolveParent),
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?$/ }, async (args) => {
336
- const absolutePath = path.resolve(args.path);
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: getBootstrapBuildLoaderForPath(args.path),
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) => {