stylex-webpack
Version:
The another Webpack Plugin for Facebook's StyleX
175 lines (170 loc) • 8.33 kB
JavaScript
;
var stylexBabelPlugin = require('@stylexjs/babel-plugin');
var path = require('path');
const PLUGIN_NAME = 'stylex';
const VIRTUAL_CSS_PATH = require.resolve('./stylex.virtual.css');
const VIRTUAL_CSS_PATTERN = /stylex\.virtual\.css/;
const STYLEX_CHUNK_NAME = '_stylex-webpack-generated';
const INCLUDE_REGEXP = /\.[cm]?[jt]sx?$/;
function _define_property(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
const stylexLoaderPath = require.resolve('./stylex-loader');
const stylexVirtualLoaderPath = require.resolve('./stylex-virtual-css-loader');
const getStyleXRules = (stylexRules, useCSSLayers)=>{
if (stylexRules.size === 0) {
return null;
}
// Take styles for the modules that were included in the last compilation.
const allRules = Array.from(stylexRules.values()).flat();
return stylexBabelPlugin.processStylexRules(allRules, useCSSLayers);
};
const identityTransfrom = (css)=>css;
class StyleXPlugin {
apply(compiler) {
var _compiler_options_optimization_splitChunks;
// If splitChunk is enabled, we create a dedicated chunk for stylex css
if (!compiler.options.optimization.splitChunks) {
throw new Error([
'You don\'t have "optimization.splitChunks" enabled.',
'"optimization.splitChunks" should be enabled for "stylex-webpack" to function properly.'
].join(' '));
}
(_compiler_options_optimization_splitChunks = compiler.options.optimization.splitChunks).cacheGroups ?? (_compiler_options_optimization_splitChunks.cacheGroups = {});
compiler.options.optimization.splitChunks.cacheGroups[STYLEX_CHUNK_NAME] = {
name: STYLEX_CHUNK_NAME,
test: VIRTUAL_CSS_PATTERN,
type: 'css/mini-extract',
chunks: 'all',
enforce: true
};
// const IS_RSPACK = Object.prototype.hasOwnProperty.call(compiler.webpack, 'rspackVersion');
// stylex-loader adds virtual css import (which triggers virtual-loader)
// This prevents "stylex.virtual.css" files from being tree shaken by forcing
// "sideEffects" setting.
// TODO-RSPACK: rspack does support normalModuleFactory, we need to test this out
compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (nmf)=>{
nmf.hooks.createModule.tap(PLUGIN_NAME, (createData)=>{
const modPath = createData.matchResource ?? createData.resourceResolveData?.path;
if (modPath === VIRTUAL_CSS_PATH) {
var _createData;
(_createData = createData).settings ?? (_createData.settings = {});
createData.settings.sideEffects = true;
}
});
});
const { Compilation, NormalModule, sources } = compiler.webpack;
const { RawSource, ConcatSource } = sources;
// Apply loader to JS modules
compiler.hooks.make.tap(PLUGIN_NAME, (compilation)=>{
NormalModule.getCompilationHooks(compilation).loader.tap(PLUGIN_NAME, (loaderContext, mod)=>{
const extname = path.extname(mod.matchResource || mod.resource);
if (INCLUDE_REGEXP.test(extname)) {
loaderContext.StyleXWebpackContextKey = {
registerStyleXRules: (resourcePath, stylexRules)=>{
this.stylexRules.set(resourcePath, stylexRules);
}
};
// We use .push() here instead of .unshift()
// Webpack usually runs loaders in reverse order and we want to ideally run
// our loader before anything else.
mod.loaders.push({
loader: stylexLoaderPath,
options: this.loaderOption,
ident: null,
type: null
});
}
if (VIRTUAL_CSS_PATTERN.test(mod.matchResource || mod.resource)) {
mod.loaders.push({
loader: stylexVirtualLoaderPath,
options: {},
ident: null,
type: null
});
}
});
compilation.hooks.processAssets.tapPromise({
name: PLUGIN_NAME,
stage: Compilation.PROCESS_ASSETS_STAGE_PRE_PROCESS
}, async (assets)=>{
// on previous step, we create a "stylex" chunk to hold all virtual stylex css
// the chunk contains all css chunks generated by mini-css-extract-plugin
// TODO-RSPACK: once again, rspack doesn't have this
const stylexChunk = compilation.namedChunks.get(STYLEX_CHUNK_NAME);
if (stylexChunk == null) {
return;
}
// Collect stylex rules from module instead of self maintained map
if (this.loaderOption.nextjsMode) {
const cssModulesInStylexChunk = compilation.chunkGraph.getChunkModulesIterableBySourceType(stylexChunk, 'css/mini-extract');
// we only re-collect stylex rules if we can found css in the stylex chunk
if (cssModulesInStylexChunk) {
this.stylexRules.clear();
for (const cssModule of cssModulesInStylexChunk){
const stringifiedStylexRule = cssModule._identifier.split('!').pop()?.split('?').pop();
if (!stringifiedStylexRule) {
continue;
}
const params = new URLSearchParams(stringifiedStylexRule);
const stylex = params.get('stylex');
if (stylex != null) {
this.stylexRules.set(cssModule.identifier(), JSON.parse(stylex));
}
}
}
}
// Let's find the css file that belongs to the stylex chunk
const cssAssetDetails = Object.entries(assets).filter(([assetName])=>stylexChunk.files.has(assetName) && assetName.endsWith('.css'));
if (cssAssetDetails.length === 0) {
return;
}
if (cssAssetDetails.length > 1) {
console.warn('[stylex-webpack] Multiple CSS assets found for the stylex chunk. This should not happen. Please report this issue.');
}
const stylexAsset = cssAssetDetails[0];
const stylexCSS = getStyleXRules(this.stylexRules, this.useCSSLayers);
if (stylexCSS == null) {
return;
}
const finalCss = await this.transformCss(stylexCSS);
compilation.updateAsset(stylexAsset[0], (source)=>new ConcatSource(source, new RawSource(finalCss)));
});
});
}
constructor({ stylexImports = [
'stylex',
'@stylexjs/stylex'
], useCSSLayers = false, stylexOption = {}, nextjsMode = false, transformCss = identityTransfrom } = {}){
_define_property(this, "stylexRules", new Map());
_define_property(this, "useCSSLayers", void 0);
_define_property(this, "loaderOption", void 0);
_define_property(this, "transformCss", void 0);
this.useCSSLayers = useCSSLayers;
this.loaderOption = {
stylexImports,
stylexOption: {
dev: process.env.NODE_ENV === 'development',
useRemForFontSize: true,
runtimeInjection: false,
genConditionalClasses: true,
treeshakeCompensation: true,
importSources: stylexImports,
...stylexOption
},
nextjsMode
};
this.transformCss = transformCss;
}
}
exports.StyleXPlugin = StyleXPlugin;