diff --git a/api.md b/api.md index e1e6415..87fd298 100644 --- a/api.md +++ b/api.md @@ -30,6 +30,7 @@ This file documents the functions exported by `customize-cra`. - [setWebpackOptimizationSplitChunks](#setwebpackoptimizationsplitchunkstarget) - [adjustWorkbox](#adjustworkboxfn) - [addLessLoader](#addlessloaderloaderoptions) + - [addStylusLoader](#addstylusloaderloaderoptions) - [addPostcssPlugins](#addpostcsspluginsplugins) - [disableChunk](#disablechunk) - [removeModuleScopePlugin](#removemodulescopeplugin) @@ -414,6 +415,59 @@ declare module "*.module.less" { } ``` +### addStylusLoader(loaderOptions) + +First, install `stylus` and `stylus-loader` packages: + +```bash +yarn add --dev stylus stylus-loader +``` + +or: + +```bash +npm i -D stylus stylus-loader +``` + +After it's done, call `addStylusLoader` in `override` like below: + +```js +const { addStylusLoader } = require("customize-cra"); + +module.exports = override(addStylusLoader(loaderOptions)); +``` + +`loaderOptions` is optional. If you have Stylus specific options, you can pass to it. For example: + +```js +const { addStylusLoader } = require("customize-cra"); + +module.exports = override( + addStylusLoader({ + stylusOptions: {}, // pass any options to stylus. + cssLoaderOptions: {}, // .styl file used css-loader option, not all CSS file. + cssModules: { + localIdentName: "[path][name]__[local]--[hash:base64:5]", // if you use CSS Modules, and custom `localIdentName`, default is '[local]--[hash:base64:5]'. + }, + }) +); +``` + +Check [stylus-loader document](https://github.com/webpack-contrib/stylus-loader#options) and [stylus document](https://stylus-lang.com/docs/js.html) for all available specific options you can use. + +Once `stylus-loader` is enabled, you can import `.styl` file in your project. + +`.module.styl` will use CSS Modules. + +> if you use TypeScript (npm init react-app my-app --typescript) with CSS Modules, you should edit `react-app-env.d.ts`. + +```typescript +declare module "*.module.styl" { + const classes: { [key: string]: string }; + export default classes; +} +``` + ### addPostcssPlugins([plugins]) To add post-css plugins, you can use `addPostcssPlugins`. diff --git a/src/customizers/webpack.js b/src/customizers/webpack.js index 1002df4..ee3fd43 100644 --- a/src/customizers/webpack.js +++ b/src/customizers/webpack.js @@ -239,6 +239,128 @@ export const addLessLoader = (loaderOptions = {}, customCssModules = {}) => conf return config; }; +export const addStylusLoader = (loaderOptions = {}, customCssModules = {}) => config => { + const MiniCssExtractPlugin = require("mini-css-extract-plugin"); + const postcssNormalize = require("postcss-normalize"); + + const cssLoaderOptions = loaderOptions.cssLoaderOptions || {}; + delete loaderOptions.cssLoaderOptions; + + const { localIdentName } = loaderOptions; + let cssModules = loaderOptions.cssModules || { localIdentName }; + + if (!cssModules.localIdentName) { + cssModules = customCssModules; + } + + cssModules.localIdentName = cssModules.localIdentName || "[local]--[hash:base64:5]"; + + const stylusRegex = /\.styl$/; + const stylusModuleRegex = /\.module\.styl$/; + + const webpackEnv = process.env.NODE_ENV; + const isEnvDevelopment = webpackEnv === "development"; + const isEnvProduction = webpackEnv === "production"; + const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false"; + const publicPath = config.output.publicPath; + const shouldUseRelativeAssetPaths = publicPath === "./"; + + // copy from react-scripts + // https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/config/webpack.config.js#L93 + const getStyleLoaders = (cssOptions, preProcessor) => { + const loaders = [ + isEnvDevelopment && require.resolve("style-loader"), + isEnvProduction && { + loader: MiniCssExtractPlugin.loader, + options: shouldUseRelativeAssetPaths ? { publicPath: "../../" } : {} + }, + { + loader: require.resolve("css-loader"), + options: cssOptions + }, + { + loader: require.resolve("postcss-loader"), + options: { + ident: "postcss", + plugins: () => [ + require("postcss-flexbugs-fixes"), + require("postcss-preset-env")({ + autoprefixer: { + flexbox: "no-2009" + }, + stage: 3 + }), + postcssNormalize() + ], + sourceMap: isEnvProduction && shouldUseSourceMap + } + } + ].filter(Boolean); + if (preProcessor) { + loaders.push( + { + loader: require.resolve("resolve-url-loader"), + options: { + sourceMap: isEnvProduction && shouldUseSourceMap + } + }, + { + loader: require.resolve(preProcessor), + // not the same as react-scripts + options: Object.assign( + { + sourceMap: true + }, + loaderOptions + ) + } + ); + } + return loaders; + }; + + const loaders = config.module.rules.find(rule => Array.isArray(rule.oneOf)) + .oneOf; + + // Insert stylus-loader as the penultimate item of loaders (before file-loader) + loaders.splice( + loaders.length - 1, + 0, + { + test: stylusRegex, + exclude: stylusModuleRegex, + use: getStyleLoaders( + Object.assign( + { + importLoaders: 2, + sourceMap: isEnvProduction && shouldUseSourceMap + }, + cssLoaderOptions + ), + "stylus-loader" + ) + }, + { + test: stylusModuleRegex, + use: getStyleLoaders( + Object.assign( + { + importLoaders: 2, + sourceMap: isEnvProduction && shouldUseSourceMap + }, + cssLoaderOptions, + { + modules: cssModules + } + ), + "stylus-loader" + ) + } + ); + + return config; +}; + // to be used inside `overrideDevServer`, makes CRA watch all the folders // included `node_modules`, useful when you are working with linked packages // usage: `yarn start --watch-all` diff --git a/src/customizers/webpack.test.js b/src/customizers/webpack.test.js index e67bb87..30a9a30 100644 --- a/src/customizers/webpack.test.js +++ b/src/customizers/webpack.test.js @@ -177,6 +177,8 @@ test("adjustStyleLoaders find all style loaders and callback one by one", () => test("addLessLoader", () => {}); +test("addStylusLoader", () => {}); + test("watchAll removes the watchOptions from config if --watch-all passed", () => { const watchOptions = { watch: true }; const inputConfig = { watchOptions };