Skip to content

Commit 64e5952

Browse files
Theme: Make JavaScript token fallback transforms syntax-aware (#83356)
* Theme: Make JavaScript token fallback transforms syntax-aware * Theme: Link JavaScript transform changelog to PR * Theme: Remove duplicate unknown-token test * Theme: Preserve compiler syntax and source-map behavior * Theme: Preserve script syntax, enum names, and Windows source maps * Theme: Preserve indexed input source maps * Theme: Preserve source URLs and string-named re-exports * Theme: Accept TypeScript decorator non-null assertions * Theme: Leave unsupported syntax to downstream compilers --- Co-authored-by: ciampo <mciampini@git.wordpress.org> Co-authored-by: manzoorwanijk <manzoorwanijk@git.wordpress.org>
1 parent e5a8049 commit 64e5952

12 files changed

Lines changed: 885 additions & 30 deletions

‎package-lock.json‎

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/theme/CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Bug Fixes
66

77
- Avoid intercepting esbuild virtual modules, preserve root custom-property priorities on cleanup, and reject non-finite RGB seed channels while treating missing (`none`) RGB channels as zero. ([#83355](https://github.com/WordPress/gutenberg/pull/83355))
8+
- Make JavaScript token fallback transforms syntax-aware, preserve tagged-template values and source maps, and support Vite query-string module IDs. Use the TypeScript loader for `.ts`, `.mts`, and `.cts` files handled by the esbuild plugin, including files whose token values are unchanged. ([#83356](https://github.com/WordPress/gutenberg/pull/83356))
89

910
## 2.2.0 (2026-09-23)
1011

‎packages/theme/README.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,13 @@ The build plugins inject generated fallbacks into bare `var(--wpds-*)` reference
278278
| `@wordpress/theme/esbuild-plugins/esbuild-ds-token-fallbacks` | esbuild | JS/TS |
279279
| `@wordpress/theme/vite-plugins/vite-ds-token-fallbacks` | Vite | JS/TS |
280280

281-
Existing fallbacks are unchanged. An unknown token in a bare reference fails the build.
281+
Existing fallbacks are unchanged. An unknown token in a bare reference in transformed values fails the build.
282+
283+
The JavaScript plugins treat token references in string values, JSX attribute values, and static template parts as CSS. This includes tagged templates such as `String.raw`. They leave comments, regular expressions, property names, module paths, JSX text, and TypeScript types unchanged. Token names assembled across template expressions are not resolved. As before, a token reference in a runtime message string is also treated as CSS.
284+
285+
Files the JavaScript parser cannot read are left unchanged for the downstream compiler. To add fallbacks in Vite files that use custom syntax, configure the syntax-stripping plugin with `enforce: 'pre'` and list it before the token fallback plugin.
286+
287+
Both JavaScript plugins preserve source maps. The Vite plugin runs before JavaScript and TypeScript compilation and supports module IDs with query strings. It skips `?raw` and `?url` imports so their exported file contents and URLs stay unchanged.
282288

283289
### PostCSS
284290

‎packages/theme/esbuild-plugins/esbuild-ds-token-fallbacks.mjs‎

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import { readFile } from 'fs/promises';
2-
import { addFallbackToVar } from '../postcss-plugins/ds-token-fallbacks.mjs';
2+
import { fileURLToPath, pathToFileURL } from 'url';
3+
import remapping from '@jridgewell/remapping';
4+
import { AnyMap } from '@jridgewell/trace-mapping';
5+
import { transformDsTokenFallbacks } from '../js-plugins/transform-ds-token-fallbacks.mjs';
36

47
/** @type {Record<string, import('esbuild').Loader>} */
58
const LOADER_MAP = {
69
'.js': 'jsx',
710
'.jsx': 'jsx',
8-
'.ts': 'tsx',
11+
'.ts': 'ts',
912
'.tsx': 'tsx',
1013
'.mjs': 'jsx',
11-
'.mts': 'tsx',
14+
'.mts': 'ts',
1215
'.cjs': 'jsx',
13-
'.cts': 'tsx',
16+
'.cts': 'ts',
1417
};
1518

1619
/**
@@ -40,12 +43,79 @@ const plugin = {
4043
}
4144

4245
const ext = args.path.match( /(\.[^.]+)$/ )?.[ 1 ] || '.js';
46+
const result = transformDsTokenFallbacks( source, args.path );
47+
if ( ! result ) {
48+
return {
49+
contents: source,
50+
loader: LOADER_MAP[ ext ] || 'jsx',
51+
};
52+
}
53+
let sourceMap = result.map.toUrl();
54+
/** @type {string[]} */
55+
const watchFiles = [];
56+
/** @type {import('esbuild').PartialMessage[]} */
57+
const warnings = [];
58+
if ( result.sourceMappingURL ) {
59+
try {
60+
const url = new URL(
61+
result.sourceMappingURL,
62+
pathToFileURL( args.path )
63+
);
64+
/** @type {string | undefined} */
65+
let inputMap;
66+
let mapPath = args.path;
67+
if ( url.protocol === 'data:' ) {
68+
const comma = url.href.indexOf( ',' );
69+
const data = decodeURIComponent(
70+
url.href.slice( comma + 1 )
71+
);
72+
inputMap = url.href
73+
.slice( 0, comma )
74+
.endsWith( ';base64' )
75+
? Buffer.from( data, 'base64' ).toString(
76+
'utf8'
77+
)
78+
: data;
79+
} else if ( url.protocol === 'file:' ) {
80+
mapPath = fileURLToPath( url );
81+
watchFiles.push( mapPath );
82+
inputMap = await readFile( url, 'utf8' );
83+
}
84+
// Like esbuild, do not fetch remote source-map URLs.
85+
if ( inputMap !== undefined ) {
86+
const map = remapping(
87+
[
88+
result.map.toString(),
89+
new AnyMap(
90+
inputMap,
91+
pathToFileURL( mapPath ).href
92+
),
93+
],
94+
() => null
95+
);
96+
sourceMap = `data:application/json;base64,${ Buffer.from( map.toString() ).toString( 'base64' ) }`;
97+
}
98+
} catch ( error ) {
99+
// esbuild ignores missing maps and warns for malformed maps.
100+
if ( ! (
101+
error &&
102+
typeof error === 'object' &&
103+
'code' in error &&
104+
error.code === 'ENOENT'
105+
) ) {
106+
warnings.push( {
107+
text: `Could not load the input source map: ${ error instanceof Error ? error.message : String( error ) }`,
108+
detail: error,
109+
} );
110+
}
111+
}
112+
}
43113

44114
return {
45-
contents: addFallbackToVar( source, {
46-
escapeQuotes: true,
47-
} ),
115+
contents: `${ result.code }\n//# sourceMappingURL=${ sourceMap }`,
48116
loader: LOADER_MAP[ ext ] || 'jsx',
117+
watchFiles,
118+
warnings,
49119
};
50120
}
51121
);

0 commit comments

Comments
 (0)