[Enhancement] Improving the Webpack plugin?
nmn opened this issue ยท 5 comments
Problem
The Webpack plugin and Next JS plugin have a bunch of issues at the moment. Thanks to help from Tobias Koppers, I have some ideas on how to fix these problems.
Background
StyleX compilation is essentially two steps:
- Transform JS files and collect metadata (an array of objects)
- Take the metadata from all the JS files and convert them to a CSS file.
We haven't done this right within Webpack and caching doesn't work at all. But we now have some pointers on how to do this right.
Approach 1: Using CSS data imports
We can take the generated metadata from each JS file and convert that in the CSS data file import within the file itself.
Instead of generating:
["xabnbd", { "ltr": ".xabnbd{color: red}" }, 4001]
We would insert this import within the file itself:
import "data:text/css;@layer priority_4001{.xabnbd{color:red}}";
Then, the usual plugins will collect all the CSS from all the files combine them in a CSS file.
(We need to ensure that there is only ever 1 CSS file. Figure out how to avoid bundle splitting for CSS)
This generated CSS file will then need to be processed with a custom PostCSS or LightningCSS plugin to do all the work that is currently done while generating the CSS file. (sorting rules, renaming layers. Removing unused styles etc.)
Challenges
- Need to ensure that no bundle splitting happens. We want a single CSS file
- We are planning to add new metadata that are not styles but information about which styles are defined and which ones are actually used. We will need to abuse CSS syntax to encode this into CSS itself.
- We will need some way to marking StyleX styles to distinguish them from non-stylex styles.
- All of the HACKs need to survive post-processing already done.
Possible solutions:
- We can use CSS layers with a special
__stylex__
prefix and encode priority information in a suffix.@layer __stylex__priority__3008 {...}
- Within such layers, we can use special classNames to mean other things:
.__stylex__VAR_USED {--variable-name: 1}
- We can configure Webpack to not split CSS with config that looks like this:
optimization: {
splitChunks: {
cacheGroups: {
styles: {
name: "styles",
type: "css",
chunks: "all",
enforce: true,
},
},
},
},
Approach 2: Write module metadata and combine it
This is the approach we use with the Rollup plugin today but we haven't done things in Webpack correctly.
The approach is similar to the first one, but here we can use a custom mime type to keep the metadata in JSON format.
We would insert an import into the source JS file that looks like this:
import "data:text/stylex,[\"xabnbd\", { \"ltr\": \".xabnbd{color: red}\" }, 4001]";
A webpack loader can be configured:
{mimetype: "text/stylex", use: "stylex-loader"}
This loader will need to read and attach the metadata to the module being processed:
this._module.buildInfo.stylex ??= [];
this._module.buildInfo.stylex.push(JSON.parse(content))
NOTE: the buildInfo
is cached and needs to be serialisable.
Finally a Webpack plugin will need to iterate all the modules from compilation.modules
to read the styles and combine them, generate a CSS file and use emitAsset
to generate the CSS file.
In the case of HTML files being used, we would need to add the generated CSS to every entrypoint:
compilation.entrypoints
/entrypoint.files
.
NextJS challenges
Even after all of this, there is a challenge with NextJS generating separate server and client bundles. The current approach handles this in a hacky unreliable way that can't be cached. Both of these approaches may be stuck with separate server and client CSS files which is not what we want. There was a mention of a "manifest" file as a possible way to get around this issue.
Workaround: Pre-compile everything
A third and final solution might be to build a CLI that can take a folder or "source" files and transforms them all along with a CSS file to a "src" folder. This would happen while you program rather than a part of bundling.
In more detail:
A pre-app
or pre-src
folder with your actual source code. A babel-watch
command will transform this folder into app
or src
, respectively. Which runs in parallel with a standard swc
-based next dev
command combined with a single PostCSS or LightningCSS plugin.
Initially this won't support importing styles from node_modules
, but we will be able to fix that with some import re-writing and configuration as well.
#476 You can add this to the list of issues. The Webpack plugin is unusable for production because it interferes with Webpack's tree shaking/optimization. I'm using the Babel plugin as a substitute, but that's not meant for production.
@nmn FYI, here is currently my implementation (kinda like approach 1 and 2 combined)
stylex-loader
collects style rules from jsx/tsx files and append noop css imports to them:
import '/path/to/stylex-noop.css?rules=[serialized and encoded stylex rules]'
- Configure code splitting (
cacheGroup
) targets allstylex-noop.css
and merge them into a single file. - Instead of using
buildInfo
, we can just use module identifier. Inside theprocessAssets
hook (phasePROCESS_ASSETS_STAGE_PRE_PROCESS
), we find the stylex chunk (it should contain manystylex-noop.css?rules=
modules) and iterates through modules of the chunk, collect and deserialize stylex rules from modules'_identifier
. - Inject the actual CSS to the chunk directly.
Actual implementation: stylex-webpack. This works with Next.js as well, since stylex-loader
generated CSS imports can be handled by Next.js.
@SukkaW If your implementation works more reliably than the hacky plugin we have currently, I would love a PR to make it the official implementation going forward.
In the meantime, I'm going to do some testing with it and see how you got around some of the problems I encountered while trying to use that approach.
If your implementation works more reliably than the hacky plugin we have currently, I would love a PR to make it the official implementation going forward.
In the meantime, I'm going to do some testing with it and see how you got around some of the problems I encountered while trying to use that approach.
Sure! IMHO we could do some experiments based on the stylex-webpack
project before merging it back to stylex.
@SukkaW Found it now and it seems to work perfectly for us
Solves many issues that we had that really hindered our development process with nextjs + stylex