Skip to content

Commit 8bed000

Browse files
committed
Add build plugins to inject design token fallbacks
1 parent aff5aff commit 8bed000

11 files changed

Lines changed: 145 additions & 1 deletion

File tree

package-lock.json

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

packages/theme/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@
4343
"import": "./build-module/prebuilt/js/design-tokens.mjs",
4444
"default": "./build/prebuilt/js/design-tokens.cjs"
4545
},
46+
"./postcss-plugins/postcss-ds-token-fallbacks": {
47+
"types": "./src/postcss-plugins/postcss-ds-token-fallbacks.d.mts",
48+
"import": "./src/postcss-plugins/postcss-ds-token-fallbacks.mjs",
49+
"default": "./src/postcss-plugins/postcss-ds-token-fallbacks.mjs"
50+
},
51+
"./esbuild-plugins/esbuild-ds-token-fallbacks": {
52+
"types": "./src/esbuild-plugins/esbuild-ds-token-fallbacks.d.mts",
53+
"import": "./src/esbuild-plugins/esbuild-ds-token-fallbacks.mjs",
54+
"default": "./src/esbuild-plugins/esbuild-ds-token-fallbacks.mjs"
55+
},
56+
"./vite-plugins/vite-ds-token-fallbacks": {
57+
"types": "./src/vite-plugins/vite-ds-token-fallbacks.d.mts",
58+
"import": "./src/vite-plugins/vite-ds-token-fallbacks.mjs",
59+
"default": "./src/vite-plugins/vite-ds-token-fallbacks.mjs"
60+
},
4661
"./stylelint-plugins/no-unknown-ds-tokens": {
4762
"types": "./build-types/stylelint-plugins/no-unknown-ds-tokens.d.ts",
4863
"import": "./src/stylelint-plugins/no-unknown-ds-tokens.mjs",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import type { Plugin } from 'esbuild';
2+
3+
declare const plugin: Plugin;
4+
export default plugin;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { readFile } from 'fs/promises';
2+
import { addFallbackToVar } from '../postcss-plugins/ds-token-fallbacks.mjs';
3+
4+
const LOADER_MAP = {
5+
'.js': 'jsx',
6+
'.jsx': 'jsx',
7+
'.ts': 'tsx',
8+
'.tsx': 'tsx',
9+
'.mjs': 'jsx',
10+
};
11+
12+
/**
13+
* esbuild plugin that injects design-system token fallbacks into JS/TS files.
14+
*
15+
* Replaces bare `var(--wpds-*)` references in string literals with
16+
* `var(--wpds-*, <fallback>)` so components render correctly without
17+
* a ThemeProvider.
18+
*/
19+
const plugin = {
20+
name: 'ds-token-fallbacks-js',
21+
setup( build ) {
22+
build.onLoad( { filter: /\.[mc]?[jt]sx?$/ }, async ( args ) => {
23+
// Skip node_modules.
24+
if ( args.path.includes( 'node_modules' ) ) {
25+
return undefined;
26+
}
27+
28+
const source = await readFile( args.path, 'utf8' );
29+
30+
if ( ! source.includes( '--wpds-' ) ) {
31+
return undefined;
32+
}
33+
34+
const ext = args.path.match( /(\.[^.]+)$/ )?.[ 1 ] || '.js';
35+
36+
return {
37+
contents: addFallbackToVar( source ),
38+
loader: LOADER_MAP[ ext ] || 'jsx',
39+
};
40+
} );
41+
},
42+
};
43+
44+
export default plugin;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import type { PluginCreator } from 'postcss';
2+
3+
declare const plugin: PluginCreator<never>;
4+
export default plugin;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { addFallbackToVar } from './ds-token-fallbacks.mjs';
2+
3+
const plugin = () => ( {
4+
postcssPlugin: 'postcss-ds-token-fallbacks',
5+
/** @param {import('postcss').Declaration} decl */
6+
Declaration( decl ) {
7+
const updated = addFallbackToVar( decl.value );
8+
if ( updated !== decl.value ) {
9+
decl.value = updated;
10+
}
11+
},
12+
} );
13+
14+
plugin.postcss = true;
15+
16+
export default plugin;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import type { Plugin } from 'vite';
2+
3+
declare const plugin: () => Plugin;
4+
export default plugin;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { addFallbackToVar } from '../postcss-plugins/ds-token-fallbacks.mjs';
2+
3+
/**
4+
* Vite plugin that injects design-system token fallbacks into JS/TS files.
5+
*
6+
* Replaces bare `var(--wpds-*)` references in string literals with
7+
* `var(--wpds-*, <fallback>)` so components render correctly without
8+
* a ThemeProvider.
9+
*/
10+
const plugin = () => ( {
11+
name: 'ds-token-fallbacks-js',
12+
transform( code, id ) {
13+
if ( ! /\.[mc]?[jt]sx?$/.test( id ) ) {
14+
return null;
15+
}
16+
if ( ! code.includes( '--wpds-' ) ) {
17+
return null;
18+
}
19+
return addFallbackToVar( code );
20+
},
21+
} );
22+
23+
export default plugin;

packages/wp-build/lib/build.mjs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,26 @@ import babel from 'esbuild-plugin-babel';
2121
import { camelCase } from 'change-case';
2222
import { NodePackageImporter } from 'sass-embedded';
2323

24+
// Optional dependency: @wordpress/theme provides plugins that inject fallback
25+
// values for design system tokens. Fails gracefully when the package is not
26+
// installed (it is an optional peerDependency).
27+
let dsTokenFallbacks;
28+
let dsTokenFallbacksJs;
29+
try {
30+
const { default: postcssPlugin } = await import(
31+
// eslint-disable-next-line import/no-unresolved
32+
'@wordpress/theme/postcss-plugins/postcss-ds-token-fallbacks'
33+
);
34+
const { default: esbuildPlugin } = await import(
35+
// eslint-disable-next-line import/no-unresolved
36+
'@wordpress/theme/esbuild-plugins/esbuild-ds-token-fallbacks'
37+
);
38+
dsTokenFallbacks = postcssPlugin;
39+
dsTokenFallbacksJs = esbuildPlugin;
40+
} catch {
41+
// @wordpress/theme is optional; skip token fallbacks if not available.
42+
}
43+
2444
/**
2545
* Internal dependencies
2646
*/
@@ -150,8 +170,9 @@ function compileInlineStyle( { cssModules = false, minify = true } = {} ) {
150170

151171
let moduleExports = null;
152172

153-
// Transform the code: CSS modules and minification.
173+
// Transform the code: token fallbacks, CSS modules and minification.
154174
const plugins = [
175+
dsTokenFallbacks,
155176
cssModules &&
156177
postcssModules( {
157178
generateScopedName: '[contenthash]__[local]',
@@ -1242,6 +1263,7 @@ async function transpilePackage( packageName ) {
12421263
},
12431264
};
12441265
const plugins = [
1266+
dsTokenFallbacksJs,
12451267
needsEmotionPlugin && emotionPlugin,
12461268
wasmInlinePlugin,
12471269
externalizeAllExceptCssPlugin,

storybook/main.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
} from 'vite';
77
import react from '@vitejs/plugin-react';
88
import type { StorybookConfig } from '@storybook/react-vite';
9+
import dsTokenFallbacks from '@wordpress/theme/postcss-plugins/postcss-ds-token-fallbacks';
10+
import dsTokenFallbacksJs from '@wordpress/theme/vite-plugins/vite-ds-token-fallbacks';
911

1012
const { NODE_ENV = 'development' } = process.env;
1113

@@ -77,6 +79,7 @@ const config: StorybookConfig = {
7779
viteFinal: async ( viteConfig ) => {
7880
return mergeConfig( viteConfig, {
7981
plugins: [
82+
dsTokenFallbacksJs(),
8083
react( {
8184
jsxImportSource: '@emotion/react',
8285
babel: {
@@ -174,6 +177,13 @@ const config: StorybookConfig = {
174177
NODE_ENV === 'development'
175178
),
176179
},
180+
css: {
181+
postcss: {
182+
// Vite bundles its own PostCSS, creating a deep
183+
// type incompatibility with the top-level PostCSS.
184+
plugins: [ dsTokenFallbacks as any ],
185+
},
186+
},
177187
optimizeDeps: {
178188
esbuildOptions: {
179189
loader: {

0 commit comments

Comments
 (0)