UNPKG

stylex-webpack

Version:

The another Webpack Plugin for Facebook's StyleX

176 lines (171 loc) 10.3 kB
'use strict'; var nextMiniCssExtractPluginExports = require('next/dist/build/webpack/plugins/mini-css-extract-plugin'); var log = require('next/dist/build/output/log'); var browserslist = require('next/dist/compiled/browserslist'); var css = require('next/dist/build/webpack/config/blocks/css'); var index = require('./index'); require.resolve('./stylex.css'); require.resolve('./stylex-virtual.css'); const VIRTUAL_CSS_PATTERN = /stylex\.css|stylex-virtual\.css/; /** Next.js' precompilation add "__esModule: true", but doesn't add an actual default exports */ // @ts-expect-error -- Next.js fucks something up const NextMiniCssExtractPlugin = nextMiniCssExtractPluginExports.default; // Adopted from https://github.com/vercel/next.js/blob/1f1632979c78b3edfe59fd85d8cce62efcdee688/packages/next/build/webpack-config.ts#L60-L72 function getSupportedBrowsers(dir, isDevelopment) { try { return browserslist.loadConfig({ path: dir, env: isDevelopment ? 'development' : 'production' }); } catch {} } function getNextMiniCssExtractPlugin(isDev) { // Use own MiniCssExtractPlugin to ensure HMR works // v9 has issues when using own plugin in production // v10.2.1 has issues when using built-in plugin in development since it // doesn't bundle HMR files // v12.1.7 finaly fixes the issue by adding the missing hmr/hotModuleReplacement.js file if (isDev) { try { // Check if hotModuleReplacement exists require.resolve('next/dist/compiled/mini-css-extract-plugin/hmr/hotModuleReplacement'); return NextMiniCssExtractPlugin; } catch { log.warn('Next.js built-in mini-css-extract-plugin is broken, will fallback to "mini-css-extract-plugin"'); // eslint-disable-next-line @typescript-eslint/no-require-imports -- Require only when needed return require('mini-css-extract-plugin'); } } // Always use Next.js built-in MiniCssExtractPlugin in production return NextMiniCssExtractPlugin; } // Adopt from Next.js' getGlobalCssLoader // https://github.com/vercel/next.js/blob/d61b0761efae09bd9cb1201ff134ed8950d9deca/packages/next/src/build/webpack/config/blocks/css/loaders/global.ts#L7 function getStyleXVirtualCssLoader(ctx, MiniCssExtractPlugin, postcss) { const loaders = []; // Adopt from Next.js' getClientStyleLoader // https://github.com/vercel/next.js/blob/56d35ede8ed2ab25fa8e29583d4e81e3e76a0e29/packages/next/src/build/webpack/config/blocks/css/loaders/global.ts#L7 if (!ctx.isServer) { // https://github.com/vercel/next.js/blob/56d35ede8ed2ab25fa8e29583d4e81e3e76a0e29/packages/next/src/build/webpack/config/blocks/css/loaders/global.ts#L18 // https://github.com/vercel/next.js/blob/56d35ede8ed2ab25fa8e29583d4e81e3e76a0e29/packages/next/src/build/webpack/config/blocks/css/loaders/client.ts#L3 loaders.push({ loader: MiniCssExtractPlugin.loader, options: { publicPath: `${ctx.config.assetPrefix}/_next/`, esModule: false } }); } // We don't actually use postcss-loader or css-loader to run against // the stylex css (which doesn't exist yet). // We use this loader to run against the virtual dummy css. loaders.push({ // https://github.com/vercel/next.js/blob/0572e218afe130656be53f7367bf18c4ea389f7d/packages/next/build/webpack/config/blocks/css/loaders/global.ts#L29-L38 loader: require.resolve('next/dist/build/webpack/loaders/css-loader/src'), options: { // https://github.com/vercel/next.js/blob/88a5f263f11cb55907f0d89a4cd53647ee8e96ac/packages/next/build/webpack/config/blocks/css/index.ts#L142-L147 postcss, importLoaders: 1, modules: false } }); return loaders; } function withStyleX(pluginOptions) { return (nextConfig = {})=>{ const config = { ...nextConfig, webpack (config, ctx) { var // For some reason, Next 11.0.1 has `config.optimization.splitChunks` // set to `false` when webpack 5 is enabled. _config, _config_optimization, _config_optimization_splitChunks, _config1; if (typeof nextConfig.webpack === 'function') { config = nextConfig.webpack(config, ctx); } (_config = config).optimization || (_config.optimization = {}); (_config_optimization = config.optimization).splitChunks || (_config_optimization.splitChunks = {}); (_config_optimization_splitChunks = config.optimization.splitChunks).cacheGroups || (_config_optimization_splitChunks.cacheGroups = {}); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- could be undefined const useLightningcss = config.experimental?.useLightningcss; let lazyPostCSSPromise = null; const postcss = ()=>{ lazyPostCSSPromise || (lazyPostCSSPromise = css.lazyPostCSS(ctx.dir, getSupportedBrowsers(ctx.dir, ctx.dev), undefined, useLightningcss)); return lazyPostCSSPromise; }; const MiniCssExtractPlugin = getNextMiniCssExtractPlugin(ctx.dev); // Based on https://github.com/vercel/next.js/blob/88a5f263f11cb55907f0d89a4cd53647ee8e96ac/packages/next/build/webpack/config/helpers.ts#L12-L18 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; // Here we matches virtual css file emitted by StyleXPlugin cssRules?.unshift({ test: VIRTUAL_CSS_PATTERN, use: getStyleXVirtualCssLoader(ctx, MiniCssExtractPlugin, postcss) }); (_config1 = config).plugins ?? (_config1.plugins = []); // StyleX need to emit the css file on both server and client, both during the // development and production. // However, Next.js only add MiniCssExtractPlugin on client + production. // // To simplify the logic at our side, we will add MiniCssExtractPlugin based on // the "instanceof" check (We will only add our required MiniCssExtractPlugin if // Next.js hasn't added it yet). // This also prevent multiple MiniCssExtractPlugin being added (which will cause // RealContentHashPlugin to panic) if (!config.plugins.some((plugin)=>plugin instanceof MiniCssExtractPlugin)) { // HMR reloads the CSS file when the content changes but does not use // the new file name, which means it can't contain a hash. const filename = ctx.dev ? 'static/css/[name].css' : 'static/css/[contenthash].css'; // Logic adopted from https://github.com/vercel/next.js/blob/143769bc8304423ba2038accf6f10de240733821/packages/next/src/build/webpack/config/blocks/css/index.ts#L606 config.plugins.push(new MiniCssExtractPlugin({ filename, chunkFilename: filename, // Next.js guarantees that CSS order "doesn't matter", due to imposed // restrictions: // 1. Global CSS can only be defined in a single entrypoint (_app) // 2. CSS Modules generate scoped class names by default and cannot // include Global CSS (:global() selector). // // While not a perfect guarantee (e.g. liberal use of `:global()` // selector), this assumption is required to code-split CSS. // // As for StyleX, the CSS is always atomic (so classes are always unique), // and StyleX Plugin will always sort the css based on media query and pseudo // selector. // // If this warning were to trigger, it'd be unactionable by the user, // but likely not valid -- so just disable it. ignoreOrder: true })); } config.plugins.push(new index.StyleXPlugin({ nextjsMode: true, nextjsAppRouterMode: true, async transformCss (css) { const { postcssWithPlugins } = await postcss(); // add from: undefined to avoid source map warning const result = await postcssWithPlugins.process(css, { from: undefined }); if (pluginOptions?.transformCss) { return pluginOptions.transformCss(result.css); } return result.css; }, ...pluginOptions, stylexOption: { ...pluginOptions?.stylexOption, dev: ctx.dev } })); return config; } }; // https://github.com/vercel/next.js/blob/ad6907a8a37e930639af071203f4ce49a5d69ee5/packages/next/src/build/index.ts#L1723 // Actually, if a custom webpack config is provided, Next.js will always disable parallel bundling // But we should not take that as an assumption, so we just warn and disable the options if (config.experimental?.webpackBuildWorker) { log.warn('[stylex-webpack] "experimental.webpackBuildWorker" is not supported with "stylex-webpack", the option will be disabled.'); config.experimental.webpackBuildWorker = false; } return config; }; } exports.withStyleX = withStyleX;