@astryxdesign/build 0.0.0-bootstrap.0 → 0.1.0-canary.08d4cf4

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Meta Platforms, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,247 @@
1
1
  # @astryxdesign/build
2
2
 
3
- This is a `0.0.0-bootstrap.0` placeholder published only to claim the npm name while configuring npm trusted publishing. Do not install it; it will be superseded by the first real release.
3
+ Build plugins for XDS source builds. Provides babel, PostCSS, and Vite integrations that compile XDS library and product code with separate class name prefixes, which enables independent CSS layers:
4
+
5
+ ```
6
+ reset < astryx-base (library, astryx prefix) < astryx-theme < product (app, x prefix)
7
+ ```
8
+
9
+ ## Why?
10
+
11
+ StyleX generates atomic CSS: same declaration = same class name. Without separate prefixes, library and product classes collide and can't be placed in independent CSS layers, which breaks theme overrides.
12
+
13
+ `@astryxdesign/build` solves this by:
14
+
15
+ 1. Compiling XDS library code with `astryx` prefix (`.astryx78zum5`)
16
+ 2. Compiling product code with default `x` prefix (`.x78zum5`)
17
+ 3. Placing each group in its own CSS `@layer`
18
+
19
+ ## Packages
20
+
21
+ | Export | Purpose | Platform |
22
+ | -------------------- | --------------------------------------------- | --------------------------- |
23
+ | `@astryxdesign/build/babel` | Babel plugin: splits class prefixes per file | Next.js, any babel pipeline |
24
+ | `@astryxdesign/build/postcss` | PostCSS plugin: compiles + splits CSS layers | Next.js |
25
+ | `@astryxdesign/build/vite` | Vite plugin: wraps unplugin + splits layers | Vite, Storybook |
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ npm install -D @astryxdesign/build @stylexjs/babel-plugin @babel/core
31
+ ```
32
+
33
+ For Vite, also install:
34
+
35
+ ```bash
36
+ npm install -D @stylexjs/unplugin
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Next.js Setup
42
+
43
+ ### 1. babel.config.js
44
+
45
+ ```js
46
+ const path = require('path');
47
+
48
+ module.exports = {
49
+ presets: ['next/babel'],
50
+ plugins: [
51
+ [
52
+ '@astryxdesign/build/babel',
53
+ {
54
+ dev: process.env.NODE_ENV !== 'production',
55
+ runtimeInjection: false,
56
+ treeshakeCompensation: true,
57
+ enableInlinedConditionalMerge: true,
58
+ aliases: {
59
+ '@astryxdesign/core/*': [path.join(__dirname, 'node_modules/@astryxdesign/core/*')],
60
+ '@astryxdesign/core': [path.join(__dirname, 'node_modules/@astryxdesign/core')],
61
+ },
62
+ unstable_moduleResolution: {type: 'commonJS'},
63
+ },
64
+ ],
65
+ ],
66
+ };
67
+ ```
68
+
69
+ ### 2. postcss.config.js
70
+
71
+ ```js
72
+ const path = require('path');
73
+
74
+ module.exports = {
75
+ plugins: {
76
+ '@astryxdesign/build/postcss': {
77
+ appDir: 'src',
78
+ babelPlugins: [
79
+ [
80
+ '@stylexjs/babel-plugin',
81
+ {
82
+ dev: process.env.NODE_ENV !== 'production',
83
+ runtimeInjection: false,
84
+ treeshakeCompensation: true,
85
+ enableInlinedConditionalMerge: true,
86
+ aliases: {
87
+ '@astryxdesign/core/*': [path.join(__dirname, 'node_modules/@astryxdesign/core/*')],
88
+ '@astryxdesign/core': [path.join(__dirname, 'node_modules/@astryxdesign/core')],
89
+ },
90
+ unstable_moduleResolution: {type: 'commonJS'},
91
+ },
92
+ ],
93
+ ],
94
+ },
95
+ },
96
+ };
97
+ ```
98
+
99
+ ### 3. next.config.mjs
100
+
101
+ ```js
102
+ const nextConfig = {
103
+ transpilePackages: ['@astryxdesign/core', '@astryxdesign/theme-neutral'],
104
+ webpack: config => {
105
+ // Resolve to source TypeScript instead of dist
106
+ config.resolve.conditionNames = ['source', 'import', 'require', 'default'];
107
+ return config;
108
+ },
109
+ };
110
+
111
+ export default nextConfig;
112
+ ```
113
+
114
+ ### 4. CSS files
115
+
116
+ `src/app/layers.css`:
117
+
118
+ ```css
119
+ @layer reset, astryx-base, astryx-theme, product;
120
+ ```
121
+
122
+ `src/app/globals.css`:
123
+
124
+ ```css
125
+ @import './layers.css';
126
+ @import '@astryxdesign/core/reset.css';
127
+ @import '@astryxdesign/theme-neutral/theme.css';
128
+
129
+ @stylex;
130
+ ```
131
+
132
+ > `layers.css` must be a separate file because webpack hoists `@import` content above inline CSS.
133
+
134
+ ### 5. Browserslist
135
+
136
+ ```json
137
+ {
138
+ "browserslist": ["last 1 Chrome version"]
139
+ }
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Vite Setup
145
+
146
+ ```ts
147
+ import {astryxStylex} from '@astryxdesign/build/vite';
148
+ import react from '@vitejs/plugin-react';
149
+
150
+ export default defineConfig({
151
+ plugins: [
152
+ ...astryxStylex({
153
+ stylexOptions: {
154
+ dev: process.env.NODE_ENV === 'development',
155
+ runtimeInjection: false,
156
+ treeshakeCompensation: true,
157
+ unstable_moduleResolution: {
158
+ type: 'commonJS',
159
+ rootDir: __dirname,
160
+ },
161
+ },
162
+ }),
163
+ react(),
164
+ ],
165
+ resolve: {
166
+ alias: {
167
+ '@astryxdesign/core': path.resolve(__dirname, 'node_modules/@astryxdesign/core/src'),
168
+ },
169
+ },
170
+ optimizeDeps: {
171
+ exclude: ['@astryxdesign/core', '@astryxdesign/theme-neutral'],
172
+ },
173
+ });
174
+ ```
175
+
176
+ ---
177
+
178
+ ## How it works
179
+
180
+ ### Babel plugin (`@astryxdesign/build/babel`)
181
+
182
+ Wraps `@stylexjs/babel-plugin` with two internal instances: one with `classNamePrefix: 'astryx'` for library files, one with default `'x'` for product files. Routes each file to the correct instance based on its path.
183
+
184
+ Library patterns (configurable):
185
+
186
+ - `packages/core/`
187
+ - `packages/themes/`
188
+ - `node_modules/@astryxdesign/`
189
+
190
+ ### PostCSS plugin (`@astryxdesign/build/postcss`)
191
+
192
+ Compiles StyleX from both library and product source files in two separate passes with different prefixes. Wraps the results in named `@layer` blocks:
193
+
194
+ - Library rules → `@layer astryx-base`
195
+ - Product rules → `@layer product`
196
+
197
+ ### Vite plugin (`@astryxdesign/build/vite`)
198
+
199
+ Wraps `@stylexjs/unplugin` and intercepts the dev CSS endpoint (`/virtual:stylex.css`). Partitions the collected rules by file path and serves split-layer CSS.
200
+
201
+ ---
202
+
203
+ ## Advanced Options
204
+
205
+ ### Babel plugin
206
+
207
+ ```js
208
+ [
209
+ '@astryxdesign/build/babel',
210
+ {
211
+ // Patterns to identify library files (default shown)
212
+ libraryPatterns: [
213
+ 'packages/core/',
214
+ 'packages/themes/',
215
+ 'node_modules/@astryxdesign/',
216
+ ],
217
+
218
+ // Class name prefix for library styles (default: 'astryx')
219
+ libraryPrefix: 'astryx',
220
+
221
+ // Class name prefix for product styles (default: 'x')
222
+ classNamePrefix: 'x',
223
+
224
+ // ... all @stylexjs/babel-plugin options
225
+ },
226
+ ];
227
+ ```
228
+
229
+ ### PostCSS plugin
230
+
231
+ ```js
232
+ '@astryxdesign/build/postcss': {
233
+ appDir: 'src', // Your app source directory
234
+ babelPlugins: [...], // StyleX babel plugin config
235
+ libraryPrefix: 'astryx', // Prefix for library CSS (default: 'astryx')
236
+ extraInclude: [...], // Additional glob patterns
237
+ layers: { // Layer names (defaults shown)
238
+ library: 'astryx-base',
239
+ product: 'product',
240
+ },
241
+ }
242
+ ```
243
+
244
+ ## Related
245
+
246
+ - [example-nextjs-source](../../apps/example-nextjs-source/): full Next.js source build example
247
+ - [`@stylexjs/babel-plugin`](https://github.com/facebook/stylex): the underlying StyleX compiler
package/dist/vite.mjs ADDED
@@ -0,0 +1,284 @@
1
+ // Built from src/vite.ts — do not edit directly
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/vite.ts
10
+ import stylexBabelPlugin from "@stylexjs/babel-plugin";
11
+ import stylex from "@stylexjs/unplugin";
12
+ import path from "path";
13
+ import { fileURLToPath } from "url";
14
+ var __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ var LIBRARY_PATTERN = "node_modules/@astryxdesign/";
16
+ var STYLEX_CSS_PATH = "/virtual:stylex.css";
17
+ var LIGHTNINGCSS_TARGETS = {
18
+ chrome: 123 << 16,
19
+ firefox: 120 << 16,
20
+ safari: 17 << 16 | 5 << 8
21
+ };
22
+ function astryxStylex(options = {}) {
23
+ if ("stylexOptions" in options && options.stylexOptions) {
24
+ return astryxStylexLegacy(options);
25
+ }
26
+ const opts = options;
27
+ const {
28
+ dev = process.env.NODE_ENV !== "production",
29
+ rootDir = process.cwd(),
30
+ libraryPattern = LIBRARY_PATTERN,
31
+ layers = {},
32
+ lightningcssTargets,
33
+ stylexPrefix = "astryx",
34
+ stylexOverrides = {}
35
+ } = opts;
36
+ const libraryLayer = layers.library ?? "astryx-base";
37
+ const productLayer = layers.product ?? "product";
38
+ const stylexOptions = {
39
+ dev,
40
+ runtimeInjection: false,
41
+ treeshakeCompensation: true,
42
+ unstable_moduleResolution: {
43
+ type: "commonJS",
44
+ rootDir
45
+ },
46
+ ...lightningcssTargets && {
47
+ lightningcssOptions: { targets: lightningcssTargets }
48
+ },
49
+ ...stylexOverrides
50
+ };
51
+ const astryxBabelPlugin = path.resolve(__dirname, "../src/babel.js");
52
+ const basePlugin = stylex.vite({
53
+ ...stylexOptions,
54
+ useCSSLayers: true,
55
+ babelConfig: {
56
+ plugins: [
57
+ [
58
+ astryxBabelPlugin,
59
+ {
60
+ ...stylexOptions,
61
+ libraryPrefix: stylexPrefix,
62
+ babelConfig: void 0
63
+ }
64
+ ]
65
+ ]
66
+ }
67
+ });
68
+ const layerOrderPlugin = {
69
+ name: "astryx-css-layer-order",
70
+ transformIndexHtml() {
71
+ return [
72
+ {
73
+ tag: "style",
74
+ children: `@layer reset, ${libraryLayer}, astryx-theme, ${productLayer};`,
75
+ injectTo: "head-prepend"
76
+ }
77
+ ];
78
+ }
79
+ };
80
+ const configPlugin = {
81
+ name: "astryx-config",
82
+ config() {
83
+ let xdsPackages = ["@astryxdesign/core"];
84
+ try {
85
+ const fs = __require("fs");
86
+ const xdsDir = path.resolve(rootDir, "node_modules/@astryxdesign");
87
+ if (fs.existsSync(xdsDir)) {
88
+ xdsPackages = fs.readdirSync(xdsDir).filter((name) => !name.startsWith(".")).map((name) => `@astryxdesign/${name}`);
89
+ }
90
+ } catch {
91
+ }
92
+ return {
93
+ resolve: {
94
+ alias: {
95
+ "@astryxdesign/core/theme/tokens.stylex": path.resolve(
96
+ rootDir,
97
+ "node_modules/@astryxdesign/core/src/theme/tokens.stylex.ts"
98
+ ),
99
+ "@astryxdesign/core": path.resolve(rootDir, "node_modules/@astryxdesign/core/src")
100
+ }
101
+ },
102
+ optimizeDeps: {
103
+ exclude: xdsPackages
104
+ }
105
+ };
106
+ }
107
+ };
108
+ const splitLayerPlugin = {
109
+ name: "astryx-split-layers",
110
+ configureServer(server) {
111
+ let stylexPlugin = null;
112
+ return () => {
113
+ for (const p of server.config.plugins.flat()) {
114
+ if (p?.__stylexGetSharedStore) {
115
+ stylexPlugin = p;
116
+ break;
117
+ }
118
+ }
119
+ server.middlewares.stack.unshift({
120
+ route: "",
121
+ handle: (req, res, next) => {
122
+ if (!req.url?.startsWith(STYLEX_CSS_PATH)) {
123
+ return next();
124
+ }
125
+ if (!stylexPlugin) {
126
+ res.statusCode = 200;
127
+ res.setHeader("Content-Type", "text/css");
128
+ res.end("");
129
+ return;
130
+ }
131
+ const shared = stylexPlugin.__stylexGetSharedStore?.();
132
+ const rulesById = shared?.rulesById;
133
+ if (!rulesById || rulesById.size === 0) {
134
+ res.statusCode = 200;
135
+ res.setHeader("Content-Type", "text/css");
136
+ res.end("");
137
+ return;
138
+ }
139
+ const libraryRules = [];
140
+ const productRules = [];
141
+ for (const [filePath, rules] of rulesById.entries()) {
142
+ if (filePath.includes(libraryPattern)) {
143
+ libraryRules.push(...rules);
144
+ } else {
145
+ productRules.push(...rules);
146
+ }
147
+ }
148
+ const libraryCss = libraryRules.length ? stylexBabelPlugin.processStylexRules(libraryRules, {
149
+ useLayers: true
150
+ }) : "";
151
+ const productCss = productRules.length ? stylexBabelPlugin.processStylexRules(productRules, {
152
+ useLayers: true
153
+ }) : "";
154
+ const parts = [];
155
+ if (libraryCss)
156
+ parts.push(`@layer ${libraryLayer} {
157
+ ${libraryCss}
158
+ }`);
159
+ if (productCss)
160
+ parts.push(`@layer ${productLayer} {
161
+ ${productCss}
162
+ }`);
163
+ res.statusCode = 200;
164
+ res.setHeader("Content-Type", "text/css");
165
+ res.setHeader("Cache-Control", "no-store");
166
+ res.end(parts.join("\n\n"));
167
+ }
168
+ });
169
+ };
170
+ }
171
+ };
172
+ return [configPlugin, layerOrderPlugin, basePlugin, splitLayerPlugin];
173
+ }
174
+ function astryxStylexLegacy(options) {
175
+ const {
176
+ stylexOptions,
177
+ libraryPattern = LIBRARY_PATTERN,
178
+ stylexPrefix = "astryx",
179
+ layers = {}
180
+ } = options;
181
+ const libraryLayer = layers.library ?? "astryx-base";
182
+ const productLayer = layers.product ?? "product";
183
+ const astryxBabelPlugin = path.resolve(__dirname, "../src/babel.js");
184
+ const existingPlugins = stylexOptions.babelConfig?.plugins ?? [];
185
+ const basePlugin = stylex.vite({
186
+ ...stylexOptions,
187
+ useCSSLayers: true,
188
+ babelConfig: {
189
+ ...stylexOptions.babelConfig,
190
+ plugins: [
191
+ [
192
+ astryxBabelPlugin,
193
+ {
194
+ ...stylexOptions,
195
+ libraryPrefix: stylexPrefix,
196
+ babelConfig: void 0
197
+ }
198
+ ],
199
+ ...existingPlugins
200
+ ]
201
+ }
202
+ });
203
+ const layerOrderPlugin = {
204
+ name: "astryx-css-layer-order",
205
+ transformIndexHtml() {
206
+ return [
207
+ {
208
+ tag: "style",
209
+ children: `@layer reset, ${libraryLayer}, astryx-theme, ${productLayer};`,
210
+ injectTo: "head-prepend"
211
+ }
212
+ ];
213
+ }
214
+ };
215
+ const splitLayerPlugin = {
216
+ name: "astryx-split-layers",
217
+ configureServer(server) {
218
+ let stylexPlugin = null;
219
+ return () => {
220
+ for (const p of server.config.plugins.flat()) {
221
+ if (p?.__stylexGetSharedStore) {
222
+ stylexPlugin = p;
223
+ break;
224
+ }
225
+ }
226
+ server.middlewares.stack.unshift({
227
+ route: "",
228
+ handle: (req, res, next) => {
229
+ if (!req.url?.startsWith(STYLEX_CSS_PATH)) {
230
+ return next();
231
+ }
232
+ if (!stylexPlugin) {
233
+ res.statusCode = 200;
234
+ res.setHeader("Content-Type", "text/css");
235
+ res.end("");
236
+ return;
237
+ }
238
+ const shared = stylexPlugin.__stylexGetSharedStore?.();
239
+ const rulesById = shared?.rulesById;
240
+ if (!rulesById || rulesById.size === 0) {
241
+ res.statusCode = 200;
242
+ res.setHeader("Content-Type", "text/css");
243
+ res.end("");
244
+ return;
245
+ }
246
+ const libraryRules = [];
247
+ const productRules = [];
248
+ for (const [filePath, rules] of rulesById.entries()) {
249
+ if (filePath.includes(libraryPattern)) {
250
+ libraryRules.push(...rules);
251
+ } else {
252
+ productRules.push(...rules);
253
+ }
254
+ }
255
+ const libraryCss = libraryRules.length ? stylexBabelPlugin.processStylexRules(libraryRules, {
256
+ useLayers: true
257
+ }) : "";
258
+ const productCss = productRules.length ? stylexBabelPlugin.processStylexRules(productRules, {
259
+ useLayers: true
260
+ }) : "";
261
+ const parts = [];
262
+ if (libraryCss)
263
+ parts.push(`@layer ${libraryLayer} {
264
+ ${libraryCss}
265
+ }`);
266
+ if (productCss)
267
+ parts.push(`@layer ${productLayer} {
268
+ ${productCss}
269
+ }`);
270
+ res.statusCode = 200;
271
+ res.setHeader("Content-Type", "text/css");
272
+ res.setHeader("Cache-Control", "no-store");
273
+ res.end(parts.join("\n\n"));
274
+ }
275
+ });
276
+ };
277
+ }
278
+ };
279
+ return [layerOrderPlugin, basePlugin, splitLayerPlugin];
280
+ }
281
+ export {
282
+ LIGHTNINGCSS_TARGETS,
283
+ astryxStylex
284
+ };
package/package.json CHANGED
@@ -1,11 +1,60 @@
1
1
  {
2
2
  "name": "@astryxdesign/build",
3
- "version": "0.0.0-bootstrap.0",
4
- "description": "Placeholder published to claim the npm name during trusted publishing setup. Do not install.",
5
- "homepage": "https://github.com/facebook/astryx",
3
+ "version": "0.1.0-canary.08d4cf4",
4
+ "description": "Build plugins for XDS source builds babel, PostCSS, and Vite integrations",
5
+ "author": "Meta Open Source",
6
6
  "license": "MIT",
7
+ "homepage": "https://github.com/facebook/astryx#readme",
7
8
  "repository": {
8
9
  "type": "git",
9
- "url": "git+https://github.com/facebook/astryx.git"
10
+ "url": "git+https://github.com/facebook/astryx.git",
11
+ "directory": "packages/build"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/facebook/astryx/issues"
15
+ },
16
+ "keywords": [
17
+ "astryx",
18
+ "postcss",
19
+ "stylex",
20
+ "css-layers",
21
+ "design-system"
22
+ ],
23
+ "main": "./src/config.js",
24
+ "exports": {
25
+ ".": "./src/config.js",
26
+ "./babel": "./src/babel.js",
27
+ "./postcss": "./src/index.js",
28
+ "./vite": "./dist/vite.mjs",
29
+ "./next": "./src/next.js"
30
+ },
31
+ "files": [
32
+ "src",
33
+ "dist"
34
+ ],
35
+ "peerDependencies": {
36
+ "@stylexjs/babel-plugin": ">=0.18.0",
37
+ "@babel/core": ">=7.0.0",
38
+ "@stylexjs/unplugin": ">=0.18.0",
39
+ "vite": ">=5.0.0"
40
+ },
41
+ "dependencies": {
42
+ "fast-glob": "^3.3.0",
43
+ "glob-parent": "^6.0.0",
44
+ "is-glob": "^4.0.0"
45
+ },
46
+ "devDependencies": {
47
+ "esbuild": "^0.28.1"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "@stylexjs/unplugin": {
51
+ "optional": true
52
+ },
53
+ "vite": {
54
+ "optional": true
55
+ }
56
+ },
57
+ "scripts": {
58
+ "build": "node build.mjs"
10
59
  }
11
- }
60
+ }