@jk2908/cxx 0.2.4 → 0.3.1

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.
@@ -4,38 +4,61 @@ var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
7
12
  var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
8
20
  target = mod != null ? __create(__getProtoOf(mod)) : {};
9
21
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
- for (let key of __getOwnPropNames(mod))
11
- if (!__hasOwnProp.call(to, key))
12
- __defProp(to, key, {
13
- get: () => mod[key],
14
- enumerable: true
15
- });
22
+ if (mod && typeof mod === "object" || typeof mod === "function") {
23
+ for (let key of __getOwnPropNames(mod))
24
+ if (!__hasOwnProp.call(to, key))
25
+ __defProp(to, key, {
26
+ get: __accessProp.bind(mod, key),
27
+ enumerable: true
28
+ });
29
+ }
30
+ if (canCache)
31
+ cache.set(mod, to);
16
32
  return to;
17
33
  };
18
- var __moduleCache = /* @__PURE__ */ new WeakMap;
19
34
  var __toCommonJS = (from) => {
20
- var entry = __moduleCache.get(from), desc;
35
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
21
36
  if (entry)
22
37
  return entry;
23
38
  entry = __defProp({}, "__esModule", { value: true });
24
- if (from && typeof from === "object" || typeof from === "function")
25
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
26
- get: () => from[key],
27
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
28
- }));
39
+ if (from && typeof from === "object" || typeof from === "function") {
40
+ for (var key of __getOwnPropNames(from))
41
+ if (!__hasOwnProp.call(entry, key))
42
+ __defProp(entry, key, {
43
+ get: __accessProp.bind(from, key),
44
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
45
+ });
46
+ }
29
47
  __moduleCache.set(from, entry);
30
48
  return entry;
31
49
  };
50
+ var __moduleCache;
51
+ var __returnValue = (v) => v;
52
+ function __exportSetter(name, newValue) {
53
+ this[name] = __returnValue.bind(null, newValue);
54
+ }
32
55
  var __export = (target, all) => {
33
56
  for (var name in all)
34
57
  __defProp(target, name, {
35
58
  get: all[name],
36
59
  enumerable: true,
37
60
  configurable: true,
38
- set: (newValue) => all[name] = () => newValue
61
+ set: __exportSetter.bind(all, name)
39
62
  });
40
63
  };
41
64
 
@@ -54,20 +77,14 @@ var LEVELS = {
54
77
  error: 3,
55
78
  fatal: 4
56
79
  };
80
+ var defaultLevel = process.env["NODE_ENV"] === "production" ? "error" : "info";
57
81
 
