evolit 0.1.0-alpha.2 → 0.1.0-alpha.21

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/src/compiler.js CHANGED
@@ -17,6 +17,15 @@ import { ensureDirectory } from "./fs-utils.js";
17
17
 
18
18
  const MODULE_SPECIFIER_PATTERN =
19
19
  /\b(?:import|export)\s+(?:[^"']*?\s+from\s+)?["']([^"']+)["']|\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
20
+ const RESOLVABLE_IMPORT_EXTENSIONS = [
21
+ ...MODULE_EXTENSIONS,
22
+ ...STATIC_ASSET_EXTENSIONS,
23
+ ];
24
+ const developmentGraphCache = new Map();
25
+ const developmentGraphDependencies = new Map();
26
+ const developmentModuleNamespaceCache = new Map();
27
+ const developmentGraphVersions = new Map();
28
+ const developmentUnmanagedImportWarnings = new Set();
20
29
 
21
30
  function isRelativeSpecifier(specifier) {
22
31
  return specifier.startsWith("./") || specifier.startsWith("../");
@@ -44,10 +53,37 @@ function isStaticAssetPath(filePath) {
44
53
  return STATIC_ASSET_EXTENSIONS.some((extension) => filePath.endsWith(extension));
45
54
  }
46
55
 
56
+ function hasResolvableImportExtension(filePath) {
57
+ return RESOLVABLE_IMPORT_EXTENSIONS.some((extension) => filePath.endsWith(extension));
58
+ }
59
+
47
60
  function isStyleAssetPath(filePath) {
48
61
  return filePath.endsWith(".css");
49
62
  }
50
63
 
64
+ function isPathOutsideProject(projectRoot, filePath) {
65
+ const relativePath = path.relative(projectRoot, filePath);
66
+ return (
67
+ relativePath === ".." ||
68
+ relativePath.startsWith(`..${path.sep}`) ||
69
+ path.isAbsolute(relativePath)
70
+ );
71
+ }
72
+
73
+ function reportDevelopmentUnmanagedImport(projectRoot, sourcePath, resolvedImportPath, onDevelopmentEvent) {
74
+ const warningKey = `${path.resolve(projectRoot)}::${sourcePath}::${resolvedImportPath}`;
75
+ if (developmentUnmanagedImportWarnings.has(warningKey)) {
76
+ return;
77
+ }
78
+
79
+ developmentUnmanagedImportWarnings.add(warningKey);
80
+ onDevelopmentEvent?.({
81
+ type: "unmanaged-import",
82
+ sourcePath,
83
+ resolvedImportPath,
84
+ });
85
+ }
86
+
51
87
  function createStaticAssetStubSource(relativeAssetPath, mode, target = "server", staticAssetPublicUrls = null) {
52
88
  const normalizedAssetPath = relativeAssetPath.split(path.sep).join("/");
53
89
  if (normalizedAssetPath.endsWith(".css")) {
@@ -65,8 +101,8 @@ async function resolveImportPath(importerPath, specifier) {
65
101
  const basePath = path.resolve(path.dirname(importerPath), specifier);
66
102
  const candidates = [basePath];
67
103
 
68
- if (!path.extname(basePath)) {
69
- for (const extension of [...MODULE_EXTENSIONS, ...STATIC_ASSET_EXTENSIONS]) {
104
+ if (!hasResolvableImportExtension(basePath)) {
105
+ for (const extension of RESOLVABLE_IMPORT_EXTENSIONS) {
70
106
  candidates.push(`${basePath}${extension}`);
71
107
  candidates.push(path.join(basePath, `index${extension}`));
72
108
  }
@@ -100,7 +136,7 @@ function getTypedOutputRoot(projectRoot, mode, target = "server") {
100
136
  }
101
137
 
102
138
  function toOutputPath(projectRoot, outputRoot, sourcePath) {
103
- const relativePath = path.relative(projectRoot, sourcePath);
139
+ const relativePath = toOutputRelativePath(projectRoot, sourcePath);
104
140
  const extension = path.extname(relativePath);
105
141
  if (!extension) {
106
142
  return path.join(outputRoot, `${relativePath}.mjs`);
@@ -112,6 +148,22 @@ function toOutputPath(projectRoot, outputRoot, sourcePath) {
112
148
  );
113
149
  }
114
150
 
151
+ function toOutputRelativePath(projectRoot, sourcePath) {
152
+ const relativePath = path.relative(projectRoot, sourcePath);
153
+ const isOutsideProject =
154
+ relativePath === ".." ||
155
+ relativePath.startsWith(`..${path.sep}`) ||
156
+ path.isAbsolute(relativePath);
157
+ if (!isOutsideProject) {
158
+ return relativePath;
159
+ }
160
+
161
+ return path.join(
162
+ "__unmanaged__",
163
+ ...relativePath.split(path.sep).map((segment) => segment === ".." ? "__up__" : segment),
164
+ );
165
+ }
166
+
115
167
  async function rewriteRelativeSpecifiers({
116
168
  projectRoot,
117
169
  outputRoot,
@@ -126,6 +178,7 @@ async function rewriteRelativeSpecifiers({
126
178
  sourceMaps,
127
179
  outputPath,
128
180
  inputSourceMap,
181
+ onDevelopmentEvent,
129
182
  }) {
130
183
  const magicSource = new MagicString(code);
131
184
  let didRewrite = false;
@@ -157,13 +210,17 @@ async function rewriteRelativeSpecifiers({
157
210
  continue;
158
211
  }
159
212
 
213
+ if (mode === "development" && isPathOutsideProject(projectRoot, resolvedImportPath)) {
214
+ reportDevelopmentUnmanagedImport(projectRoot, sourcePath, resolvedImportPath, onDevelopmentEvent);
215
+ }
216
+
160
217
  const importerOutputPath = toOutputPath(projectRoot, outputRoot, sourcePath);
161
218
  let compiledImportPath = null;
162
219
 
163
220
  if (shouldCompileModule(resolvedImportPath)) {
164
221
  compiledImportPath = await compileModule(resolvedImportPath);
165
222
  } else if (isStaticAssetPath(resolvedImportPath)) {
166
- const relativeAssetPath = path.relative(projectRoot, resolvedImportPath);
223
+ const relativeAssetPath = toOutputRelativePath(projectRoot, resolvedImportPath);
167
224
  const assetOutputPath = path.join(outputRoot, relativeAssetPath);
168
225
  const stubOutputPath = `${assetOutputPath}.mjs`;
169
226
 
@@ -264,7 +321,54 @@ async function rewriteRelativeSpecifiers({
264
321
  };
265
322
  }
266
323
 
267
- export async function compileModuleGraph(entryPath, options = {}) {
324
+ function getDevelopmentGraphCacheKey(entryPath, options) {
325
+ return [
326
+ options.projectRoot,
327
+ entryPath,
328
+ options.target ?? "server",
329
+ options.ssr === true ? "ssr" : "client",
330
+ options.sourceMaps === false ? "without-maps" : "with-maps",
331
+ ].join("::");
332
+ }
333
+
334
+ export function invalidateDevelopmentCompilationCache(projectRoot, changedPaths = null) {
335
+ const prefix = `${path.resolve(projectRoot)}::`;
336
+ const normalizedChangedPaths = Array.isArray(changedPaths) && changedPaths.length > 0
337
+ ? new Set(changedPaths.map((changedPath) => path.resolve(changedPath)))
338
+ : null;
339
+ let invalidated = false;
340
+
341
+ if (normalizedChangedPaths == null) {
342
+ for (const warningKey of developmentUnmanagedImportWarnings) {
343
+ if (warningKey.startsWith(prefix)) {
344
+ developmentUnmanagedImportWarnings.delete(warningKey);
345
+ }
346
+ }
347
+ }
348
+
349
+ for (const key of developmentGraphCache.keys()) {
350
+ if (!key.startsWith(prefix)) {
351
+ continue;
352
+ }
353
+
354
+ const dependencies = developmentGraphDependencies.get(key);
355
+ const shouldInvalidate =
356
+ normalizedChangedPaths == null ||
357
+ !dependencies ||
358
+ [...dependencies].some((dependency) => normalizedChangedPaths.has(dependency));
359
+ if (shouldInvalidate) {
360
+ developmentGraphCache.delete(key);
361
+ developmentGraphDependencies.delete(key);
362
+ developmentModuleNamespaceCache.delete(key);
363
+ developmentGraphVersions.set(key, (developmentGraphVersions.get(key) ?? 0) + 1);
364
+ invalidated = true;
365
+ }
366
+ }
367
+
368
+ return invalidated;
369
+ }
370
+
371
+ async function compileModuleGraphUncached(entryPath, options = {}) {
268
372
  const {
269
373
  projectRoot,
270
374
  mode = "development",
@@ -272,6 +376,7 @@ export async function compileModuleGraph(entryPath, options = {}) {
272
376
  ssr = false,
273
377
  target = "server",
274
378
  staticAssetPublicUrls = null,
379
+ onDevelopmentEvent,
275
380
  } = options;
276
381
 
277
382
  const outputRoot = getTypedOutputRoot(projectRoot, mode, target);
@@ -310,6 +415,7 @@ export async function compileModuleGraph(entryPath, options = {}) {
310
415
  sourceMaps,
311
416
  outputPath,
312
417
  inputSourceMap: transformed.map ?? null,
418
+ onDevelopmentEvent,
313
419
  });
314
420
 
315
421
  await fs.writeFile(outputPath, rewritten.code, "utf8");
@@ -338,16 +444,77 @@ export async function compileModuleGraph(entryPath, options = {}) {
338
444
  return {
339
445
  entrypoint: await compileModule(entryPath),
340
446
  outputRoot,
447
+ sourceFiles: [...visited.keys()],
448
+ };
449
+ }
450
+
451
+ export async function compileModuleGraph(entryPath, options = {}) {
452
+ const mode = options.mode ?? "development";
453
+ if (mode !== "development") {
454
+ return compileModuleGraphUncached(entryPath, options);
455
+ }
456
+
457
+ const normalizedOptions = {
458
+ ...options,
459
+ projectRoot: path.resolve(options.projectRoot ?? process.cwd()),
341
460
  };
461
+ const cacheKey = getDevelopmentGraphCacheKey(
462
+ path.resolve(entryPath),
463
+ normalizedOptions,
464
+ );
465
+ let pendingCompile = developmentGraphCache.get(cacheKey);
466
+ if (!pendingCompile) {
467
+ pendingCompile = compileModuleGraphUncached(entryPath, normalizedOptions);
468
+ developmentGraphCache.set(cacheKey, pendingCompile);
469
+ pendingCompile.then((result) => {
470
+ if (developmentGraphCache.get(cacheKey) === pendingCompile) {
471
+ developmentGraphDependencies.set(cacheKey, new Set(result.sourceFiles));
472
+ }
473
+ }, () => {});
474
+ }
475
+
476
+ try {
477
+ return await pendingCompile;
478
+ } catch (error) {
479
+ if (developmentGraphCache.get(cacheKey) === pendingCompile) {
480
+ developmentGraphCache.delete(cacheKey);
481
+ developmentGraphDependencies.delete(cacheKey);
482
+ }
483
+ throw error;
484
+ }
342
485
  }
343
486
 
344
487
  export async function importCompiledModule(entryPath, options = {}) {
345
- const { entrypoint } = await compileModuleGraph(entryPath, options);
488
+ const mode = options.mode ?? "development";
489
+ const normalizedOptions = mode === "development"
490
+ ? { ...options, projectRoot: path.resolve(options.projectRoot ?? process.cwd()) }
491
+ : options;
492
+ const cacheKey = mode === "development"
493
+ ? getDevelopmentGraphCacheKey(path.resolve(entryPath), normalizedOptions)
494
+ : null;
495
+ const cachedModule = cacheKey ? developmentModuleNamespaceCache.get(cacheKey) : null;
496
+ if (cachedModule) {
497
+ return cachedModule;
498
+ }
499
+
500
+ const { entrypoint } = await compileModuleGraph(entryPath, normalizedOptions);
346
501
  const moduleUrl = new URL(pathToFileURL(entrypoint).href);
347
502
 
348
- if (options.mode === "development") {
349
- moduleUrl.searchParams.set("t", String(Date.now()));
503
+ if (mode === "development") {
504
+ moduleUrl.searchParams.set("t", String(developmentGraphVersions.get(cacheKey) ?? 0));
350
505
  }
351
506
 
352
- return import(moduleUrl.href);
507
+ const pendingModule = import(moduleUrl.href);
508
+ if (cacheKey) {
509
+ developmentModuleNamespaceCache.set(cacheKey, pendingModule);
510
+ }
511
+
512
+ try {
513
+ return await pendingModule;
514
+ } catch (error) {
515
+ if (developmentModuleNamespaceCache.get(cacheKey) === pendingModule) {
516
+ developmentModuleNamespaceCache.delete(cacheKey);
517
+ }
518
+ throw error;
519
+ }
353
520
  }