stylex-webpack 0.3.2 → 0.4.0-beta.2

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/README.md CHANGED
@@ -1,29 +1,42 @@
1
1
  # stylex-webpack
2
2
 
3
- [First introduced by Frank Yan at React Conf 2020](https://www.youtube.com/watch?v=9JZHodNR184), [StyleX](https://stylexjs.com/) framework agnostic CSS-in-JS system with near-zero runtime, ahead-of-time compiler, atomic CSS extraction that powers Facebook and Instagram.
3
+ [First introduced by Frank Yan at React Conf 2020](https://www.youtube.com/watch?v=9JZHodNR184), [StyleX](https://stylexjs.com/) framework agnostic CSS-in-JS system with near-zero runtime, ahead-of-time compiler, atomic CSS extraction that powers Facebook and Instagram. `stylex-webpack` is a webpack and Next.js plugin for StyleX.
4
4
 
5
5
  ## Motivation
6
6
 
7
- stylex offers a CSS-in-JS compiler, allowing you to write CSS in your JavaScript/JSX/TSX. However, unlike other CSS-in-JS solutions that gather and process styles within the browser, stylex will read your source code, collect your style and transform your JS/JSX/TSX, stripping runtime calls as much as possible (making the value of `className` a static string literal), and output CSS elsewhere.
7
+ StyleX offers a CSS-in-JS compiler, allowing you to write CSS in your JavaScript/JSX/TSX. However, unlike other CSS-in-JS solutions that gather and process styles within the browser, StyleX will read your source code, collect your style and transform your JS/JSX/TSX, stripping runtime calls as much as possible (making the value of `className` a static string literal), and output the minimum required CSS elsewhere.
8
8
 
9
- StyleX does provide a webpack plugin. Under the hood, it will traverse through the source code, collect styles, and emit a new CSS asset during the webpack compilation. However, it does come with some limitations:
9
+ StyleX does provide an official webpack plugin. Under the hood, it will traverse through the source code, collect styles, and emit a new CSS asset during the webpack compilation. However, it does come with some limitations:
10
10
 
11
11
  - StyleX's official Next.js setup requires a `.babelrc` file, which disables Next.js' built-in SWC compiler.
12
12
  - StyleX's official Next.js plugin requires a CSS asset to pre-exist so that it can append the extracted CSS to it.
13
13
 
14
- I start this project as a Proof of Concept, to see if it is possible to make a webpack plugin for ststylex that doesn't disable Next.js' SWC compiler. I have already made [a similar webpack plugin for style9](https://github.com/sukkaw/style9-webpack), which is also an AoT atomic CSS-in-JS system that is inspired by StyleX.
14
+ I start this project as a Proof of Concept, to see if it is possible to make a webpack plugin for StyleX that doesn't disable Next.js' SWC compiler. I have already made [a similar webpack plugin for style9](https://github.com/sukkaw/style9-webpack), which is also an AoT atomic CSS-in-JS system that is inspired by StyleX.
15
15
 
16
- Unlike stylex's official webpack plugin, `stylex-webpack` requires you have setup `css-loader` and `MiniCssExtractPlugin` in your webpack configuration, just like your normal CSS based webpack project. `stylex-webpack`'s built-in loader will generate a virtual CSS import containing a dummy CSS rule. This allows the `MiniCssExtractPlugin` to collect those virtual CSS imports and emit a CSS asset, which `stylex-webpack` will later inject the actual extracted CSS into at the `processAssets` stage.
16
+ Unlike StyleX's official webpack plugin, `stylex-webpack` requires you have setup `css-loader` and `MiniCssExtractPlugin` in your webpack configuration, or enables webpack's built-in CSS feature (as the time of writing this, it is still experimental, but it would become stable on webpack 6 or newer), just like your traditional CSS based webpack project. You will also have to import a virtual (dummy) CSS at the entrypoint of your App (for Next.js it is either `app/layout.tsx` for App Router or `pages/_app.tsx` for Pages Router). This allows the those webpack css solutions to collect the virtual CSS imports and emit a CSS asset, which `stylex-webpack` will later replace the dummy CSS with the actual generated CSS during webpack's `processAssets` stage.
17
17
 
18
18
  ## Installation
19
19
 
20
20
  ```sh
21
21
  # npm
22
- npm i stylex-webpack
22
+ npm i stylex-webpack @stylexjs/babel-plugin
23
23
  # Yarn
24
- yarn add stylex-webpack
24
+ yarn add stylex-webpack @stylexjs/babel-plugin
25
25
  # pnpm
26
- pnpm add stylex-webpack
26
+ pnpm add stylex-webpack @stylexjs/babel-plugin
27
+ ```
28
+
29
+ `@stylexjs/babel-plugin` is declared as a mandatory peer dependency. But we still recommend you install it directly in your project to specify the version you want to use.
30
+
31
+ Also you will most likely need to install `@stylexjs/stylex` as well if you haven't already:
32
+
33
+ ```sh
34
+ # npm
35
+ npm i @stylexjs/stylex
36
+ # Yarn
37
+ yarn add @stylexjs/stylex
38
+ # pnpm
39
+ pnpm add @stylexjs/stylex
27
40
  ```
28
41
 
29
42
  ## Usage
@@ -58,6 +71,12 @@ module.exports = {
58
71
  };
59
72
  ```
60
73
 
74
+ Then import `stylex-webpack/stylex.css` inside the entry point of your App (something like `App.tsx`, `index.tsx`, etc.):
75
+
76
+ ```js
77
+ import 'stylex-webpack/stylex.css';
78
+ ```
79
+
61
80
  ### Next.js
62
81
 
63
82
  ```js
@@ -72,6 +91,12 @@ module.exports = withStyleX({
72
91
  });
73
92
  ```
74
93
 
94
+ Then import `stylex-webpack/stylex.css` inside the entry point of your Next.js App, as if you are importing a global CSS file. For Next.js App Router, your app entry point would be the root layout file (e.g. `app/layout.tsx`). For Next.js Pages Router, your app entry point would be the `_app` file (e.g. `pages/_app.tsx`).
95
+
96
+ ```tsx
97
+ import 'stylex-webpack/stylex.css';
98
+ ```
99
+
75
100
  ## Options
76
101
 
77
102
  ### webpack
@@ -117,6 +142,7 @@ new StyleXPlugin({
117
142
  *
118
143
  * https://github.com/facebook/stylex/issues/455
119
144
  * https://github.com/facebook/stylex/issues/517
145
+ * https://github.com/facebook/stylex/issues/1157
120
146
  *
121
147
  * For now, it is recommended to use postcss-sort-media-queries as a workaround.
122
148
  */
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import webpack from 'webpack';
2
1
  import { Options, Rule } from '@stylexjs/babel-plugin';
2
+ import * as webpack from 'webpack';
3
+ import { Buffer } from 'node:buffer';
3
4
 
4
5
  interface StyleXLoaderOptions {
5
6
  stylexImports: string[];
@@ -47,8 +48,10 @@ declare class StyleXPlugin {
47
48
  useCSSLayers: boolean;
48
49
  loaderOption: StyleXLoaderOptions;
49
50
  transformCss: CSSTransformer;
51
+ private readonly _virtualModuleInstance;
50
52
  constructor({ stylexImports, useCSSLayers, stylexOption, nextjsMode, transformCss }?: StyleXPluginOption);
51
53
  apply(compiler: webpack.Compiler): void;
52
54
  }
53
55
 
54
- export { type RegisterStyleXRules, StyleXPlugin, type StyleXPluginOption };
56
+ export { StyleXPlugin };
57
+ export type { RegisterStyleXRules, StyleXPluginOption };
package/dist/index.js CHANGED
@@ -1,12 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  var stylexBabelPlugin = require('@stylexjs/babel-plugin');
4
- var path = require('path');
4
+ var path = require('node:path');
5
+ var process = require('node:process');
6
+ var VirtualModulesPlugin = require('webpack-virtual-modules');
5
7
 
6
8
  const PLUGIN_NAME = 'stylex';
7
- const VIRTUAL_CSS_PATH = require.resolve('./stylex.virtual.css');
8
- const VIRTUAL_CSS_PATTERN = /stylex\.virtual\.css/;
9
+ const VIRTUAL_ENTRYPOINT_CSS_PATH = require.resolve('./stylex.css');
10
+ const VIRTUAL_ENTRYPOINT_CSS_PATTERN = /stylex\.css/;
9
11
  const STYLEX_CHUNK_NAME = '_stylex-webpack-generated';
12
+ require.resolve('./stylex.fuck-nextjs.virtual-carrier.js');
13
+ const FUCK_NEXTJS_VIRTUAL_CARRIER_PATTERN = /stylex\.fuck-nextjs\.virtual-carrier\.js/;
14
+ const FUCK_NEXTJS_VIRTUAL_CARRIERCHUNK_NAME = '_stylex-fuck-nextjs-collect-stylex-rules';
10
15
  const INCLUDE_REGEXP = /\.[cm]?[jt]sx?$/;
11
16
 
12
17
  function _define_property(obj, key, value) {
@@ -23,15 +28,15 @@ function _define_property(obj, key, value) {
23
28
  return obj;
24
29
  }
25
30
  const stylexLoaderPath = require.resolve('./stylex-loader');
26
- const stylexVirtualLoaderPath = require.resolve('./stylex-virtual-css-loader');
27
- const getStyleXRules = (stylexRules, useCSSLayers)=>{
31
+ const fuckNextjsVirtualLoaderPath = require.resolve('./stylex-fuck-nextjs-virtual-carrier-loader');
32
+ function getStyleXRules(stylexRules, useCSSLayers) {
28
33
  if (stylexRules.size === 0) {
29
34
  return null;
30
35
  }
31
36
  // Take styles for the modules that were included in the last compilation.
32
37
  const allRules = Array.from(stylexRules.values()).flat();
33
38
  return stylexBabelPlugin.processStylexRules(allRules, useCSSLayers);
34
- };
39
+ }
35
40
  const identityTransfrom = (css)=>css;
36
41
  class StyleXPlugin {
37
42
  apply(compiler) {
@@ -43,14 +48,23 @@ class StyleXPlugin {
43
48
  '"optimization.splitChunks" should be enabled for "stylex-webpack" to function properly.'
44
49
  ].join(' '));
45
50
  }
51
+ this._virtualModuleInstance.apply(compiler);
46
52
  (_compiler_options_optimization_splitChunks = compiler.options.optimization.splitChunks).cacheGroups ?? (_compiler_options_optimization_splitChunks.cacheGroups = {});
47
53
  compiler.options.optimization.splitChunks.cacheGroups[STYLEX_CHUNK_NAME] = {
48
54
  name: STYLEX_CHUNK_NAME,
49
- test: VIRTUAL_CSS_PATTERN,
55
+ test: VIRTUAL_ENTRYPOINT_CSS_PATTERN,
50
56
  type: 'css/mini-extract',
51
57
  chunks: 'all',
52
58
  enforce: true
53
59
  };
60
+ if (this.loaderOption.nextjsMode) {
61
+ compiler.options.optimization.splitChunks.cacheGroups[FUCK_NEXTJS_VIRTUAL_CARRIERCHUNK_NAME] = {
62
+ name: FUCK_NEXTJS_VIRTUAL_CARRIERCHUNK_NAME,
63
+ test: FUCK_NEXTJS_VIRTUAL_CARRIER_PATTERN,
64
+ chunks: 'all',
65
+ enforce: true
66
+ };
67
+ }
54
68
  // const IS_RSPACK = Object.prototype.hasOwnProperty.call(compiler.webpack, 'rspackVersion');
55
69
  // stylex-loader adds virtual css import (which triggers virtual-loader)
56
70
  // This prevents "stylex.virtual.css" files from being tree shaken by forcing
@@ -59,19 +73,27 @@ class StyleXPlugin {
59
73
  compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (nmf)=>{
60
74
  nmf.hooks.createModule.tap(PLUGIN_NAME, (createData)=>{
61
75
  const modPath = createData.matchResource ?? createData.resourceResolveData?.path;
62
- if (modPath === VIRTUAL_CSS_PATH) {
76
+ if (modPath === VIRTUAL_ENTRYPOINT_CSS_PATH) {
63
77
  var _createData;
64
78
  (_createData = createData).settings ?? (_createData.settings = {});
65
79
  createData.settings.sideEffects = true;
66
80
  }
67
81
  });
68
82
  });
69
- const { Compilation, NormalModule, sources } = compiler.webpack;
70
- const { RawSource, ConcatSource } = sources;
83
+ const { Compilation, NormalModule } = compiler.webpack;
84
+ // const { RawSource } = sources;
71
85
  // Apply loader to JS modules
72
86
  compiler.hooks.make.tap(PLUGIN_NAME, (compilation)=>{
73
87
  NormalModule.getCompilationHooks(compilation).loader.tap(PLUGIN_NAME, (loaderContext, mod)=>{
74
88
  const extname = path.extname(mod.matchResource || mod.resource);
89
+ if (this.loaderOption.nextjsMode && FUCK_NEXTJS_VIRTUAL_CARRIER_PATTERN.test(mod.matchResource || mod.resource)) {
90
+ mod.loaders.push({
91
+ loader: fuckNextjsVirtualLoaderPath,
92
+ options: {},
93
+ ident: null,
94
+ type: null
95
+ });
96
+ }
75
97
  if (INCLUDE_REGEXP.test(extname)) {
76
98
  loaderContext.StyleXWebpackContextKey = {
77
99
  registerStyleXRules: (resourcePath, stylexRules)=>{
@@ -88,60 +110,49 @@ class StyleXPlugin {
88
110
  type: null
89
111
  });
90
112
  }
91
- if (VIRTUAL_CSS_PATTERN.test(mod.matchResource || mod.resource)) {
92
- mod.loaders.push({
93
- loader: stylexVirtualLoaderPath,
94
- options: {},
95
- ident: null,
96
- type: null
97
- });
98
- }
99
113
  });
100
114
  compilation.hooks.processAssets.tapPromise({
101
115
  name: PLUGIN_NAME,
102
116
  stage: Compilation.PROCESS_ASSETS_STAGE_PRE_PROCESS
103
- }, async (assets)=>{
104
- // on previous step, we create a "stylex" chunk to hold all virtual stylex css
105
- // the chunk contains all css chunks generated by mini-css-extract-plugin
106
- // TODO-RSPACK: once again, rspack doesn't have this
107
- const stylexChunk = compilation.namedChunks.get(STYLEX_CHUNK_NAME);
108
- if (stylexChunk == null) {
109
- return;
110
- }
111
- // Collect stylex rules from module instead of self maintained map
117
+ }, async (_)=>{
112
118
  if (this.loaderOption.nextjsMode) {
113
- const cssModulesInStylexChunk = compilation.chunkGraph.getChunkModulesIterableBySourceType(stylexChunk, 'css/mini-extract');
114
- // we only re-collect stylex rules if we can found css in the stylex chunk
115
- if (cssModulesInStylexChunk) {
116
- this.stylexRules.clear();
117
- for (const cssModule of cssModulesInStylexChunk){
118
- const stringifiedStylexRule = cssModule._identifier.split('!').pop()?.split('?').pop();
119
- if (!stringifiedStylexRule) {
120
- continue;
121
- }
122
- const params = new URLSearchParams(stringifiedStylexRule);
123
- const stylex = params.get('stylex');
124
- if (stylex != null) {
125
- this.stylexRules.set(cssModule.identifier(), JSON.parse(stylex));
119
+ // Collect stylex rules from module instead of self maintained map
120
+ // on previous step, we create a "stylex" chunk to hold all virtual stylex css
121
+ // the chunk contains all css chunks generated by mini-css-extract-plugin
122
+ // TODO-RSPACK: once again, rspack doesn't have this
123
+ const fuckNextjsChunk = compilation.namedChunks.get(FUCK_NEXTJS_VIRTUAL_CARRIERCHUNK_NAME);
124
+ if (fuckNextjsChunk) {
125
+ const cssModulesInFuckNextjsChunk = compilation.chunkGraph.getChunkModulesIterableBySourceType(fuckNextjsChunk, 'css/mini-extract');
126
+ // we only re-collect stylex rules if we can found css in the stylex chunk
127
+ if (cssModulesInFuckNextjsChunk) {
128
+ this.stylexRules.clear();
129
+ for (const cssModule of cssModulesInFuckNextjsChunk){
130
+ if (!('_identifier' in cssModule) || typeof cssModule._identifier !== 'string') {
131
+ continue;
132
+ }
133
+ const stringifiedStylexRule = cssModule._identifier.split('!').pop()?.split('?').pop();
134
+ if (!stringifiedStylexRule) {
135
+ continue;
136
+ }
137
+ const params = new URLSearchParams(stringifiedStylexRule);
138
+ const stylex = params.get('stylex');
139
+ const from = params.get('from');
140
+ if (stylex != null && from != null) {
141
+ this.stylexRules.set(from, JSON.parse(stylex));
142
+ }
126
143
  }
127
144
  }
128
145
  }
129
146
  }
130
- // Let's find the css file that belongs to the stylex chunk
131
- const cssAssetDetails = Object.entries(assets).filter(([assetName])=>stylexChunk.files.has(assetName) && assetName.endsWith('.css'));
132
- if (cssAssetDetails.length === 0) {
133
- return;
134
- }
135
- if (cssAssetDetails.length > 1) {
136
- console.warn('[stylex-webpack] Multiple CSS assets found for the stylex chunk. This should not happen. Please report this issue.');
137
- }
138
- const stylexAsset = cssAssetDetails[0];
139
147
  const stylexCSS = getStyleXRules(this.stylexRules, this.useCSSLayers);
140
148
  if (stylexCSS == null) {
141
149
  return;
142
150
  }
143
151
  const finalCss = await this.transformCss(stylexCSS);
144
- compilation.updateAsset(stylexAsset[0], (source)=>new ConcatSource(source, new RawSource(finalCss)));
152
+ /**
153
+ * Now we write final CSS to virtual module, which acts like `stylex-webpack/stylex.css` has been
154
+ * updated locally on the disk, and Next.js and webpack will have no choice but to update the global css
155
+ */ this._virtualModuleInstance.writeModule(VIRTUAL_ENTRYPOINT_CSS_PATH, finalCss.toString());
145
156
  });
146
157
  });
147
158
  }
@@ -153,6 +164,7 @@ class StyleXPlugin {
153
164
  _define_property(this, "useCSSLayers", void 0);
154
165
  _define_property(this, "loaderOption", void 0);
155
166
  _define_property(this, "transformCss", void 0);
167
+ _define_property(this, "_virtualModuleInstance", new VirtualModulesPlugin());
156
168
  this.useCSSLayers = useCSSLayers;
157
169
  this.loaderOption = {
158
170
  stylexImports,
package/dist/next.d.ts CHANGED
@@ -1,41 +1,6 @@
1
1
  import { NextConfig } from 'next/dist/server/config-shared';
2
- import { Options } from '@stylexjs/babel-plugin';
2
+ import { StyleXPluginOption } from './index';
3
3
 
4
- type CSSTransformer = (css: string) => string | Buffer | Promise<string | Buffer>;
5
- interface StyleXPluginOption {
6
- /**
7
- * stylex options passed to stylex babel plugin
8
- *
9
- * @see https://stylexjs.com/docs/api/configuration/babel-plugin/
10
- */
11
- stylexOption?: Partial<Options>;
12
- /**
13
- * Specify where stylex will be imported from
14
- *
15
- * @default ['stylex', '@stylexjs/stylex']
16
- */
17
- stylexImports?: string[];
18
- /**
19
- * Whether to use CSS layers
20
- *
21
- * @default false
22
- */
23
- useCSSLayers?: boolean;
24
- /**
25
- * Next.js Mode
26
- *
27
- * @default false
28
- */
29
- nextjsMode?: boolean;
30
- /**
31
- * Enable other CSS transformation
32
- *
33
- * Since stylex-webpack only inject CSS after all loaders, you can not use postcss-loader.
34
- * With this you can incovate `postcss()` here.
35
- */
36
- transformCss?: CSSTransformer;
37
- }
38
-
39
- declare const withStyleX: (pluginOptions?: StyleXPluginOption) => (nextConfig?: NextConfig) => NextConfig;
4
+ declare function withStyleX(pluginOptions?: StyleXPluginOption): (nextConfig?: NextConfig) => NextConfig;
40
5
 
41
6
  export { withStyleX };
package/dist/next.js CHANGED
@@ -6,21 +6,22 @@ var browserslist = require('next/dist/compiled/browserslist');
6
6
  var css = require('next/dist/build/webpack/config/blocks/css');
7
7
  var index = require('./index');
8
8
 
9
- require.resolve('./stylex.virtual.css');
10
- const VIRTUAL_CSS_PATTERN = /stylex\.virtual\.css/;
9
+ require.resolve('./stylex.css');
10
+ const VIRTUAL_ENTRYPOINT_CSS_PATTERN = /stylex\.css/;
11
+ require.resolve('./stylex.fuck-nextjs.virtual-carrier.js');
11
12
 
12
13
  /** Next.js' precompilation add "__esModule: true", but doesn't add an actual default exports */ // @ts-expect-error -- Next.js fucks something up
13
14
  const NextMiniCssExtractPlugin = nextMiniCssExtractPluginExports.default;
14
15
  // Adopted from https://github.com/vercel/next.js/blob/1f1632979c78b3edfe59fd85d8cce62efcdee688/packages/next/build/webpack-config.ts#L60-L72
15
- const getSupportedBrowsers = (dir, isDevelopment)=>{
16
+ function getSupportedBrowsers(dir, isDevelopment) {
16
17
  try {
17
18
  return browserslist.loadConfig({
18
19
  path: dir,
19
20
  env: isDevelopment ? 'development' : 'production'
20
21
  });
21
22
  } catch {}
22
- };
23
- const getNextMiniCssExtractPlugin = (isDev)=>{
23
+ }
24
+ function getNextMiniCssExtractPlugin(isDev) {
24
25
  // Use own MiniCssExtractPlugin to ensure HMR works
25
26
  // v9 has issues when using own plugin in production
26
27
  // v10.2.1 has issues when using built-in plugin in development since it
@@ -33,12 +34,13 @@ const getNextMiniCssExtractPlugin = (isDev)=>{
33
34
  return NextMiniCssExtractPlugin;
34
35
  } catch {
35
36
  log.warn('Next.js built-in mini-css-extract-plugin is broken, will fallback to "mini-css-extract-plugin"');
37
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- Require only when needed
36
38
  return require('mini-css-extract-plugin');
37
39
  }
38
40
  }
39
41
  // Always use Next.js built-in MiniCssExtractPlugin in production
40
42
  return NextMiniCssExtractPlugin;
41
- };
43
+ }
42
44
  // Adopt from Next.js' getGlobalCssLoader
43
45
  // https://github.com/vercel/next.js/blob/d61b0761efae09bd9cb1201ff134ed8950d9deca/packages/next/src/build/webpack/config/blocks/css/loaders/global.ts#L7
44
46
  function getStyleXVirtualCssLoader(ctx, MiniCssExtractPlugin, postcss) {
@@ -71,8 +73,8 @@ function getStyleXVirtualCssLoader(ctx, MiniCssExtractPlugin, postcss) {
71
73
  });
72
74
  return loaders;
73
75
  }
74
- const withStyleX = (pluginOptions)=>(nextConfig = {})=>{
75
- return {
76
+ function withStyleX(pluginOptions) {
77
+ return (nextConfig = {})=>({
76
78
  ...nextConfig,
77
79
  webpack (config, ctx) {
78
80
  var // For some reason, Next 11.0.1 has `config.optimization.splitChunks`
@@ -84,9 +86,11 @@ const withStyleX = (pluginOptions)=>(nextConfig = {})=>{
84
86
  (_config = config).optimization || (_config.optimization = {});
85
87
  (_config_optimization = config.optimization).splitChunks || (_config_optimization.splitChunks = {});
86
88
  (_config_optimization_splitChunks = config.optimization.splitChunks).cacheGroups || (_config_optimization_splitChunks.cacheGroups = {});
89
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- could be undefined
90
+ const useLightningcss = config.experimental?.useLightningcss;
87
91
  let lazyPostCSSPromise = null;
88
92
  const postcss = ()=>{
89
- lazyPostCSSPromise || (lazyPostCSSPromise = css.lazyPostCSS(ctx.dir, getSupportedBrowsers(ctx.dir, ctx.dev), undefined));
93
+ lazyPostCSSPromise || (lazyPostCSSPromise = css.lazyPostCSS(ctx.dir, getSupportedBrowsers(ctx.dir, ctx.dev), undefined, useLightningcss));
90
94
  return lazyPostCSSPromise;
91
95
  };
92
96
  const MiniCssExtractPlugin = getNextMiniCssExtractPlugin(ctx.dev);
@@ -94,7 +98,7 @@ const withStyleX = (pluginOptions)=>(nextConfig = {})=>{
94
98
  const cssRules = (config.module?.rules?.find((rule)=>typeof rule === 'object' && rule !== null && Array.isArray(rule.oneOf) && rule.oneOf.some((setRule)=>setRule && setRule.test instanceof RegExp && typeof setRule.test.test === 'function' && setRule.test.test('filename.css')))).oneOf;
95
99
  // Here we matches virtual css file emitted by StyleXPlugin
96
100
  cssRules?.unshift({
97
- test: VIRTUAL_CSS_PATTERN,
101
+ test: VIRTUAL_ENTRYPOINT_CSS_PATTERN,
98
102
  use: getStyleXVirtualCssLoader(ctx, MiniCssExtractPlugin, postcss)
99
103
  });
100
104
  (_config1 = config).plugins ?? (_config1.plugins = []);
@@ -111,7 +115,7 @@ const withStyleX = (pluginOptions)=>(nextConfig = {})=>{
111
115
  // HMR reloads the CSS file when the content changes but does not use
112
116
  // the new file name, which means it can't contain a hash.
113
117
  const filename = ctx.dev ? 'static/css/[name].css' : 'static/css/[contenthash].css';
114
- // Logic adopted from https://git.io/JtdBy
118
+ // Logic adopted from https://github.com/vercel/next.js/blob/143769bc8304423ba2038accf6f10de240733821/packages/next/src/build/webpack/config/blocks/css/index.ts#L606
115
119
  config.plugins.push(new MiniCssExtractPlugin({
116
120
  filename,
117
121
  chunkFilename: filename,
@@ -152,7 +156,7 @@ const withStyleX = (pluginOptions)=>(nextConfig = {})=>{
152
156
  }));
153
157
  return config;
154
158
  }
155
- };
156
- };
159
+ });
160
+ }
157
161
 
158
162
  exports.withStyleX = withStyleX;
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ var loaderUtils = require('loader-utils');
4
+ var tsDedent = require('ts-dedent');
5
+
6
+ // prefer loader-utils over self-implemented hash function to utilize caching + bulk hashing
7
+ function stylexFuckNextjsVirtualCarrierLoader(inputCode, inputSourceMap) {
8
+ const callback = this.async();
9
+ const data = new URLSearchParams(this.resourceQuery.slice(1));
10
+ try {
11
+ const stylex = data.get('stylex');
12
+ if (stylex == null) {
13
+ callback(null, inputCode, inputSourceMap);
14
+ return;
15
+ }
16
+ // @ts-expect-error -- getHashDigest supports string & xxhash64
17
+ const hash = loaderUtils.getHashDigest(stylex, 'xxhash64', 'base62', 32);
18
+ const code = tsDedent.dedent`
19
+ /** stylex rules: ${stylex} */
20
+ export default ${JSON.stringify('stylex-webpack-fuck-nextjs-' + hash)};
21
+ `;
22
+ callback(null, inputCode + '\n' + code);
23
+ } catch (e) {
24
+ callback(e);
25
+ }
26
+ }
27
+
28
+ module.exports = stylexFuckNextjsVirtualCarrierLoader;
@@ -2,18 +2,18 @@
2
2
 
3
3
  var core = require('@babel/core');
4
4
  var stylexBabelPlugin = require('@stylexjs/babel-plugin');
5
- var loaderUtils = require('loader-utils');
5
+
6
+ require.resolve('./stylex.css');
7
+ const FUCK_NEXTJS_VIRTUAL_CARRIER_PATH = require.resolve('./stylex.fuck-nextjs.virtual-carrier.js');
8
+ function isSupplementedLoaderContext(context) {
9
+ // eslint-disable-next-line prefer-object-has-own -- target older
10
+ return Object.prototype.hasOwnProperty.call(context, 'StyleXWebpackContextKey');
11
+ }
6
12
 
7
13
  function stringifyRequest(loaderContext, request) {
8
14
  return JSON.stringify(loaderContext.utils.contextify(loaderContext.context || loaderContext.rootContext, request));
9
15
  }
10
16
 
11
- const VIRTUAL_CSS_PATH = require.resolve('./stylex.virtual.css');
12
- const isSupplementedLoaderContext = (context)=>{
13
- // eslint-disable-next-line prefer-object-has-own -- target older
14
- return Object.prototype.hasOwnProperty.call(context, 'StyleXWebpackContextKey');
15
- };
16
-
17
17
  const PLUGIN_NAME = 'stylex';
18
18
  async function stylexLoader(inputCode, inputSourceMap) {
19
19
  const callback = this.async();
@@ -51,30 +51,21 @@ async function stylexLoader(inputCode, inputSourceMap) {
51
51
  }
52
52
  // this.stylexRules[filename] = metadata.stylex;
53
53
  logger?.debug(`Read stylex styles from ${this.resourcePath}:`, metadata.stylex);
54
- // TODO: rspack doesn't support custom loader context
54
+ // TODO-RSPACK: doesn't support custom loader context
55
55
  // Find a better way to register stylex rules to the compiler instance
56
56
  this.StyleXWebpackContextKey.registerStyleXRules(this.resourcePath, metadata.stylex);
57
- // color: #fff is not url safe
58
- const serializedStyleXRules = JSON.stringify(metadata.stylex);
59
- const urlParams = new URLSearchParams({
60
- from: this.resourcePath,
61
- stylex: serializedStyleXRules
62
- });
63
- if (!nextjsMode) {
64
- // Normal webpack mode
65
- // We generate a virtual css file that looks like it is relative to the source
66
- const virtualFileName = loaderUtils.interpolateName(this, '[path][name].[hash:base64:8].stylex.virtual.css', {
67
- content: serializedStyleXRules
57
+ if (nextjsMode) {
58
+ // Next.js App Router doesn't support inline matchResource and inline loaders
59
+ // So we adapt Next.js' "external" css import approach instead
60
+ const urlParams = new URLSearchParams({
61
+ from: this.resourcePath,
62
+ stylex: JSON.stringify(metadata.stylex) // color: #fff is not url safe, let's get through JSON.stringify
68
63
  });
69
- const virtualCssRequest = stringifyRequest(this, `${virtualFileName}!=!${VIRTUAL_CSS_PATH}?${urlParams.toString()}`);
64
+ const virtualCssRequest = stringifyRequest(this, `${FUCK_NEXTJS_VIRTUAL_CARRIER_PATH}?${urlParams.toString()}`);
70
65
  const postfix = `\nimport ${virtualCssRequest};`;
71
66
  return callback(null, code + postfix, map ?? undefined);
72
67
  }
73
- // Next.js App Router doesn't support inline matchResource and inline loaders
74
- // So we adapt Next.js' "external" css import approach instead
75
- const virtualCssRequest = stringifyRequest(this, `${VIRTUAL_CSS_PATH}?${urlParams.toString()}`);
76
- const postfix = `\nimport ${virtualCssRequest};`;
77
- return callback(null, code + postfix, map ?? undefined);
68
+ return callback(null, code ?? undefined, map ?? undefined);
78
69
  } catch (error) {
79
70
  return callback(error);
80
71
  }
@@ -0,0 +1,8 @@
1
+ /*
2
+ * This is a noop file to be imported at the entrypoint of your App
3
+ *
4
+ * This has to be an actual file as webpack requires a file to exist on disk
5
+ *
6
+ * During bundling, the stylex webpack plugin will replace content with generated CSS
7
+ */
8
+ .__stylex_entrypoint__ {}
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a noop file specifically for Next.js app dir
3
+ *
4
+ * This has to be an actual file as webpack requires a file to exist on disk
5
+ *
6
+ * Due to how Next.js stupidly handles server code and client code in two different webpack namespaces,
7
+ * the client compiler instance can't have access to the server code, including styles registered in the server code.
8
+ *
9
+ * In order for the client bundle to collect all the styles, we use this virtual noop file as a bridge. This file doesn't
10
+ * do anything, but it will become a carrier, holding collected style rules. It is done by appending a fake import with
11
+ * a url query using the "stylex-loader".
12
+ *
13
+ * Later in webpack's "processAsset" phase, we collect these imports from chunk information, extract those url query from
14
+ * module identifiers, collect style rules for later CSS generation.
15
+ */
package/next/index.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('../dist/next');
@@ -0,0 +1,5 @@
1
+ {
2
+ "private": true,
3
+ "main": "../dist/next.js",
4
+ "types": "../dist/next.d.ts"
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stylex-webpack",
3
- "version": "0.3.2",
3
+ "version": "0.4.0-beta.2",
4
4
  "description": "The another Webpack Plugin for Facebook's StyleX",
5
5
  "homepage": "https://github.com/SukkaW/style9-webpack#readme",
6
6
  "repository": {
@@ -14,17 +14,28 @@
14
14
  "types": "dist/index.d.ts",
15
15
  "files": [
16
16
  "dist",
17
- "next.d.ts",
18
- "next.js"
17
+ "next"
19
18
  ],
19
+ "exports": {
20
+ "./package.json": "./package.json",
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./next": {
26
+ "types": "./next/index.d.ts",
27
+ "default": "./next/index.js"
28
+ },
29
+ "./stylex.css": "./dist/stylex.css"
30
+ },
20
31
  "scripts": {
21
32
  "prebuild": "rimraf dist",
22
33
  "build": "rollup -c rollup.config.ts --configPlugin swc3 --bundleConfigAsCjs",
23
34
  "lint": "eslint --format=sukka .",
24
- "test": "mocha --require @swc-node/register test/index.ts",
35
+ "test": "mocha --require @swc-node/register --require mocha-expect-snapshot test/index.ts",
25
36
  "test:update": "mocha --update --require @swc-node/register test/index.ts",
26
- "prerelease": "npm run build && npm run lint",
27
- "release": "bumpp -r --all --commit=\"release: %s\" --tag=\"%s\""
37
+ "prerelease": "pnpm run build && pnpm run lint",
38
+ "release": "bumpp -r --all --commit \"release: %s\" --tag \"%s\""
28
39
  },
29
40
  "keywords": [
30
41
  "css",
@@ -38,48 +49,52 @@
38
49
  "author": "Sukka <https://skk.moe>",
39
50
  "license": "MIT",
40
51
  "dependencies": {
41
- "@babel/core": "^7.24.4",
42
- "@babel/plugin-syntax-jsx": "^7.24.1",
43
- "@babel/plugin-syntax-typescript": "^7.24.1",
44
- "@stylexjs/babel-plugin": "^0.5.1",
45
- "loader-utils": "^3.2.1"
52
+ "@babel/core": "^7.28.3",
53
+ "@babel/plugin-syntax-jsx": "^7.27.1",
54
+ "@babel/plugin-syntax-typescript": "^7.27.1",
55
+ "@types/webpack": "^5.28.5",
56
+ "foxts": "^3.12.0",
57
+ "loader-utils": "^3.3.1",
58
+ "ts-dedent": "^2.2.0",
59
+ "webpack-virtual-modules": "^0.6.2"
46
60
  },
47
61
  "devDependencies": {
48
- "@eslint-sukka/node": "^5.1.2",
49
- "@eslint-sukka/ts": "^5.1.2",
50
- "@swc-node/register": "^1.9.0",
51
- "@swc/core": "^1.4.12",
62
+ "@eslint-sukka/node": "^6.23.1",
63
+ "@swc-node/register": "^1.11.1",
64
+ "@swc/core": "^1.13.5",
52
65
  "@types/babel__core": "^7.20.5",
53
- "@types/chai": "^4.3.14",
54
- "@types/loader-utils": "^2.0.6",
55
- "@types/mocha": "^10.0.6",
56
- "@types/node": "^20.12.4",
57
- "browserslist": "^4.23.0",
58
- "bumpp": "^9.3.0",
59
- "chai": "^4.4.1",
60
- "css-loader": "^6.10.0",
61
- "eslint": "^8.57.0",
62
- "eslint-config-sukka": "^5.1.2",
63
- "eslint-formatter-sukka": "^5.1.2",
64
- "memfs": "^4.8.1",
65
- "mini-css-extract-plugin": "^2.8.1",
66
- "mocha": "^10.4.0",
67
- "mocha-chai-jest-snapshot": "^1.1.4",
68
- "next": "^14.1.4",
69
- "postcss": "^8.4.38",
70
- "rimraf": "^5.0.5",
71
- "rollup": "^4.14.0",
66
+ "@types/loader-utils": "^3.0.0",
67
+ "@types/mocha": "^10.0.10",
68
+ "@types/node": "^22.18.1",
69
+ "browserslist": "^4.25.4",
70
+ "bumpp": "^10.2.3",
71
+ "css-loader": "^7.1.2",
72
+ "eslint": "^9.34.0",
73
+ "eslint-config-sukka": "^6.23.1",
74
+ "eslint-formatter-sukka": "^6.23.1",
75
+ "expect": "^30.1.2",
76
+ "memfs": "^4.38.2",
77
+ "mini-css-extract-plugin": "^2.9.4",
78
+ "mocha": "^11.7.2",
79
+ "mocha-expect-snapshot": "^8.0.0",
80
+ "next": "^15.5.2",
81
+ "postcss": "^8.5.6",
82
+ "rimraf": "^6.0.1",
83
+ "rollup": "^4.50.0",
72
84
  "rollup-plugin-copy": "^3.5.0",
73
- "rollup-plugin-dts": "^6.1.0",
74
- "rollup-plugin-swc3": "^0.11.0",
85
+ "rollup-plugin-dts": "^6.2.3",
86
+ "rollup-plugin-swc3": "^0.12.1",
75
87
  "swc-loader": "^0.2.6",
76
- "typescript": "^5.4.4",
77
- "webpack": "^5.91.0"
88
+ "typescript": "^5.9.2",
89
+ "webpack": "^5.101.3"
78
90
  },
79
91
  "peerDependencies": {
80
- "@stylexjs/stylex": "*"
92
+ "@stylexjs/babel-plugin": "*"
81
93
  },
82
- "overrides": {
83
- "chai": "$chai"
94
+ "packageManager": "pnpm@10.15.1",
95
+ "pnpm": {
96
+ "overrides": {
97
+ "eslint>chalk": "npm:picocolors@^1.1.1"
98
+ }
84
99
  }
85
100
  }
@@ -1,36 +0,0 @@
1
- 'use strict';
2
-
3
- var loaderUtils = require('loader-utils');
4
-
5
- // prefer loader-utils over self-implemented hash function to utilize caching + bulk hashing
6
- function stylexVirtualCssLoader(inputCode, inputSourceMap) {
7
- const callback = this.async();
8
- const data = new URLSearchParams(this.resourceQuery.slice(1));
9
- try {
10
- const stylex = data.get('stylex');
11
- if (stylex == null) {
12
- callback(null, inputCode, inputSourceMap);
13
- return;
14
- }
15
- // If we got stylex in the virtual css import, we need to disable the cache
16
- // to fix HMR and Next.js navigation
17
- // TODO: find a better way to mark the generated chunk as uncacheable instead
18
- // of disable caching the result of this loader
19
- this.cacheable(false);
20
- // @ts-expect-error -- getHashDigest supports string & xxhash64
21
- const hash = loaderUtils.getHashDigest(stylex, 'xxhash64', 'base62', 32);
22
- const css = `
23
- /*
24
- * dummy css generated by stylex-webpack
25
- * real css will be injected later directly to the module
26
- *
27
- * stylex rules: ${stylex}
28
- */
29
- .__stylex_dummy_${hash} {}`;
30
- callback(null, css);
31
- } catch (e) {
32
- callback(e);
33
- }
34
- }
35
-
36
- module.exports = stylexVirtualCssLoader;
@@ -1,7 +0,0 @@
1
- /*
2
- * This is a noop file for extracted CSS source to point to, as webpack
3
- * requires a file to exist on disk for virtual source files
4
- *
5
- * The filename "virtual.stylexrules" is intensionally chose to be not
6
- * matched by any other loaders
7
- */
package/next.d.ts DELETED
@@ -1 +0,0 @@
1
- export * from './dist/next';
package/next.js DELETED
@@ -1,5 +0,0 @@
1
- 'use strict';
2
-
3
- /** @type {import('./dist/next')} */
4
- const next = require('./dist/next');
5
- module.exports = next;