@jk2908/cxx 0.2.3 → 0.2.6

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
@@ -98,7 +98,7 @@ See [CHANGELOG.md](./CHANGELOG.md) for release notes.
98
98
 
99
99
  ## Syntax highlighting
100
100
 
101
- The [`vscode-cxx`](../vscode-cxx) extension provides syntax highlighting for `cxx` and `cxx.tag()` tagged template literals in VS Code. See its [README](../vscode-cxx) for install instructions.
101
+ The [`vscode-cxx`](https://github.com/jk2908/cxx/tree/main/packages/vscode-cxx) extension provides syntax highlighting for `cxx` and `cxx.tag()` tagged template literals in VS Code. See its [README](https://github.com/jk2908/cxx/tree/main/packages/vscode-cxx) for install instructions.
102
102
 
103
103
  ## License
104
104
 
@@ -0,0 +1,37 @@
1
+ import type { BuildContext, PluginConfig, Tags } from './types.js';
2
+ /**
3
+ * Returns whether the error was caused by a missing file or directory.
4
+ */
5
+ export declare function isENOENT(err: unknown): boolean;
6
+ /**
7
+ * Write a file only if the content has changed since the last build.
8
+ */
9
+ export declare function maybeWrite(filePath: string, content: string): Promise<boolean>;
10
+ export declare class DuplicateTagError extends Error {
11
+ name: string;
12
+ constructor(tag: string);
13
+ }
14
+ /**
15
+ * Transform `cxx` templates in one source file and collect any exported tag types.
16
+ */
17
+ export declare function collect(source: string, id: string, config: PluginConfig): {
18
+ source: string;
19
+ template: string;
20
+ tags: Tags;
21
+ } | {
22
+ template: string;
23
+ tags: Tags;
24
+ };
25
+ /**
26
+ * Write the generated declaration file for the current set of collected tags.
27
+ */
28
+ export declare function writeTypes(tags: Tags): Promise<void>;
29
+ /**
30
+ * Render collected tags as a module augmentation for the package entrypoint.
31
+ */
32
+ export declare function tagsToType(tags: Tags): string;
33
+ /**
34
+ * Merge per-file tag maps into one application-wide tag map.
35
+ */
36
+ export declare function flattenTags(tagsByFile: BuildContext['tagsByFile']): Map<string, string[]>;
37
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAiBlE;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,WAEpC;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,oBAyBjE;AAeD,qBAAa,iBAAkB,SAAQ,KAAK;IAC3C,IAAI,SAAsB;gBAEd,GAAG,EAAE,MAAM;CAGvB;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;YArE5B,MAAM;cAAY,MAAM;UAAQ,IAAI;;;;EA4H/E;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,IAAI,iBA2B1C;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,UAepC;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,yBAWjE"}
package/dist/build.js ADDED
@@ -0,0 +1,188 @@
1
+ // src/build.ts
2
+ import { createHash } from "node:crypto";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { transform } from "lightningcss";
6
+
7
+ // src/config.ts
8
+ var NAME = "cxx";
9
+ var PKG_NAME = `@jk2908/${NAME}`;
10
+ var GENERATED_DIR = `.${NAME}`;
11
+ var AUTOGEN_MSG = `// auto-generated by ${NAME}`;
12
+
13
+ // src/lru.ts
14
+ var CACHE_MAX_SIZE = 100;
15
+
16
+ class LruCache {
17
+ #cache = new Map;
18
+ #maxSize;
19
+ constructor(maxSize = CACHE_MAX_SIZE) {
20
+ this.#maxSize = maxSize;
21
+ }
22
+ get size() {
23
+ return this.#cache.size;
24
+ }
25
+ get(key) {
26
+ const cached = this.#cache.get(key);
27
+ if (this.#cache.has(key))
28
+ this.set(key, cached);
29
+ return cached;
30
+ }
31
+ set(key, value) {
32
+ this.#cache.delete(key);
33
+ if (this.#cache.size >= this.#maxSize) {
34
+ const lru = this.#cache.keys().next().value;
35
+ if (lru !== undefined)
36
+ this.#cache.delete(lru);
37
+ }
38
+ this.#cache.set(key, value);
39
+ }
40
+ delete(key) {
41
+ return this.#cache.delete(key);
42
+ }
43
+ clear() {
44
+ return this.#cache.clear();
45
+ }
46
+ }
47
+
48
+ // src/utils.ts
49
+ function capitalise(str) {
50
+ return str.charAt(0).toUpperCase() + str.slice(1);
51
+ }
52
+ function pascalise(str) {
53
+ return str.split(/[ _-]+/).map(capitalise).join("");
54
+ }
55
+ function debounce(fn, wait) {
56
+ let timeoutId = null;
57
+ return (...args) => {
58
+ if (timeoutId) {
59
+ clearTimeout(timeoutId);
60
+ }
61
+ timeoutId = setTimeout(() => {
62
+ fn.apply(null, args);
63
+ }, wait);
64
+ };
65
+ }
66
+
67
+ // src/build.ts
68
+ var collectCache = new LruCache;
69
+ var fileCache = new LruCache;
70
+ function isENOENT(err) {
71
+ return err instanceof Error && "code" in err && err.code === "ENOENT";
72
+ }
73
+ async function maybeWrite(filePath, content) {
74
+ const cached = fileCache.get(filePath);
75
+ if (cached !== content) {
76
+ await fs.writeFile(filePath, content);
77
+ fileCache.set(filePath, content);
78
+ return true;
79
+ }
80
+ try {
81
+ if (await fs.readFile(filePath, "utf-8") === content) {
82
+ fileCache.set(filePath, content);
83
+ return false;
84
+ }
85
+ } catch (err) {
86
+ if (!isENOENT(err))
87
+ throw err;
88
+ }
89
+ await fs.writeFile(filePath, content);
90
+ fileCache.set(filePath, content);
91
+ return true;
92
+ }
93
+ 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;
94
+ function hashCss(code) {
95
+ return `cxx-${createHash("sha256").update(code).digest("base64url").slice(0, 12)}`;
96
+ }
97
+
98
+ class DuplicateTagError extends Error {
99
+ name = "DuplicateTagError";
100
+ constructor(tag) {
101
+ super(`Tag ${tag} has already been seen. Tags must be unique across your application`);
102
+ }
103
+ }
104
+ function collect(source, id, config) {
105
+ const tags = new Map;
106
+ const cached = collectCache.get(id);
107
+ if (cached?.source === source)
108
+ return cached;
109
+ const template = source.replace(regex, (_match, varOne, varTwo, varThree, plainCss, _quote, tag, taggedCss) => {
110
+ const css = plainCss ?? taggedCss;
111
+ const { code, exports = {} } = transform({
112
+ minify: true,
113
+ cssModules: true,
114
+ filename: id,
115
+ code: new TextEncoder().encode(css)
116
+ });
117
+ const href = hashCss(code);
118
+ const styles = Object.fromEntries(Object.entries(exports).map(([k, v]) => [k, v.name]));
119
+ if (tag) {
120
+ const suffix = typeof config.typeSuffix === "string" ? config.typeSuffix : config.typeSuffix === false ? "" : "Classes";
121
+ const tagWithSuffix = pascalise(`${tag}${suffix}`);
122
+ if (tags.has(tagWithSuffix))
123
+ throw new DuplicateTagError(tagWithSuffix);
124
+ tags.set(tagWithSuffix, Object.keys(styles));
125
+ }
126
+ return [
127
+ varOne && `const ${varOne} = \`${code}\``,
128
+ varTwo && `const ${varTwo} = ${JSON.stringify(styles)}`,
129
+ varThree && `const ${varThree} = \`${href}\``
130
+ ].filter(Boolean).join(`
131
+ `);
132
+ });
133
+ collectCache.set(id, { source, template, tags });
134
+ return {
135
+ template,
136
+ tags
137
+ };
138
+ }
139
+ async function writeTypes(tags) {
140
+ const filePath = path.join(GENERATED_DIR, "index.d.ts");
141
+ if (!tags.size) {
142
+ try {
143
+ await fs.rm(filePath, { force: true });
144
+ fileCache.delete(filePath);
145
+ } catch (err) {
146
+ if (!isENOENT(err))
147
+ throw err;
148
+ }
149
+ return;
150
+ }
151
+ await fs.mkdir(GENERATED_DIR, { recursive: true });
152
+ await maybeWrite(filePath, `${AUTOGEN_MSG}
153
+
154
+ import '${PKG_NAME}'
155
+
156
+ declare module '${PKG_NAME}' {
157
+ ${tagsToType(tags)}
158
+ }
159
+ `);
160
+ }
161
+ function tagsToType(tags) {
162
+ return [...tags].map(([tag, classes]) => `
163
+ export type ${tag} = ${classes.length > 0 ? classes.toSorted((a, b) => a.length - b.length).map((c) => `'${c}'`).join(" | ") : "never"}
164
+ `).join(`
165
+ `);
166
+ }
167
+ function flattenTags(tagsByFile) {
168
+ const tags = new Map;
169
+ for (const fileTags of tagsByFile.values()) {
170
+ for (const [name, classes] of fileTags) {
171
+ if (tags.has(name))
172
+ throw new DuplicateTagError(name);
173
+ tags.set(name, classes);
174
+ }
175
+ }
176
+ return tags;
177
+ }
178
+ export {
179
+ writeTypes,
180
+ tagsToType,
181
+ maybeWrite,
182
+ isENOENT,
183
+ flattenTags,
184
+ collect,
185
+ DuplicateTagError
186
+ };
187
+
188
+ //# debugId=ADE632508CD5BB0264756E2164756E21
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/build.ts", "../src/config.ts", "../src/lru.ts", "../src/utils.ts"],
4
+ "sourcesContent": [
5
+ "import { createHash } from 'node:crypto'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\n\nimport { transform } from 'lightningcss'\n\nimport type { BuildContext, PluginConfig, Tags } from './types.js'\nimport { AUTOGEN_MSG, GENERATED_DIR, PKG_NAME } from './config.js'\nimport { LruCache } from './lru.js'\nimport { pascalise } from './utils.js'\n\n/**\n * LRU cache of `collect` results by file id and last seen source so watcher and transform can\n * reuse the same parsed template/tags.\n */\nconst collectCache = new LruCache<{ source: string; template: string; tags: Tags }>()\n\n/**\n * LRU cache for file content. Stores the last written content per file path so `maybeWrite`\n * can skip disk i/o when nothing has changed.\n */\nconst fileCache = new LruCache<string>()\n\n/**\n * Returns whether the error was caused by a missing file or directory.\n */\nexport function isENOENT(err: unknown) {\n\treturn err instanceof Error && 'code' in err && err.code === 'ENOENT'\n}\n\n/**\n * Write a file only if the content has changed since the last build.\n */\nexport async function maybeWrite(filePath: string, content: string) {\n\tconst cached = fileCache.get(filePath)\n\n\tif (cached !== content) {\n\t\t// cache says content changed, write without reading\n\t\tawait fs.writeFile(filePath, content)\n\t\tfileCache.set(filePath, content)\n\n\t\treturn true\n\t}\n\n\t// cache miss or cache hit with same content — verify on disk\n\ttry {\n\t\tif ((await fs.readFile(filePath, 'utf-8')) === content) {\n\t\t\tfileCache.set(filePath, content)\n\t\t\treturn false\n\t\t}\n\t} catch (err) {\n\t\tif (!isENOENT(err)) throw err\n\t}\n\n\tawait fs.writeFile(filePath, content)\n\tfileCache.set(filePath, content)\n\n\treturn true\n}\n\n// matches `[css, classes, href] = cxx\\`...\\`` and `cxx.tag<T>('name')\\`...\\``\n// capture groups: 1-3 assigned variable names, 4 plain css body, 5 quote\n// kind, 6 tag name, 7 tagged css body\nconst regex =\n\t/(?: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\n\n/**\n * Hash transformed CSS into a stable href for style deduplication.\n */\nfunction hashCss(code: Uint8Array) {\n\treturn `cxx-${createHash('sha256').update(code).digest('base64url').slice(0, 12)}`\n}\n\nexport class DuplicateTagError extends Error {\n\tname = 'DuplicateTagError'\n\n\tconstructor(tag: string) {\n\t\tsuper(`Tag ${tag} has already been seen. Tags must be unique across your application`)\n\t}\n}\n\n/**\n * Transform `cxx` templates in one source file and collect any exported tag types.\n */\nexport function collect(source: string, id: string, config: PluginConfig) {\n\tconst tags: Tags = new Map()\n\n\tconst cached = collectCache.get(id)\n\tif (cached?.source === source) return cached\n\n\tconst template = source.replace(\n\t\tregex,\n\t\t(_match, varOne, varTwo, varThree, plainCss, _quote, tag, taggedCss) => {\n\t\t\tconst css = plainCss ?? taggedCss\n\n\t\t\tconst { code, exports = {} } = transform({\n\t\t\t\tminify: true,\n\t\t\t\tcssModules: true,\n\t\t\t\tfilename: id,\n\t\t\t\tcode: new TextEncoder().encode(css),\n\t\t\t})\n\n\t\t\tconst href = hashCss(code)\n\t\t\tconst styles = Object.fromEntries(\n\t\t\t\tObject.entries(exports).map(([k, v]) => [k, v.name]),\n\t\t\t)\n\n\t\t\tif (tag) {\n\t\t\t\tconst suffix =\n\t\t\t\t\ttypeof config.typeSuffix === 'string'\n\t\t\t\t\t\t? config.typeSuffix\n\t\t\t\t\t\t: config.typeSuffix === false\n\t\t\t\t\t\t\t? ''\n\t\t\t\t\t\t\t: 'Classes'\n\t\t\t\tconst tagWithSuffix = pascalise(`${tag}${suffix}`)\n\n\t\t\t\tif (tags.has(tagWithSuffix)) throw new DuplicateTagError(tagWithSuffix)\n\n\t\t\t\t// side effect but neccessary\n\t\t\t\ttags.set(tagWithSuffix, Object.keys(styles))\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\tvarOne && `const ${varOne} = \\`${code}\\``,\n\t\t\t\tvarTwo && `const ${varTwo} = ${JSON.stringify(styles)}`,\n\t\t\t\tvarThree && `const ${varThree} = \\`${href}\\``,\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join('\\n')\n\t\t},\n\t)\n\n\t// cache\n\tcollectCache.set(id, { source, template, tags })\n\n\treturn {\n\t\ttemplate,\n\t\ttags,\n\t}\n}\n\n/**\n * Write the generated declaration file for the current set of collected tags.\n */\nexport async function writeTypes(tags: Tags) {\n\tconst filePath = path.join(GENERATED_DIR, 'index.d.ts')\n\n\tif (!tags.size) {\n\t\ttry {\n\t\t\tawait fs.rm(filePath, { force: true })\n\t\t\tfileCache.delete(filePath)\n\t\t} catch (err) {\n\t\t\tif (!isENOENT(err)) throw err\n\t\t}\n\n\t\treturn\n\t}\n\n\tawait fs.mkdir(GENERATED_DIR, { recursive: true })\n\n\tawait maybeWrite(\n\t\tfilePath,\n\t\t`${AUTOGEN_MSG}\n\n\t\t\timport '${PKG_NAME}'\n\n\t\t\tdeclare module '${PKG_NAME}' {\n\t\t\t\t${tagsToType(tags)}\n\t\t\t}\n\t\t`,\n\t)\n}\n\n/**\n * Render collected tags as a module augmentation for the package entrypoint.\n */\nexport function tagsToType(tags: Tags) {\n\treturn [...tags]\n\t\t.map(\n\t\t\t([tag, classes]) => `\n\t\texport type ${tag} = ${\n\t\t\tclasses.length > 0\n\t\t\t\t? classes\n\t\t\t\t\t\t.toSorted((a, b) => a.length - b.length)\n\t\t\t\t\t\t.map(c => `'${c}'`)\n\t\t\t\t\t\t.join(' | ')\n\t\t\t\t: 'never'\n\t\t}\n\t`,\n\t\t)\n\t\t.join('\\n')\n}\n\n/**\n * Merge per-file tag maps into one application-wide tag map.\n */\nexport function flattenTags(tagsByFile: BuildContext['tagsByFile']) {\n\tconst tags = new Map<string, string[]>()\n\n\tfor (const fileTags of tagsByFile.values()) {\n\t\tfor (const [name, classes] of fileTags) {\n\t\t\tif (tags.has(name)) throw new DuplicateTagError(name)\n\t\t\ttags.set(name, classes)\n\t\t}\n\t}\n\n\treturn tags\n}\n",
6
+ "export const NAME = 'cxx'\nexport const PKG_NAME = `@jk2908/${NAME}`\nexport const GENERATED_DIR = `.${NAME}`\nexport const AUTOGEN_MSG = `// auto-generated by ${NAME}`\n",
7
+ "export const CACHE_MAX_SIZE = 100\n\nexport class LruCache<T> {\n\t#cache: Map<string, T> = new Map()\n\t#maxSize: number\n\n\tconstructor(maxSize = CACHE_MAX_SIZE) {\n\t\tthis.#maxSize = maxSize\n\t}\n\n\tget size() {\n\t\treturn this.#cache.size\n\t}\n\n\t/**\n\t * Retrieve a cached value and promote it to most-recently-used so it won't\n\t * be evicted while still actively referenced.\n\t */\n\tget(key: string) {\n\t\tconst cached = this.#cache.get(key)\n\t\tif (this.#cache.has(key)) this.set(key, cached as T)\n\n\t\treturn cached\n\t}\n\n\t/**\n\t * Promote an existing cache entry to most-recently-used by deleting and\n\t * re-inserting it, which moves it to the end of the Map's iteration\n\t * order. If the cache is at capacity, evict the least recently used\n\t * entry (front) before inserting.\n\t */\n\tset(key: string, value: T) {\n\t\t// drop any existing entry\n\t\tthis.#cache.delete(key)\n\n\t\tif (this.#cache.size >= this.#maxSize) {\n\t\t\tconst lru = this.#cache.keys().next().value\n\t\t\tif (lru !== undefined) this.#cache.delete(lru)\n\t\t}\n\n\t\tthis.#cache.set(key, value)\n\t}\n\n\tdelete(key: string) {\n\t\treturn this.#cache.delete(key)\n\t}\n\n\tclear() {\n\t\treturn this.#cache.clear()\n\t}\n}\n",
8
+ "export function capitalise(str: string) {\n\treturn str.charAt(0).toUpperCase() + str.slice(1)\n}\n\nexport function pascalise(str: string) {\n\treturn str\n\t\t.split(/[ _-]+/)\n\t\t.map(capitalise)\n\t\t.join('')\n}\n\nexport function debounce<T extends unknown[]>(fn: (...args: T) => void, wait: number) {\n\tlet timeoutId: ReturnType<typeof setTimeout> | null = null\n\n\treturn (...args: T) => {\n\t\tif (timeoutId) {\n\t\t\tclearTimeout(timeoutId)\n\t\t}\n\n\t\ttimeoutId = setTimeout(() => {\n\t\t\tfn.apply(null, args)\n\t\t}, wait)\n\t}\n}\n"
9
+ ],
10
+ "mappings": "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",
11
+ "debugId": "ADE632508CD5BB0264756E2164756E21",
12
+ "names": []
13
+ }
package/dist/index.d.ts CHANGED
@@ -1,40 +1,7 @@
1
- import type { BuildContext, Cxx, PluginConfig, Tags } from './types.js';
1
+ import type { Cxx } from './types.js';
2
+ export type { Cxx, Result, Tag } from './types.js';
2
3
  /**
3
- * Returns whether the error was caused by a missing file or directory
4
- */
5
- export declare function isENOENT(err: unknown): boolean;
6
- /**
7
- * Write a file only if the content has changed since the last build
8
- */
9
- export declare function maybeWrite(filePath: string, content: string): Promise<boolean>;
10
- export declare class TagDuplicateError extends Error {
11
- constructor(tag: string);
12
- }
13
- /**
14
- * Transform `cxx` templates in one source file and collect any exported tag types
15
- */
16
- export declare function collect(source: string, id: string, config: PluginConfig): {
17
- source: string;
18
- template: string;
19
- tags: Tags;
20
- } | {
21
- template: string;
22
- tags: Tags;
23
- };
24
- /**
25
- * Write the generated declaration file for the current set of collected tags
26
- */
27
- export declare function writeTypes(tags: Tags): Promise<void>;
28
- /**
29
- * Render collected tags as a module augmentation for the package entrypoint
30
- */
31
- export declare function tagsToType(tags: Tags): string;
32
- /**
33
- * Merge per-file tag maps into one application-wide tag map
34
- */
35
- export declare function flattenTags(tagsByFile: BuildContext['tagsByFile']): Map<string, string[]>;
36
- /**
37
- * Build-time template tag placeholder used by the transform to emit CSS, classes and href values
4
+ * Build-time template tag placeholder used by the transform to emit CSS, classes and href values.
38
5
  */
39
6
  export declare const cxx: Cxx;
40
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,EAAU,IAAI,EAAE,MAAM,YAAY,CAAA;AAiB/E;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,WAEpC;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,oBAyBjE;AAeD,qBAAa,iBAAkB,SAAQ,KAAK;gBAC/B,GAAG,EAAE,MAAM;CAGvB;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;YAnE5B,MAAM;cAAY,MAAM;UAAQ,IAAI;;;;EA0H/E;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,IAAI,iBA2B1C;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,UAepC;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,yBAWjE;AAED;;GAEG;AACH,eAAO,MAAM,GAAG,EAAE,GAQhB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAU,MAAM,YAAY,CAAA;AAE7C,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,YAAY,CAAA;AAElD;;GAEG;AACH,eAAO,MAAM,GAAG,EAAE,GAQhB,CAAA"}
package/dist/index.js CHANGED
@@ -1,179 +1,4 @@
1
1
  // src/index.ts
2
- import { createHash } from "node:crypto";
3
- import fs from "node:fs/promises";
4
- import path from "node:path";
5
- import { transform } from "lightningcss";
6
-
7
- // src/config.ts
8
- var NAME = "cxx";
9
- var PKG_NAME = `@jk2908/${NAME}`;
10
- var GENERATED_DIR = `.${NAME}`;
11
- var AUTOGEN_MSG = `// auto-generated by ${NAME}`;
12
-
13
- // src/lru.ts
14
- var CACHE_MAX_SIZE = 100;
15
-
16
- class LruCache {
17
- #cache = new Map;
18
- #maxSize;
19
- constructor(maxSize = CACHE_MAX_SIZE) {
20
- this.#maxSize = maxSize;
21
- }
22
- get size() {
23
- return this.#cache.size;
24
- }
25
- get(key) {
26
- const cached = this.#cache.get(key);
27
- if (this.#cache.has(key))
28
- this.set(key, cached);
29
- return cached;
30
- }
31
- set(key, value) {
32
- this.#cache.delete(key);
33
- if (this.#cache.size >= this.#maxSize) {
34
- const lru = this.#cache.keys().next().value;
35
- if (lru !== undefined)
36
- this.#cache.delete(lru);
37
- }
38
- this.#cache.set(key, value);
39
- }
40
- delete(key) {
41
- return this.#cache.delete(key);
42
- }
43
- clear() {
44
- return this.#cache.clear();
45
- }
46
- }
47
-
48
- // src/utils.ts
49
- function capitalise(str) {
50
- return str.charAt(0).toUpperCase() + str.slice(1);
51
- }
52
- function pascalise(str) {
53
- return str.split(/[ _-]+/).map(capitalise).join("");
54
- }
55
- function debounce(fn, wait) {
56
- let timeoutId = null;
57
- return (...args) => {
58
- if (timeoutId) {
59
- clearTimeout(timeoutId);
60
- }
61
- timeoutId = setTimeout(() => {
62
- fn.apply(null, args);
63
- }, wait);
64
- };
65
- }
66
-
67
- // src/index.ts
68
- var collectCache = new LruCache;
69
- var fileCache = new LruCache;
70
- function isENOENT(err) {
71
- return err instanceof Error && "code" in err && err.code === "ENOENT";
72
- }
73
- async function maybeWrite(filePath, content) {
74
- const cached = fileCache.get(filePath);
75
- if (cached !== content) {
76
- await fs.writeFile(filePath, content);
77
- fileCache.set(filePath, content);
78
- return true;
79
- }
80
- try {
81
- if (await fs.readFile(filePath, "utf-8") === content) {
82
- fileCache.set(filePath, content);
83
- return false;
84
- }
85
- } catch (err) {
86
- if (!isENOENT(err))
87
- throw err;
88
- }
89
- await fs.writeFile(filePath, content);
90
- fileCache.set(filePath, content);
91
- return true;
92
- }
93
- 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;
94
- function hashCss(code) {
95
- return `cxx-${createHash("sha256").update(code).digest("base64url").slice(0, 12)}`;
96
- }
97
-
98
- class TagDuplicateError extends Error {
99
- constructor(tag) {
100
- super(`Tag ${tag} has already been seen. Tags must be unique across your application`);
101
- }
102
- }
103
- function collect(source, id, config) {
104
- const tags = new Map;
105
- const cached = collectCache.get(id);
106
- if (cached?.source === source)
107
- return cached;
108
- const template = source.replace(regex, (_match, varOne, varTwo, varThree, plainCss, _quote, tag, taggedCss) => {
109
- const css = plainCss ?? taggedCss;
110
- const { code, exports = {} } = transform({
111
- minify: true,
112
- cssModules: true,
113
- filename: id,
114
- code: new TextEncoder().encode(css)
115
- });
116
- const href = hashCss(code);
117
- const styles = Object.fromEntries(Object.entries(exports).map(([k, v]) => [k, v.name]));
118
- if (tag) {
119
- const suffix = typeof config.typeSuffix === "string" ? config.typeSuffix : config.typeSuffix === false ? "" : "Classes";
120
- const tagWithSuffix = pascalise(`${tag}${suffix}`);
121
- if (tags.has(tagWithSuffix))
122
- throw new TagDuplicateError(tagWithSuffix);
123
- tags.set(tagWithSuffix, Object.keys(styles));
124
- }
125
- return [
126
- varOne && `const ${varOne} = \`${code}\``,
127
- varTwo && `const ${varTwo} = ${JSON.stringify(styles)}`,
128
- varThree && `const ${varThree} = \`${href}\``
129
- ].filter(Boolean).join(`
130
- `);
131
- });
132
- collectCache.set(id, { source, template, tags });
133
- return {
134
- template,
135
- tags
136
- };
137
- }
138
- async function writeTypes(tags) {
139
- const filePath = path.join(GENERATED_DIR, "index.d.ts");
140
- if (!tags.size) {
141
- try {
142
- await fs.rm(filePath, { force: true });
143
- fileCache.delete(filePath);
144
- } catch (err) {
145
- if (!isENOENT(err))
146
- throw err;
147
- }
148
- return;
149
- }
150
- await fs.mkdir(GENERATED_DIR, { recursive: true });
151
- await maybeWrite(filePath, `${AUTOGEN_MSG}
152
-
153
- import '${PKG_NAME}'
154
-
155
- declare module '${PKG_NAME}' {
156
- ${tagsToType(tags)}
157
- }
158
- `);
159
- }
160
- function tagsToType(tags) {
161
- return [...tags].map(([tag, classes]) => `
162
- export type ${tag} = ${classes.length > 0 ? classes.toSorted((a, b) => a.length - b.length).map((c) => `'${c}'`).join(" | ") : "never"}
163
- `).join(`
164
- `);
165
- }
166
- function flattenTags(tagsByFile) {
167
- const tags = new Map;
168
- for (const fileTags of tagsByFile.values()) {
169
- for (const [name, classes] of fileTags) {
170
- if (tags.has(name))
171
- throw new TagDuplicateError(name);
172
- tags.set(name, classes);
173
- }
174
- }
175
- return tags;
176
- }
177
2
  var cxx = Object.assign((_) => ["", {}, ""], {
178
3
  tag: (_) => (_strings) => [
179
4
  "",
@@ -182,14 +7,7 @@ var cxx = Object.assign((_) => ["", {}, ""], {
182
7
  ]
183
8
  });
184
9
  export {
185
- writeTypes,
186
- tagsToType,
187
- maybeWrite,
188
- isENOENT,
189
- flattenTags,
190
- cxx,
191
- collect,
192
- TagDuplicateError
10
+ cxx
193
11
  };
194
12
 
195
- //# debugId=D5DA13CCB4684E3C64756E2164756E21
13
+ //# debugId=40D13C798825EC8F64756E2164756E21
package/dist/index.js.map CHANGED
@@ -1,13 +1,10 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/index.ts", "../src/config.ts", "../src/lru.ts", "../src/utils.ts"],
3
+ "sources": ["../src/index.ts"],
4
4
  "sourcesContent": [
5
- "import { createHash } from 'node:crypto'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\n\nimport { transform } from 'lightningcss'\n\nimport type { BuildContext, Cxx, PluginConfig, Result, Tags } from './types.js'\nimport { AUTOGEN_MSG, GENERATED_DIR, PKG_NAME } from './config.js'\nimport { LruCache } from './lru.js'\nimport { pascalise } from './utils.js'\n\n/**\n * LRU cache of `collect` results by file id and last seen source so watcher and transform can\n * reuse the same parsed template/tags\n */\nconst collectCache = new LruCache<{ source: string; template: string; tags: Tags }>()\n\n/**\n * LRU cache for file content. Stores the last written content per file path so `maybeWrite`\n * can skip disk i/o when nothing has changed\n */\nconst fileCache = new LruCache<string>()\n\n/**\n * Returns whether the error was caused by a missing file or directory\n */\nexport function isENOENT(err: unknown) {\n\treturn err instanceof Error && 'code' in err && err.code === 'ENOENT'\n}\n\n/**\n * Write a file only if the content has changed since the last build\n */\nexport async function maybeWrite(filePath: string, content: string) {\n\tconst cached = fileCache.get(filePath)\n\n\tif (cached !== content) {\n\t\t// cache says content changed, write without reading\n\t\tawait fs.writeFile(filePath, content)\n\t\tfileCache.set(filePath, content)\n\n\t\treturn true\n\t}\n\n\t// cache miss or cache hit with same content — verify on disk\n\ttry {\n\t\tif ((await fs.readFile(filePath, 'utf-8')) === content) {\n\t\t\tfileCache.set(filePath, content)\n\t\t\treturn false\n\t\t}\n\t} catch (err) {\n\t\tif (!isENOENT(err)) throw err\n\t}\n\n\tawait fs.writeFile(filePath, content)\n\tfileCache.set(filePath, content)\n\n\treturn true\n}\n\n// matches `[css, classes, href] = cxx\\`...\\`` and `cxx.tag<T>('name')\\`...\\``\n// capture groups: 1-3 assigned variable names, 4 plain css body, 5 quote\n// kind, 6 tag name, 7 tagged css body\nconst regex =\n\t/(?: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\n\n/**\n * Hash transformed CSS into a stable href for style deduplication\n */\nfunction hashCss(code: Uint8Array) {\n\treturn `cxx-${createHash('sha256').update(code).digest('base64url').slice(0, 12)}`\n}\n\nexport class TagDuplicateError extends Error {\n\tconstructor(tag: string) {\n\t\tsuper(`Tag ${tag} has already been seen. Tags must be unique across your application`)\n\t}\n}\n\n/**\n * Transform `cxx` templates in one source file and collect any exported tag types\n */\nexport function collect(source: string, id: string, config: PluginConfig) {\n\tconst tags: Tags = new Map()\n\n\tconst cached = collectCache.get(id)\n\tif (cached?.source === source) return cached\n\n\tconst template = source.replace(\n\t\tregex,\n\t\t(_match, varOne, varTwo, varThree, plainCss, _quote, tag, taggedCss) => {\n\t\t\tconst css = plainCss ?? taggedCss\n\n\t\t\tconst { code, exports = {} } = transform({\n\t\t\t\tminify: true,\n\t\t\t\tcssModules: true,\n\t\t\t\tfilename: id,\n\t\t\t\tcode: new TextEncoder().encode(css),\n\t\t\t})\n\n\t\t\tconst href = hashCss(code)\n\t\t\tconst styles = Object.fromEntries(\n\t\t\t\tObject.entries(exports).map(([k, v]) => [k, v.name]),\n\t\t\t)\n\n\t\t\tif (tag) {\n\t\t\t\tconst suffix =\n\t\t\t\t\ttypeof config.typeSuffix === 'string'\n\t\t\t\t\t\t? config.typeSuffix\n\t\t\t\t\t\t: config.typeSuffix === false\n\t\t\t\t\t\t\t? ''\n\t\t\t\t\t\t\t: 'Classes'\n\t\t\t\tconst tagWithSuffix = pascalise(`${tag}${suffix}`)\n\n\t\t\t\tif (tags.has(tagWithSuffix)) throw new TagDuplicateError(tagWithSuffix)\n\n\t\t\t\t// side effect but neccessary\n\t\t\t\ttags.set(tagWithSuffix, Object.keys(styles))\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\tvarOne && `const ${varOne} = \\`${code}\\``,\n\t\t\t\tvarTwo && `const ${varTwo} = ${JSON.stringify(styles)}`,\n\t\t\t\tvarThree && `const ${varThree} = \\`${href}\\``,\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join('\\n')\n\t\t},\n\t)\n\n\t// cache\n\tcollectCache.set(id, { source, template, tags })\n\n\treturn {\n\t\ttemplate,\n\t\ttags,\n\t}\n}\n\n/**\n * Write the generated declaration file for the current set of collected tags\n */\nexport async function writeTypes(tags: Tags) {\n\tconst filePath = path.join(GENERATED_DIR, 'index.d.ts')\n\n\tif (!tags.size) {\n\t\ttry {\n\t\t\tawait fs.rm(filePath, { force: true })\n\t\t\tfileCache.delete(filePath)\n\t\t} catch (err) {\n\t\t\tif (!isENOENT(err)) throw err\n\t\t}\n\n\t\treturn\n\t}\n\n\tawait fs.mkdir(GENERATED_DIR, { recursive: true })\n\n\tawait maybeWrite(\n\t\tfilePath,\n\t\t`${AUTOGEN_MSG}\n\n\t\t\timport '${PKG_NAME}'\n\n\t\t\tdeclare module '${PKG_NAME}' {\n\t\t\t\t${tagsToType(tags)}\n\t\t\t}\n\t\t`,\n\t)\n}\n\n/**\n * Render collected tags as a module augmentation for the package entrypoint\n */\nexport function tagsToType(tags: Tags) {\n\treturn [...tags]\n\t\t.map(\n\t\t\t([tag, classes]) => `\n\t\texport type ${tag} = ${\n\t\t\tclasses.length > 0\n\t\t\t\t? classes\n\t\t\t\t\t\t.toSorted((a, b) => a.length - b.length)\n\t\t\t\t\t\t.map(c => `'${c}'`)\n\t\t\t\t\t\t.join(' | ')\n\t\t\t\t: 'never'\n\t\t}\n\t`,\n\t\t)\n\t\t.join('\\n')\n}\n\n/**\n * Merge per-file tag maps into one application-wide tag map\n */\nexport function flattenTags(tagsByFile: BuildContext['tagsByFile']) {\n\tconst tags = new Map<string, string[]>()\n\n\tfor (const fileTags of tagsByFile.values()) {\n\t\tfor (const [name, classes] of fileTags) {\n\t\t\tif (tags.has(name)) throw new TagDuplicateError(name)\n\t\t\ttags.set(name, classes)\n\t\t}\n\t}\n\n\treturn tags\n}\n\n/**\n * Build-time template tag placeholder used by the transform to emit CSS, classes and href values\n */\nexport const cxx: Cxx = Object.assign((_: TemplateStringsArray): Result => ['', {}, ''], {\n\ttag:\n\t\t<ClassName extends string = string>(_: string) =>\n\t\t(_strings: TemplateStringsArray): Result<ClassName> => [\n\t\t\t'',\n\t\t\t{} as Readonly<Record<ClassName, string>>,\n\t\t\t'',\n\t\t],\n})\n",
6
- "export const NAME = 'cxx'\nexport const PKG_NAME = `@jk2908/${NAME}`\nexport const GENERATED_DIR = `.${NAME}`\nexport const AUTOGEN_MSG = `// auto-generated by ${NAME}`\n",
7
- "export const CACHE_MAX_SIZE = 100\n\nexport class LruCache<T> {\n\t#cache: Map<string, T> = new Map()\n\t#maxSize: number\n\n\tconstructor(maxSize = CACHE_MAX_SIZE) {\n\t\tthis.#maxSize = maxSize\n\t}\n\n\tget size() {\n\t\treturn this.#cache.size\n\t}\n\n\t/**\n\t * Retrieve a cached value and promote it to most-recently-used so it won't\n\t * be evicted while still actively referenced\n\t */\n\tget(key: string) {\n\t\tconst cached = this.#cache.get(key)\n\t\tif (this.#cache.has(key)) this.set(key, cached as T)\n\n\t\treturn cached\n\t}\n\n\t/**\n\t * Promote an existing cache entry to most-recently-used by deleting and\n\t * re-inserting it, which moves it to the end of the Map's iteration\n\t * order. If the cache is at capacity, evict the least recently used\n\t * entry (front) before inserting\n\t */\n\tset(key: string, value: T) {\n\t\t// drop any existing entry\n\t\tthis.#cache.delete(key)\n\n\t\tif (this.#cache.size >= this.#maxSize) {\n\t\t\tconst lru = this.#cache.keys().next().value\n\t\t\tif (lru !== undefined) this.#cache.delete(lru)\n\t\t}\n\n\t\tthis.#cache.set(key, value)\n\t}\n\n\tdelete(key: string) {\n\t\treturn this.#cache.delete(key)\n\t}\n\n\tclear() {\n\t\treturn this.#cache.clear()\n\t}\n}\n",
8
- "export function capitalise(str: string) {\n\treturn str.charAt(0).toUpperCase() + str.slice(1)\n}\n\nexport function pascalise(str: string) {\n\treturn str\n\t\t.split(/[ _-]+/)\n\t\t.map(capitalise)\n\t\t.join('')\n}\n\nexport function debounce<T extends unknown[]>(fn: (...args: T) => void, wait: number) {\n\tlet timeoutId: ReturnType<typeof setTimeout> | null = null\n\n\treturn (...args: T) => {\n\t\tif (timeoutId) {\n\t\t\tclearTimeout(timeoutId)\n\t\t}\n\n\t\ttimeoutId = setTimeout(() => {\n\t\t\tfn.apply(null, args)\n\t\t}, wait)\n\t}\n}\n"
5
+ "import type { Cxx, Result } from './types.js'\n\nexport type { Cxx, Result, Tag } from './types.js'\n\n/**\n * Build-time template tag placeholder used by the transform to emit CSS, classes and href values.\n */\nexport const cxx: Cxx = Object.assign((_: TemplateStringsArray): Result => ['', {}, ''], {\n\ttag:\n\t\t<ClassName extends string = string>(_: string) =>\n\t\t(_strings: TemplateStringsArray): Result<ClassName> => [\n\t\t\t'',\n\t\t\t{} as Readonly<Record<ClassName, string>>,\n\t\t\t'',\n\t\t],\n})\n"
9
6
  ],
10
- "mappings": "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",
11
- "debugId": "D5DA13CCB4684E3C64756E2164756E21",
7
+ "mappings": ";AAOO,IAAM,MAAW,OAAO,OAAO,CAAC,MAAoC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG;AAAA,EACxF,KACC,CAAoC,MACpC,CAAC,aAAsD;AAAA,IACtD;AAAA,IACA,CAAC;AAAA,IACD;AAAA,EACD;AACF,CAAC;",
8
+ "debugId": "40D13C798825EC8F64756E2164756E21",
12
9
  "names": []
13
10
  }
package/dist/logger.d.ts CHANGED
@@ -7,7 +7,7 @@ declare const LEVELS: {
7
7
  };
8
8
  export type LogLevel = keyof typeof LEVELS;
9
9
  /**
10
- * Log messages with different severity levels
10
+ * Log messages with different severity levels.
11
11
  */
12
12
  export declare class Logger {
13
13
  #private;
@@ -16,37 +16,37 @@ export declare class Logger {
16
16
  static set defaultLevel(level: LogLevel);
17
17
  static get defaultLevel(): LogLevel;
18
18
  /**
19
- * Convert a value to an Error instance
19
+ * Convert a value to an Error instance.
20
20
  */
21
21
  static toError(err: unknown): Error;
22
22
  /**
23
- * Stringify the error for logging
23
+ * Stringify the error for logging.
24
24
  */
25
25
  static print(err: unknown): string;
26
26
  set level(level: LogLevel);
27
27
  get level(): LogLevel;
28
28
  /**
29
- * Log a message with a specific level
29
+ * Log a message with a specific level.
30
30
  */
31
31
  log(level: LogLevel, message: string, error?: Error): void;
32
32
  /**
33
- * Log a debug message
33
+ * Log a debug message.
34
34
  */
35
35
  debug(...messages: string[]): void;
36
36
  /**
37
- * Log an info message
37
+ * Log an info message.
38
38
  */
39
39
  info(...messages: string[]): void;
40
40
  /**
41
- * Log a warning message
41
+ * Log a warning message.
42
42
  */
43
43
  warn(...messages: string[]): void;
44
44
  /**
45
- * Log an error message
45
+ * Log an error message.
46
46
  */
47
47
  error(message: string, error?: unknown): void;
48
48
  /**
49
- * Log a fatal error message
49
+ * Log a fatal error message.
50
50
  */
51
51
  fatal(message: string, error?: unknown): void;
52
52
  }
package/dist/lru.d.ts CHANGED
@@ -5,14 +5,14 @@ export declare class LruCache<T> {
5
5
  get size(): number;
6
6
  /**
7
7
  * Retrieve a cached value and promote it to most-recently-used so it won't
8
- * be evicted while still actively referenced
8
+ * be evicted while still actively referenced.
9
9
  */
10
10
  get(key: string): T | undefined;
11
11
  /**
12
12
  * Promote an existing cache entry to most-recently-used by deleting and
13
13
  * re-inserting it, which moves it to the end of the Map's iteration
14
14
  * order. If the cache is at capacity, evict the least recently used
15
- * entry (front) before inserting
15
+ * entry (front) before inserting.
16
16
  */
17
17
  set(key: string, value: T): void;
18
18
  delete(key: string): boolean;