@plumeria/utils 21.1.7 → 21.2.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/dist/compile.js CHANGED
@@ -37,6 +37,7 @@ exports.compileCSS = compileCSS;
37
37
  const core_1 = require("@swc/core");
38
38
  const zss_engine_1 = require("zss-engine");
39
39
  const sourceCache_1 = require("./sourceCache");
40
+ const namespaceImport_1 = require("./namespaceImport");
40
41
  const parser_1 = require("./parser");
41
42
  const path = __importStar(require("path"));
42
43
  const rs = __importStar(require("@rust-gear/glob"));
@@ -333,9 +334,18 @@ function compileCSS(options) {
333
334
  propsByFile.set(file, entries);
334
335
  }
335
336
  const processFile = (filePath, writes) => {
336
- const scannedTables = readTables(tables, writes);
337
337
  const resourcePath = path.resolve(cwd, filePath);
338
- const source = options.sources?.[resourcePath] ?? (0, sourceCache_1.readSource)(resourcePath);
338
+ const written = options.sources?.[resourcePath] ?? (0, sourceCache_1.readSource)(resourcePath);
339
+ const namespaces = (0, namespaceImport_1.expandNamespaces)(resourcePath, written);
340
+ try {
341
+ return extractFile(resourcePath, namespaces?.source ?? written, writes);
342
+ }
343
+ catch (error) {
344
+ throw (0, namespaceImport_1.asWritten)(namespaces, error);
345
+ }
346
+ };
347
+ const extractFile = (resourcePath, source, writes) => {
348
+ const scannedTables = readTables(tables, writes);
339
349
  const extractedSheets = [];
340
350
  const ast = (0, core_1.parseSync)(source, {
341
351
  syntax: 'typescript',
@@ -0,0 +1,18 @@
1
+ export interface NamespaceImport {
2
+ local: string;
3
+ start: number;
4
+ end: number;
5
+ appendedStart: number;
6
+ appendedEnd: number;
7
+ unused: boolean;
8
+ dots: number[];
9
+ bindings: string[];
10
+ }
11
+ export interface Namespaces {
12
+ source: string;
13
+ originalLength: number;
14
+ separated: boolean;
15
+ imports: NamespaceImport[];
16
+ }
17
+ export declare function asWritten<T>(namespaces: Namespaces | undefined, error: T): T;
18
+ export declare function expandNamespaces(filePath: string, source: string): Namespaces | undefined;
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.asWritten = asWritten;
4
+ exports.expandNamespaces = expandNamespaces;
5
+ const core_1 = require("@swc/core");
6
+ const ast_1 = require("./ast");
7
+ const parser_1 = require("./parser");
8
+ const references_1 = require("./references");
9
+ const resolver_1 = require("./resolver");
10
+ const NAMESPACE_IMPORT = /import\s*\*\s*as\s+([\p{L}\p{N}_$]+)\s*from\s*(['"])(.*?)\2/gu;
11
+ const cache = new Map();
12
+ const LIMIT = 4096;
13
+ const sameTargets = (a, b) => a.length === b.length &&
14
+ a.every(([path, actualPath, exports], i) => path === b[i][0] &&
15
+ actualPath === b[i][1] &&
16
+ exports.length === b[i][2].length &&
17
+ exports.every((name, j) => name === b[i][2][j]));
18
+ const NAME = '[\\p{L}\\p{N}_$]';
19
+ function asWritten(namespaces, error) {
20
+ if (!namespaces)
21
+ return error;
22
+ const restore = (text) => {
23
+ for (const { local, bindings } of namespaces.imports)
24
+ for (const binding of bindings) {
25
+ const pattern = new RegExp(`(?<!${NAME})${binding.replace(/\$/g, '\\$')}(?!${NAME})`, 'gu');
26
+ const dotted = `${local}.${binding.slice(local.length + 1)}`;
27
+ text = text.replace(pattern, () => dotted);
28
+ }
29
+ return text;
30
+ };
31
+ if (typeof error === 'string')
32
+ return restore(error);
33
+ if (error instanceof Error) {
34
+ error.message = restore(error.message);
35
+ if (error.stack)
36
+ error.stack = restore(error.stack);
37
+ }
38
+ return error;
39
+ }
40
+ const unwrap = (node) => {
41
+ while (node?.type === 'ParenthesisExpression')
42
+ node = node.expression;
43
+ return node;
44
+ };
45
+ function expandNamespaces(filePath, source) {
46
+ const targets = [];
47
+ for (const [, local, , path] of source.matchAll(NAMESPACE_IMPORT)) {
48
+ if (path === '@plumeria/core' ||
49
+ !source.includes(`${local}.`) ||
50
+ targets.some(([seen]) => seen === path))
51
+ continue;
52
+ const actualPath = (0, resolver_1.resolveImportPath)(path, filePath);
53
+ if (!actualPath)
54
+ continue;
55
+ const exports = (0, parser_1.exportNames)(actualPath);
56
+ if (exports.length > 0)
57
+ targets.push([path, actualPath, exports]);
58
+ }
59
+ if (targets.length === 0)
60
+ return undefined;
61
+ const cached = cache.get(filePath);
62
+ if (cached &&
63
+ cached.source === source &&
64
+ sameTargets(cached.targets, targets))
65
+ return cached.namespaces;
66
+ const namespaces = expand(source, targets);
67
+ if (cache.size >= LIMIT)
68
+ cache.clear();
69
+ cache.set(filePath, { source, targets, namespaces });
70
+ return namespaces;
71
+ }
72
+ function expand(source, resolved) {
73
+ let ast;
74
+ try {
75
+ ast = (0, core_1.parseSync)(source, {
76
+ syntax: 'typescript',
77
+ tsx: true,
78
+ target: 'es2022',
79
+ });
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ const buffer = Buffer.from(source, 'utf-8');
85
+ const leadingBytes = Buffer.byteLength(source.slice(0, (0, ast_1.getLeadingCommentLength)(source)), 'utf-8');
86
+ const base = ast.span.start - leadingBytes;
87
+ const targets = new Map();
88
+ for (const statement of ast.body) {
89
+ if (statement.type !== 'ImportDeclaration' || statement.typeOnly)
90
+ continue;
91
+ const specifier = statement.specifiers[0];
92
+ if (specifier?.type !== 'ImportNamespaceSpecifier')
93
+ continue;
94
+ const target = resolved.find(([path]) => path === statement.source.value);
95
+ if (!target)
96
+ continue;
97
+ targets.set(specifier.local.value, {
98
+ decl: statement,
99
+ exports: new Set(target[2]),
100
+ });
101
+ }
102
+ if (targets.size === 0)
103
+ return undefined;
104
+ const { names: references } = (0, references_1.collectReferenceIdentifiers)(ast, {
105
+ runtimeReads: true,
106
+ });
107
+ const kept = new Set();
108
+ const found = [];
109
+ const keyOf = (node) => `${node.span.start}:${node.span.end}`;
110
+ const keep = (node) => {
111
+ node = unwrap(node);
112
+ if (node?.type === 'MemberExpression')
113
+ kept.add(keyOf(node));
114
+ };
115
+ const walk = (n) => {
116
+ if (!n || typeof n !== 'object')
117
+ return;
118
+ if (Array.isArray(n)) {
119
+ n.forEach(walk);
120
+ return;
121
+ }
122
+ switch (n.type) {
123
+ case 'CallExpression':
124
+ keep(n.callee);
125
+ break;
126
+ case 'TaggedTemplateExpression':
127
+ keep(n.tag);
128
+ break;
129
+ case 'AssignmentExpression':
130
+ case 'AssignmentPattern':
131
+ case 'ForInStatement':
132
+ case 'ForOfStatement':
133
+ keep(n.left);
134
+ break;
135
+ case 'UpdateExpression':
136
+ case 'RestElement':
137
+ keep(n.argument);
138
+ break;
139
+ case 'UnaryExpression':
140
+ if (n.operator === 'delete')
141
+ keep(n.argument);
142
+ break;
143
+ case 'ArrayPattern':
144
+ n.elements?.forEach(keep);
145
+ break;
146
+ case 'KeyValuePatternProperty':
147
+ keep(n.value);
148
+ break;
149
+ case 'MemberExpression': {
150
+ const { object, property } = n;
151
+ const target = object?.type === 'Identifier' ? targets.get(object.value) : undefined;
152
+ if (target &&
153
+ references.get(object.span.start) === object.value &&
154
+ property?.type === 'Identifier' &&
155
+ target.exports.has(property.value)) {
156
+ found.push({
157
+ local: object.value,
158
+ name: property.value,
159
+ object,
160
+ key: keyOf(n),
161
+ });
162
+ }
163
+ break;
164
+ }
165
+ }
166
+ for (const key in n) {
167
+ if (key === 'span')
168
+ continue;
169
+ walk(n[key]);
170
+ }
171
+ };
172
+ ast.body.forEach(walk);
173
+ const rewritten = new Set();
174
+ const names = new Map();
175
+ const dots = new Map();
176
+ const taken = new Map();
177
+ const isTaken = (binding) => {
178
+ let hit = taken.get(binding);
179
+ if (hit === undefined)
180
+ taken.set(binding, (hit = source.includes(binding)));
181
+ return hit;
182
+ };
183
+ for (const { local, name, object, key } of found) {
184
+ const dot = object.span.end - base;
185
+ if (kept.has(key) ||
186
+ buffer[dot] !== 0x2e ||
187
+ buffer.subarray(dot + 1, dot + 1 + Buffer.byteLength(name)).toString() !==
188
+ name ||
189
+ isTaken(`${local}$${name}`)) {
190
+ continue;
191
+ }
192
+ buffer[dot] = 0x24;
193
+ rewritten.add(object.span.start);
194
+ const list = names.get(local) ?? [];
195
+ if (!list.includes(name))
196
+ list.push(name);
197
+ names.set(local, list);
198
+ const at = dots.get(local) ?? [];
199
+ at.push(dot);
200
+ dots.set(local, at);
201
+ }
202
+ if (names.size === 0)
203
+ return undefined;
204
+ let output = buffer.toString('utf-8');
205
+ const originalLength = buffer.length;
206
+ const separated = !output.endsWith('\n');
207
+ if (separated)
208
+ output += '\n';
209
+ let length = Buffer.byteLength(output);
210
+ const imports = [];
211
+ const locals = [...names.keys()].sort((a, b) => targets.get(a).decl.span.start - targets.get(b).decl.span.start);
212
+ for (const local of locals) {
213
+ const { decl } = targets.get(local);
214
+ const raw = buffer
215
+ .subarray(decl.source.span.start - base, decl.source.span.end - base)
216
+ .toString('utf-8');
217
+ const specifiers = names
218
+ .get(local)
219
+ .map((name) => `${name} as ${local}$${name}`)
220
+ .join(', ');
221
+ const statement = `import { ${specifiers} } from ${raw};`;
222
+ output += statement;
223
+ const appendedStart = length;
224
+ length += Buffer.byteLength(statement);
225
+ const unused = [...references].every(([start, name]) => name !== local || rewritten.has(start));
226
+ imports.push({
227
+ local,
228
+ start: decl.span.start - base,
229
+ end: decl.span.end - base,
230
+ appendedStart,
231
+ appendedEnd: length,
232
+ unused,
233
+ dots: dots.get(local),
234
+ bindings: names.get(local).map((name) => `${local}$${name}`),
235
+ });
236
+ }
237
+ return { source: output, originalLength, separated, imports };
238
+ }
package/dist/parser.js CHANGED
@@ -55,6 +55,7 @@ exports.resolveExport = resolveExport;
55
55
  exports.extractOndemandStyles = extractOndemandStyles;
56
56
  exports.deepMerge = deepMerge;
57
57
  const sourceCache_1 = require("./sourceCache");
58
+ const namespaceImport_1 = require("./namespaceImport");
58
59
  const createTheme_1 = require("./createTheme");
59
60
  const core_1 = require("@swc/core");
60
61
  const path = __importStar(require("path"));
@@ -1328,9 +1329,16 @@ function runScan(scanCwd, styleProp) {
1328
1329
  }
1329
1330
  catch {
1330
1331
  }
1331
- const resolutionChanged = resolutionConfigStamp !== undefined &&
1332
- resolutionConfigStamp !== configStamp;
1333
- if (resolutionConfigStamp !== configStamp)
1332
+ const manifestStamp = fs.statSync(path.join(cwd, 'package.json'), {
1333
+ throwIfNoEntry: false,
1334
+ });
1335
+ if (manifestStamp)
1336
+ configStamp += `:${manifestStamp.mtimeMs}`;
1337
+ const packagesStale = (0, resolver_1.sourcePackagesStale)();
1338
+ const resolutionChanged = packagesStale ||
1339
+ (resolutionConfigStamp !== undefined &&
1340
+ resolutionConfigStamp !== configStamp);
1341
+ if (packagesStale || resolutionConfigStamp !== configStamp)
1334
1342
  (0, resolver_1.resetImportResolutionCache)(cwd);
1335
1343
  resolutionConfigStamp = configStamp;
1336
1344
  const segments = cwd.split(path.sep);
@@ -1339,18 +1347,26 @@ function runScan(scanCwd, styleProp) {
1339
1347
  ? cwd
1340
1348
  : segments.slice(0, dependencyDirectory).join(path.sep) ||
1341
1349
  path.parse(cwd).root;
1342
- const files = rs
1343
- .globSync(path.join(root, '**/*.{js,jsx,ts,tsx}'), {
1344
- exclude: [
1345
- '**/node_modules/**',
1346
- '**/dist/**',
1347
- '**/build/**',
1348
- '**/.next/**',
1349
- ],
1350
- cwd: root,
1351
- sort: true,
1352
- })
1353
- .sort();
1350
+ const exclude = [
1351
+ '**/node_modules/**',
1352
+ '**/dist/**',
1353
+ '**/build/**',
1354
+ '**/.next/**',
1355
+ ];
1356
+ const files = [
1357
+ ...new Set([
1358
+ ...rs.globSync(path.join(root, '**/*.{js,jsx,ts,tsx}'), {
1359
+ exclude,
1360
+ cwd: root,
1361
+ sort: true,
1362
+ }),
1363
+ ...(0, resolver_1.getSourcePackages)()
1364
+ .flatMap(resolver_1.sourceDirsOf)
1365
+ .flatMap((dir) => rs
1366
+ .globSync('**/*.{js,jsx,ts,tsx}', { exclude, cwd: dir })
1367
+ .map((file) => path.join(dir, file))),
1368
+ ]),
1369
+ ].sort();
1354
1370
  const currentFiles = new Set(files);
1355
1371
  const deletedFiles = [];
1356
1372
  for (const fp of knownFiles) {
@@ -1451,6 +1467,7 @@ function runScan(scanCwd, styleProp) {
1451
1467
  stripFileContributions(filePath, cached);
1452
1468
  }
1453
1469
  const parsedFiles = [];
1470
+ const expanded = new Map();
1454
1471
  for (const filePath of uncachedFiles) {
1455
1472
  try {
1456
1473
  const stats = fs.statSync(filePath);
@@ -1478,7 +1495,10 @@ function runScan(scanCwd, styleProp) {
1478
1495
  };
1479
1496
  continue;
1480
1497
  }
1481
- const ast = (0, core_1.parseSync)(source, {
1498
+ const namespaces = (0, namespaceImport_1.expandNamespaces)(filePath, source);
1499
+ if (namespaces)
1500
+ expanded.set(filePath, namespaces);
1501
+ const ast = (0, core_1.parseSync)(namespaces?.source ?? source, {
1482
1502
  syntax: 'typescript',
1483
1503
  tsx: true,
1484
1504
  target: 'es2022',
@@ -1486,7 +1506,7 @@ function runScan(scanCwd, styleProp) {
1486
1506
  parsedFiles.push({ filePath, ast, mtimeMs: stats.mtimeMs });
1487
1507
  }
1488
1508
  catch (e) {
1489
- recordFileError(filePath, e);
1509
+ recordFileError(filePath, (0, namespaceImport_1.asWritten)(expanded.get(filePath), e));
1490
1510
  }
1491
1511
  }
1492
1512
  const jsxPhaseQueue = [];
@@ -1495,7 +1515,7 @@ function runScan(scanCwd, styleProp) {
1495
1515
  extractAndCacheExports(file.filePath, file.ast, file.mtimeMs);
1496
1516
  }
1497
1517
  catch (error) {
1498
- recordFileError(file.filePath, error);
1518
+ recordFileError(file.filePath, (0, namespaceImport_1.asWritten)(expanded.get(file.filePath), error));
1499
1519
  }
1500
1520
  }
1501
1521
  const byPath = new Map(parsedFiles.map((file) => [file.filePath, file]));
@@ -1908,7 +1928,7 @@ function runScan(scanCwd, styleProp) {
1908
1928
  });
1909
1929
  }
1910
1930
  catch (e) {
1911
- recordFileError(filePath, e);
1931
+ recordFileError(filePath, (0, namespaceImport_1.asWritten)(expanded.get(filePath), e));
1912
1932
  }
1913
1933
  }
1914
1934
  }
@@ -2374,7 +2394,7 @@ function runScan(scanCwd, styleProp) {
2374
2394
  updateDependencyEdges(filePath, prevDependencies, fileCache[filePath].dependencies);
2375
2395
  }
2376
2396
  catch (e) {
2377
- recordFileError(entry.filePath, e);
2397
+ recordFileError(entry.filePath, (0, namespaceImport_1.asWritten)(expanded.get(entry.filePath), e));
2378
2398
  }
2379
2399
  }
2380
2400
  for (const entry of jsxPhaseQueue) {
@@ -2596,7 +2616,10 @@ function refreshExports(filePath) {
2596
2616
  tsx: true,
2597
2617
  target: 'es2022',
2598
2618
  });
2619
+ const known = filePath in fileCache;
2599
2620
  extractAndCacheExports(filePath, ast, stats.mtimeMs);
2621
+ if (!known)
2622
+ fileCache[filePath].mtimeMs = undefined;
2600
2623
  }
2601
2624
  }
2602
2625
  catch (e) {
@@ -2,5 +2,8 @@ import type { Module } from '@swc/core';
2
2
  export interface ReferenceIdentifiers {
3
3
  references: Set<number>;
4
4
  shorthands: Set<number>;
5
+ names: Map<number, string>;
5
6
  }
6
- export declare function collectReferenceIdentifiers(ast: Module): ReferenceIdentifiers;
7
+ export declare function collectReferenceIdentifiers(ast: Module, { runtimeReads }?: {
8
+ runtimeReads?: boolean;
9
+ }): ReferenceIdentifiers;
@@ -133,11 +133,27 @@ const collectHoistedNames = (n, out) => {
133
133
  collectHoistedNames(n[key], out);
134
134
  }
135
135
  };
136
- function collectReferenceIdentifiers(ast) {
136
+ function collectReferenceIdentifiers(ast, { runtimeReads = false } = {}) {
137
137
  const references = new Set();
138
138
  const shorthands = new Set();
139
+ const names = new Map();
139
140
  const scopes = [];
140
141
  const isShadowed = (name) => scopes.some((scope) => scope.has(name));
142
+ const reference = (ident) => {
143
+ if (isShadowed(ident.value))
144
+ return false;
145
+ references.add(ident.span.start);
146
+ names.set(ident.span.start, ident.value);
147
+ return true;
148
+ };
149
+ const elementName = (name) => {
150
+ if (name?.type === 'Identifier' && /^[a-z]/.test(name.value))
151
+ return;
152
+ while (name?.type === 'JSXMemberExpression')
153
+ name = name.object;
154
+ if (name?.type === 'Identifier')
155
+ reference(name);
156
+ };
141
157
  const walkPattern = (n) => {
142
158
  if (!n || typeof n !== 'object')
143
159
  return;
@@ -205,8 +221,7 @@ function collectReferenceIdentifiers(ast) {
205
221
  }
206
222
  const type = n.type;
207
223
  if (type === 'Identifier') {
208
- if (!isShadowed(n.value))
209
- references.add(n.span.start);
224
+ reference(n);
210
225
  return;
211
226
  }
212
227
  if (FUNCTION_TYPES.has(type)) {
@@ -214,6 +229,10 @@ function collectReferenceIdentifiers(ast) {
214
229
  return;
215
230
  }
216
231
  switch (type) {
232
+ case 'ExportNamedDeclaration':
233
+ if (n.source)
234
+ return;
235
+ break;
217
236
  case 'BlockStatement':
218
237
  walkBlock(n, n.stmts);
219
238
  return;
@@ -278,10 +297,8 @@ function collectReferenceIdentifiers(ast) {
278
297
  case 'ObjectExpression':
279
298
  n.properties?.forEach((prop) => {
280
299
  if (prop?.type === 'Identifier') {
281
- if (!isShadowed(prop.value)) {
282
- references.add(prop.span.start);
300
+ if (reference(prop))
283
301
  shorthands.add(prop.span.start);
284
- }
285
302
  return;
286
303
  }
287
304
  walk(prop);
@@ -306,11 +323,15 @@ function collectReferenceIdentifiers(ast) {
306
323
  const child = n[key];
307
324
  if (child?.type === 'Computed')
308
325
  walk(child);
326
+ else if (runtimeReads && type === 'JSXOpeningElement')
327
+ elementName(child);
328
+ else if (runtimeReads && type === 'ExportSpecifier' && key === 'orig')
329
+ reference(child);
309
330
  continue;
310
331
  }
311
332
  walk(n[key]);
312
333
  }
313
334
  }
314
335
  ast.body.forEach(walk);
315
- return { references, shorthands };
336
+ return { references, shorthands, names };
316
337
  }
@@ -1,2 +1,12 @@
1
+ export interface SourcePackage {
2
+ name: string;
3
+ dir: string;
4
+ exports: unknown;
5
+ linked: string;
6
+ manifestMtime: number | undefined;
7
+ }
8
+ export declare function sourceDirsOf(pkg: SourcePackage): string[];
9
+ export declare function sourcePackagesStale(): boolean;
1
10
  export declare function resetImportResolutionCache(cwd?: string): void;
11
+ export declare function getSourcePackages(): SourcePackage[];
2
12
  export declare function resolveImportPath(importPath: string, importerPath: string): string | null;
package/dist/resolver.js CHANGED
@@ -33,12 +33,17 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.sourceDirsOf = sourceDirsOf;
37
+ exports.sourcePackagesStale = sourcePackagesStale;
36
38
  exports.resetImportResolutionCache = resetImportResolutionCache;
39
+ exports.getSourcePackages = getSourcePackages;
37
40
  exports.resolveImportPath = resolveImportPath;
38
41
  const fs = __importStar(require("fs"));
39
42
  const path = __importStar(require("path"));
40
43
  const CONFIG_DIR = '${configDir}';
41
44
  let cachedPathsMatcher = undefined;
45
+ let cachedSourcePackages = undefined;
46
+ const SOURCE_CONDITION = 'plumeria';
42
47
  function stripComments(text) {
43
48
  let out = '';
44
49
  let inString = false;
@@ -260,8 +265,141 @@ function loadPathsMatcher(configPath) {
260
265
  const patterns = collectPatterns(configPath, new Set());
261
266
  return patterns ? buildMatcher(patterns) : null;
262
267
  }
268
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
269
+ const hasSubpaths = (exports) => isRecord(exports) && Object.keys(exports).some((key) => key.startsWith('.'));
270
+ const sourceTargetOf = (entry) => {
271
+ if (!isRecord(entry))
272
+ return null;
273
+ const target = entry[SOURCE_CONDITION];
274
+ return typeof target === 'string' ? target : null;
275
+ };
276
+ function sourceTarget(pkg, subpath) {
277
+ const { exports } = pkg;
278
+ if (!hasSubpaths(exports))
279
+ return subpath === '.' ? sourceTargetOf(exports) : null;
280
+ if (Object.hasOwn(exports, subpath))
281
+ return sourceTargetOf(exports[subpath]);
282
+ let best = null;
283
+ for (const key of Object.keys(exports)) {
284
+ const star = key.indexOf('*');
285
+ if (star < 0 || key.indexOf('*', star + 1) >= 0)
286
+ continue;
287
+ const suffix = key.slice(star + 1);
288
+ if (subpath.length < key.length ||
289
+ !subpath.startsWith(key.slice(0, star)) ||
290
+ !subpath.endsWith(suffix))
291
+ continue;
292
+ if (best &&
293
+ (star < best.star ||
294
+ (star === best.star && key.length <= best.key.length)))
295
+ continue;
296
+ best = {
297
+ key,
298
+ star,
299
+ matched: subpath.slice(star, subpath.length - suffix.length),
300
+ };
301
+ }
302
+ if (!best)
303
+ return null;
304
+ const target = sourceTargetOf(exports[best.key]);
305
+ return target === null ? null : target.replaceAll('*', best.matched);
306
+ }
307
+ const sourceDirOf = (dir, target) => {
308
+ const star = target.indexOf('*');
309
+ if (star < 0)
310
+ return path.dirname(path.resolve(dir, target));
311
+ const base = target.slice(0, star);
312
+ return base.endsWith('/')
313
+ ? path.resolve(dir, base)
314
+ : path.dirname(path.resolve(dir, base));
315
+ };
316
+ function sourceDirsOf(pkg) {
317
+ const entries = hasSubpaths(pkg.exports)
318
+ ? Object.values(pkg.exports)
319
+ : [pkg.exports];
320
+ const dirs = [
321
+ ...new Set(entries
322
+ .map(sourceTargetOf)
323
+ .filter((target) => target !== null)
324
+ .map((target) => sourceDirOf(pkg.dir, target))
325
+ .filter((dir) => dir === pkg.dir || dir.startsWith(pkg.dir + path.sep))),
326
+ ].sort();
327
+ return dirs.filter((dir) => !dirs.some((other) => other !== dir && dir.startsWith(other + path.sep)));
328
+ }
329
+ const manifestMtimeOf = (dir) => fs.statSync(path.join(dir, 'package.json'), { throwIfNoEntry: false })
330
+ ?.mtimeMs;
331
+ const realpathOf = (linked) => {
332
+ try {
333
+ return fs.realpathSync(linked);
334
+ }
335
+ catch {
336
+ return null;
337
+ }
338
+ };
339
+ function sourcePackagesStale() {
340
+ return (cachedSourcePackages?.some((pkg) => realpathOf(pkg.linked) !== pkg.dir ||
341
+ manifestMtimeOf(pkg.dir) !== pkg.manifestMtime) ?? false);
342
+ }
343
+ function loadSourcePackages(cwd) {
344
+ const manifest = readConfig(path.join(cwd, 'package.json'));
345
+ if (!manifest)
346
+ return [];
347
+ const names = [
348
+ ...new Set([
349
+ 'dependencies',
350
+ 'devDependencies',
351
+ 'peerDependencies',
352
+ 'optionalDependencies',
353
+ ].flatMap((field) => isRecord(manifest[field]) ? Object.keys(manifest[field]) : [])),
354
+ ].sort();
355
+ const packages = [];
356
+ for (const name of names) {
357
+ let linked = null;
358
+ for (let dir = cwd;; dir = path.dirname(dir)) {
359
+ const candidate = path.join(dir, 'node_modules', name);
360
+ if (fs.statSync(candidate, { throwIfNoEntry: false })?.isDirectory()) {
361
+ linked = candidate;
362
+ break;
363
+ }
364
+ if (dir === path.dirname(dir))
365
+ break;
366
+ }
367
+ if (!linked)
368
+ continue;
369
+ const dir = fs.realpathSync(linked);
370
+ const exports = readConfig(path.join(dir, 'package.json'))?.exports;
371
+ if (exports === undefined)
372
+ continue;
373
+ const pkg = {
374
+ name,
375
+ dir,
376
+ exports,
377
+ linked,
378
+ manifestMtime: manifestMtimeOf(dir),
379
+ };
380
+ if (sourceDirsOf(pkg).length > 0)
381
+ packages.push(pkg);
382
+ }
383
+ return packages;
384
+ }
263
385
  function resetImportResolutionCache(cwd = process.cwd()) {
264
386
  cachedPathsMatcher = loadPathsMatcher(path.join(cwd, 'tsconfig.json'));
387
+ cachedSourcePackages = loadSourcePackages(cwd);
388
+ }
389
+ function getSourcePackages() {
390
+ cachedSourcePackages ??= loadSourcePackages(process.cwd());
391
+ return cachedSourcePackages;
392
+ }
393
+ function resolveSourcePackage(importPath) {
394
+ const name = packageNameOf(importPath);
395
+ const pkg = getSourcePackages().find((p) => p.name === name);
396
+ if (!pkg)
397
+ return null;
398
+ const target = sourceTarget(pkg, `.${importPath.slice(name.length)}`);
399
+ if (target === null)
400
+ return null;
401
+ const resolved = path.resolve(pkg.dir, target);
402
+ return isFile(resolved) ? resolved : null;
265
403
  }
266
404
  function getPathsMatcher() {
267
405
  if (cachedPathsMatcher !== undefined)
@@ -319,6 +457,9 @@ function resolveImportPath(importPath, importerPath) {
319
457
  return result;
320
458
  }
321
459
  }
460
+ const sourcePackage = resolveSourcePackage(importPath);
461
+ if (sourcePackage)
462
+ return sourcePackage;
322
463
  let currentDir = path.dirname(importerPath);
323
464
  while (currentDir !== path.parse(currentDir).root) {
324
465
  if (fs.existsSync(path.join(currentDir, 'package.json'))) {
package/dist/transform.js CHANGED
@@ -82,6 +82,7 @@ const createTheme_1 = require("./createTheme");
82
82
  const resolver_1 = require("./resolver");
83
83
  const ast_1 = require("./ast");
84
84
  const references_1 = require("./references");
85
+ const namespaceImport_1 = require("./namespaceImport");
85
86
  const propertyPolicy_1 = require("./propertyPolicy");
86
87
  const unwrapExport = (node) => {
87
88
  if (node?.type === 'ExportDeclaration')
@@ -182,9 +183,21 @@ const foldDynamicVars = (vars) => {
182
183
  });
183
184
  };
184
185
  const transformSource = async (env) => {
185
- const { source, root, styleProp, propertyPolicy, isDev, addDependency } = env;
186
+ const scannedTables = (0, parser_1.scanOverlay)(undefined, env.styleProp);
187
+ const namespaces = (0, namespaceImport_1.expandNamespaces)(env.moduleId, env.source);
188
+ try {
189
+ return await transformExpanded(env, scannedTables, namespaces);
190
+ }
191
+ catch (error) {
192
+ throw (0, namespaceImport_1.asWritten)(namespaces, error);
193
+ }
194
+ };
195
+ exports.transformSource = transformSource;
196
+ const transformExpanded = async (env, scannedTables, namespaces) => {
197
+ const { root, styleProp, propertyPolicy, isDev, addDependency } = env;
186
198
  const classProp = env.classProp ?? 'className';
187
199
  const id = env.moduleId;
200
+ const source = namespaces?.source ?? env.source;
188
201
  const resourcePath = env.moduleId;
189
202
  const baseId = env.filePath;
190
203
  const ast = (0, core_1.parseSync)(source, {
@@ -244,12 +257,13 @@ const transformSource = async (env) => {
244
257
  }
245
258
  }
246
259
  };
247
- const scannedTables = (0, parser_1.scanOverlay)(undefined, styleProp);
248
260
  const ownFailure = (0, parser_1.resolveFileError)(baseId, '');
249
261
  if (ownFailure)
250
262
  throwCompilationError(`[plumeria] ${ownFailure.message}`);
263
+ const appended = namespaces?.originalLength ?? Infinity;
251
264
  for (const node of ast.body) {
252
- if (node.type === 'ImportDeclaration') {
265
+ if (node.type === 'ImportDeclaration' &&
266
+ node.span.start - baseByteOffset < appended) {
253
267
  const sourcePath = node.source.value;
254
268
  const actualPath = (0, resolver_1.resolveImportPath)(sourcePath, id);
255
269
  if (actualPath) {
@@ -2685,17 +2699,109 @@ const transformSource = async (env) => {
2685
2699
  for (const replacement of replacements)
2686
2700
  replacement.content = replacement.content.replaceAll(deferred.token, content);
2687
2701
  }
2688
- let offset = 0;
2689
- const parts = [];
2690
- replacements.forEach((r) => {
2691
- if (r.start < offset)
2692
- return;
2693
- parts.push(sourceBuffer.subarray(offset, r.start));
2694
- parts.push(Buffer.from(r.content));
2695
- offset = r.end;
2702
+ const splice = (list) => {
2703
+ let offset = 0;
2704
+ const parts = [];
2705
+ list.forEach((r) => {
2706
+ if (r.start < offset)
2707
+ return;
2708
+ parts.push(sourceBuffer.subarray(offset, r.start));
2709
+ parts.push(Buffer.from(r.content));
2710
+ offset = r.end;
2711
+ });
2712
+ parts.push(sourceBuffer.subarray(offset));
2713
+ return Buffer.concat(parts).toString();
2714
+ };
2715
+ const transformedSource = splice(replacements);
2716
+ const elided = elidedImports(ast, baseByteOffset, replacements, transformedSource);
2717
+ if (namespaces)
2718
+ namespaceElisions(namespaces, replacements, elided, splice);
2719
+ if (elided.length === 0)
2720
+ return { code: transformedSource };
2721
+ replacements.push(...elided);
2722
+ replacements.sort((a, b) => a.start - b.start || b.end - a.end);
2723
+ return { code: splice(replacements) };
2724
+ };
2725
+ const namespaceElisions = (namespaces, replacements, elided, splice) => {
2726
+ const covered = (at) => replacements.some((r) => r.start <= at && at < r.end);
2727
+ const removed = ({ appendedStart }) => elided.some((r) => r.start === appendedStart);
2728
+ const appended = ({ appendedStart, appendedEnd }) => ({
2729
+ start: appendedStart,
2730
+ end: appendedEnd,
2731
+ content: '',
2696
2732
  });
2697
- parts.push(sourceBuffer.subarray(offset));
2698
- const transformedSource = Buffer.concat(parts).toString();
2699
- return { code: transformedSource };
2733
+ const extra = [];
2734
+ for (const namespace of namespaces.imports) {
2735
+ if (removed(namespace)) {
2736
+ if (namespace.unused)
2737
+ extra.push({ start: namespace.start, end: namespace.end, content: '' });
2738
+ continue;
2739
+ }
2740
+ for (const dot of namespace.dots)
2741
+ if (!covered(dot))
2742
+ extra.push({ start: dot, end: dot + 1, content: '.' });
2743
+ }
2744
+ const pending = namespaces.imports.filter((namespace) => !removed(namespace));
2745
+ const draft = splice([...replacements, ...elided, ...extra, ...pending.map(appended)].sort((a, b) => a.start - b.start || b.end - a.end));
2746
+ let kept = false;
2747
+ for (const namespace of pending) {
2748
+ if (namespace.bindings.some((binding) => draft.includes(binding)))
2749
+ kept = true;
2750
+ else
2751
+ extra.push(appended(namespace));
2752
+ }
2753
+ elided.push(...extra);
2754
+ if (!kept && namespaces.separated) {
2755
+ const first = namespaces.imports[0].appendedStart;
2756
+ for (const replacement of elided)
2757
+ if (replacement.start === first)
2758
+ replacement.start = namespaces.originalLength;
2759
+ }
2760
+ };
2761
+ const elidedImports = (ast, baseByteOffset, replacements, transformedSource) => {
2762
+ if (replacements.length === 0)
2763
+ return [];
2764
+ const overlaps = (start, end) => replacements.some((r) => r.start <= end && r.end >= start);
2765
+ const covered = (position) => replacements.some((r) => r.start <= position && r.end > position);
2766
+ const imports = ast.body.filter((statement) => statement.type === 'ImportDeclaration' &&
2767
+ !statement.typeOnly &&
2768
+ statement.specifiers.length > 0 &&
2769
+ statement.specifiers.every((specifier) => specifier.type !== 'ImportSpecifier' || !specifier.isTypeOnly) &&
2770
+ !overlaps(statement.span.start - baseByteOffset, statement.span.end - baseByteOffset));
2771
+ if (imports.length === 0)
2772
+ return [];
2773
+ const positionsOf = new Map();
2774
+ const { names } = (0, references_1.collectReferenceIdentifiers)(ast, { runtimeReads: true });
2775
+ for (const [start, name] of names) {
2776
+ const positions = positionsOf.get(name);
2777
+ if (positions)
2778
+ positions.push(start - baseByteOffset);
2779
+ else
2780
+ positionsOf.set(name, [start - baseByteOffset]);
2781
+ }
2782
+ const candidates = imports.filter((node) => node.specifiers.every(({ local }) => {
2783
+ const positions = positionsOf.get(local.value);
2784
+ return positions !== undefined && positions.every(covered);
2785
+ }));
2786
+ if (candidates.length === 0)
2787
+ return [];
2788
+ let output;
2789
+ try {
2790
+ output = (0, core_1.parseSync)(transformedSource, {
2791
+ syntax: 'typescript',
2792
+ tsx: true,
2793
+ target: 'es2022',
2794
+ });
2795
+ }
2796
+ catch {
2797
+ return [];
2798
+ }
2799
+ const remaining = new Set((0, references_1.collectReferenceIdentifiers)(output, { runtimeReads: true }).names.values());
2800
+ return candidates
2801
+ .filter((node) => node.specifiers.every(({ local }) => !remaining.has(local.value)))
2802
+ .map((node) => ({
2803
+ start: node.span.start - baseByteOffset,
2804
+ end: node.span.end - baseByteOffset,
2805
+ content: '',
2806
+ }));
2700
2807
  };
2701
- exports.transformSource = transformSource;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumeria/utils",
3
- "version": "21.1.7",
3
+ "version": "21.2.0",
4
4
  "description": "Plumeria Utils",
5
5
  "author": "Refirst 11",
6
6
  "license": "MIT",