evolit 0.1.2 → 0.1.4

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 CHANGED
@@ -46,6 +46,22 @@ 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; CSS and other static assets do not enter vendor chunks.
64
+
49
65
  ## Commands
50
66
 
51
67
  ```sh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolit",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "A convention-driven application framework for LitSX and web components.",
5
5
  "type": "module",
6
6
  "packageManager": "yarn@4.10.3",
@@ -42,7 +42,7 @@
42
42
  "start": "node ./src/cli.js start",
43
43
  "test": "node --test test/*.test.js",
44
44
  "test:browser": "playwright test",
45
- "typecheck": "litsx-tsc -p jsconfig.json --noEmit",
45
+ "typecheck": "tsc -p jsconfig.json --noEmit",
46
46
  "release:check": "yarn test && yarn typecheck && yarn pack --dry-run"
47
47
  },
48
48
  "keywords": [
@@ -59,10 +59,9 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@jridgewell/remapping": "^2.3.5",
62
- "@litsx/compiler": "1.0.0-next.8",
63
- "@litsx/core": "1.0.0-next.5",
64
- "@litsx/ssr": "1.0.0-next.3",
65
- "@litsx/typescript": "^0.9.0",
62
+ "@litsx/compiler": "1.0.0-next.12",
63
+ "@litsx/core": "1.0.0-next.8",
64
+ "@litsx/ssr": "1.0.0-next.5",
66
65
  "@rollup/plugin-commonjs": "^29.0.0",
67
66
  "@rollup/plugin-node-resolve": "^16.0.3",
68
67
  "lit": "^3.3.3",
@@ -144,11 +144,36 @@ export async function resolvePackageRoot(packageName, options = {}) {
144
144
  }
145
145
 
146
146
  const pendingResolution = (async () => {
147
- let packageEntryPath;
148
- try {
149
- packageEntryPath = createRequire(path.join(resolveFrom, "package.json")).resolve(packageName);
150
- } catch {
151
- packageEntryPath = requireFromHere.resolve(packageName);
147
+ const resolvers = [
148
+ createRequire(path.join(resolveFrom, "package.json")),
149
+ requireFromHere,
150
+ ];
151
+
152
+ for (const resolver of resolvers) {
153
+ try {
154
+ const packageJsonPath = resolver.resolve(`${packageName}/package.json`);
155
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
156
+ if (packageJson?.name === packageName) {
157
+ return { packageRoot: path.dirname(packageJsonPath), packageJson };
158
+ }
159
+ } catch {
160
+ // Some packages intentionally do not export package.json. Resolve the
161
+ // entrypoint below and walk back to its owning manifest instead.
162
+ }
163
+ }
164
+
165
+ let packageEntryPath = null;
166
+ let resolutionError = null;
167
+ for (const resolver of resolvers) {
168
+ try {
169
+ packageEntryPath = resolver.resolve(packageName);
170
+ break;
171
+ } catch (error) {
172
+ resolutionError = error;
173
+ }
174
+ }
175
+ if (!packageEntryPath) {
176
+ throw resolutionError ?? new Error(`Unable to resolve package entry for ${packageName}`);
152
177
  }
153
178
  let currentPath = path.dirname(packageEntryPath);
154
179
 
@@ -190,6 +215,14 @@ function pickBrowserExportTarget(target) {
190
215
  return target;
191
216
  }
192
217
 
218
+ if (Array.isArray(target)) {
219
+ for (const candidate of target) {
220
+ const resolved = pickBrowserExportTarget(candidate);
221
+ if (resolved) return resolved;
222
+ }
223
+ return null;
224
+ }
225
+
193
226
  if (!target || typeof target !== "object") {
194
227
  return null;
195
228
  }
@@ -202,6 +235,33 @@ function pickBrowserExportTarget(target) {
202
235
  ?? null;
203
236
  }
204
237
 
238
+ function resolveBrowserPackageExport(exportsField, exportKey) {
239
+ if (typeof exportsField === "string" || Array.isArray(exportsField)) {
240
+ return exportKey === "." ? pickBrowserExportTarget(exportsField) : null;
241
+ }
242
+ if (!exportsField || typeof exportsField !== "object") {
243
+ return null;
244
+ }
245
+ if (!Object.keys(exportsField).some((key) => key.startsWith("."))) {
246
+ return exportKey === "." ? pickBrowserExportTarget(exportsField) : null;
247
+ }
248
+ if (exportsField[exportKey] != null) {
249
+ return pickBrowserExportTarget(exportsField[exportKey]);
250
+ }
251
+
252
+ for (const [pattern, target] of Object.entries(exportsField)) {
253
+ const wildcardIndex = pattern.indexOf("*");
254
+ if (wildcardIndex < 0) continue;
255
+ const prefix = pattern.slice(0, wildcardIndex);
256
+ const suffix = pattern.slice(wildcardIndex + 1);
257
+ if (!exportKey.startsWith(prefix) || !exportKey.endsWith(suffix)) continue;
258
+ const wildcard = exportKey.slice(prefix.length, exportKey.length - suffix.length);
259
+ const resolved = pickBrowserExportTarget(target);
260
+ if (resolved) return resolved.replace("*", wildcard);
261
+ }
262
+ return null;
263
+ }
264
+
205
265
  export async function resolveBrowserSpecifierFilePath(specifier, options = {}) {
206
266
  if (!isBareSpecifier(specifier)) {
207
267
  return null;
@@ -222,17 +282,15 @@ export async function resolveBrowserSpecifierFilePath(specifier, options = {}) {
222
282
  const { packageName, subpath } = parsedSpecifier;
223
283
  const { packageRoot, packageJson } = await resolvePackageRoot(packageName, { projectRoot: resolveFrom });
224
284
  const exportKey = subpath.length > 0 ? `./${subpath}` : ".";
225
- const exportsField = packageJson.exports;
226
- const exportTarget = pickBrowserExportTarget(
227
- exportsField && typeof exportsField === "object" && Object.keys(exportsField).some((key) => key.startsWith("."))
228
- ? exportsField[exportKey]
229
- : exportKey === "." ? exportsField : null,
230
- );
231
- const fallbackTarget = subpath.length > 0
232
- ? `./${subpath}`
233
- : typeof packageJson.browser === "string"
234
- ? packageJson.browser
235
- : packageJson.module ?? packageJson.main ?? null;
285
+ const hasExports = packageJson.exports != null;
286
+ const exportTarget = resolveBrowserPackageExport(packageJson.exports, exportKey);
287
+ const fallbackTarget = hasExports
288
+ ? null
289
+ : subpath.length > 0
290
+ ? `./${subpath}`
291
+ : typeof packageJson.browser === "string"
292
+ ? packageJson.browser
293
+ : packageJson.module ?? packageJson.main ?? null;
236
294
  let resolvedTarget = exportTarget ?? fallbackTarget;
237
295
  if (resolvedTarget && packageJson.browser && typeof packageJson.browser === "object") {
238
296
  const normalizedTarget = resolvedTarget.startsWith("./")
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 basePath = path.resolve(path.dirname(importerPath), specifier);
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
- ?? (parsed.subpath
665
- ? `./${parsed.subpath}`
666
- : typeof packageJson.browser === "string"
667
- ? packageJson.browser
668
- : packageJson.module ?? packageJson.main);
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]
@@ -828,8 +856,19 @@ async function rewriteRelativeSpecifiers({
828
856
  const aliasedImportPath = isBareSpecifier(specifier)
829
857
  ? await resolveProjectMappedImport(projectRoot, sourcePath, specifier)
830
858
  : null;
859
+ const packageImportPath = isBareSpecifier(specifier) && !aliasedImportPath
860
+ ? await resolveProjectPackageImport(projectRoot, sourcePath, specifier)
861
+ : null;
862
+ const packageAssetPath = packageImportPath && isStaticAssetPath(packageImportPath)
863
+ ? packageImportPath
864
+ : null;
831
865
 
832
- if (target === "client" && isBareSpecifier(specifier) && !aliasedImportPath) {
866
+ if (
867
+ target === "client"
868
+ && isBareSpecifier(specifier)
869
+ && !aliasedImportPath
870
+ && !packageAssetPath
871
+ ) {
833
872
  const sourceMetadata = moduleMetadata.get(sourcePath) ?? {
834
873
  moduleImports: new Set(),
835
874
  vendorImports: new Set(),
@@ -841,11 +880,13 @@ async function rewriteRelativeSpecifiers({
841
880
  continue;
842
881
  }
843
882
 
844
- if (!isRelativeSpecifier(specifier) && !aliasedImportPath) {
883
+ if (!isRelativeSpecifier(specifier) && !aliasedImportPath && !packageAssetPath) {
845
884
  continue;
846
885
  }
847
886
 
848
- const resolvedImportPath = aliasedImportPath ?? await resolveImportPath(sourcePath, specifier);
887
+ const resolvedImportPath = packageAssetPath
888
+ ?? aliasedImportPath
889
+ ?? await resolveImportPath(sourcePath, specifier);
849
890
  if (!resolvedImportPath) {
850
891
  continue;
851
892
  }
@@ -875,7 +916,10 @@ async function rewriteRelativeSpecifiers({
875
916
  );
876
917
  }
877
918
  } else if (isStaticAssetPath(resolvedImportPath)) {
878
- staticAssetFiles?.add(resolvedImportPath);
919
+ const staticAssetGraph = await collectStaticAssetGraph(resolvedImportPath);
920
+ for (const staticAssetPath of staticAssetGraph) {
921
+ staticAssetFiles?.add(staticAssetPath);
922
+ }
879
923
  const relativeAssetPath = toOutputRelativePath(projectRoot, resolvedImportPath);
880
924
  const assetOutputPath = path.join(outputRoot, relativeAssetPath);
881
925
  const stubOutputPath = `${assetOutputPath}.mjs`;
@@ -888,8 +932,14 @@ async function rewriteRelativeSpecifiers({
888
932
  );
889
933
 
890
934
  if (target === "client") {
891
- await ensureDirectory(path.dirname(assetOutputPath));
892
- await fs.copyFile(resolvedImportPath, assetOutputPath);
935
+ for (const staticAssetPath of staticAssetGraph) {
936
+ const staticAssetOutputPath = path.join(
937
+ outputRoot,
938
+ toOutputRelativePath(projectRoot, staticAssetPath),
939
+ );
940
+ await ensureDirectory(path.dirname(staticAssetOutputPath));
941
+ await fs.copyFile(staticAssetPath, staticAssetOutputPath);
942
+ }
893
943
  const sourceMetadata = moduleMetadata.get(sourcePath) ?? {
894
944
  moduleImports: new Set(),
895
945
  vendorImports: new Set(),
@@ -1239,8 +1289,6 @@ export async function compileModuleGraph(entryPath, options = {}) {
1239
1289
  }
1240
1290
  }
1241
1291
 
1242
- const CSS_DEPENDENCY_PATTERN = /(?:@import\s+(?:url\(\s*)?["']([^"']+)["']\s*\)?|url\(\s*["']?([^"')]+)["']?\s*\))/g;
1243
-
1244
1292
  function normalizeProjectRelativePath(projectRoot, filePath) {
1245
1293
  return toOutputRelativePath(projectRoot, filePath).split(path.sep).join("/");
1246
1294
  }
@@ -1406,8 +1454,13 @@ export async function emitClientStaticAssets(assetPaths, options = {}) {
1406
1454
  const outputRoot = getTypedOutputRoot(projectRoot, mode, "client");
1407
1455
  const emitted = [];
1408
1456
 
1457
+ const expandedAssetPaths = new Set();
1409
1458
  for (const assetPath of new Set(assetPaths ?? [])) {
1410
1459
  if (!isStaticAssetPath(assetPath)) continue;
1460
+ await collectStaticAssetGraph(assetPath, expandedAssetPaths);
1461
+ }
1462
+
1463
+ for (const assetPath of expandedAssetPaths) {
1411
1464
  const relativePath = normalizeProjectRelativePath(projectRoot, assetPath);
1412
1465
  const outputPath = path.join(outputRoot, relativePath.split("/").join(path.sep));
1413
1466
  await ensureDirectory(path.dirname(outputPath));
package/src/scaffold.js CHANGED
@@ -23,14 +23,13 @@ async function writeSitePackageJson(targetDirectory, siteName) {
23
23
  dev: "evolit dev",
24
24
  build: "evolit build",
25
25
  start: "evolit start",
26
- typecheck: "litsx-tsc -p jsconfig.json --noEmit",
26
+ typecheck: "tsc -p jsconfig.json --noEmit",
27
27
  },
28
28
  dependencies: {
29
- "@litsx/core": "1.0.0-next.5",
29
+ "@litsx/core": "1.0.0-next.8",
30
30
  evolit: frameworkVersion,
31
31
  },
32
32
  devDependencies: {
33
- "@litsx/typescript": "^0.9.0",
34
33
  "typescript": "^6.0.0",
35
34
  },
36
35
  };
@@ -13,12 +13,7 @@
13
13
  "@/*": [
14
14
  "./*"
15
15
  ]
16
- },
17
- "plugins": [
18
- {
19
- "name": "@litsx/typescript"
20
- }
21
- ]
16
+ }
22
17
  },
23
18
  "include": [
24
19
  "app/**/*",