@yahoo/uds-create-loader 1.1.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.
@@ -0,0 +1,5 @@
1
+ import { createRequire } from "node:module";
2
+ //#region \0rolldown/runtime.js
3
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
+ //#endregion
5
+ export { __require };
@@ -0,0 +1,4 @@
1
+ import transformLoader from "./loader.js";
2
+ import { LoaderTransformOutputSpecs, LoaderTransformResult, RunLoaderTransformOptions, getLoaderTransformOutputSpecs, runLoaderTransform } from "./transform-runner.js";
3
+ import { UdsPluginOptions, udsPlugin } from "./unplugin.js";
4
+ export { type LoaderTransformOutputSpecs, type LoaderTransformResult, type RunLoaderTransformOptions, type UdsPluginOptions, getLoaderTransformOutputSpecs, runLoaderTransform, transformLoader, udsPlugin };
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import transformLoader from "./loader.js";
2
+ import { getLoaderTransformOutputSpecs, runLoaderTransform } from "./transform-runner.js";
3
+ import { udsPlugin } from "./unplugin.js";
4
+ export { getLoaderTransformOutputSpecs, runLoaderTransform, transformLoader, udsPlugin };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,63 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { runLoaderTransform } from './transform-runner';
5
+ function readFlag(flag) {
6
+ const index = process.argv.indexOf(flag);
7
+ if (index === -1)
8
+ return null;
9
+ const value = process.argv[index + 1];
10
+ if (!value || value.startsWith('-'))
11
+ return null;
12
+ return value;
13
+ }
14
+ function printUsage() {
15
+ console.error([
16
+ 'Usage:',
17
+ ' bun run src/inspect-transform.ts --file <path-to-tsx> --config <path-to-uds.config.ts>',
18
+ '',
19
+ 'Example:',
20
+ ' bun run --filter @yahoo/uds-create-loader inspect:transform -- --file packages/components/src/accordion.tsx --config apps/docs/uds.config.ts',
21
+ ].join('\n'));
22
+ process.exit(1);
23
+ }
24
+ async function loadUdsConfig(configPath) {
25
+ const resolvedPath = path.resolve(configPath);
26
+ const configUrl = pathToFileURL(resolvedPath).href;
27
+ const module = (await import(configUrl));
28
+ if (module.default !== undefined) {
29
+ return module.default;
30
+ }
31
+ if (module.udsConfig !== undefined) {
32
+ return module.udsConfig;
33
+ }
34
+ return module;
35
+ }
36
+ async function main() {
37
+ const sourcePath = readFlag('--file');
38
+ const configPath = readFlag('--config');
39
+ if (!sourcePath || !configPath) {
40
+ printUsage();
41
+ }
42
+ const sourceAbsolutePath = path.resolve(sourcePath);
43
+ if (!fs.existsSync(sourceAbsolutePath)) {
44
+ throw new Error(`Source file not found: ${sourceAbsolutePath}`);
45
+ }
46
+ const rawConfig = await loadUdsConfig(configPath);
47
+ const config = rawConfig.toJSON();
48
+ const source = fs.readFileSync(sourceAbsolutePath, 'utf8');
49
+ const result = runLoaderTransform(source, {
50
+ config,
51
+ resourcePath: sourceAbsolutePath,
52
+ isDev: false,
53
+ });
54
+ if ('error' in result) {
55
+ throw new Error(result.error);
56
+ }
57
+ process.stdout.write(result.output);
58
+ }
59
+ void main().catch((error) => {
60
+ const message = error instanceof Error ? error.message : String(error);
61
+ console.error(`[inspect-transform] ${message}`);
62
+ process.exit(1);
63
+ });
@@ -0,0 +1,30 @@
1
+ import type { SerializedConfig } from '@yahoo/uds-create-config';
2
+ export interface LoaderOptions {
3
+ /** Path to `.uds/manifest.json`. The loader extracts `.config` at the
4
+ * read boundary; the manifest's wrapper fields (`catalogMeta`,
5
+ * `sourceMap`, …) exist for Studio/MCP and aren't read here. */
6
+ dataPath: string;
7
+ isDev: boolean;
8
+ /** Skip addDependency calls to prevent webpack from watching .uds/ artifacts.
9
+ * Use this when the app has its own reload mechanism (e.g., Studio WebSocket). */
10
+ skipWatchDependencies?: boolean;
11
+ }
12
+ /**
13
+ * Derived projection of one loaded config — built lazily on first use,
14
+ * cached by config object identity so subsequent transforms reuse the
15
+ * same set. Carries the registered-component-name set the loader uses
16
+ * for the pre-filter and the import-rewrite visitor's eligibility check.
17
+ */
18
+ export interface LoaderProjection {
19
+ readonly componentNames: ReadonlySet<string>;
20
+ }
21
+ export declare const loadUdsConfig: (filePath: string, isDev: boolean) => SerializedConfig | null;
22
+ export declare const seedUdsConfig: (filePath: string, config: SerializedConfig) => void;
23
+ /**
24
+ * Build (or return a cached) projection of the config for the loader's
25
+ * hot path. Keyed by config object identity. The cache survives across
26
+ * transforms within one process; reloading `.uds/manifest.json`
27
+ * produces a fresh object, which transparently invalidates the
28
+ * projection.
29
+ */
30
+ export declare function getLoaderProjection(config: SerializedConfig): LoaderProjection;
@@ -0,0 +1,60 @@
1
+ import fs from "node:fs";
2
+ //#region src/loader/artifacts.ts
3
+ /**
4
+ * `.uds/config.json` IS the serialized config — no envelope. Parse and
5
+ * cache by file mtime so subsequent transforms reuse the same object.
6
+ */
7
+ function createCachedJsonLoader() {
8
+ let cached = null;
9
+ let cachedPath = null;
10
+ let cachedMtime = 0;
11
+ return (filePath, isDev) => {
12
+ try {
13
+ const mtime = fs.statSync(filePath).mtimeMs;
14
+ if (cached && cachedPath === filePath && (!isDev || cachedMtime === mtime)) return cached;
15
+ const content = fs.readFileSync(filePath, "utf-8");
16
+ cached = JSON.parse(content);
17
+ cachedPath = filePath;
18
+ cachedMtime = mtime;
19
+ return cached;
20
+ } catch {
21
+ return null;
22
+ }
23
+ };
24
+ }
25
+ function createCachedJsonLoaderWithSeed() {
26
+ const loader = createCachedJsonLoader();
27
+ let seededPath = null;
28
+ let seededData = null;
29
+ return {
30
+ load: (filePath, isDev) => {
31
+ if (seededPath === filePath && seededData) return seededData;
32
+ return loader(filePath, isDev);
33
+ },
34
+ seed: (filePath, config) => {
35
+ seededPath = filePath;
36
+ seededData = config;
37
+ }
38
+ };
39
+ }
40
+ const configLoader = createCachedJsonLoaderWithSeed();
41
+ const loadUdsConfig = configLoader.load;
42
+ const seedUdsConfig = configLoader.seed;
43
+ const projectionCache = /* @__PURE__ */ new WeakMap();
44
+ /**
45
+ * Build (or return a cached) projection of the config for the loader's
46
+ * hot path. Keyed by config object identity. The cache survives across
47
+ * transforms within one process; reloading `.uds/manifest.json`
48
+ * produces a fresh object, which transparently invalidates the
49
+ * projection.
50
+ */
51
+ function getLoaderProjection(config) {
52
+ const cached = projectionCache.get(config);
53
+ if (cached) return cached;
54
+ const components = config.components ?? {};
55
+ const projection = { componentNames: new Set(Object.keys(components)) };
56
+ projectionCache.set(config, projection);
57
+ return projection;
58
+ }
59
+ //#endregion
60
+ export { getLoaderProjection, loadUdsConfig, seedUdsConfig };
@@ -0,0 +1,9 @@
1
+ import * as t from '@babel/types';
2
+ /**
3
+ * Check whether a callee expression is a compiled JSX call.
4
+ *
5
+ * Matches:
6
+ * - Direct identifiers: `jsx(...)`, `_jsxs(...)`, `jsxDEV(...)`, etc.
7
+ * - Member expressions: `React.jsx(...)`, `_reactJsxRuntime.jsxs(...)`, etc.
8
+ */
9
+ export declare function isCompiledJsxCallee(node: t.Expression | t.V8IntrinsicIdentifier | null | undefined): boolean;
@@ -0,0 +1,29 @@
1
+ import * as t from "@babel/types";
2
+ //#region src/loader/babel-utils.ts
3
+ /** JSX function names used by compiled JSX (React, etc.) */
4
+ const COMPILED_JSX_NAMES = new Set([
5
+ "jsx",
6
+ "jsxs",
7
+ "jsxDEV",
8
+ "_jsx",
9
+ "_jsxs",
10
+ "_jsxDEV"
11
+ ]);
12
+ /**
13
+ * Check whether a callee expression is a compiled JSX call.
14
+ *
15
+ * Matches:
16
+ * - Direct identifiers: `jsx(...)`, `_jsxs(...)`, `jsxDEV(...)`, etc.
17
+ * - Member expressions: `React.jsx(...)`, `_reactJsxRuntime.jsxs(...)`, etc.
18
+ */
19
+ function isCompiledJsxCallee(node) {
20
+ if (!node) return false;
21
+ if (t.isIdentifier(node)) return COMPILED_JSX_NAMES.has(node.name);
22
+ if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
23
+ if (!node.computed && t.isIdentifier(node.property)) return COMPILED_JSX_NAMES.has(node.property.name);
24
+ if (node.computed && t.isStringLiteral(node.property)) return COMPILED_JSX_NAMES.has(node.property.value);
25
+ }
26
+ return false;
27
+ }
28
+ //#endregion
29
+ export { isCompiledJsxCallee };
@@ -0,0 +1,22 @@
1
+ import { LoaderDefinitionFunction } from "webpack";
2
+
3
+ //#region src/loader.d.ts
4
+ /**
5
+ * The UDS loader's only build-time job: rewrite alias-prefixed local
6
+ * imports of registered UDS components from `@/uds/components/<seg>`
7
+ * (the authoring file) to `'#uds/components/<seg>'` (the codegen-
8
+ * emitted per-source-file module at `.uds/components/<seg>.tsx`).
9
+ *
10
+ * The authoring file is read only by codegen — production renders the
11
+ * downleveled FC. Per-file targets keep Next.js Fast Refresh surgical:
12
+ * editing one authoring source only invalidates that one downleveled
13
+ * module.
14
+ *
15
+ * No JSX-site work: composite / variant / bool / style-prop resolution
16
+ * and motion wiring are owned by the codegen-emitted FC. The loader
17
+ * only changes import sources.
18
+ */
19
+ declare const transformLoader: LoaderDefinitionFunction;
20
+ declare const normal: LoaderDefinitionFunction<{}, {}>;
21
+ //#endregion
22
+ export { transformLoader as default, normal };
package/dist/loader.js ADDED
@@ -0,0 +1,87 @@
1
+ import { getLoaderProjection, loadUdsConfig } from "./loader/artifacts.js";
2
+ import { parse } from "@babel/parser";
3
+ import _traverse from "@babel/traverse";
4
+ //#region src/loader.ts
5
+ const traverse = _traverse.default ?? _traverse;
6
+ /**
7
+ * The UDS loader's only build-time job: rewrite alias-prefixed local
8
+ * imports of registered UDS components from `@/uds/components/<seg>`
9
+ * (the authoring file) to `'#uds/components/<seg>'` (the codegen-
10
+ * emitted per-source-file module at `.uds/components/<seg>.tsx`).
11
+ *
12
+ * The authoring file is read only by codegen — production renders the
13
+ * downleveled FC. Per-file targets keep Next.js Fast Refresh surgical:
14
+ * editing one authoring source only invalidates that one downleveled
15
+ * module.
16
+ *
17
+ * No JSX-site work: composite / variant / bool / style-prop resolution
18
+ * and motion wiring are owned by the codegen-emitted FC. The loader
19
+ * only changes import sources.
20
+ */
21
+ const transformLoader = function(source) {
22
+ const loaderContext = this ?? void 0;
23
+ if (!loaderContext?.getOptions) return source;
24
+ const { dataPath, isDev, skipWatchDependencies } = loaderContext.getOptions();
25
+ const resourcePath = loaderContext.resourcePath ?? "";
26
+ if (resourcePath.includes(".uds/")) return source;
27
+ if (!skipWatchDependencies) loaderContext.addDependency?.(dataPath);
28
+ const config = loadUdsConfig(dataPath, isDev);
29
+ if (!config) return source;
30
+ const projection = getLoaderProjection(config);
31
+ let hasComponentReference = false;
32
+ for (const name of projection.componentNames) if (source.includes(name)) {
33
+ hasComponentReference = true;
34
+ break;
35
+ }
36
+ if (!hasComponentReference) return source;
37
+ const isTS = resourcePath?.endsWith(".ts") || resourcePath?.endsWith(".tsx");
38
+ const isJSX = resourcePath?.endsWith(".tsx") || resourcePath?.endsWith(".jsx");
39
+ const parserPlugins = [];
40
+ if (isJSX || !isTS) parserPlugins.push("jsx");
41
+ if (isTS) parserPlugins.push("typescript");
42
+ let ast;
43
+ try {
44
+ ast = parse(source, {
45
+ sourceType: "module",
46
+ plugins: parserPlugins
47
+ });
48
+ } catch {
49
+ return source;
50
+ }
51
+ const edits = [];
52
+ traverse(ast, { ImportDeclaration(nodePath) {
53
+ const importSource = nodePath.node.source?.value;
54
+ if (!importSource) return;
55
+ if (!(!importSource.startsWith(".") && !importSource.startsWith("/"))) return;
56
+ let shouldRewriteSource = false;
57
+ for (const specifier of nodePath.node.specifiers) {
58
+ if (specifier.type !== "ImportSpecifier") continue;
59
+ const importedName = specifier.imported?.type === "Identifier" ? specifier.imported.name : void 0;
60
+ if (!importedName) continue;
61
+ if (projection.componentNames.has(importedName)) shouldRewriteSource = true;
62
+ }
63
+ const segmentMatch = importSource.match(/\/uds\/components\/([^/]+)$/);
64
+ if (!shouldRewriteSource && segmentMatch) {
65
+ const pascal = segmentMatch[1].split("-").filter(Boolean).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
66
+ if (projection.componentNames.has(pascal)) shouldRewriteSource = true;
67
+ }
68
+ if (shouldRewriteSource && segmentMatch) {
69
+ const sourceNode = nodePath.node.source;
70
+ if (typeof sourceNode.start !== "number" || typeof sourceNode.end !== "number") return;
71
+ const quote = source[sourceNode.start] ?? "'";
72
+ edits.push({
73
+ start: sourceNode.start,
74
+ end: sourceNode.end,
75
+ value: `${quote}#uds/components/${segmentMatch[1]}${quote}`
76
+ });
77
+ }
78
+ } });
79
+ if (edits.length === 0) return source;
80
+ edits.sort((a, b) => b.start - a.start);
81
+ let output = source;
82
+ for (const edit of edits) output = output.slice(0, edit.start) + edit.value + output.slice(edit.end);
83
+ return output;
84
+ };
85
+ const normal = transformLoader;
86
+ //#endregion
87
+ export { transformLoader as default, normal };
package/dist/next.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { NextConfig } from "next";
2
+
3
+ //#region src/next.d.ts
4
+ interface WithUdsOptions {
5
+ /** Output directory for generated files (default: '.uds') */
6
+ outputDir?: string;
7
+ /** Skip addDependency calls to prevent webpack from watching .uds/ artifacts.
8
+ * Use this when the app has its own reload mechanism (e.g., Studio WebSocket). */
9
+ skipWatchDependencies?: boolean;
10
+ }
11
+ type NextConfigFn = (phase: string, ctx: {
12
+ defaultConfig: NextConfig;
13
+ }) => NextConfig | Promise<NextConfig>;
14
+ declare function withUds(nextConfigOrFn: NextConfig | NextConfigFn, options?: WithUdsOptions): NextConfig | NextConfigFn;
15
+ //#endregion
16
+ export { WithUdsOptions, withUds };
package/dist/next.js ADDED
@@ -0,0 +1,77 @@
1
+ import { __require } from "./_virtual/_rolldown/runtime.js";
2
+ import path from "node:path";
3
+ //#region src/next.ts
4
+ function resolveLoaderPath() {
5
+ return __require.resolve("@yahoo/uds-create-loader/loader");
6
+ }
7
+ function applyUds(nextConfig, options) {
8
+ const outputDir = options?.outputDir ?? ".uds";
9
+ const skipWatch = options?.skipWatchDependencies ?? false;
10
+ const dataPath = path.resolve(process.cwd(), outputDir, "config.json");
11
+ const loaderPath = resolveLoaderPath();
12
+ const turbopack = nextConfig.turbopack ?? {};
13
+ const existingRules = turbopack.rules ?? {};
14
+ const udsLoader = {
15
+ loader: loaderPath,
16
+ options: {
17
+ dataPath,
18
+ isDev: true,
19
+ skipWatchDependencies: skipWatch
20
+ }
21
+ };
22
+ const udsRules = {};
23
+ for (const ext of [
24
+ "tsx",
25
+ "ts",
26
+ "js"
27
+ ]) {
28
+ const key = `*.${ext}`;
29
+ const udsKey = `**/*.${ext}`;
30
+ if (existingRules[key]) udsRules[udsKey] = { loaders: [udsLoader] };
31
+ else udsRules[key] = { loaders: [udsLoader] };
32
+ }
33
+ return {
34
+ ...nextConfig,
35
+ turbopack: {
36
+ ...turbopack,
37
+ rules: {
38
+ ...existingRules,
39
+ ...udsRules
40
+ }
41
+ },
42
+ webpack(config, webpackOptions) {
43
+ config.module?.rules?.push({
44
+ test: /\.(tsx|ts|js)$/,
45
+ use: [{
46
+ loader: loaderPath,
47
+ options: {
48
+ dataPath,
49
+ isDev: webpackOptions.dev,
50
+ skipWatchDependencies: skipWatch
51
+ }
52
+ }]
53
+ });
54
+ if (skipWatch) {
55
+ const existing = config.watchOptions?.ignored;
56
+ config.watchOptions = {
57
+ ...config.watchOptions,
58
+ ignored: [
59
+ ...Array.isArray(existing) ? existing : existing ? [existing] : [],
60
+ "**/.uds/**",
61
+ "**/uds.config.ts"
62
+ ]
63
+ };
64
+ }
65
+ if (typeof nextConfig.webpack === "function") return nextConfig.webpack(config, webpackOptions);
66
+ return config;
67
+ }
68
+ };
69
+ }
70
+ function withUds(nextConfigOrFn, options) {
71
+ if (typeof nextConfigOrFn === "function") return async (phase, ctx) => {
72
+ return applyUds(await nextConfigOrFn(phase, ctx), options);
73
+ };
74
+ return applyUds(nextConfigOrFn, options);
75
+ }
76
+ //#endregion
77
+ export { withUds };
@@ -0,0 +1,27 @@
1
+ import { SerializedConfig } from "@yahoo/uds-create-config";
2
+
3
+ //#region src/transform-runner.d.ts
4
+ interface LoaderTransformOutputSpecs {
5
+ outputGzipBytes: number;
6
+ classNameAssignments: number;
7
+ utilityClassTokens: number;
8
+ uniqueUtilityClassTokens: number;
9
+ runtimeStyleParserCalls: number;
10
+ }
11
+ interface LoaderTransformSuccess {
12
+ output: string;
13
+ specs: LoaderTransformOutputSpecs;
14
+ }
15
+ interface LoaderTransformFailure {
16
+ error: string;
17
+ }
18
+ type LoaderTransformResult = LoaderTransformSuccess | LoaderTransformFailure;
19
+ interface RunLoaderTransformOptions {
20
+ config: SerializedConfig;
21
+ resourcePath?: string;
22
+ isDev?: boolean;
23
+ }
24
+ declare function getLoaderTransformOutputSpecs(output: string): LoaderTransformOutputSpecs;
25
+ declare function runLoaderTransform(source: string, options: RunLoaderTransformOptions): LoaderTransformResult;
26
+ //#endregion
27
+ export { LoaderTransformOutputSpecs, LoaderTransformResult, RunLoaderTransformOptions, getLoaderTransformOutputSpecs, runLoaderTransform };
@@ -0,0 +1,200 @@
1
+ import { seedUdsConfig } from "./loader/artifacts.js";
2
+ import transformLoader from "./loader.js";
3
+ import { isCompiledJsxCallee } from "./loader/babel-utils.js";
4
+ import { parse } from "@babel/parser";
5
+ import path from "node:path";
6
+ import { gzipSync } from "node:zlib";
7
+ import * as t from "@babel/types";
8
+ import { VISITOR_KEYS } from "@babel/types";
9
+ //#region src/transform-runner.ts
10
+ function getClassTokens(value) {
11
+ return value.split(/\s+/).map((token) => token.trim()).filter(Boolean);
12
+ }
13
+ function collectClassTokensFromNode(node, tokens) {
14
+ if (!node) return;
15
+ if (t.isStringLiteral(node)) {
16
+ tokens.push(...getClassTokens(node.value));
17
+ return;
18
+ }
19
+ if (t.isTemplateLiteral(node)) {
20
+ for (const quasi of node.quasis) tokens.push(...getClassTokens(quasi.value.cooked ?? quasi.value.raw ?? ""));
21
+ for (const expression of node.expressions) collectClassTokensFromNode(expression, tokens);
22
+ return;
23
+ }
24
+ if (t.isJSXExpressionContainer(node)) {
25
+ if (!t.isJSXEmptyExpression(node.expression)) collectClassTokensFromNode(node.expression, tokens);
26
+ return;
27
+ }
28
+ if (t.isParenthesizedExpression(node) || t.isTSAsExpression(node) || t.isTSTypeAssertion(node) || t.isTSNonNullExpression(node)) {
29
+ collectClassTokensFromNode(node.expression, tokens);
30
+ return;
31
+ }
32
+ if (t.isUnaryExpression(node) || t.isUpdateExpression(node)) {
33
+ collectClassTokensFromNode(node.argument, tokens);
34
+ return;
35
+ }
36
+ if (t.isBinaryExpression(node) || t.isLogicalExpression(node) || t.isAssignmentExpression(node)) {
37
+ collectClassTokensFromNode(node.left, tokens);
38
+ collectClassTokensFromNode(node.right, tokens);
39
+ return;
40
+ }
41
+ if (t.isConditionalExpression(node)) {
42
+ collectClassTokensFromNode(node.test, tokens);
43
+ collectClassTokensFromNode(node.consequent, tokens);
44
+ collectClassTokensFromNode(node.alternate, tokens);
45
+ return;
46
+ }
47
+ if (t.isSequenceExpression(node)) {
48
+ for (const expression of node.expressions) collectClassTokensFromNode(expression, tokens);
49
+ return;
50
+ }
51
+ if (t.isArrayExpression(node)) {
52
+ for (const element of node.elements) {
53
+ if (!element) continue;
54
+ if (t.isSpreadElement(element)) {
55
+ collectClassTokensFromNode(element.argument, tokens);
56
+ continue;
57
+ }
58
+ collectClassTokensFromNode(element, tokens);
59
+ }
60
+ return;
61
+ }
62
+ if (t.isObjectExpression(node)) {
63
+ for (const property of node.properties) {
64
+ if (t.isSpreadElement(property)) {
65
+ collectClassTokensFromNode(property.argument, tokens);
66
+ continue;
67
+ }
68
+ if (t.isObjectProperty(property)) collectClassTokensFromNode(property.value, tokens);
69
+ }
70
+ return;
71
+ }
72
+ if (t.isCallExpression(node) || t.isOptionalCallExpression(node)) {
73
+ collectClassTokensFromNode(node.callee, tokens);
74
+ for (const argument of node.arguments) {
75
+ if (t.isSpreadElement(argument)) {
76
+ collectClassTokensFromNode(argument.argument, tokens);
77
+ continue;
78
+ }
79
+ collectClassTokensFromNode(argument, tokens);
80
+ }
81
+ return;
82
+ }
83
+ if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
84
+ collectClassTokensFromNode(node.object, tokens);
85
+ if (node.computed) collectClassTokensFromNode(node.property, tokens);
86
+ return;
87
+ }
88
+ if (t.isNewExpression(node)) {
89
+ collectClassTokensFromNode(node.callee, tokens);
90
+ for (const argument of node.arguments ?? []) {
91
+ if (t.isSpreadElement(argument)) {
92
+ collectClassTokensFromNode(argument.argument, tokens);
93
+ continue;
94
+ }
95
+ collectClassTokensFromNode(argument, tokens);
96
+ }
97
+ }
98
+ }
99
+ function isClassNameObjectProperty(node) {
100
+ if (node.computed) return false;
101
+ return t.isIdentifier(node.key) && node.key.name === "className" || t.isStringLiteral(node.key) && node.key.value === "className";
102
+ }
103
+ function isBabelNode(value) {
104
+ return typeof value === "object" && value !== null && "type" in value;
105
+ }
106
+ function isClassNamePropOnCompiledJsxCall(node, parent, grandParent) {
107
+ if (!isClassNameObjectProperty(node)) return false;
108
+ if (!parent || !t.isObjectExpression(parent)) return false;
109
+ if (!grandParent || !t.isCallExpression(grandParent)) return false;
110
+ if (!isCompiledJsxCallee(grandParent.callee)) return false;
111
+ return grandParent.arguments.some((argument) => argument === parent);
112
+ }
113
+ function getClassTokenStats(output) {
114
+ try {
115
+ const ast = parse(output, {
116
+ sourceType: "module",
117
+ plugins: ["jsx", "typescript"]
118
+ });
119
+ const uniqueTokens = /* @__PURE__ */ new Set();
120
+ let total = 0;
121
+ let assignments = 0;
122
+ const addTokens = (node) => {
123
+ const nextTokens = [];
124
+ collectClassTokensFromNode(node, nextTokens);
125
+ total += nextTokens.length;
126
+ for (const token of nextTokens) uniqueTokens.add(token);
127
+ };
128
+ const visit = (node, parent, grandParent) => {
129
+ if (!node) return;
130
+ if (t.isJSXAttribute(node) && t.isJSXIdentifier(node.name) && node.name.name === "className") {
131
+ assignments += 1;
132
+ addTokens(node.value);
133
+ } else if (t.isObjectProperty(node)) {
134
+ if (isClassNamePropOnCompiledJsxCall(node, parent, grandParent)) {
135
+ assignments += 1;
136
+ addTokens(node.value);
137
+ }
138
+ }
139
+ const visitorKeys = VISITOR_KEYS[node.type] ?? [];
140
+ const typedNode = node;
141
+ for (const key of visitorKeys) {
142
+ const childValue = typedNode[key];
143
+ if (Array.isArray(childValue)) {
144
+ for (const item of childValue) if (isBabelNode(item)) visit(item, node, parent);
145
+ continue;
146
+ }
147
+ if (isBabelNode(childValue)) visit(childValue, node, parent);
148
+ }
149
+ };
150
+ visit(ast.program, null, null);
151
+ return {
152
+ total,
153
+ unique: uniqueTokens.size,
154
+ assignments
155
+ };
156
+ } catch {
157
+ return {
158
+ total: 0,
159
+ unique: 0,
160
+ assignments: (output.match(/className\s*[:=]/g) ?? []).length
161
+ };
162
+ }
163
+ }
164
+ function getRuntimeStyleParserCallCount(output) {
165
+ return [/extractStyleProps\s*\(/g, /getStyles\s*\(/g].reduce((count, pattern) => count + (output.match(pattern) ?? []).length, 0);
166
+ }
167
+ function getLoaderTransformOutputSpecs(output) {
168
+ const classTokenStats = getClassTokenStats(output);
169
+ return {
170
+ outputGzipBytes: gzipSync(output).byteLength,
171
+ classNameAssignments: classTokenStats.assignments,
172
+ utilityClassTokens: classTokenStats.total,
173
+ uniqueUtilityClassTokens: classTokenStats.unique,
174
+ runtimeStyleParserCalls: getRuntimeStyleParserCallCount(output)
175
+ };
176
+ }
177
+ const VIRTUAL_CONFIG_PATH = "/virtual/uds-config.json";
178
+ function runLoaderTransform(source, options) {
179
+ if (typeof source !== "string") return { error: "`source` must be a string." };
180
+ seedUdsConfig(VIRTUAL_CONFIG_PATH, options.config);
181
+ const context = {
182
+ getOptions: () => ({
183
+ dataPath: VIRTUAL_CONFIG_PATH,
184
+ isDev: options.isDev ?? false
185
+ }),
186
+ addDependency: () => {},
187
+ resourcePath: options.resourcePath ?? path.resolve(process.cwd(), "playground-input.tsx")
188
+ };
189
+ try {
190
+ const output = transformLoader.call(context, source);
191
+ return {
192
+ output,
193
+ specs: getLoaderTransformOutputSpecs(output)
194
+ };
195
+ } catch (error) {
196
+ return { error: error instanceof Error ? error.message : "Transform failed unexpectedly." };
197
+ }
198
+ }
199
+ //#endregion
200
+ export { getLoaderTransformOutputSpecs, runLoaderTransform };