@astryxdesign/build 0.0.0-bootstrap.0 → 0.0.15
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/LICENSE +21 -0
- package/README.md +245 -1
- package/dist/vite.mjs +284 -0
- package/package.json +54 -5
- package/src/babel.js +106 -0
- package/src/babel.test.mjs +73 -0
- package/src/config.js +92 -0
- package/src/index.js +251 -0
- package/src/next.js +66 -0
- package/src/vite.test.ts +45 -0
- package/src/vite.ts +427 -0
package/src/vite.ts
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import type {Plugin, UserConfig} from 'vite';
|
|
4
|
+
import stylexBabelPlugin from '@stylexjs/babel-plugin';
|
|
5
|
+
import stylex from '@stylexjs/unplugin';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import {fileURLToPath} from 'url';
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
const LIBRARY_PATTERN = 'node_modules/@astryxdesign/';
|
|
12
|
+
const STYLEX_CSS_PATH = '/virtual:stylex.css';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Browser targets for lightningcss (opt-in).
|
|
16
|
+
* Only needed if your StyleX version lowers light-dark() without them.
|
|
17
|
+
* Exported for consumers who want to opt in explicitly.
|
|
18
|
+
*/
|
|
19
|
+
export const LIGHTNINGCSS_TARGETS = {
|
|
20
|
+
chrome: 123 << 16,
|
|
21
|
+
firefox: 120 << 16,
|
|
22
|
+
safari: (17 << 16) | (5 << 8),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Legacy options shape — kept for backward compatibility.
|
|
27
|
+
* Prefer the zero-config form: xdsStylex()
|
|
28
|
+
*/
|
|
29
|
+
export interface XDSVitePluginLegacyOptions {
|
|
30
|
+
stylexOptions: Parameters<typeof stylex.vite>[0];
|
|
31
|
+
libraryPattern?: string;
|
|
32
|
+
/** StyleX atomic class-name prefix for Astryx library styles. @default 'astryx' */
|
|
33
|
+
stylexPrefix?: string;
|
|
34
|
+
layers?: {
|
|
35
|
+
library?: string;
|
|
36
|
+
product?: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface XDSVitePluginOptions {
|
|
41
|
+
/**
|
|
42
|
+
* Whether to enable dev mode for StyleX.
|
|
43
|
+
* @default process.env.NODE_ENV !== 'production'
|
|
44
|
+
*/
|
|
45
|
+
dev?: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Root directory for module resolution.
|
|
49
|
+
* @default process.cwd()
|
|
50
|
+
*/
|
|
51
|
+
rootDir?: string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Pattern to identify Astryx library files vs product files.
|
|
55
|
+
* @default 'node_modules/@astryxdesign/'
|
|
56
|
+
*/
|
|
57
|
+
libraryPattern?: string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* CSS layer names for the split output.
|
|
61
|
+
*/
|
|
62
|
+
layers?: {
|
|
63
|
+
/** Layer name for Astryx library styles @default 'astryx-base' */
|
|
64
|
+
library?: string;
|
|
65
|
+
/** Layer name for product styles @default 'product' */
|
|
66
|
+
product?: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* LightningCSS browser targets. Only needed if your StyleX version
|
|
71
|
+
* lowers light-dark() without them. Most recent versions preserve
|
|
72
|
+
* light-dark() by default.
|
|
73
|
+
* @default undefined (no targets set)
|
|
74
|
+
*/
|
|
75
|
+
lightningcssTargets?: Record<string, number>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* StyleX atomic class-name prefix for Astryx *library* styles. The product
|
|
79
|
+
* build uses a distinct prefix so library and product atoms never collide
|
|
80
|
+
* across layers.
|
|
81
|
+
*
|
|
82
|
+
* Configurable to support the Astryx-prefix migration (P2380608025): a consumer
|
|
83
|
+
* can rebrand the library atom prefix to `astryx` before the final cutover.
|
|
84
|
+
* Defaults to `xds` so existing consumers are unaffected.
|
|
85
|
+
*
|
|
86
|
+
* @default 'astryx'
|
|
87
|
+
*/
|
|
88
|
+
stylexPrefix?: string;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Extra StyleX options to merge.
|
|
92
|
+
*/
|
|
93
|
+
stylexOverrides?: Record<string, unknown>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Astryx Vite plugin for source builds.
|
|
98
|
+
*
|
|
99
|
+
* Provides sensible defaults for StyleX compilation with Astryx.
|
|
100
|
+
* Just spread into your plugins array:
|
|
101
|
+
*
|
|
102
|
+
* plugins: [...xdsStylex(), react()]
|
|
103
|
+
*
|
|
104
|
+
* Handles:
|
|
105
|
+
* - StyleX compilation with correct settings
|
|
106
|
+
* - CSS layer ordering (reset < astryx-base < astryx-theme < product)
|
|
107
|
+
* - resolve.alias for @astryxdesign/core source
|
|
108
|
+
* - optimizeDeps.exclude to prevent Vite pre-bundling Astryx
|
|
109
|
+
*
|
|
110
|
+
* @param options — optional overrides
|
|
111
|
+
*/
|
|
112
|
+
export function xdsStylex(
|
|
113
|
+
options: XDSVitePluginOptions | XDSVitePluginLegacyOptions = {},
|
|
114
|
+
): Plugin[] {
|
|
115
|
+
// Detect legacy API: xdsStylex({stylexOptions: {...}})
|
|
116
|
+
if ('stylexOptions' in options && options.stylexOptions) {
|
|
117
|
+
return xdsStylexLegacy(options as XDSVitePluginLegacyOptions);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const opts = options as XDSVitePluginOptions;
|
|
121
|
+
const {
|
|
122
|
+
dev = process.env.NODE_ENV !== 'production',
|
|
123
|
+
rootDir = process.cwd(),
|
|
124
|
+
libraryPattern = LIBRARY_PATTERN,
|
|
125
|
+
layers = {},
|
|
126
|
+
lightningcssTargets,
|
|
127
|
+
stylexPrefix = 'astryx',
|
|
128
|
+
stylexOverrides = {},
|
|
129
|
+
} = opts;
|
|
130
|
+
|
|
131
|
+
const libraryLayer = layers.library ?? 'astryx-base';
|
|
132
|
+
const productLayer = layers.product ?? 'product';
|
|
133
|
+
|
|
134
|
+
// Build StyleX options with sensible defaults
|
|
135
|
+
const stylexOptions: Record<string, unknown> = {
|
|
136
|
+
dev,
|
|
137
|
+
runtimeInjection: false,
|
|
138
|
+
treeshakeCompensation: true,
|
|
139
|
+
unstable_moduleResolution: {
|
|
140
|
+
type: 'commonJS',
|
|
141
|
+
rootDir,
|
|
142
|
+
},
|
|
143
|
+
...(lightningcssTargets && {
|
|
144
|
+
lightningcssOptions: {targets: lightningcssTargets},
|
|
145
|
+
}),
|
|
146
|
+
...stylexOverrides,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Inject our babel wrapper as a user plugin — it runs before the
|
|
150
|
+
// unplugin's hardcoded StyleX instance and handles prefix routing.
|
|
151
|
+
const xdsBabelPlugin = path.resolve(__dirname, 'babel.js');
|
|
152
|
+
|
|
153
|
+
const basePlugin = stylex.vite({
|
|
154
|
+
...(stylexOptions as any),
|
|
155
|
+
useCSSLayers: true,
|
|
156
|
+
babelConfig: {
|
|
157
|
+
plugins: [
|
|
158
|
+
[
|
|
159
|
+
xdsBabelPlugin,
|
|
160
|
+
{
|
|
161
|
+
...stylexOptions,
|
|
162
|
+
libraryPrefix: stylexPrefix,
|
|
163
|
+
babelConfig: undefined,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Layer order declaration plugin
|
|
171
|
+
const layerOrderPlugin: Plugin = {
|
|
172
|
+
name: 'xds-css-layer-order',
|
|
173
|
+
transformIndexHtml() {
|
|
174
|
+
return [
|
|
175
|
+
{
|
|
176
|
+
tag: 'style',
|
|
177
|
+
children: `@layer reset, ${libraryLayer}, astryx-theme, ${productLayer};`,
|
|
178
|
+
injectTo: 'head-prepend',
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Config plugin — injects resolve.alias and optimizeDeps
|
|
185
|
+
const configPlugin: Plugin = {
|
|
186
|
+
name: 'xds-config',
|
|
187
|
+
config(): UserConfig {
|
|
188
|
+
// Discover all @astryxdesign/* packages to exclude from pre-bundling.
|
|
189
|
+
// Astryx ships as source that must be compiled by StyleX — pre-bundling
|
|
190
|
+
// strips stylex.create/defineVars calls and causes runtime errors.
|
|
191
|
+
let xdsPackages: string[] = ['@astryxdesign/core'];
|
|
192
|
+
try {
|
|
193
|
+
const fs = require('fs');
|
|
194
|
+
const xdsDir = path.resolve(rootDir, 'node_modules/@astryxdesign');
|
|
195
|
+
if (fs.existsSync(xdsDir)) {
|
|
196
|
+
xdsPackages = fs
|
|
197
|
+
.readdirSync(xdsDir)
|
|
198
|
+
.filter((name: string) => !name.startsWith('.'))
|
|
199
|
+
.map((name: string) => `@astryxdesign/${name}`);
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
// Fallback to just @astryxdesign/core if discovery fails
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
resolve: {
|
|
207
|
+
alias: {
|
|
208
|
+
'@astryxdesign/core/theme/tokens.stylex': path.resolve(
|
|
209
|
+
rootDir,
|
|
210
|
+
'node_modules/@astryxdesign/core/src/theme/tokens.stylex.ts',
|
|
211
|
+
),
|
|
212
|
+
'@astryxdesign/core': path.resolve(rootDir, 'node_modules/@astryxdesign/core/src'),
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
optimizeDeps: {
|
|
216
|
+
exclude: xdsPackages,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Split-layer interceptor plugin (dev server only)
|
|
223
|
+
const splitLayerPlugin: Plugin = {
|
|
224
|
+
name: 'xds-split-layers',
|
|
225
|
+
configureServer(server) {
|
|
226
|
+
let stylexPlugin: any = null;
|
|
227
|
+
|
|
228
|
+
return () => {
|
|
229
|
+
for (const p of server.config.plugins.flat()) {
|
|
230
|
+
if ((p as any)?.__stylexGetSharedStore) {
|
|
231
|
+
stylexPlugin = p;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
server.middlewares.stack.unshift({
|
|
237
|
+
route: '',
|
|
238
|
+
handle: (req: any, res: any, next: any) => {
|
|
239
|
+
if (!req.url?.startsWith(STYLEX_CSS_PATH)) {
|
|
240
|
+
return next();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!stylexPlugin) {
|
|
244
|
+
res.statusCode = 200;
|
|
245
|
+
res.setHeader('Content-Type', 'text/css');
|
|
246
|
+
res.end('');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const shared = stylexPlugin.__stylexGetSharedStore?.();
|
|
251
|
+
const rulesById = shared?.rulesById;
|
|
252
|
+
|
|
253
|
+
if (!rulesById || rulesById.size === 0) {
|
|
254
|
+
res.statusCode = 200;
|
|
255
|
+
res.setHeader('Content-Type', 'text/css');
|
|
256
|
+
res.end('');
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const libraryRules: any[] = [];
|
|
261
|
+
const productRules: any[] = [];
|
|
262
|
+
|
|
263
|
+
for (const [filePath, rules] of rulesById.entries()) {
|
|
264
|
+
if (filePath.includes(libraryPattern)) {
|
|
265
|
+
libraryRules.push(...rules);
|
|
266
|
+
} else {
|
|
267
|
+
productRules.push(...rules);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const libraryCss = libraryRules.length
|
|
272
|
+
? stylexBabelPlugin.processStylexRules(libraryRules, {
|
|
273
|
+
useLayers: true,
|
|
274
|
+
})
|
|
275
|
+
: '';
|
|
276
|
+
const productCss = productRules.length
|
|
277
|
+
? stylexBabelPlugin.processStylexRules(productRules, {
|
|
278
|
+
useLayers: true,
|
|
279
|
+
})
|
|
280
|
+
: '';
|
|
281
|
+
|
|
282
|
+
const parts: string[] = [];
|
|
283
|
+
if (libraryCss)
|
|
284
|
+
parts.push(`@layer ${libraryLayer} {\n${libraryCss}\n}`);
|
|
285
|
+
if (productCss)
|
|
286
|
+
parts.push(`@layer ${productLayer} {\n${productCss}\n}`);
|
|
287
|
+
|
|
288
|
+
res.statusCode = 200;
|
|
289
|
+
res.setHeader('Content-Type', 'text/css');
|
|
290
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
291
|
+
res.end(parts.join('\n\n'));
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
return [configPlugin, layerOrderPlugin, basePlugin, splitLayerPlugin];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Legacy implementation — handles the old xdsStylex({stylexOptions: {...}}) API.
|
|
303
|
+
* Used by Storybook and other existing configs.
|
|
304
|
+
*/
|
|
305
|
+
function xdsStylexLegacy(options: XDSVitePluginLegacyOptions): Plugin[] {
|
|
306
|
+
const {
|
|
307
|
+
stylexOptions,
|
|
308
|
+
libraryPattern = LIBRARY_PATTERN,
|
|
309
|
+
stylexPrefix = 'astryx',
|
|
310
|
+
layers = {},
|
|
311
|
+
} = options;
|
|
312
|
+
|
|
313
|
+
const libraryLayer = layers.library ?? 'astryx-base';
|
|
314
|
+
const productLayer = layers.product ?? 'product';
|
|
315
|
+
|
|
316
|
+
const xdsBabelPlugin = path.resolve(__dirname, 'babel.js');
|
|
317
|
+
const existingPlugins = (stylexOptions as any).babelConfig?.plugins ?? [];
|
|
318
|
+
|
|
319
|
+
const basePlugin = stylex.vite({
|
|
320
|
+
...(stylexOptions as any),
|
|
321
|
+
useCSSLayers: true,
|
|
322
|
+
babelConfig: {
|
|
323
|
+
...(stylexOptions as any).babelConfig,
|
|
324
|
+
plugins: [
|
|
325
|
+
[
|
|
326
|
+
xdsBabelPlugin,
|
|
327
|
+
{
|
|
328
|
+
...(stylexOptions as any),
|
|
329
|
+
libraryPrefix: stylexPrefix,
|
|
330
|
+
babelConfig: undefined,
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
...existingPlugins,
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const layerOrderPlugin: Plugin = {
|
|
339
|
+
name: 'xds-css-layer-order',
|
|
340
|
+
transformIndexHtml() {
|
|
341
|
+
return [
|
|
342
|
+
{
|
|
343
|
+
tag: 'style',
|
|
344
|
+
children: `@layer reset, ${libraryLayer}, astryx-theme, ${productLayer};`,
|
|
345
|
+
injectTo: 'head-prepend',
|
|
346
|
+
},
|
|
347
|
+
];
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const splitLayerPlugin: Plugin = {
|
|
352
|
+
name: 'xds-split-layers',
|
|
353
|
+
configureServer(server) {
|
|
354
|
+
let stylexPlugin: any = null;
|
|
355
|
+
|
|
356
|
+
return () => {
|
|
357
|
+
for (const p of server.config.plugins.flat()) {
|
|
358
|
+
if ((p as any)?.__stylexGetSharedStore) {
|
|
359
|
+
stylexPlugin = p;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
server.middlewares.stack.unshift({
|
|
365
|
+
route: '',
|
|
366
|
+
handle: (req: any, res: any, next: any) => {
|
|
367
|
+
if (!req.url?.startsWith(STYLEX_CSS_PATH)) {
|
|
368
|
+
return next();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (!stylexPlugin) {
|
|
372
|
+
res.statusCode = 200;
|
|
373
|
+
res.setHeader('Content-Type', 'text/css');
|
|
374
|
+
res.end('');
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const shared = stylexPlugin.__stylexGetSharedStore?.();
|
|
379
|
+
const rulesById = shared?.rulesById;
|
|
380
|
+
|
|
381
|
+
if (!rulesById || rulesById.size === 0) {
|
|
382
|
+
res.statusCode = 200;
|
|
383
|
+
res.setHeader('Content-Type', 'text/css');
|
|
384
|
+
res.end('');
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const libraryRules: any[] = [];
|
|
389
|
+
const productRules: any[] = [];
|
|
390
|
+
|
|
391
|
+
for (const [filePath, rules] of rulesById.entries()) {
|
|
392
|
+
if (filePath.includes(libraryPattern)) {
|
|
393
|
+
libraryRules.push(...rules);
|
|
394
|
+
} else {
|
|
395
|
+
productRules.push(...rules);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const libraryCss = libraryRules.length
|
|
400
|
+
? stylexBabelPlugin.processStylexRules(libraryRules, {
|
|
401
|
+
useLayers: true,
|
|
402
|
+
})
|
|
403
|
+
: '';
|
|
404
|
+
const productCss = productRules.length
|
|
405
|
+
? stylexBabelPlugin.processStylexRules(productRules, {
|
|
406
|
+
useLayers: true,
|
|
407
|
+
})
|
|
408
|
+
: '';
|
|
409
|
+
|
|
410
|
+
const parts: string[] = [];
|
|
411
|
+
if (libraryCss)
|
|
412
|
+
parts.push(`@layer ${libraryLayer} {\n${libraryCss}\n}`);
|
|
413
|
+
if (productCss)
|
|
414
|
+
parts.push(`@layer ${productLayer} {\n${productCss}\n}`);
|
|
415
|
+
|
|
416
|
+
res.statusCode = 200;
|
|
417
|
+
res.setHeader('Content-Type', 'text/css');
|
|
418
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
419
|
+
res.end(parts.join('\n\n'));
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
};
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
return [layerOrderPlugin, basePlugin, splitLayerPlugin];
|
|
427
|
+
}
|