evolit 0.2.1 → 0.4.0

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
@@ -20,6 +20,7 @@ import { ensureDirectory } from "./fs-utils.js";
20
20
  const MODULE_SPECIFIER_PATTERN =
21
21
  /\b(?:import|export)\s+(?:[^"']*?\s+from\s+)?["']([^"']+)["']|\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
22
22
  const CSS_DEPENDENCY_PATTERN = /(?:@import\s+(?:url\(\s*)?["']([^"']+)["']\s*\)?|url\(\s*["']?([^"')]+)["']?\s*\))/g;
23
+ const DYNAMIC_IMPORT_WILDCARD = "\u0000EVOLIT_DYNAMIC_IMPORT_WILDCARD\u0000";
23
24
  const RESOLVABLE_IMPORT_EXTENSIONS = [
24
25
  ...MODULE_EXTENSIONS,
25
26
  ...STATIC_ASSET_EXTENSIONS,
@@ -181,14 +182,28 @@ async function transformModuleSource(source, {
181
182
  sourcePath,
182
183
  sourceMaps,
183
184
  ssr = false,
185
+ target = ssr ? "server" : "client",
186
+ litsxPipeline = null,
184
187
  }) {
185
188
  if (isLitsxAuthoredModule(sourcePath, source)) {
186
- const transformed = await transformLitsx(source, {
187
- filename: sourcePath,
188
- sourceMaps,
189
- ssr,
190
- });
191
- return restoreSideEffectImports(source, sourcePath, transformed, sourceMaps);
189
+ const compilerOptions = litsxPipeline
190
+ ? litsxPipeline.getCompilerOptions({ filename: sourcePath, sourceMaps, ssr })
191
+ : { filename: sourcePath, sourceMaps, ssr };
192
+ const transformed = restoreSideEffectImports(
193
+ source,
194
+ sourcePath,
195
+ await transformLitsx(source, compilerOptions),
196
+ compilerOptions.sourceMaps === true,
197
+ );
198
+ return litsxPipeline
199
+ ? litsxPipeline.processModule(transformed, {
200
+ source,
201
+ sourcePath,
202
+ sourceMaps: compilerOptions.sourceMaps === true,
203
+ ssr: ssr === true,
204
+ target,
205
+ })
206
+ : transformed;
192
207
  }
193
208
 
194
209
  if (sourcePath.endsWith(".ts")) {
@@ -388,17 +403,30 @@ function getCompiledServerComponentExports(code, sourcePath) {
388
403
  return exports;
389
404
  }
390
405
 
391
- function collectImportedNamesBySpecifier(code, sourcePath) {
392
- const sourceFile = ts.createSourceFile(sourcePath, code, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
406
+ function analyzeModuleReferences(code, sourcePath, scriptKind = ts.ScriptKind.JS) {
407
+ const sourceFile = ts.createSourceFile(sourcePath, code, ts.ScriptTarget.Latest, true, scriptKind);
393
408
  const bySpecifier = new Map();
409
+ const staticReferences = [];
410
+ const dynamicReferences = [];
394
411
  const add = (specifier, name) => {
395
412
  const names = bySpecifier.get(specifier) ?? new Set();
396
413
  names.add(name);
397
414
  bySpecifier.set(specifier, names);
398
415
  };
416
+ const addStaticReference = (node, importedName = null) => {
417
+ const specifier = node.text;
418
+ staticReferences.push({
419
+ specifier,
420
+ start: node.getStart(sourceFile) + 1,
421
+ end: node.end - 1,
422
+ });
423
+ if (importedName) add(specifier, importedName);
424
+ };
425
+
399
426
  for (const statement of sourceFile.statements) {
400
- if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
427
+ if (ts.isImportDeclaration(statement) && ts.isStringLiteralLike(statement.moduleSpecifier)) {
401
428
  const specifier = statement.moduleSpecifier.text;
429
+ addStaticReference(statement.moduleSpecifier);
402
430
  if (statement.importClause?.name) add(specifier, "default");
403
431
  if (statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)) {
404
432
  for (const element of statement.importClause.namedBindings.elements) {
@@ -408,9 +436,10 @@ function collectImportedNamesBySpecifier(code, sourcePath) {
408
436
  } else if (
409
437
  ts.isExportDeclaration(statement)
410
438
  && statement.moduleSpecifier
411
- && ts.isStringLiteral(statement.moduleSpecifier)
439
+ && ts.isStringLiteralLike(statement.moduleSpecifier)
412
440
  ) {
413
441
  const specifier = statement.moduleSpecifier.text;
442
+ addStaticReference(statement.moduleSpecifier);
414
443
  if (!statement.exportClause) add(specifier, "*");
415
444
  else if (ts.isNamedExports(statement.exportClause)) {
416
445
  for (const element of statement.exportClause.elements) add(specifier, element.propertyName?.text ?? element.name.text);
@@ -421,13 +450,119 @@ function collectImportedNamesBySpecifier(code, sourcePath) {
421
450
  if (
422
451
  ts.isCallExpression(node)
423
452
  && node.expression.kind === ts.SyntaxKind.ImportKeyword
424
- && node.arguments.length === 1
425
- && ts.isStringLiteral(node.arguments[0])
426
- ) add(node.arguments[0].text, "*");
453
+ && node.arguments.length >= 1
454
+ ) {
455
+ const argument = node.arguments[0];
456
+ if (ts.isStringLiteralLike(argument)) {
457
+ addStaticReference(argument, "*");
458
+ } else if (ts.isTemplateExpression(argument)) {
459
+ const staticParts = [
460
+ argument.head.text,
461
+ ...argument.templateSpans.map((span) => span.literal.text),
462
+ ];
463
+ dynamicReferences.push({
464
+ pattern: staticParts.join(DYNAMIC_IMPORT_WILDCARD),
465
+ displayPattern: staticParts.join("*"),
466
+ expressionText: code.slice(argument.getStart(sourceFile), argument.end),
467
+ optionsText: node.arguments.length > 1
468
+ ? code.slice(node.arguments[1].getStart(sourceFile), node.arguments.at(-1).end)
469
+ : null,
470
+ start: node.getStart(sourceFile),
471
+ end: node.end,
472
+ });
473
+ }
474
+ }
427
475
  ts.forEachChild(node, visit);
428
476
  };
429
477
  visit(sourceFile);
430
- return bySpecifier;
478
+
479
+ return {
480
+ importedNamesBySpecifier: bySpecifier,
481
+ staticReferences,
482
+ dynamicReferences,
483
+ };
484
+ }
485
+
486
+ function escapeRegExp(value) {
487
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
488
+ }
489
+
490
+ function createDynamicImportSegmentPattern(segment) {
491
+ return new RegExp(
492
+ `^${segment.split(DYNAMIC_IMPORT_WILDCARD).map(escapeRegExp).join("[^/]+")}$`,
493
+ );
494
+ }
495
+
496
+ async function discoverDynamicImportCandidates({
497
+ projectRoot,
498
+ sourcePath,
499
+ pattern,
500
+ displayPattern,
501
+ managedSourceRoots,
502
+ }) {
503
+ const normalizedPattern = pattern.replaceAll("\\", "/");
504
+ if (!isRelativeSpecifier(normalizedPattern)) {
505
+ throw new Error(
506
+ `Dynamic import pattern ${JSON.stringify(displayPattern)} in ${sourcePath} must be relative.`,
507
+ );
508
+ }
509
+ if (!MODULE_EXTENSIONS.some((extension) => normalizedPattern.endsWith(extension))) {
510
+ throw new Error(
511
+ `Dynamic import pattern ${JSON.stringify(displayPattern)} in ${sourcePath} must end in a supported module extension.`,
512
+ );
513
+ }
514
+
515
+ const segments = normalizedPattern.split("/");
516
+ const firstDynamicSegment = segments.findIndex((segment) => segment.includes(DYNAMIC_IMPORT_WILDCARD));
517
+ if (firstDynamicSegment < 0) return { candidates: [], watchedDirectories: [] };
518
+
519
+ const scanRoot = path.resolve(path.dirname(sourcePath), ...segments.slice(0, firstDynamicSegment));
520
+ if (isPathOutsideManagedRoots(projectRoot, managedSourceRoots, scanRoot)) {
521
+ throw new Error(
522
+ `Dynamic import pattern ${JSON.stringify(displayPattern)} in ${sourcePath} resolves outside the managed source roots.`,
523
+ );
524
+ }
525
+
526
+ const segmentPatterns = segments
527
+ .slice(firstDynamicSegment)
528
+ .map(createDynamicImportSegmentPattern);
529
+ const candidates = [];
530
+ const watchedDirectories = new Set();
531
+
532
+ async function visitDirectory(directory, segmentIndex) {
533
+ watchedDirectories.add(directory);
534
+ let entries;
535
+ try {
536
+ entries = await fs.readdir(directory, { withFileTypes: true });
537
+ } catch (error) {
538
+ if (error?.code === "ENOENT" || error?.code === "ENOTDIR") return;
539
+ throw error;
540
+ }
541
+
542
+ const matcher = segmentPatterns[segmentIndex];
543
+ const isFinalSegment = segmentIndex === segmentPatterns.length - 1;
544
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
545
+ if (!matcher.test(entry.name)) continue;
546
+ const candidatePath = path.join(directory, entry.name);
547
+ if (isFinalSegment) {
548
+ if (entry.isFile()) candidates.push(candidatePath);
549
+ } else if (entry.isDirectory()) {
550
+ await visitDirectory(candidatePath, segmentIndex + 1);
551
+ }
552
+ }
553
+ }
554
+
555
+ await visitDirectory(scanRoot, 0);
556
+ if (candidates.length === 0) {
557
+ throw new Error(
558
+ `Dynamic import pattern ${JSON.stringify(displayPattern)} in ${sourcePath} did not match any modules.`,
559
+ );
560
+ }
561
+
562
+ return {
563
+ candidates,
564
+ watchedDirectories: [...watchedDirectories],
565
+ };
431
566
  }
432
567
 
433
568
  function createClientServerComponentImportError(entryPath, sourcePath, parents) {
@@ -841,23 +976,32 @@ async function rewriteRelativeSpecifiers({
841
976
  managedSourceRoots,
842
977
  serverExportsByModule,
843
978
  packageImports,
979
+ dynamicImportDirectories,
980
+ litsxPipeline,
844
981
  }) {
845
982
  const magicSource = new MagicString(code);
846
983
  let didRewrite = false;
984
+ const moduleReferences = analyzeModuleReferences(code, sourcePath);
847
985
  const importedNamesBySpecifier = target === "client"
848
- ? collectImportedNamesBySpecifier(code, sourcePath)
986
+ ? moduleReferences.importedNamesBySpecifier
849
987
  : new Map();
850
988
 
851
- for (const match of code.matchAll(MODULE_SPECIFIER_PATTERN)) {
852
- const specifier = match[1] ?? match[2] ?? null;
853
- if (!specifier) {
854
- continue;
855
- }
989
+ for (const reference of moduleReferences.staticReferences) {
990
+ const { specifier } = reference;
856
991
 
857
- const aliasedImportPath = isBareSpecifier(specifier)
992
+ const virtualImportPath = litsxPipeline
993
+ ? await litsxPipeline.resolveModule(specifier, {
994
+ importer: sourcePath,
995
+ target,
996
+ ssr: target === "server",
997
+ sourceMaps,
998
+ })
999
+ : null;
1000
+
1001
+ const aliasedImportPath = !virtualImportPath && isBareSpecifier(specifier)
858
1002
  ? await resolveProjectMappedImport(projectRoot, sourcePath, specifier)
859
1003
  : null;
860
- const packageImportPath = isBareSpecifier(specifier) && !aliasedImportPath
1004
+ const packageImportPath = !virtualImportPath && isBareSpecifier(specifier) && !aliasedImportPath
861
1005
  ? await resolveProjectPackageImport(projectRoot, sourcePath, specifier)
862
1006
  : null;
863
1007
  const packageAssetPath = packageImportPath && isStaticAssetPath(packageImportPath)
@@ -867,6 +1011,7 @@ async function rewriteRelativeSpecifiers({
867
1011
  if (
868
1012
  target === "server"
869
1013
  && isBareSpecifier(specifier)
1014
+ && !virtualImportPath
870
1015
  && !aliasedImportPath
871
1016
  && !packageAssetPath
872
1017
  ) {
@@ -876,6 +1021,7 @@ async function rewriteRelativeSpecifiers({
876
1021
  if (
877
1022
  target === "client"
878
1023
  && isBareSpecifier(specifier)
1024
+ && !virtualImportPath
879
1025
  && !aliasedImportPath
880
1026
  && !packageAssetPath
881
1027
  ) {
@@ -890,11 +1036,12 @@ async function rewriteRelativeSpecifiers({
890
1036
  continue;
891
1037
  }
892
1038
 
893
- if (!isRelativeSpecifier(specifier) && !aliasedImportPath && !packageAssetPath) {
1039
+ if (!virtualImportPath && !isRelativeSpecifier(specifier) && !aliasedImportPath && !packageAssetPath) {
894
1040
  continue;
895
1041
  }
896
1042
 
897
- const resolvedImportPath = packageAssetPath
1043
+ const resolvedImportPath = virtualImportPath
1044
+ ?? packageAssetPath
898
1045
  ?? aliasedImportPath
899
1046
  ?? await resolveImportPath(sourcePath, specifier);
900
1047
  if (!resolvedImportPath) {
@@ -913,6 +1060,9 @@ async function rewriteRelativeSpecifiers({
913
1060
 
914
1061
  if (shouldCompileModule(resolvedImportPath)) {
915
1062
  compiledImportPath = await compileModule(resolvedImportPath);
1063
+ if (virtualImportPath) {
1064
+ litsxPipeline?.registerResolvedModule(specifier, resolvedImportPath, compiledImportPath);
1065
+ }
916
1066
  const serverExports = serverExportsByModule?.get(resolvedImportPath) ?? new Set();
917
1067
  const importedNames = importedNamesBySpecifier.get(specifier) ?? new Set();
918
1068
  if (
@@ -996,18 +1146,99 @@ async function rewriteRelativeSpecifiers({
996
1146
  ) {
997
1147
  normalizedReplacement = `${normalizedReplacement}?${serverImportQuery}`;
998
1148
  }
999
- const quotedSpecifierIndex = match.index + match[0].indexOf(specifier);
1000
1149
  const replacementValue = normalizedReplacement.split(path.sep).join("/");
1001
1150
  if (replacementValue !== specifier) {
1002
1151
  didRewrite = true;
1003
1152
  magicSource.update(
1004
- quotedSpecifierIndex,
1005
- quotedSpecifierIndex + specifier.length,
1153
+ reference.start,
1154
+ reference.end,
1006
1155
  replacementValue,
1007
1156
  );
1008
1157
  }
1009
1158
  }
1010
1159
 
1160
+ const dynamicImportHelpers = [];
1161
+ for (const [index, reference] of moduleReferences.dynamicReferences.entries()) {
1162
+ const discovery = await discoverDynamicImportCandidates({
1163
+ projectRoot,
1164
+ sourcePath,
1165
+ pattern: reference.pattern,
1166
+ displayPattern: reference.displayPattern,
1167
+ managedSourceRoots,
1168
+ });
1169
+ discovery.watchedDirectories.forEach((directory) => dynamicImportDirectories.add(directory));
1170
+
1171
+ const cases = [];
1172
+ for (const resolvedImportPath of discovery.candidates) {
1173
+ const compiledImportPath = await compileModule(resolvedImportPath);
1174
+ const serverExports = serverExportsByModule?.get(resolvedImportPath) ?? new Set();
1175
+ if (target === "client" && serverExports.size > 0) {
1176
+ throw new Error(
1177
+ `Client module ${sourcePath} dynamically imports Server Component export(s) from ${resolvedImportPath}: `
1178
+ + `${[...serverExports].join(", ")}. Split the server exports into a separate module.`,
1179
+ );
1180
+ }
1181
+
1182
+ const importerOutputPath = toOutputPath(projectRoot, outputRoot, sourcePath);
1183
+ let replacementPath = path.relative(path.dirname(importerOutputPath), compiledImportPath);
1184
+ if (!replacementPath.startsWith(".")) replacementPath = `./${replacementPath}`;
1185
+ replacementPath = replacementPath.split(path.sep).join("/");
1186
+ if (
1187
+ target === "server"
1188
+ && mode === "development"
1189
+ && typeof serverImportQuery === "string"
1190
+ && serverImportQuery.length > 0
1191
+ ) {
1192
+ replacementPath = `${replacementPath}?${serverImportQuery}`;
1193
+ }
1194
+
1195
+ let sourceSpecifier = path.relative(path.dirname(sourcePath), resolvedImportPath)
1196
+ .split(path.sep)
1197
+ .join("/");
1198
+ if (!sourceSpecifier.startsWith(".")) sourceSpecifier = `./${sourceSpecifier}`;
1199
+ cases.push(
1200
+ ` case ${JSON.stringify(sourceSpecifier)}: return import(${JSON.stringify(replacementPath)}${reference.optionsText ? ", options" : ""});`,
1201
+ );
1202
+
1203
+ if (target === "client") {
1204
+ const sourceMetadata = moduleMetadata.get(sourcePath) ?? {
1205
+ moduleImports: new Set(),
1206
+ vendorImports: new Set(),
1207
+ styleImports: new Set(),
1208
+ assetImports: new Set(),
1209
+ };
1210
+ sourceMetadata.moduleImports.add(
1211
+ path.relative(outputRoot, compiledImportPath).split(path.sep).join("/"),
1212
+ );
1213
+ moduleMetadata.set(sourcePath, sourceMetadata);
1214
+ }
1215
+ }
1216
+
1217
+ let helperName = `__evolit_dynamic_import_${index}`;
1218
+ while (code.includes(helperName)) helperName = `_${helperName}`;
1219
+ const sourceModuleId = path.relative(projectRoot, sourcePath).split(path.sep).join("/");
1220
+ const unknownImportPrefix = `Unknown dynamic import in ${sourceModuleId}: `;
1221
+ dynamicImportHelpers.push([
1222
+ `function ${helperName}(specifier${reference.optionsText ? ", options" : ""}) {`,
1223
+ " switch (specifier) {",
1224
+ ...cases,
1225
+ " default:",
1226
+ ` return Promise.reject(new Error(${JSON.stringify(unknownImportPrefix)} + specifier));`,
1227
+ " }",
1228
+ "}",
1229
+ ].join("\n"));
1230
+ magicSource.update(
1231
+ reference.start,
1232
+ reference.end,
1233
+ `${helperName}(${reference.expressionText}${reference.optionsText ? `, ${reference.optionsText}` : ""})`,
1234
+ );
1235
+ didRewrite = true;
1236
+ }
1237
+
1238
+ if (dynamicImportHelpers.length > 0) {
1239
+ magicSource.append(`\n\n${dynamicImportHelpers.join("\n\n")}\n`);
1240
+ }
1241
+
1011
1242
  const rewrittenCode = magicSource.toString();
1012
1243
  if (!sourceMaps || !didRewrite) {
1013
1244
  return {
@@ -1044,6 +1275,7 @@ function getDevelopmentGraphCacheKey(entryPath, options) {
1044
1275
  options.target ?? "server",
1045
1276
  options.ssr === true ? "ssr" : "client",
1046
1277
  options.sourceMaps === false ? "without-maps" : "with-maps",
1278
+ options.litsxPipeline?.identity?.id ?? "default-litsx",
1047
1279
  ].join("::");
1048
1280
  }
1049
1281
 
@@ -1131,7 +1363,10 @@ export function invalidateDevelopmentCompilationCache(projectRoot, changedPaths
1131
1363
  const shouldInvalidate =
1132
1364
  normalizedChangedPaths == null ||
1133
1365
  !dependencies ||
1134
- [...dependencies].some((dependency) => normalizedChangedPaths.has(dependency));
1366
+ [...dependencies.files].some((dependency) => normalizedChangedPaths.has(dependency)) ||
1367
+ [...dependencies.directories].some((directory) =>
1368
+ [...normalizedChangedPaths].some((changedPath) => !isPathOutsideProject(directory, changedPath))
1369
+ );
1135
1370
  if (shouldInvalidate) {
1136
1371
  developmentGraphCache.delete(key);
1137
1372
  developmentGraphDependencies.delete(key);
@@ -1154,6 +1389,7 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
1154
1389
  staticAssetPublicUrls = null,
1155
1390
  onDevelopmentEvent,
1156
1391
  managedSourceRoots,
1392
+ litsxPipeline,
1157
1393
  } = options;
1158
1394
 
1159
1395
  const outputRoot = getTypedOutputRoot(projectRoot, mode, target);
@@ -1163,6 +1399,7 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
1163
1399
  const moduleMetadata = new Map();
1164
1400
  const serverExportsByModule = new Map();
1165
1401
  const packageImports = new Set();
1402
+ const dynamicImportDirectories = new Set();
1166
1403
  const serverImportQuery = target === "server" && mode === "development"
1167
1404
  ? `t=${Date.now()}`
1168
1405
  : null;
@@ -1189,6 +1426,8 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
1189
1426
  sourcePath,
1190
1427
  sourceMaps,
1191
1428
  ssr,
1429
+ target,
1430
+ litsxPipeline,
1192
1431
  });
1193
1432
  const isServerComponentModule = isCompiledServerComponentModule(transformed.code);
1194
1433
  const isMixedComponentModule = isServerComponentModule
@@ -1233,6 +1472,8 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
1233
1472
  managedSourceRoots,
1234
1473
  serverExportsByModule,
1235
1474
  packageImports,
1475
+ dynamicImportDirectories,
1476
+ litsxPipeline,
1236
1477
  });
1237
1478
 
1238
1479
  await fs.writeFile(outputPath, rewritten.code, "utf8");
@@ -1261,7 +1502,13 @@ async function compileModuleGraphUncached(entryPath, options = {}) {
1261
1502
  return {
1262
1503
  entrypoint: await compileModule(entryPath),
1263
1504
  outputRoot,
1264
- sourceFiles: [...new Set([...visited.keys(), ...staticAssetFiles])],
1505
+ sourceFiles: [...new Set([
1506
+ ...visited.keys(),
1507
+ ...staticAssetFiles,
1508
+ ...(litsxPipeline?.dependencies ?? []),
1509
+ ])],
1510
+ integrationDependencies: litsxPipeline?.dependencies ?? [],
1511
+ watchedDirectories: [...dynamicImportDirectories],
1265
1512
  packageImports: [...packageImports].sort(),
1266
1513
  };
1267
1514
  }
@@ -1286,7 +1533,10 @@ export async function compileModuleGraph(entryPath, options = {}) {
1286
1533
  developmentGraphCache.set(cacheKey, pendingCompile);
1287
1534
  pendingCompile.then((result) => {
1288
1535
  if (developmentGraphCache.get(cacheKey) === pendingCompile) {
1289
- developmentGraphDependencies.set(cacheKey, new Set(result.sourceFiles));
1536
+ developmentGraphDependencies.set(cacheKey, {
1537
+ files: new Set(result.sourceFiles),
1538
+ directories: new Set(result.watchedDirectories ?? []),
1539
+ });
1290
1540
  }
1291
1541
  }, () => {});
1292
1542
  }
@@ -1415,6 +1665,8 @@ export async function collectClientGraphInventory(entryPaths, options = {}) {
1415
1665
  projectRoot,
1416
1666
  sourcePath,
1417
1667
  sourceMaps: false,
1668
+ target: "client",
1669
+ litsxPipeline: options.litsxPipeline,
1418
1670
  });
1419
1671
  const isServer = isCompiledServerComponentModule(transformed.code);
1420
1672
  const isComponent = isCompiledClientBoundaryModule(transformed.code);
@@ -1425,15 +1677,15 @@ export async function collectClientGraphInventory(entryPaths, options = {}) {
1425
1677
  if (isComponent && isServer) boundaries.add(sourcePath);
1426
1678
  if (!traverseNonBoundary) return;
1427
1679
  const componentImportSpecifiers = collectComponentImportSpecifiers(source, sourcePath);
1428
- for (const match of source.matchAll(MODULE_SPECIFIER_PATTERN)) {
1429
- const specifier = match[1] ?? match[2];
1430
- if (!specifier) continue;
1680
+ const moduleReferences = analyzeModuleReferences(source, sourcePath, ts.ScriptKind.TSX);
1681
+
1682
+ async function visitResolvedSpecifier(specifier) {
1431
1683
  const aliasResolved = isBareSpecifier(specifier)
1432
1684
  ? await resolveProjectMappedImport(projectRoot, sourcePath, specifier)
1433
1685
  : null;
1434
1686
  const resolved = aliasResolved
1435
1687
  ?? await resolveProjectModuleSpecifier(projectRoot, sourcePath, specifier);
1436
- if (!resolved) continue;
1688
+ if (!resolved) return;
1437
1689
  if (shouldCompileModule(resolved)) {
1438
1690
  if (isBareSpecifier(specifier) && aliasResolved == null) {
1439
1691
  if (componentImportSpecifiers.has(specifier)) {
@@ -1450,6 +1702,21 @@ export async function collectClientGraphInventory(entryPaths, options = {}) {
1450
1702
  assets.add(resolved);
1451
1703
  }
1452
1704
  }
1705
+
1706
+ for (const reference of moduleReferences.staticReferences) {
1707
+ await visitResolvedSpecifier(reference.specifier);
1708
+ }
1709
+ for (const reference of moduleReferences.dynamicReferences) {
1710
+ const discovery = await discoverDynamicImportCandidates({
1711
+ projectRoot,
1712
+ sourcePath,
1713
+ pattern: reference.pattern,
1714
+ displayPattern: reference.displayPattern,
1715
+ });
1716
+ for (const candidate of discovery.candidates) {
1717
+ await visit(candidate, true);
1718
+ }
1719
+ }
1453
1720
  }
1454
1721
 
1455
1722
  for (const entryPath of entryPaths) await visit(entryPath);