UNPKG

stylex-webpack

Version:

The another Webpack Plugin for Facebook's StyleX

175 lines (170 loc) 8.33 kB
'use strict'; 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;