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 +34 -8
- package/dist/index.d.ts +5 -2
- package/dist/index.js +62 -50
- package/dist/next.d.ts +2 -37
- package/dist/next.js +17 -13
- package/dist/stylex-fuck-nextjs-virtual-carrier-loader.js +28 -0
- package/dist/stylex-loader.js +16 -25
- package/dist/stylex.css +8 -0
- package/dist/stylex.fuck-nextjs.virtual-carrier.js +15 -0
- package/next/index.js +1 -0
- package/next/package.json +5 -0
- package/package.json +56 -41
- package/dist/stylex-virtual-css-loader.js +0 -36
- package/dist/stylex.virtual.css +0 -7
- package/next.d.ts +0 -1
- package/next.js +0 -5
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
8
|
-
const
|
|
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
|
|
27
|
-
|
|
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:
|
|
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 ===
|
|
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
|
|
70
|
-
const { RawSource
|
|
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 (
|
|
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
|
-
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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 {
|
|
2
|
+
import { StyleXPluginOption } from './index';
|
|
3
3
|
|
|
4
|
-
|
|
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.
|
|
10
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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:
|
|
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://
|
|
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;
|
package/dist/stylex-loader.js
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
var core = require('@babel/core');
|
|
4
4
|
var stylexBabelPlugin = require('@stylexjs/babel-plugin');
|
|
5
|
-
|
|
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:
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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, `${
|
|
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
|
-
|
|
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
|
}
|
package/dist/stylex.css
ADDED
|
@@ -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');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stylex-webpack",
|
|
3
|
-
"version": "0.
|
|
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
|
|
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": "
|
|
27
|
-
"release": "bumpp -r --all --commit
|
|
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.
|
|
42
|
-
"@babel/plugin-syntax-jsx": "^7.
|
|
43
|
-
"@babel/plugin-syntax-typescript": "^7.
|
|
44
|
-
"@
|
|
45
|
-
"
|
|
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": "^
|
|
49
|
-
"@
|
|
50
|
-
"@swc
|
|
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/
|
|
54
|
-
"@types/
|
|
55
|
-
"@types/
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"eslint": "^
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"mocha": "^
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
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.
|
|
74
|
-
"rollup-plugin-swc3": "^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.
|
|
77
|
-
"webpack": "^5.
|
|
88
|
+
"typescript": "^5.9.2",
|
|
89
|
+
"webpack": "^5.101.3"
|
|
78
90
|
},
|
|
79
91
|
"peerDependencies": {
|
|
80
|
-
"@stylexjs/
|
|
92
|
+
"@stylexjs/babel-plugin": "*"
|
|
81
93
|
},
|
|
82
|
-
"
|
|
83
|
-
|
|
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;
|
package/dist/stylex.virtual.css
DELETED
package/next.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './dist/next';
|