@stylexjs/unplugin 0.17.0

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/lib/index.js ADDED
@@ -0,0 +1,490 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.unplugin = exports.default = void 0;
7
+ var _unplugin = require("unplugin");
8
+ var _core = require("@babel/core");
9
+ var _babelPlugin = _interopRequireDefault(require("@stylexjs/babel-plugin"));
10
+ var _pluginSyntaxFlow = _interopRequireDefault(require("@babel/plugin-syntax-flow"));
11
+ var _pluginSyntaxJsx = _interopRequireDefault(require("@babel/plugin-syntax-jsx"));
12
+ var _pluginSyntaxTypescript = _interopRequireDefault(require("@babel/plugin-syntax-typescript"));
13
+ var _nodePath = _interopRequireDefault(require("node:path"));
14
+ var _nodeFs = _interopRequireDefault(require("node:fs"));
15
+ var _promises = _interopRequireDefault(require("node:fs/promises"));
16
+ var _lightningcss = require("lightningcss");
17
+ var _browserslist = _interopRequireDefault(require("browserslist"));
18
+ var _devInjectMiddleware = require("./dev-inject-middleware");
19
+ var _consts = require("./consts");
20
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
21
+ function pickCssAssetFromRollupBundle(bundle, choose) {
22
+ const assets = Object.values(bundle).filter(a => a && a.type === 'asset' && typeof a.fileName === 'string' && a.fileName.endsWith('.css'));
23
+ if (assets.length === 0) return null;
24
+ if (typeof choose === 'function') {
25
+ const chosen = assets.find(a => choose(a.fileName));
26
+ if (chosen) return chosen;
27
+ }
28
+ const best = assets.find(a => /(^|\/)index\.css$/.test(a.fileName)) || assets.find(a => /(^|\/)style\.css$/.test(a.fileName));
29
+ return best || assets[0];
30
+ }
31
+ function processCollectedRulesToCSS(rules, options) {
32
+ if (!rules || rules.length === 0) return '';
33
+ const collectedCSS = _babelPlugin.default.processStylexRules(rules, {
34
+ useLayers: !!options.useCSSLayers,
35
+ enableLTRRTLComments: options?.enableLTRRTLComments
36
+ });
37
+ const {
38
+ code
39
+ } = (0, _lightningcss.transform)({
40
+ targets: (0, _lightningcss.browserslistToTargets)((0, _browserslist.default)('>= 1%')),
41
+ ...options.lightningcssOptions,
42
+ filename: 'stylex.css',
43
+ code: Buffer.from(collectedCSS)
44
+ });
45
+ return code.toString();
46
+ }
47
+ const unpluginInstance = (0, _unplugin.createUnplugin)((userOptions = {}) => {
48
+ const {
49
+ dev = process.env.NODE_ENV === 'development' || process.env.BABEL_ENV === 'development',
50
+ unstable_moduleResolution = {
51
+ type: 'commonJS',
52
+ rootDir: process.cwd()
53
+ },
54
+ babelConfig: {
55
+ plugins = [],
56
+ presets = []
57
+ } = {},
58
+ importSources = ['stylex', '@stylexjs/stylex'],
59
+ useCSSLayers = false,
60
+ lightningcssOptions,
61
+ cssInjectionTarget,
62
+ devPersistToDisk = false,
63
+ devMode = 'full',
64
+ ...stylexOptions
65
+ } = userOptions;
66
+ const stylexRulesById = new Map();
67
+ function getSharedStore() {
68
+ try {
69
+ const g = globalThis;
70
+ if (!g.__stylex_unplugin_store) {
71
+ g.__stylex_unplugin_store = {
72
+ rulesById: new Map(),
73
+ version: 0
74
+ };
75
+ }
76
+ return g.__stylex_unplugin_store;
77
+ } catch {
78
+ return {
79
+ rulesById: stylexRulesById,
80
+ version: 0
81
+ };
82
+ }
83
+ }
84
+ let viteServer = null;
85
+ let viteOutDir = null;
86
+ function findNearestNodeModules(startDir) {
87
+ let dir = startDir;
88
+ for (;;) {
89
+ const candidate = _nodePath.default.join(dir, 'node_modules');
90
+ if (_nodeFs.default.existsSync(candidate)) {
91
+ const stat = _nodeFs.default.statSync(candidate);
92
+ if (stat.isDirectory()) return candidate;
93
+ }
94
+ const parent = _nodePath.default.dirname(dir);
95
+ if (parent === dir) break;
96
+ dir = parent;
97
+ }
98
+ return null;
99
+ }
100
+ const NEAREST_NODE_MODULES = findNearestNodeModules(process.cwd());
101
+ const DISK_RULES_DIR = NEAREST_NODE_MODULES ? _nodePath.default.join(NEAREST_NODE_MODULES, '.stylex') : _nodePath.default.join(process.cwd(), 'node_modules', '.stylex');
102
+ const DISK_RULES_PATH = _nodePath.default.join(DISK_RULES_DIR, 'rules.json');
103
+ async function runBabelTransform(inputCode, id, callerName) {
104
+ const result = await (0, _core.transformAsync)(inputCode, {
105
+ babelrc: false,
106
+ filename: id,
107
+ presets,
108
+ plugins: [...plugins, /\.jsx?/.test(_nodePath.default.extname(id)) ? _pluginSyntaxFlow.default : [_pluginSyntaxTypescript.default, {
109
+ isTSX: true
110
+ }], _pluginSyntaxJsx.default, _babelPlugin.default.withOptions({
111
+ ...stylexOptions,
112
+ dev,
113
+ unstable_moduleResolution
114
+ })],
115
+ caller: {
116
+ name: callerName,
117
+ supportsStaticESM: true,
118
+ supportsDynamicImport: true,
119
+ supportsTopLevelAwait: !inputCode.includes('require('),
120
+ supportsExportNamespaceFrom: true
121
+ }
122
+ });
123
+ if (!result || result.code == null) {
124
+ return {
125
+ code: inputCode,
126
+ map: null,
127
+ metadata: {}
128
+ };
129
+ }
130
+ return result;
131
+ }
132
+ function escapeReg(src) {
133
+ return src.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
134
+ }
135
+ function containsStylexImport(code, source) {
136
+ const s = escapeReg(typeof source === 'string' ? source : source.from);
137
+ const re = new RegExp(`(?:from\\s*['"]${s}['"]|import\\s*\\(\\s*['"]${s}['"]\\s*\\)|require\\s*\\(\\s*['"]${s}['"]\\s*\\)|^\\s*import\\s*['"]${s}['"])`, 'm');
138
+ return re.test(code);
139
+ }
140
+ function shouldHandle(code) {
141
+ if (!code) return false;
142
+ return importSources.some(src => containsStylexImport(code, src));
143
+ }
144
+ function resetState() {
145
+ stylexRulesById.clear();
146
+ if (devPersistToDisk) {
147
+ try {
148
+ _nodeFs.default.rmSync(DISK_RULES_PATH, {
149
+ force: true
150
+ });
151
+ } catch {}
152
+ }
153
+ }
154
+ function collectCss() {
155
+ const merged = new Map();
156
+ if (devPersistToDisk) {
157
+ try {
158
+ if (_nodeFs.default.existsSync(DISK_RULES_PATH)) {
159
+ const json = JSON.parse(_nodeFs.default.readFileSync(DISK_RULES_PATH, 'utf8'));
160
+ for (const [k, v] of Object.entries(json)) merged.set(k, v);
161
+ }
162
+ } catch {}
163
+ }
164
+ try {
165
+ const shared = getSharedStore().rulesById;
166
+ for (const [k, v] of shared.entries()) merged.set(k, v);
167
+ } catch {}
168
+ for (const [k, v] of stylexRulesById.entries()) merged.set(k, v);
169
+ const allRules = Array.from(merged.values()).flat();
170
+ return processCollectedRulesToCSS(allRules, {
171
+ useCSSLayers,
172
+ lightningcssOptions,
173
+ enableLTRRTLComments: stylexOptions?.enableLTRRTLComments
174
+ });
175
+ }
176
+ async function persistRulesToDisk(id, rules) {
177
+ if (!devPersistToDisk) return;
178
+ try {
179
+ let current = {};
180
+ try {
181
+ const txt = await _promises.default.readFile(DISK_RULES_PATH, 'utf8');
182
+ current = JSON.parse(txt);
183
+ } catch {}
184
+ if (rules && Array.isArray(rules) && rules.length > 0) {
185
+ current[id] = rules;
186
+ } else if (current[id]) {
187
+ delete current[id];
188
+ }
189
+ await _promises.default.writeFile(DISK_RULES_PATH, JSON.stringify(current), 'utf8');
190
+ } catch {}
191
+ }
192
+ return {
193
+ name: '@stylexjs/unplugin',
194
+ apply: (config, env) => {
195
+ try {
196
+ const command = env?.command || (typeof config === 'string' ? undefined : undefined);
197
+ if (devMode === 'off' && command === 'serve') return false;
198
+ } catch {}
199
+ return true;
200
+ },
201
+ enforce: 'pre',
202
+ buildStart() {
203
+ resetState();
204
+ },
205
+ buildEnd() {},
206
+ async transform(code, id) {
207
+ const JS_LIKE_RE = /\.[cm]?[jt]sx?(\?|$)/;
208
+ if (!JS_LIKE_RE.test(id)) return null;
209
+ if (!shouldHandle(code)) return null;
210
+ const result = await runBabelTransform(code, id, '@stylexjs/unplugin');
211
+ const {
212
+ metadata
213
+ } = result;
214
+ if (!stylexOptions.runtimeInjection) {
215
+ const hasRules = metadata && Array.isArray(metadata.stylex) && metadata.stylex.length > 0;
216
+ const shared = getSharedStore();
217
+ if (hasRules) {
218
+ stylexRulesById.set(id, metadata.stylex);
219
+ shared.rulesById.set(id, metadata.stylex);
220
+ shared.version++;
221
+ await persistRulesToDisk(id, metadata.stylex);
222
+ } else {
223
+ stylexRulesById.delete(id);
224
+ if (shared.rulesById.has(id)) {
225
+ shared.rulesById.delete(id);
226
+ shared.version++;
227
+ }
228
+ await persistRulesToDisk(id, []);
229
+ }
230
+ if (viteServer) {
231
+ try {
232
+ viteServer.ws.send({
233
+ type: 'custom',
234
+ event: 'stylex:css-update'
235
+ });
236
+ } catch {}
237
+ }
238
+ }
239
+ const ctx = this;
240
+ if (ctx && ctx.meta && ctx.meta.watchMode && typeof ctx.parse === 'function') {
241
+ try {
242
+ const ast = ctx.parse(result.code);
243
+ for (const stmt of ast.body) {
244
+ if (stmt.type === 'ImportDeclaration') {
245
+ const resolved = await ctx.resolve(stmt.source.value, id);
246
+ if (resolved && !resolved.external) {
247
+ const loaded = await ctx.load(resolved);
248
+ if (loaded && loaded.meta && 'stylex' in loaded.meta) {
249
+ stylexRulesById.set(resolved.id, loaded.meta.stylex);
250
+ }
251
+ }
252
+ }
253
+ }
254
+ } catch {}
255
+ }
256
+ return {
257
+ code: result.code,
258
+ map: result.map
259
+ };
260
+ },
261
+ shouldTransformCachedModule({
262
+ id,
263
+ meta
264
+ }) {
265
+ if (meta && 'stylex' in meta) {
266
+ stylexRulesById.set(id, meta.stylex);
267
+ }
268
+ return false;
269
+ },
270
+ generateBundle(_opts, bundle) {
271
+ const css = collectCss();
272
+ if (!css) return;
273
+ const target = pickCssAssetFromRollupBundle(bundle, cssInjectionTarget);
274
+ if (target) {
275
+ const current = typeof target.source === 'string' ? target.source : target.source?.toString() || '';
276
+ target.source = current ? current + '\n' + css : css;
277
+ } else {}
278
+ },
279
+ vite: devMode === 'off' ? undefined : {
280
+ configResolved(config) {
281
+ try {
282
+ viteOutDir = config.build?.outDir || viteOutDir;
283
+ } catch {}
284
+ },
285
+ configureServer(server) {
286
+ viteServer = server;
287
+ if (devMode === 'full') {
288
+ server.middlewares.use(_devInjectMiddleware.devInjectMiddleware);
289
+ }
290
+ server.middlewares.use((req, res, next) => {
291
+ if (!req.url) return next();
292
+ if (req.url.startsWith(_consts.DEV_CSS_PATH)) {
293
+ res.statusCode = 200;
294
+ res.setHeader('Content-Type', 'text/css');
295
+ res.setHeader('Cache-Control', 'no-store');
296
+ const css = collectCss();
297
+ res.end(css || '');
298
+ return;
299
+ }
300
+ next();
301
+ });
302
+ const shared = getSharedStore();
303
+ let lastVersion = shared.version;
304
+ const interval = setInterval(() => {
305
+ const curr = shared.version;
306
+ if (curr !== lastVersion) {
307
+ lastVersion = curr;
308
+ try {
309
+ server.ws.send({
310
+ type: 'custom',
311
+ event: 'stylex:css-update'
312
+ });
313
+ } catch {}
314
+ }
315
+ }, 150);
316
+ server.httpServer?.once('close', () => clearInterval(interval));
317
+ },
318
+ resolveId(id) {
319
+ if (devMode === 'full' && id === 'virtual:stylex:runtime') return id;
320
+ if (devMode === 'css-only' && id === 'virtual:stylex:css-only') return id;
321
+ return null;
322
+ },
323
+ load(id) {
324
+ if (devMode === 'full' && id === 'virtual:stylex:runtime') {
325
+ return _consts.VIRTUAL_STYLEX_RUNTIME_SCRIPT;
326
+ }
327
+ if (devMode === 'css-only' && id === 'virtual:stylex:css-only') {
328
+ return _consts.VIRTUAL_STYLEX_CSS_ONLY_SCRIPT;
329
+ }
330
+ return null;
331
+ },
332
+ transformIndexHtml() {
333
+ if (devMode !== 'full') return null;
334
+ if (!viteServer) return null;
335
+ return [{
336
+ tag: 'script',
337
+ attrs: {
338
+ type: 'module',
339
+ src: '/@id/virtual:stylex:runtime'
340
+ },
341
+ injectTo: 'head'
342
+ }, {
343
+ tag: 'link',
344
+ attrs: {
345
+ rel: 'stylesheet',
346
+ href: _consts.DEV_CSS_PATH
347
+ },
348
+ injectTo: 'head'
349
+ }];
350
+ },
351
+ handleHotUpdate(ctx) {
352
+ const cssMod = ctx.server.moduleGraph.getModuleById('virtual:stylex:css-module');
353
+ if (cssMod) {
354
+ ctx.server.moduleGraph.invalidateModule(cssMod);
355
+ }
356
+ try {
357
+ ctx.server.ws.send({
358
+ type: 'custom',
359
+ event: 'stylex:css-update'
360
+ });
361
+ } catch {}
362
+ }
363
+ },
364
+ webpack(compiler) {
365
+ const PLUGIN_NAME = '@stylexjs/unplugin';
366
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => {
367
+ resetState();
368
+ const wp = compiler.webpack || compiler.rspack || undefined;
369
+ const stage = wp?.Compilation?.PROCESS_ASSETS_STAGE_SUMMARIZE;
370
+ const tapOptions = stage != null ? {
371
+ name: PLUGIN_NAME,
372
+ stage
373
+ } : PLUGIN_NAME;
374
+ const toRawSource = content => {
375
+ const RawSource = wp?.sources?.RawSource;
376
+ return RawSource ? new RawSource(content) : {
377
+ source: () => content,
378
+ size: () => Buffer.byteLength(content)
379
+ };
380
+ };
381
+ compilation.hooks.processAssets.tap(tapOptions, assets => {
382
+ const css = collectCss();
383
+ if (!css) return;
384
+ const cssAssets = Object.keys(assets).filter(f => f.endsWith('.css'));
385
+ if (cssAssets.length === 0) {
386
+ compilation.warnings.push(new Error('[stylex] No CSS asset found to inject into. Skipping.'));
387
+ return;
388
+ }
389
+ const pickName = typeof cssInjectionTarget === 'function' && cssAssets.find(f => cssInjectionTarget(f)) || cssAssets.find(f => /(^|\/)index\.css$/.test(f)) || cssAssets.find(f => /(^|\/)style\.css$/.test(f)) || cssAssets[0];
390
+ const asset = compilation.getAsset(pickName);
391
+ if (!asset) return;
392
+ const existing = asset.source.source().toString();
393
+ const next = existing ? existing + '\n' + css : css;
394
+ compilation.updateAsset(pickName, toRawSource(next));
395
+ });
396
+ });
397
+ },
398
+ rspack(compiler) {
399
+ this.webpack?.(compiler);
400
+ },
401
+ esbuild: {
402
+ name: '@stylexjs/unplugin',
403
+ setup(build) {
404
+ build.onEnd(async result => {
405
+ try {
406
+ const css = collectCss();
407
+ if (!css) return;
408
+ const initial = build.initialOptions;
409
+ const outDir = initial.outdir || (initial.outfile ? _nodePath.default.dirname(initial.outfile) : null);
410
+ if (!outDir) return;
411
+ let outfile = null;
412
+ const meta = result && result.metafile;
413
+ if (meta && meta.outputs) {
414
+ const outputs = Object.keys(meta.outputs);
415
+ const cssOutputs = outputs.filter(f => f.endsWith('.css'));
416
+ const pick = cssOutputs.find(f => /(^|\/)index\.css$/.test(f)) || cssOutputs.find(f => /(^|\/)style\.css$/.test(f)) || cssOutputs[0];
417
+ if (pick) outfile = _nodePath.default.isAbsolute(pick) ? pick : _nodePath.default.join(process.cwd(), pick);
418
+ } else {
419
+ try {
420
+ const files = _nodeFs.default.readdirSync(outDir).filter(f => f.endsWith('.css'));
421
+ const pick = files.find(f => /(^|\/)index\.css$/.test(f)) || files.find(f => /(^|\/)style\.css$/.test(f)) || files[0];
422
+ if (pick) outfile = _nodePath.default.join(outDir, pick);
423
+ } catch {}
424
+ }
425
+ if (!outfile) {
426
+ const fallback = _nodePath.default.join(outDir, 'stylex.css');
427
+ await _promises.default.mkdir(_nodePath.default.dirname(fallback), {
428
+ recursive: true
429
+ });
430
+ await _promises.default.writeFile(fallback, css, 'utf8');
431
+ return;
432
+ }
433
+ try {
434
+ const current = _nodeFs.default.readFileSync(outfile, 'utf8');
435
+ if (!current.includes(css)) {
436
+ await _promises.default.writeFile(outfile, current ? current + '\n' + css : css, 'utf8');
437
+ }
438
+ } catch {}
439
+ } catch {}
440
+ });
441
+ }
442
+ },
443
+ async writeBundle(options, bundle) {
444
+ try {
445
+ const css = collectCss();
446
+ if (!css) return;
447
+ const target = pickCssAssetFromRollupBundle(bundle, cssInjectionTarget);
448
+ const outDir = options?.dir || (options?.file ? _nodePath.default.dirname(options.file) : viteOutDir);
449
+ if (!outDir) return;
450
+ try {
451
+ await _promises.default.mkdir(outDir, {
452
+ recursive: true
453
+ });
454
+ } catch {}
455
+ let outfile;
456
+ if (!target) {
457
+ try {
458
+ const assetsDir = _nodePath.default.join(outDir, 'assets');
459
+ if (_nodeFs.default.existsSync(assetsDir)) {
460
+ const files = _nodeFs.default.readdirSync(assetsDir).filter(f => f.endsWith('.css'));
461
+ const pick = files.find(f => /(^|\/)index\.css$/.test(f)) || files.find(f => /(^|\/)style\.css$/.test(f)) || files[0];
462
+ if (pick) outfile = _nodePath.default.join(assetsDir, pick);
463
+ }
464
+ } catch {}
465
+ if (!outfile) {
466
+ const assetsDir = _nodePath.default.join(outDir, 'assets');
467
+ try {
468
+ await _promises.default.mkdir(assetsDir, {
469
+ recursive: true
470
+ });
471
+ } catch {}
472
+ const fallback = _nodePath.default.join(assetsDir, 'stylex.css');
473
+ await _promises.default.writeFile(fallback, css, 'utf8');
474
+ return;
475
+ }
476
+ } else {
477
+ outfile = _nodePath.default.join(outDir, target.fileName);
478
+ }
479
+ try {
480
+ const current = _nodeFs.default.readFileSync(outfile, 'utf8');
481
+ if (!current.includes(css)) {
482
+ await _promises.default.writeFile(outfile, current ? current + '\n' + css : css, 'utf8');
483
+ }
484
+ } catch {}
485
+ } catch {}
486
+ }
487
+ };
488
+ });
489
+ var _default = exports.default = unpluginInstance;
490
+ const unplugin = exports.unplugin = unpluginInstance;
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@stylexjs/unplugin",
3
+ "version": "0.17.0",
4
+ "private": false,
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "description": "Universal bundler plugin for StyleX using unplugin",
9
+ "license": "MIT",
10
+ "main": "./lib/index.js",
11
+ "module": "./lib/es/index.mjs",
12
+ "types": "./lib/index.d.ts",
13
+ "exports": {
14
+ "import": "./lib/es/index.mjs",
15
+ "require": "./lib/index.js",
16
+ "types": "./lib/index.d.ts"
17
+ },
18
+ "keywords": [
19
+ "stylex",
20
+ "css",
21
+ "unplugin",
22
+ "vite",
23
+ "rollup",
24
+ "webpack",
25
+ "rspack"
26
+ ],
27
+ "scripts": {
28
+ "build:cjs": "cross-env BABEL_ENV=cjs babel src/ --out-dir lib/ --copy-files",
29
+ "build:esm": "cross-env BABEL_ENV=esm babel src/ --out-dir lib/es --out-file-extension .mjs",
30
+ "build": "npm run build:cjs && npm run build:esm",
31
+ "test": "jest"
32
+ },
33
+ "peerDependencies": {
34
+ "unplugin": "^1.7.1"
35
+ },
36
+ "dependencies": {
37
+ "@babel/core": "^7.26.8",
38
+ "@babel/plugin-syntax-flow": "^7.26.0",
39
+ "@babel/plugin-syntax-jsx": "^7.25.9",
40
+ "@babel/plugin-syntax-typescript": "^7.25.9",
41
+ "@stylexjs/babel-plugin": "0.17.0",
42
+ "browserslist": "^4.24.0",
43
+ "lightningcss": "^1.29.1"
44
+ },
45
+ "files": [
46
+ "lib/*"
47
+ ]
48
+ }