@kanso-labs/unplugin-style-dictionary 0.10.13 → 0.10.14
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 +53 -26
- package/dist/compile.js +14 -9
- package/dist/compile.js.map +1 -1
- package/dist/config.js +62 -10
- package/dist/config.js.map +1 -1
- package/dist/index.js +267 -33
- package/dist/index.js.map +1 -1
- package/dist/patterns.js +8 -1
- package/dist/patterns.js.map +1 -1
- package/dist/size-report.js +6 -4
- package/dist/size-report.js.map +1 -1
- package/dist/types.d.ts +23 -10
- package/dist/watch-filter.js +11 -1
- package/dist/watch-filter.js.map +1 -1
- package/package.json +1 -1
package/dist/size-report.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"size-report.js","names":[],"sources":["../src/size-report.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport zlib from 'node:zlib'\n\nimport { paint } from './colour.js'\n\n// The size-and-gzip table, in a function of its own so that the
|
|
1
|
+
{"version":3,"file":"size-report.js","names":[],"sources":["../src/size-report.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport zlib from 'node:zlib'\n\nimport { paint } from './colour.js'\n\n// The size-and-gzip table's lines, in a function of its own so that the\n// compile `try` in `runBuilds` can stop before it. Everything here is\n// presentation over files Style Dictionary has already finished writing, so a\n// throw from it is a reporting bug and nothing more.\n//\n// It returns the lines rather than printing them, because where they go is\n// the instance's to decide: a host's `info` channel where one exists, stdout\n// otherwise. `colour` says whether to paint them, which only stdout ever asks\n// for — a host renders its own output.\nexport function sizeTable(\n { colour, root }: { colour: boolean; root: string },\n generatedFiles: Set<string>,\n): string[] {\n const fileInfos: Array<{\n coloredPath: string\n gzipSizeStr: string\n relativeDisplayPath: string\n sizeStr: string\n }> = []\n\n for (const filePath of generatedFiles) {\n if (fs.existsSync(filePath)) {\n const displayPath = path.relative(root, filePath).replace(/\\\\/g, '/')\n const dir = path.dirname(displayPath)\n const base = path.basename(displayPath)\n const coloredPath =\n dir === '.'\n ? paint('32', base, colour)\n : paint('90', `${dir}/`, colour) + paint('32', base, colour)\n\n try {\n const stats = fs.statSync(filePath)\n const bytes = stats.size\n const sizeStr = `${(bytes / 1024).toFixed(2)} kB`\n\n const content = fs.readFileSync(filePath)\n const gzipBytes = zlib.gzipSync(content).length\n const gzipSizeStr = `${(gzipBytes / 1024).toFixed(2)} kB`\n\n fileInfos.push({\n coloredPath,\n gzipSizeStr,\n relativeDisplayPath: displayPath,\n sizeStr,\n })\n } catch {\n // One unreadable destination costs its row rather than the table.\n // Deliberately narrower than the caller's `catch`: it covers the\n // three filesystem and gzip calls above and not the arithmetic\n // below, so a padding bug is reported rather than quietly printing\n // short.\n }\n }\n }\n\n const lines: string[] = []\n\n if (fileInfos.length > 0) {\n const longestPathLength = Math.max(\n ...fileInfos.map((f) => f.relativeDisplayPath.length),\n 0,\n )\n const longestSizeLength = Math.max(\n ...fileInfos.map((f) => f.sizeStr.length),\n 0,\n )\n\n for (const info of fileInfos) {\n const pathPadding = ' '.repeat(\n Math.max(2, longestPathLength - info.relativeDisplayPath.length + 2),\n )\n const sizePadded = info.sizeStr.padStart(longestSizeLength)\n lines.push(\n info.coloredPath +\n pathPadding +\n paint('90', `${sizePadded} │ gzip: ${info.gzipSizeStr}`, colour),\n )\n }\n }\n\n return lines\n}\n"],"mappings":";;;;;AAeA,SAAgB,UACd,EAAE,QAAQ,QACV,gBACU;CACV,MAAM,YAKD,CAAC;CAEN,KAAK,MAAM,YAAY,gBACrB,IAAI,GAAG,WAAW,QAAQ,GAAG;EAC3B,MAAM,cAAc,KAAK,SAAS,MAAM,QAAQ,CAAC,CAAC,QAAQ,OAAO,GAAG;EACpE,MAAM,MAAM,KAAK,QAAQ,WAAW;EACpC,MAAM,OAAO,KAAK,SAAS,WAAW;EACtC,MAAM,cACJ,QAAQ,MACJ,MAAM,MAAM,MAAM,MAAM,IACxB,MAAM,MAAM,GAAG,IAAI,IAAI,MAAM,IAAI,MAAM,MAAM,MAAM,MAAM;EAE/D,IAAI;GAGF,MAAM,UAAU,IAFF,GAAG,SAAS,QACR,CAAC,CAAC,OACQ,KAAA,CAAM,QAAQ,CAAC,EAAE;GAE7C,MAAM,UAAU,GAAG,aAAa,QAAQ;GAExC,MAAM,cAAc,IADF,KAAK,SAAS,OAAO,CAAC,CAAC,SACL,KAAA,CAAM,QAAQ,CAAC,EAAE;GAErD,UAAU,KAAK;IACb;IACA;IACA,qBAAqB;IACrB;GACF,CAAC;EACH,QAAQ,CAMR;CACF;CAGF,MAAM,QAAkB,CAAC;CAEzB,IAAI,UAAU,SAAS,GAAG;EACxB,MAAM,oBAAoB,KAAK,IAC7B,GAAG,UAAU,KAAK,MAAM,EAAE,oBAAoB,MAAM,GACpD,CACF;EACA,MAAM,oBAAoB,KAAK,IAC7B,GAAG,UAAU,KAAK,MAAM,EAAE,QAAQ,MAAM,GACxC,CACF;EAEA,KAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,cAAc,IAAI,OACtB,KAAK,IAAI,GAAG,oBAAoB,KAAK,oBAAoB,SAAS,CAAC,CACrE;GACA,MAAM,aAAa,KAAK,QAAQ,SAAS,iBAAiB;GAC1D,MAAM,KACJ,KAAK,cACH,cACA,MAAM,MAAM,GAAG,WAAW,WAAW,KAAK,eAAe,MAAM,CACnE;EACF;CACF;CAEA,OAAO;AACT"}
|
package/dist/types.d.ts
CHANGED
|
@@ -11,14 +11,21 @@ import { Config } from "style-dictionary";
|
|
|
11
11
|
* build (e.g. `tsdown`/`rolldown build` without `--watch`) only builds once, in
|
|
12
12
|
* `buildStart`.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* Vite's `config.logger`, the plugin context under rollup and
|
|
16
|
-
* `compilation.warnings` under webpack, which is what
|
|
17
|
-
* `stats.toJson()`.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
14
|
+
* What the plugin says goes through the host wherever the host has a channel
|
|
15
|
+
* for it: Vite's `config.logger`, the plugin context under rollup and
|
|
16
|
+
* rolldown, and `compilation.warnings` under webpack and rspack, which is what
|
|
17
|
+
* puts a failed compile in `stats.toJson()`. The progress lines and the size
|
|
18
|
+
* table take the host's `info` channel, and webpack and rspack have none, so
|
|
19
|
+
* there both go to the console. A failure is reported on the warning channel
|
|
20
|
+
* and never the error one — rollup's `this.error` aborts the bundle, and that
|
|
21
|
+
* decision is `failOnError`'s alone. Where no host offers a channel the console
|
|
22
|
+
* is used, with colour gated on `NO_COLOR`, `FORCE_COLOR` and whether the
|
|
23
|
+
* stream is a terminal.
|
|
24
|
+
*
|
|
25
|
+
* Style Dictionary's own lines are not the plugin's. The platform name and the
|
|
26
|
+
* `✔︎` for each file it wrote are its own `console` calls, so they reach the
|
|
27
|
+
* console under every host, whatever the host's log level. `logLevel: 'silent'`
|
|
28
|
+
* quiets them, as does `log.verbosity: 'silent'` in the configuration.
|
|
22
29
|
*
|
|
23
30
|
* The three `onBuild*` hooks are called synchronously and their return value
|
|
24
31
|
* is not awaited, so a build never waits for one. A hook may still be written
|
|
@@ -153,8 +160,9 @@ export interface UnpluginStyleDictionaryOptions {
|
|
|
153
160
|
*
|
|
154
161
|
* **`false` turns discovery off.** Two of the four names are modules rather
|
|
155
162
|
* than data, and reading a module means running it: a `sd.config.js` in the
|
|
156
|
-
* root is imported
|
|
157
|
-
*
|
|
163
|
+
* root is imported when it is first read, and again whenever it or a file
|
|
164
|
+
* named in `watch` changes. Validation cannot prevent that, because the
|
|
165
|
+
* check can only look at what the import returned — so a
|
|
158
166
|
* project that names its configuration explicitly, or has none, should say
|
|
159
167
|
* `config: false` rather than rely on there being nothing to find.
|
|
160
168
|
*/
|
|
@@ -380,6 +388,11 @@ export interface UnpluginStyleDictionaryOptions {
|
|
|
380
388
|
* else, name a file here relative to the root, and in `source` relative to
|
|
381
389
|
* where the build runs.
|
|
382
390
|
*
|
|
391
|
+
* A configuration module is imported again when a file named here changes,
|
|
392
|
+
* so a data file it reads when it loads is picked up. A module that a
|
|
393
|
+
* configuration or a token module itself imports is evaluated once per
|
|
394
|
+
* process, though: naming it here buys a rebuild, not a re-read.
|
|
395
|
+
*
|
|
383
396
|
* What a change to a watched file then triggers is the host's to decide —
|
|
384
397
|
* see the interface documentation above.
|
|
385
398
|
*/
|
package/dist/watch-filter.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import picomatch from "picomatch";
|
|
2
3
|
//#region src/watch-filter.ts
|
|
3
4
|
function isWatchedSource(file, { globs, literals }, generatedDestinations) {
|
|
@@ -8,7 +9,16 @@ function matchesWatchedFile(file, patterns) {
|
|
|
8
9
|
const normalizedFile = file.replace(/\\/g, "/");
|
|
9
10
|
return patterns.some((pattern) => pattern === normalizedFile || picomatch.isMatch(normalizedFile, pattern));
|
|
10
11
|
}
|
|
12
|
+
function registeredSpellingOf(file, linkedPaths) {
|
|
13
|
+
if (linkedPaths.size === 0) return file;
|
|
14
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
15
|
+
for (let real = normalizedFile;; real = path.posix.dirname(real)) {
|
|
16
|
+
const registered = linkedPaths.get(real);
|
|
17
|
+
if (registered !== void 0) return registered + normalizedFile.slice(real.length);
|
|
18
|
+
if (path.posix.dirname(real) === real) return file;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
11
21
|
//#endregion
|
|
12
|
-
export { isWatchedSource, matchesWatchedFile };
|
|
22
|
+
export { isWatchedSource, matchesWatchedFile, registeredSpellingOf };
|
|
13
23
|
|
|
14
24
|
//# sourceMappingURL=watch-filter.js.map
|
package/dist/watch-filter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watch-filter.js","names":[],"sources":["../src/watch-filter.ts"],"sourcesContent":["import picomatch from 'picomatch'\n\nimport type { WatchPatterns } from './patterns.js'\n\n// Whether a changed file is a token or config source rather than something\n// this plugin just wrote. Both watch entry points ask through here, so\n// neither can react to its own output.\n//\n// `generatedDestinations` is the plugin instance's own record of what it\n// wrote, so it is handed in rather than held: one module serves every\n// instance in the process, and each one's output is its own.\n//\n// A literal is a path, so it is compared as one. A config file reaches here\n// that way, and so does a token file named without a glob, and neither may be\n// read as a pattern: the project's own directory can hold characters a glob\n// treats as syntax.\nexport function isWatchedSource(\n file: string,\n { globs, literals }: WatchPatterns,\n generatedDestinations: ReadonlySet<string>,\n): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return (\n !generatedDestinations.has(normalizedFile) &&\n (literals.includes(normalizedFile) ||\n matchesWatchedFile(normalizedFile, globs))\n )\n}\n\n// Whether `file` matches one of the resolved config/token watch patterns.\n// Shared by the Vite-specific `configureServer` watcher and the universal\n// `watchChange` hook — both need it, and both must skip files that don't\n// match: without this filter, `watchChange` reacts to *any* changed\n// module-graph file, including this plugin's own generated output (since\n// consuming code imports it). Every regenerate is itself a \"change\", which\n// without filtering re-triggers a rebuild forever.\n//\n// Matching a pattern is only half of that, and this function is only the half\n// it can answer. A `buildPath` inside a `source` directory is a supported\n// layout, and under any correct matcher its output matches the very glob that\n// produced it — so the caller also subtracts what the last build wrote. See\n// `isWatchedSource` above, and `generatedDestinations` in the factory.\n//\n// The patterns are Style Dictionary's own `source` and `include` globs, so the\n// filter has to admit exactly what the build reads — which is why the matching\n// is a real globber's rather than hand-rolled. The version this replaces was\n// wrong in both directions at once: it stripped `/**` out of a pattern and\n// prefix-matched the remainder, so `tokens/**/*.json` matched nothing sitting\n// directly in `tokens/` and `tokens/**` matched a `tokens-backup/` sibling,\n// while its regex branch mapped every `*` to `.*` — crossing `/` — and tested\n// it unanchored, so generated output under a watched directory matched its own\n// source glob and rebuilt forever.\n//\n// picomatch rather than `path.matchesGlob`, which would need no dependency at\n// all: that function is documented experimental, and on Node 20 — the floor\n// `engines` declares — it prints `ExperimentalWarning: glob is an experimental\n// feature and might change at any time` into the consumer's build output. The\n// dependency is free in practice, since `unplugin` depends on the same\n// picomatch and is already installed wherever this plugin is. Its `dot: false`\n// default is deliberate: it is what glob, and so Style Dictionary, reads\n// sources with, so a dotfile is invisible to the filter and to the build alike.\n//\n// **The patterns are used exactly as given.** A backslash in one is an escape:\n// the part of a pattern the plugin prefixed is escaped, and every producer\n// already writes separators as `/`. Rewriting backslashes to slashes here\n// turned each escape into a separator, so a project in `Dropbox (Personal)`\n// matched none of its own token files.\nexport function matchesWatchedFile(file: string, patterns: string[]): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return patterns.some(\n (pattern) =>\n // An absolute path a consumer wrote with a glob character in it is filed\n // as a glob, and still names itself.\n pattern === normalizedFile || picomatch.isMatch(normalizedFile, pattern),\n )\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"watch-filter.js","names":[],"sources":["../src/watch-filter.ts"],"sourcesContent":["import path from 'node:path'\nimport picomatch from 'picomatch'\n\nimport type { WatchPatterns } from './patterns.js'\n\n// Whether a changed file is a token or config source rather than something\n// this plugin just wrote. Both watch entry points ask through here, so\n// neither can react to its own output.\n//\n// `generatedDestinations` is the plugin instance's own record of what it\n// wrote, so it is handed in rather than held: one module serves every\n// instance in the process, and each one's output is its own.\n//\n// A literal is a path, so it is compared as one. A config file reaches here\n// that way, and so does a token file named without a glob, and neither may be\n// read as a pattern: the project's own directory can hold characters a glob\n// treats as syntax.\nexport function isWatchedSource(\n file: string,\n { globs, literals }: WatchPatterns,\n generatedDestinations: ReadonlySet<string>,\n): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return (\n !generatedDestinations.has(normalizedFile) &&\n (literals.includes(normalizedFile) ||\n matchesWatchedFile(normalizedFile, globs))\n )\n}\n\n// Whether `file` matches one of the resolved config/token watch patterns.\n// Shared by the Vite-specific `configureServer` watcher and the universal\n// `watchChange` hook — both need it, and both must skip files that don't\n// match: without this filter, `watchChange` reacts to *any* changed\n// module-graph file, including this plugin's own generated output (since\n// consuming code imports it). Every regenerate is itself a \"change\", which\n// without filtering re-triggers a rebuild forever.\n//\n// Matching a pattern is only half of that, and this function is only the half\n// it can answer. A `buildPath` inside a `source` directory is a supported\n// layout, and under any correct matcher its output matches the very glob that\n// produced it — so the caller also subtracts what the last build wrote. See\n// `isWatchedSource` above, and `generatedDestinations` in the factory.\n//\n// The patterns are Style Dictionary's own `source` and `include` globs, so the\n// filter has to admit exactly what the build reads — which is why the matching\n// is a real globber's rather than hand-rolled. The version this replaces was\n// wrong in both directions at once: it stripped `/**` out of a pattern and\n// prefix-matched the remainder, so `tokens/**/*.json` matched nothing sitting\n// directly in `tokens/` and `tokens/**` matched a `tokens-backup/` sibling,\n// while its regex branch mapped every `*` to `.*` — crossing `/` — and tested\n// it unanchored, so generated output under a watched directory matched its own\n// source glob and rebuilt forever.\n//\n// picomatch rather than `path.matchesGlob`, which would need no dependency at\n// all: that function is documented experimental, and on Node 20 — the floor\n// `engines` declares — it prints `ExperimentalWarning: glob is an experimental\n// feature and might change at any time` into the consumer's build output. The\n// dependency is free in practice, since `unplugin` depends on the same\n// picomatch and is already installed wherever this plugin is. Its `dot: false`\n// default is deliberate: it is what glob, and so Style Dictionary, reads\n// sources with, so a dotfile is invisible to the filter and to the build alike.\n//\n// **The patterns are used exactly as given.** A backslash in one is an escape:\n// the part of a pattern the plugin prefixed is escaped, and every producer\n// already writes separators as `/`. Rewriting backslashes to slashes here\n// turned each escape into a separator, so a project in `Dropbox (Personal)`\n// matched none of its own token files.\nexport function matchesWatchedFile(file: string, patterns: string[]): boolean {\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n return patterns.some(\n (pattern) =>\n // An absolute path a consumer wrote with a glob character in it is filed\n // as a glob, and still names itself.\n pattern === normalizedFile || picomatch.isMatch(normalizedFile, pattern),\n )\n}\n\n// The spelling the watch patterns use for `file`, which a host watching a\n// registered realpath reports by where a link leads rather than by the link.\n// `linkedPaths` maps each registered path that runs through a link from its\n// realpath back to the spelling it was registered under, and is the plugin\n// instance's, so it is handed in rather than held.\n//\n// The lookup walks up from `file` rather than asking about it alone, because\n// a pattern's static parent directory is registered so that a token file\n// created in it later is noticed — and that file arrives spelled under the\n// directory's realpath, which only the directory is a key for.\nexport function registeredSpellingOf(\n file: string,\n linkedPaths: ReadonlyMap<string, string>,\n): string {\n if (linkedPaths.size === 0) return file\n\n const normalizedFile = file.replace(/\\\\/g, '/')\n\n for (let real = normalizedFile; ; real = path.posix.dirname(real)) {\n const registered = linkedPaths.get(real)\n if (registered !== undefined) {\n return registered + normalizedFile.slice(real.length)\n }\n\n if (path.posix.dirname(real) === real) return file\n }\n}\n"],"mappings":";;;AAiBA,SAAgB,gBACd,MACA,EAAE,OAAO,YACT,uBACS;CACT,MAAM,iBAAiB,KAAK,QAAQ,OAAO,GAAG;CAE9C,OACE,CAAC,sBAAsB,IAAI,cAAc,MACxC,SAAS,SAAS,cAAc,KAC/B,mBAAmB,gBAAgB,KAAK;AAE9C;AAwCA,SAAgB,mBAAmB,MAAc,UAA6B;CAC5E,MAAM,iBAAiB,KAAK,QAAQ,OAAO,GAAG;CAE9C,OAAO,SAAS,MACb,YAGC,YAAY,kBAAkB,UAAU,QAAQ,gBAAgB,OAAO,CAC3E;AACF;AAYA,SAAgB,qBACd,MACA,aACQ;CACR,IAAI,YAAY,SAAS,GAAG,OAAO;CAEnC,MAAM,iBAAiB,KAAK,QAAQ,OAAO,GAAG;CAE9C,KAAK,IAAI,OAAO,iBAAkB,OAAO,KAAK,MAAM,QAAQ,IAAI,GAAG;EACjE,MAAM,aAAa,YAAY,IAAI,IAAI;EACvC,IAAI,eAAe,KAAA,GACjB,OAAO,aAAa,eAAe,MAAM,KAAK,MAAM;EAGtD,IAAI,KAAK,MAAM,QAAQ,IAAI,MAAM,MAAM,OAAO;CAChD;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kanso-labs/unplugin-style-dictionary",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.14",
|
|
4
4
|
"description": "Compile Style Dictionary design tokens ahead of your bundler (Vite, Rolldown, Rollup, or Webpack) from a single unplugin-based plugin, with automatic watching and rebuilding under Vite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"unplugin",
|