58
82
  class Logger {
59
83
  name;
60
- static #defaultLevel = "info";
61
84
  #level;
62
85
  constructor(level, name) {
63
86
  this.name = name;
64
- this.#level = level;
65
- }
66
- static set defaultLevel(level) {
67
- Logger.#defaultLevel = level;
68
- }
69
- static get defaultLevel() {
70
- return Logger.#defaultLevel;
87
+ this.#level = level ?? defaultLevel;
71
88
  }
72
89
  static toError(err) {
73
90
  return err instanceof Error ? err : new Error(String(err), { cause: err });
@@ -90,7 +107,7 @@ ${err.stack}` : "");
90
107
  this.#level = level;
91
108
  }
92
109
  get level() {
93
- return this.#level ?? Logger.#defaultLevel;
110
+ return this.#level;
94
111
  }
95
112
  log(level, message, error) {
96
113
  if (LEVELS[level] < LEVELS[this.level])
@@ -137,13 +154,13 @@ ${Logger.print(entry.error)}` : "";
137
154
  var import_promises2 = __toESM(require("node:fs/promises"));
138
155
  var import_node_path2 = __toESM(require("node:path"));
139
156
  var import_node_url = require("node:url");
140
- var import_watcher = __toESM(require("@parcel/watcher"));
141
157
 
142
- // src/index.ts
158
+ // src/build.ts
143
159
  var import_node_crypto = require("node:crypto");
144
160
  var import_promises = __toESM(require("node:fs/promises"));
145
161
  var import_node_path = __toESM(require("node:path"));
146
162
  var import_lightningcss = require("lightningcss");
163
+ var import_magic_string = __toESM(require("magic-string"));
147
164
 
148
165
  // src/config.ts
149
166
  var NAME = "cxx";
@@ -194,19 +211,32 @@ function pascalise(str) {
194
211
  return str.split(/[ _-]+/).map(capitalise).join("");
195
212
  }
196
213
 
197
- // src/index.ts
214
+ // src/build.ts
215
+ var decoder = new TextDecoder;
198
216
  var collectCache = new LruCache;
199
217
  var fileCache = new LruCache;
218
+ var writeChain = Promise.resolve();
200
219
  function isENOENT(err) {
201
220
  return err instanceof Error && "code" in err && err.code === "ENOENT";
202
221
  }
222
+ async function writeFileAtomic(filePath, content) {
223
+ const tmp = `${filePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
224
+ await import_promises.default.writeFile(tmp, content);
225
+ await import_promises.default.rename(tmp, filePath);
226
+ }
227
+ function enqueueWrite(task) {
228
+ const run = writeChain.then(task, task);
229
+ writeChain = run.then(() => {
230
+ return;
231
+ }, () => {
232
+ return;
233
+ });
234
+ return run;
235
+ }
203
236
  async function maybeWrite(filePath, content) {
204
237
  const cached = fileCache.get(filePath);
205
- if (cached !== content) {
206
- await import_promises.default.writeFile(filePath, content);
207
- fileCache.set(filePath, content);
208
- return true;
209
- }
238
+ if (cached === content)
239
+ return false;
210
240
  try {
211
241
  if (await import_promises.default.readFile(filePath, "utf-8") === content) {
212
242
  fileCache.set(filePath, content);
@@ -216,26 +246,42 @@ async function maybeWrite(filePath, content) {
216
246
  if (!isENOENT(err))
217
247
  throw err;
218
248
  }
219
- await import_promises.default.writeFile(filePath, content);
249
+ await writeFileAtomic(filePath, content);
220
250
  fileCache.set(filePath, content);
221
251
  return true;
222
252
  }
223
- var regex = /(?:const|var|let)\s*\[(\w+|)\s*,?\s*(\w+|)\s*,?\s*(\w+|)\s*\]\s*=\s*cxx(?:\s*`([\s\S]*?)`|\s*\.\s*tag(?:\s*<[^>]+>)?\s*\(\s*(['"])(.*?)\5\s*\)\s*`([\s\S]*?)`)/gm;
253
+ var regex = /((?:export\s+)?)(?:const|var|let)\s*\[(\w*)\s*,?\s*(\w*)\s*,?\s*(\w*)\s*\]\s*=\s*cxx(?:\s*`([\s\S]*?)`|\s*\.\s*tag(?:\s*<[^>]+>)?\s*\(\s*(['"])(.*?)\6\s*\)\s*`([\s\S]*?)`)/gm;
254
+ var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
224
255
  function hashCss(code) {
225
256
  return `cxx-${import_node_crypto.createHash("sha256").update(code).digest("base64url").slice(0, 12)}`;
226
257
  }
227
258
 
228
- class TagDuplicateError extends Error {
259
+ class DuplicateTagError extends Error {
260
+ name = "DuplicateTagError";
229
261
  constructor(tag) {
230
262
  super(`Tag ${tag} has already been seen. Tags must be unique across your application`);
231
263
  }
232
264
  }
265
+
266
+ class InvalidTagError extends Error {
267
+ name = "InvalidTagError";
268
+ constructor(tag) {
269
+ super(`Tag "${tag}" does not map to a valid TypeScript identifier. ` + `Use letters, numbers, spaces, "_" or "-"`);
270
+ }
271
+ }
233
272
  function collect(source, id, config) {
234
- const tags = new Map;
273
+ const configKey = JSON.stringify(config);
235
274
  const cached = collectCache.get(id);
236
- if (cached?.source === source)
275
+ if (cached?.source === source && cached.config === configKey)
237
276
  return cached;
238
- const template = source.replace(regex, (_match, varOne, varTwo, varThree, plainCss, _quote, tag, taggedCss) => {
277
+ const tags = new Map;
278
+ const magic = new import_magic_string.default(source);
279
+ let matched = false;
280
+ regex.lastIndex = 0;
281
+ let match;
282
+ while (match = regex.exec(source)) {
283
+ matched = true;
284
+ const [, exp, varOne, varTwo, varThree, plainCss, , tag, taggedCss] = match;
239
285
  const css = plainCss ?? taggedCss;
240
286
  const { code, exports: exports2 = {} } = import_lightningcss.transform({
241
287
  minify: true,
@@ -248,169 +294,220 @@ function collect(source, id, config) {
248
294
  if (tag) {
249
295
  const suffix = typeof config.typeSuffix === "string" ? config.typeSuffix : config.typeSuffix === false ? "" : "Classes";
250
296
  const tagWithSuffix = pascalise(`${tag}${suffix}`);
297
+ if (!IDENTIFIER.test(tagWithSuffix))
298
+ throw new InvalidTagError(tag);
251
299
  if (tags.has(tagWithSuffix))
252
- throw new TagDuplicateError(tagWithSuffix);
300
+ throw new DuplicateTagError(tagWithSuffix);
253
301
  tags.set(tagWithSuffix, Object.keys(styles));
254
302
  }
255
- return [
256
- varOne && `const ${varOne} = \`${code}\``,
257
- varTwo && `const ${varTwo} = ${JSON.stringify(styles)}`,
258
- varThree && `const ${varThree} = \`${href}\``
259
- ].filter(Boolean).join(`
303
+ const statements = [];
304
+ if (varOne) {
305
+ statements.push(`${exp}const ${varOne} = ${JSON.stringify(decoder.decode(code))}`);
306
+ }
307
+ if (varTwo) {
308
+ statements.push(`${exp}const ${varTwo} = ${JSON.stringify(styles)}`);
309
+ }
310
+ if (varThree) {
311
+ statements.push(`${exp}const ${varThree} = ${JSON.stringify(href)}`);
312
+ }
313
+ magic.overwrite(match.index, match.index + match[0].length, statements.join(`
314
+ `));
315
+ }
316
+ const template = magic.toString();
317
+ const map = matched ? magic.generateMap({ source: id, includeContent: true, hires: true }) : null;
318
+ const result = { source, config: configKey, template, tags, map };
319
+ collectCache.set(id, result);
320
+ return result;
321
+ }
322
+ function renderTypes(tags) {
323
+ return [
324
+ AUTOGEN_MSG,
325
+ "",
326
+ `import '${PKG_NAME}'`,
327
+ "",
328
+ `declare module '${PKG_NAME}' {`,
329
+ tagsToType(tags),
330
+ "}",
331
+ ""
332
+ ].join(`
260
333
  `);
261
- });
262
- collectCache.set(id, { source, template, tags });
263
- return {
264
- template,
265
- tags
266
- };
267
334
  }
268
- async function writeTypes(tags) {
335
+ async function writeRenderedTypes(tags) {
269
336
  const filePath = import_node_path.default.join(GENERATED_DIR, "index.d.ts");
270
337
  if (!tags.size) {
271
- try {
272
- await import_promises.default.rm(filePath, { force: true });
273
- fileCache.delete(filePath);
274
- } catch (err) {
275
- if (!isENOENT(err))
276
- throw err;
277
- }
338
+ await import_promises.default.rm(filePath, { force: true });
339
+ fileCache.delete(filePath);
278
340
  return;
279
341
  }
280
342
  await import_promises.default.mkdir(GENERATED_DIR, { recursive: true });
281
- await maybeWrite(filePath, `${AUTOGEN_MSG}
282
-
283
- import '${PKG_NAME}'
284
-
285
- declare module '${PKG_NAME}' {
286
- ${tagsToType(tags)}
287
- }
288
- `);
343
+ await maybeWrite(filePath, renderTypes(tags));
289
344
  }
290
- function tagsToType(tags) {
291
- return [...tags].map(([tag, classes]) => `
292
- export type ${tag} = ${classes.length > 0 ? classes.toSorted((a, b) => a.length - b.length).map((c) => `'${c}'`).join(" | ") : "never"}
293
- `).join(`
294
- `);
345
+ var PARTIAL_DIR = import_node_path.default.join(GENERATED_DIR, "tags");
346
+ function partialPath(filePath) {
347
+ return import_node_path.default.join(PARTIAL_DIR, `${import_node_crypto.createHash("sha1").update(filePath).digest("hex")}.json`);
295
348
  }
296
- function flattenTags(tagsByFile) {
349
+ async function readPartials() {
350
+ let entries;
351
+ try {
352
+ entries = await import_promises.default.readdir(PARTIAL_DIR);
353
+ } catch (err) {
354
+ if (isENOENT(err))
355
+ return new Map;
356
+ throw err;
357
+ }
297
358
  const tags = new Map;
298
- for (const fileTags of tagsByFile.values()) {
299
- for (const [name, classes] of fileTags) {
359
+ for (const entry of entries) {
360
+ if (!entry.endsWith(".json"))
361
+ continue;
362
+ const partial = import_node_path.default.join(PARTIAL_DIR, entry);
363
+ let parsed;
364
+ try {
365
+ parsed = JSON.parse(await import_promises.default.readFile(partial, "utf-8"));
366
+ } catch {
367
+ continue;
368
+ }
369
+ try {
370
+ await import_promises.default.access(parsed.file);
371
+ } catch {
372
+ await import_promises.default.rm(partial, { force: true });
373
+ continue;
374
+ }
375
+ for (const [name, classes] of Object.entries(parsed.tags)) {
300
376
  if (tags.has(name))
301
- throw new TagDuplicateError(name);
377
+ throw new DuplicateTagError(name);
302
378
  tags.set(name, classes);
303
379
  }
304
380
  }
305
381
  return tags;
306
382
  }
307
- var cxx = Object.assign((_) => ["", {}, ""], {
308
- tag: (_) => (_strings) => [
309
- "",
310
- {},
311
- ""
312
- ]
313
- });
383
+ function writeTypesForFile(filePath, tags) {
384
+ return enqueueWrite(async () => {
385
+ await import_promises.default.mkdir(PARTIAL_DIR, { recursive: true });
386
+ const partial = partialPath(filePath);
387
+ if (!tags.size) {
388
+ await import_promises.default.rm(partial, { force: true });
389
+ } else {
390
+ await writeFileAtomic(partial, JSON.stringify({ file: filePath, tags: Object.fromEntries(tags) }));
391
+ }
392
+ await writeRenderedTypes(await readPartials());
393
+ });
394
+ }
395
+ function tagsToType(tags) {
396
+ return [...tags].map(([tag, classes]) => {
397
+ const union = classes.length > 0 ? classes.toSorted((a, b) => a.length - b.length).map((c) => `'${c}'`).join(" | ") : "never";
398
+ return ` export type ${tag} = ${union}`;
399
+ }).join(`
400
+ `);
401
+ }
314
402
 
315
403
  // src/next/index.ts
316
404
  var nextDir = import_node_path2.default.dirname(import_node_url.fileURLToPath("file:///Users/jeromekenway/Documents/dev.nosync/me/repo/cxx/packages/cxx/src/next/index.ts"));
317
405
  var loader = import_node_path2.default.join(nextDir, "cxx-loader.cjs");
318
- var tagsByFile = new Map;
319
406
  var watchedFiles = new Map;
320
- var subscriptionsByDir = new Map;
321
- async function rebuildFile(filePath, event) {
322
- const watchedFile = watchedFiles.get(filePath);
323
- if (!watchedFile)
407
+ var subscription = null;
408
+ var subscriptionPromise = null;
409
+ var cleanupRegistered = false;
410
+ var watcherLogger = null;
411
+ var DEFAULT_IGNORE = [
412
+ "**/node_modules/**",
413
+ "**/.next/**",
414
+ `**/${GENERATED_DIR}/**`,
415
+ "**/.git/**"
416
+ ];
417
+ async function onEvent(event, filePath) {
418
+ const watched = watchedFiles.get(filePath);
419
+ if (!watched)
324
420
  return;
325
- const { pluginConfig, logger } = watchedFile;
326
- const hadTags = tagsByFile.has(filePath);
421
+ const { pluginConfig, logger } = watched;
327
422
  if (event === "delete") {
328
- tagsByFile.delete(filePath);
329
- if (!hadTags)
330
- return;
331
- await writeTypes(flattenTags(tagsByFile));
423
+ await writeTypesForFile(filePath, new Map);
332
424
  logger.info(`updated types from ${filePath}`);
333
425
  return;
334
426
  }
427
+ let source;
335
428
  try {
336
- const source = await import_promises2.default.readFile(filePath, "utf-8");
337
- const { tags } = collect(source, filePath, pluginConfig);
338
- const hasTags = tags.size > 0;
339
- if (hasTags) {
340
- tagsByFile.set(filePath, tags);
341
- } else {
342
- tagsByFile.delete(filePath);
343
- }
344
- if (!hadTags && !hasTags)
345
- return;
346
- await writeTypes(flattenTags(tagsByFile));
347
- logger.info(`updated types from ${filePath}`);
429
+ source = await import_promises2.default.readFile(filePath, "utf-8");
348
430
  } catch (err) {
349
431
  if (!isENOENT(err))
350
432
  throw err;
351
- if (!hadTags)
352
- return;
353
- tagsByFile.delete(filePath);
354
- await writeTypes(flattenTags(tagsByFile));
433
+ await writeTypesForFile(filePath, new Map);
355
434
  logger.info(`updated types from ${filePath}`);
435
+ return;
356
436
  }
437
+ const { tags } = collect(source, filePath, pluginConfig);
438
+ await writeTypesForFile(filePath, tags);
439
+ logger.info(`updated types from ${filePath}`);
440
+ }
441
+ function ensureWatcher(pluginConfig, logger) {
442
+ watcherLogger = logger;
443
+ if (subscriptionPromise)
444
+ return;
445
+ const root = pluginConfig.watch?.root ?? process.cwd();
446
+ const ignore = [...DEFAULT_IGNORE, ...pluginConfig.watch?.ignore ?? []];
447
+ subscriptionPromise = (async () => {
448
+ const { subscribe } = await import("@parcel/watcher");
449
+ subscription = await subscribe(root, (watchErr, events) => {
450
+ if (watchErr) {
451
+ watcherLogger?.error(`failed to watch ${root}`, watchErr);
452
+ return;
453
+ }
454
+ for (const event of events) {
455
+ const filePath = import_node_path2.default.resolve(event.path);
456
+ if (!watchedFiles.has(filePath))
457
+ continue;
458
+ onEvent(event.type, filePath).catch((err) => {
459
+ watcherLogger?.error(`failed to update types from ${filePath}`, err);
460
+ });
461
+ }
462
+ }, { ignore });
463
+ registerCleanup();
464
+ })().catch((err) => {
465
+ watcherLogger?.error("failed to start cxx watcher", err);
466
+ subscriptionPromise = null;
467
+ });
468
+ }
469
+ async function cleanup() {
470
+ const active = subscription;
471
+ subscription = null;
472
+ subscriptionPromise = null;
473
+ watchedFiles.clear();
474
+ if (active)
475
+ await active.unsubscribe();
476
+ }
477
+ async function shutdown(signal) {
478
+ try {
479
+ await cleanup();
480
+ } finally {
481
+ process.kill(process.pid, signal);
482
+ }
483
+ }
484
+ function registerCleanup() {
485
+ if (cleanupRegistered)
486
+ return;
487
+ cleanupRegistered = true;
488
+ process.once("SIGINT", () => void shutdown("SIGINT"));
489
+ process.once("SIGTERM", () => void shutdown("SIGTERM"));
357
490
  }
358
491
  function processFile(source, filePath, pluginConfig, logger) {
359
492
  const resolvedFilePath = import_node_path2.default.resolve(filePath);
360
- const directoryPath = import_node_path2.default.dirname(resolvedFilePath);
361
- let subscription = subscriptionsByDir.get(directoryPath);
362
- if (true) {
493
+ if (process.env["NODE_ENV"] !== "production") {
363
494
  watchedFiles.set(resolvedFilePath, { pluginConfig, logger });
364
- if (!subscription) {
365
- subscription = import_watcher.default.subscribe(directoryPath, async (err, events) => {
366
- if (err) {
367
- logger.error(`failed to update types from ${directoryPath}`, err);
368
- return;
369
- }
370
- for (const event of events) {
371
- const eventFilePath = import_node_path2.default.resolve(event.path);
372
- if (!watchedFiles.has(eventFilePath))
373
- continue;
374
- try {
375
- await rebuildFile(eventFilePath, event.type);
376
- } catch (watchErr) {
377
- logger.error(`failed to update types from ${eventFilePath}`, watchErr);
378
- }
379
- }
380
- });
381
- subscriptionsByDir.set(directoryPath, subscription);
382
- }
495
+ ensureWatcher(pluginConfig, logger);
383
496
  }
384
- const result = collect(source, filePath, pluginConfig);
385
- if (result.tags.size > 0) {
386
- tagsByFile.set(filePath, result.tags);
387
- } else {
388
- tagsByFile.delete(filePath);
389
- }
390
- writeTypes(flattenTags(tagsByFile));
497
+ const result = collect(source, resolvedFilePath, pluginConfig);
498
+ writeTypesForFile(resolvedFilePath, result.tags).catch((err) => {
499
+ logger.error(`failed to write types for ${resolvedFilePath}`, err);
500
+ });
391
501
  return result;
392
502
  }
393
- async function cleanup() {
394
- await Promise.all([...subscriptionsByDir].map(async ([directoryPath, subPromise]) => {
395
- subscriptionsByDir.delete(directoryPath);
396
- await (await subPromise).unsubscribe();
397
- }));
398
- subscriptionsByDir.clear();
399
- }
400
- process.once("SIGINT", () => {
401
- cleanup().finally(() => process.exit(130));
402
- });
403
- process.once("SIGTERM", () => {
404
- cleanup().finally(() => process.exit(143));
405
- });
406
503
 
407
504
  // src/next/cxx-loader.js
408
505
  var loader2 = function(source) {
409
506
  const { pluginConfig } = this.getOptions();
410
- const logger = new Logger(pluginConfig?.logger?.level ?? false ? "error" : "debug");
507
+ const logger = new Logger;
411
508
  const { template } = processFile(source, this.resourcePath, pluginConfig, logger);
412
509
  return template;
413
510
  };
414
511
  var cxx_loader_default = loader2;
415
512
 
416
- //# debugId=6E503B7704B6BACD64756E2164756E21
513
+ //# debugId=9743BC642BE06F6664756E2164756E21