From 7f2b590600dd370454a0f6e184112ffc78f1e9c4 Mon Sep 17 00:00:00 2001 From: Divya Karippath Date: Fri, 30 Oct 2020 12:42:19 -0700 Subject: [PATCH 1/2] css module rework --- packages/xarc-app-dev/src/config/archetype.ts | 9 +- .../xarc-app-dev/src/config/babel/babelrc.ts | 19 +- .../xarc-app-dev/src/config/babel/common.ts | 2 +- .../src/config/opt2/webpack-options.ts | 2 +- packages/xarc-app-dev/src/lib/dev-tasks.ts | 11 +- packages/xarc-app-dev/src/lib/utils.ts | 9 + packages/xarc-app/package.json | 1 + packages/xarc-opt-postcss/package.json | 1 - .../src/partials/extract-style.ts | 186 ++++++++++-------- .../src/util/detect-css-module.ts | 78 ++++---- 10 files changed, 175 insertions(+), 143 deletions(-) diff --git a/packages/xarc-app-dev/src/config/archetype.ts b/packages/xarc-app-dev/src/config/archetype.ts index 779533cb9..3d6d779c5 100644 --- a/packages/xarc-app-dev/src/config/archetype.ts +++ b/packages/xarc-app-dev/src/config/archetype.ts @@ -14,6 +14,13 @@ function createElectrodeTmpDir(eTmpDir = ".etmp") { createGitIgnoreDir(Path.resolve(eTmpDir), "Electrode tmp dir"); } +function jsonStringifyReplacer(key, value) { + if (value instanceof RegExp) { + return value.toString(); + } + return value; +} + function saveArchetypeConfig(config) { const filename = `${config.eTmpDir}/xarc-options.json`; const copy = { ...config, pkg: undefined, devPkg: undefined }; @@ -25,7 +32,7 @@ function saveArchetypeConfig(config) { // } - const str = JSON.stringify(copy, null, 2); + const str = JSON.stringify(copy, jsonStringifyReplacer, 2); if (str !== existStr) { try { createElectrodeTmpDir(config.eTmpDir); diff --git a/packages/xarc-app-dev/src/config/babel/babelrc.ts b/packages/xarc-app-dev/src/config/babel/babelrc.ts index 69295d0bc..50af851fc 100644 --- a/packages/xarc-app-dev/src/config/babel/babelrc.ts +++ b/packages/xarc-app-dev/src/config/babel/babelrc.ts @@ -5,11 +5,10 @@ * When transpiling for node.js, env XARC_BABEL_TARGET should be set to "node" * and this file will set preset-env targets accordingly. */ -const requireAt = require("require-at"); const ck = require("chalker"); const optionalRequire = require("optional-require")(require); const optFlow = optionalRequire("electrode-archetype-opt-flow"); -import { getPluginFrom, loadXarcOptions } from "./common"; +import { getPluginFrom, loadXarcOptions, detectCSSModule } from "./common"; const _ = require("lodash"); const xOptions = loadXarcOptions(process.env.XARC_APP_DIR); @@ -34,7 +33,7 @@ const addFlowPlugin = Boolean(enableFlow && optFlow); const { BABEL_ENV, NODE_ENV, XARC_BABEL_TARGET, ENABLE_KARMA_COV } = process.env; -const enableCssModule = Boolean(_.get(xOptions, "webpack.cssModuleSupport")); +const enableCssModule = detectCSSModule(xOptions); const enableKarmaCov = ENABLE_KARMA_COV === "true"; const isProduction = (BABEL_ENV || NODE_ENV) === "production"; const isTest = (BABEL_ENV || NODE_ENV) === "test"; @@ -58,20 +57,14 @@ const getReactCssModulePlugin = () => { return null; } - const postCssPath = optionalRequire.resolve("@xarc/opt-postcss/package.json"); - - if (!postCssPath) { - return null; - } - - const babelReactCssModulePlugin = requireAt(postCssPath)("babel-plugin-react-css-modules"); - + const enableShortenCSSNames = xOptions.webpack.enableShortenCSSNames; + const enableShortHash = isProduction && enableShortenCSSNames; return [ [ - babelReactCssModulePlugin, + "babel-plugin-react-css-modules", { context: "./src", - generateScopedName: `${isProduction ? "" : "[name]__[local]___"}[hash:base64:5]`, + generateScopedName: `${enableShortHash ? "" : "[name]__[local]___"}[hash:base64:5]`, filetypes: { ".scss": { syntax: "postcss-scss", diff --git a/packages/xarc-app-dev/src/config/babel/common.ts b/packages/xarc-app-dev/src/config/babel/common.ts index eb003bf4a..c114b18ec 100644 --- a/packages/xarc-app-dev/src/config/babel/common.ts +++ b/packages/xarc-app-dev/src/config/babel/common.ts @@ -19,4 +19,4 @@ export function getPluginFrom(host, pluginName) { throw err; } -export { loadXarcOptions } from "../../lib/utils"; +export { loadXarcOptions, detectCSSModule } from "../../lib/utils"; diff --git a/packages/xarc-app-dev/src/config/opt2/webpack-options.ts b/packages/xarc-app-dev/src/config/opt2/webpack-options.ts index 8c9cf0c3c..bb12f5768 100644 --- a/packages/xarc-app-dev/src/config/opt2/webpack-options.ts +++ b/packages/xarc-app-dev/src/config/opt2/webpack-options.ts @@ -59,7 +59,7 @@ export type WebpackOptions = { * - **Default: `undefined` (auto detect)** * - If not set, then check env `CSS_MODULE_SUPPORT` */ - cssModuleSupport?: boolean; + cssModuleSupport?: boolean | RegExp; /** * Enable loading `@babel/polyfill` for application diff --git a/packages/xarc-app-dev/src/lib/dev-tasks.ts b/packages/xarc-app-dev/src/lib/dev-tasks.ts index 755a25d02..3102ea26c 100644 --- a/packages/xarc-app-dev/src/lib/dev-tasks.ts +++ b/packages/xarc-app-dev/src/lib/dev-tasks.ts @@ -353,7 +353,6 @@ export function loadXarcDevTasks(xrun, xarcOptions: XarcOptions = {}) { // eslint-disable-next-line complexity function makeTasks(xclap2) { assert(xclap2.concurrent, "xclap version must be 0.2.28+"); - process.env.ENABLE_CSS_MODULE = "false"; process.env.ENABLE_KARMA_COV = "false"; const checkFrontendCov = (minimum = "5") => { @@ -442,7 +441,7 @@ module.exports = { ".static-files-env": () => setStaticFilesEnv(), ".remove-log-files": () => removeLogFiles(), build: { - dep: [".remove-log-files", ".production-env", ".set.css-module.env"], + dep: [".remove-log-files", ".production-env"], desc: `Build your app's ${AppMode.src.dir} directory into ${AppMode.lib.dir} for production`, task: [".build-lib", "build-dist", ".check.top.level.babelrc", "mv-to-dist"] }, @@ -460,12 +459,6 @@ module.exports = { `--colors` ); }, - ".set.css-module.env": () => { - const cssModule = detectCssModule(); - if (cssModule) { - process.env.ENABLE_CSS_MODULE = "true"; - } - }, "build-browser-coverage": { desc: "Build browser coverage", task: [ @@ -483,7 +476,6 @@ module.exports = { "build-dist": [ ".production-env", - ".set.css-module.env", ".clean.build", ".mk-dist-dir", ".copy-xarc-options-to-dist", @@ -817,7 +809,6 @@ You only need to run this if you are doing something not through the xarc tasks. dep: [".remove-log-files", ".development-env", ".build.babelrc"], task() { return [ - ".set.css-module.env", ".webpack-dev", [`server-admin ${this.args.join(" ")}`, "generate-service-worker"] ]; diff --git a/packages/xarc-app-dev/src/lib/utils.ts b/packages/xarc-app-dev/src/lib/utils.ts index bf4c74ab4..bb7b33ab4 100644 --- a/packages/xarc-app-dev/src/lib/utils.ts +++ b/packages/xarc-app-dev/src/lib/utils.ts @@ -7,6 +7,7 @@ const logger = require("./logger"); const ck = require("chalker"); const Path = require("path"); const Fs = require("fs"); +const _ = require("lodash"); const Url = require("url"); @@ -98,3 +99,11 @@ xarc's development code. }); } } + +export function detectCSSModule(xOptions) { + const cssModuleSupport = _.get(xOptions, "webpack.cssModuleSupport", undefined); + if (cssModuleSupport === undefined) { + return true; + } + return Boolean(cssModuleSupport); +} diff --git a/packages/xarc-app/package.json b/packages/xarc-app/package.json index ed33127ad..b081be125 100644 --- a/packages/xarc-app/package.json +++ b/packages/xarc-app/package.json @@ -37,6 +37,7 @@ ], "dependencies": { "@babel/runtime": "^7.8.3", + "babel-plugin-react-css-modules": "^5.2.6", "css-modules-require-hook": "^4.0.2", "ignore-styles": "^5.0.1", "isomorphic-loader": "^4.0.5", diff --git a/packages/xarc-opt-postcss/package.json b/packages/xarc-opt-postcss/package.json index b4afd2cbf..41ff8f9e7 100644 --- a/packages/xarc-opt-postcss/package.json +++ b/packages/xarc-opt-postcss/package.json @@ -30,7 +30,6 @@ "Joel Chen " ], "dependencies": { - "babel-plugin-react-css-modules": "^5.2.6", "postcss-import": "^12.0.1", "postcss-less": "^3.1.4", "postcss-loader": "^3.0.0", diff --git a/packages/xarc-webpack/src/partials/extract-style.ts b/packages/xarc-webpack/src/partials/extract-style.ts index 2f1122c29..dc189e6f0 100644 --- a/packages/xarc-webpack/src/partials/extract-style.ts +++ b/packages/xarc-webpack/src/partials/extract-style.ts @@ -26,15 +26,15 @@ const optLessRequire = getOptRequire(["@xarc/opt-less", "electrode-archetype-opt const lessLoader = optLessRequire.resolve("less-loader"); function loadPostCss() { - const cssModuleRequire = getOptRequire(["@xarc/opt-postcss", "electrode-archetype-opt-postcss"]); + const optPostcssRequire = getOptRequire(["@xarc/opt-postcss", "electrode-archetype-opt-postcss"]); - if (cssModuleRequire.invalid) { + if (optPostcssRequire.invalid) { return { hasPostCss: false }; } - const atImport = cssModuleRequire("postcss-import"); - const postcssPresetEnv = cssModuleRequire("postcss-preset-env"); - const postcssLoader = cssModuleRequire.resolve("postcss-loader"); + const atImport = optPostcssRequire("postcss-import"); + const postcssPresetEnv = optPostcssRequire("postcss-preset-env"); + const postcssLoader = optPostcssRequire.resolve("postcss-loader"); return { hasPostCss: true, atImport, postcssPresetEnv, postcssLoader }; } @@ -49,30 +49,28 @@ function loadPostCss() { * - *.scss => SASS compiled to normal CSS * - *.less => SASS compiled to normal CSS * - * cssModuleSupport: true + * cssModuleSupport: true|undefined * + * Any file that match the regex [.mod|module].[css|styl|sass|scss] will be treated as CSS module + * Files thet match the RexExp: * - *.css => CSS-Modules + CSS-Next * - *.styl => stylus compiled to normal CSS => CSS-Modules + CSS-Next * - *.scss => SASS compiled to normal CSS => CSS-Modules + CSS-Next * - *.less => LESS compiled to normal CSS => CSS-Modules + CSS-Next * - * cssModuleSupport: undefined (default) + * cssModuleSupport: RegExp * - * - *only* *.css => cssModuleSupport sets to true - * - *no* *.css (but *.styl or *.scss) => cssModuleSupport sets to false - * - * cssModuleSupport: array ["css", "styl", "scss", "less"] - * - * - individual extension enabled for CSS-Modules + CSS-Next + * - Any file matching this user provided regexp will be treated as CSS module */ +/* eslint-disable complexity */ module.exports = function() { const xarcOptions = loadXarcOptions(); const isProduction = process.env.NODE_ENV === "production"; const isDevelopment = !isProduction; - const cssModuleSupport = detectCssModule(); + const { enableCssModule, cssModuleRegExp } = detectCssModule(xarcOptions); const { hasPostCss, atImport, postcssPresetEnv, postcssLoader } = loadPostCss(); @@ -113,65 +111,79 @@ module.exports = function() { }; }; - const getCssQueryUse = (ext = "") => { - let cssModule = Boolean(cssModuleSupport); - if (ext && Array.isArray(cssModuleSupport)) { - cssModule = cssModuleSupport.indexOf(ext) >= 0; - } - + const getCssQueryUse = (isModule = false) => { return [ - cssModule + isModule ? { loader: cssLoader, options: getCSSModuleOptions() } - : { loader: cssLoader, options: { minimize: true } }, + : { loader: cssLoader, options: { minimize: true, modules: false } }, getPostCssQuery() ].filter(x => x); }; - const namePrefix = `extract-css${cssModuleSupport ? "-modules" : ""}`; + /* + * MiniCssExtractPlugin Loader + */ + + const miniCssExtractLoader = (isModule = false) => ({ + loader: MiniCssExtractPlugin.loader, + options: { + hmr: isDevelopment, + reload: isDevelopment, + publicPath: "", + esModule: true, + modules: Boolean(isModule) + } + }); /* * PLAIN css */ - rules.push({ - _name: namePrefix, - test: /\.css$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - options: { - hmr: isDevelopment, - reload: isDevelopment, - publicPath: "", - esModule: true, - modules: Boolean(cssModuleSupport) - } - }, - ...getCssQueryUse() - ] - }); + rules.push( + { + _name: `extract-css`, + test: /\.css$/, + use: [ + miniCssExtractLoader(false), + ...getCssQueryUse(false) + ], + ...(enableCssModule && { exclude: cssModuleRegExp }), + }, + enableCssModule && { + _name: `extract-css-modules`, + test: /\.css$/, + use: [ + miniCssExtractLoader(true), + ...getCssQueryUse(true) + ], + include: cssModuleRegExp + } + ); /* * SASS */ - if (xarcOptions.options.sass && sassLoader) { - rules.push({ - _name: `${namePrefix}-scss`, - test: /\.(scss|sass)$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - options: { - hmr: isDevelopment, - reload: isDevelopment, - publicPath: "", - esModule: true, - modules: Boolean(cssModuleSupport) - } - }, - ...getCssQueryUse().concat({ loader: sassLoader } as any) - ] - }); + if (sassLoader) { + rules.push( + { + _name: `extract-css-scss`, + test: /\.(scss|sass)$/, + use: [ + miniCssExtractLoader(false), + ...getCssQueryUse(false).concat({ loader: sassLoader } as any) + ], + ...(enableCssModule && { exclude: cssModuleRegExp }), + }, + enableCssModule && { + _name: `extract-css-modules-scss`, + test: /\.(scss|sass)$/, + use: [ + miniCssExtractLoader(true), + ...getCssQueryUse(true).concat({ loader: sassLoader } as any) + ], + include: cssModuleRegExp + } + ); } /* @@ -181,34 +193,46 @@ module.exports = function() { if (stylusLoader) { stylusQuery = { loader: stylusLoader }; - rules.push({ - _name: `${namePrefix}-stylus`, - test: /\.styl$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - options: { hmr: isDevelopment, reload: isDevelopment, publicPath: "" } - }, - ...getCssQueryUse().concat(stylusQuery) - ] - }); + rules.push( + { + _name: `extract-css-stylus`, + test: /\.styl$/, + use: [miniCssExtractLoader(false), ...getCssQueryUse(false).concat(stylusQuery)], + ...(enableCssModule && { exclude: cssModuleRegExp }), + }, + enableCssModule && { + _name: `extract-css-modules-stylus`, + test: /\.styl$/, + use: [miniCssExtractLoader(true), ...getCssQueryUse(true).concat(stylusQuery)], + include: cssModuleRegExp + } + ); } /* * LESS */ if (lessLoader) { - rules.push({ - _name: `${namePrefix}-less`, - test: /\.less$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - options: { hmr: isDevelopment, reload: isDevelopment, publicPath: "" } - }, - ...getCssQueryUse().concat({ loader: lessLoader } as any) - ] - }); + rules.push( + { + _name: `extract-css-less`, + test: /\.less$/, + use: [ + miniCssExtractLoader(false), + ...getCssQueryUse(false).concat({ loader: lessLoader } as any) + ], + ...(enableCssModule && { exclude: cssModuleRegExp }), + }, + enableCssModule && { + _name: `extract-css-modules-less`, + test: /\.less$/, + use: [ + miniCssExtractLoader(true), + ...getCssQueryUse(true).concat({ loader: lessLoader } as any) + ], + include: cssModuleRegExp + } + ); } const styleBundleFilename = @@ -217,7 +241,7 @@ module.exports = function() { : "[name].style.[contenthash].css"; return { - module: { rules }, + module: { rules: rules.filter(x => x) }, plugins: [ new MiniCssExtractPlugin({ filename: styleBundleFilename }), isProduction && new OptimizeCssAssetsPlugin(xarcOptions.webpack.optimizeCssOptions), diff --git a/packages/xarc-webpack/src/util/detect-css-module.ts b/packages/xarc-webpack/src/util/detect-css-module.ts index a92da82f2..f870bf7d5 100644 --- a/packages/xarc-webpack/src/util/detect-css-module.ts +++ b/packages/xarc-webpack/src/util/detect-css-module.ts @@ -1,46 +1,54 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-var-requires, max-statements */ -import * as Path from "path"; +import * as _ from "lodash"; +const logger = require("@xarc/app-dev/lib/logger"); -const filterScanDir = require("filter-scan-dir"); -const getOptRequire = require("../util/get-opt-require"); -import { loadXarcOptions } from "../util/load-xarc-options"; - -function detectCSSModule() { - const archetype = loadXarcOptions(); - const AppMode = archetype.AppMode; - - // if user explicitly says they want CSS module support, then we enable it - if (archetype.webpack.cssModuleSupport !== undefined) { - return Boolean(archetype.webpack.cssModuleSupport); - } - - // without postcss we don't want to do CSS module by default - if (getOptRequire(["@xarc/opt-postcss", "electrode-archetype-opt-postcss"]).invalid) { - return false; - } - - const scanned = filterScanDir.sync({ - dir: Path.resolve(AppMode.src.client), - grouping: true, - filter: (file, path, extras) => { - const ext = extras.ext.toLowerCase(); - return [".css", ".styl", ".scss", ".less"].indexOf(ext) >= 0 ? ext.substr(1) : false; - } - }); +function detectCSSModule(archetype) { + const cssModuleSupport = _.get(archetype, "webpack.cssModuleSupport", undefined); + const defaultRegExp = /\.(mod|module)\.(css|styl|sass|scss)/i; /* * cssModuleSupport default to undefined + * Default RexExp used - /\.(mod|module)\.(css|styl|sass|scss)/i * - * when cssModuleSupport not specified: - * - * *only* *.css, cssModuleSupport set to true - * other style files (styl, scss, less) exist => set to false + * cssModuleSupport:false => no CSS module support at all + * cssModuleSupport:true|undefined => enable CSS module support for any file that match the default RegExp + * cssModuleSupport:RegExp => enable CSS module support for any file that match the user provided RegExp */ - delete scanned.files; - - return Object.keys(scanned).length === 1 && scanned.hasOwnProperty("css"); + if (cssModuleSupport === null) { + throw new Error( + `null is not a supported value for cssModuleSupport flag. Supported types are boolean or RexExp.` + ); + } + if (cssModuleSupport === undefined) { + return { enableCssModule: true, cssModuleRegExp: defaultRegExp }; + } else if (cssModuleSupport.constructor.name === "Boolean") { + return { enableCssModule: cssModuleSupport, cssModuleRegExp: defaultRegExp }; + } else if (cssModuleSupport.constructor.name === "String") { + let moduleExp; + try { + const lastSlash = cssModuleSupport.lastIndexOf("/"); + moduleExp = new RegExp( + cssModuleSupport.slice(1, lastSlash), + cssModuleSupport.slice(lastSlash + 1) + ); + } catch (err) { + logger.error( + `Could not parse user provided value "${cssModuleSupport}" for cssModuleSupport flag. Enabling CSS Module support for ${defaultRegExp}.` + ); + + moduleExp = defaultRegExp; + } finally { + return { enableCssModule: true, cssModuleRegExp: moduleExp }; + } + } else if (cssModuleSupport.constructor.name === "RegExp") { + return { enableCssModule: true, cssModuleRegExp: cssModuleSupport }; + } else { + throw new Error( + `Type ${typeof cssModuleSupport} is not supported for cssModuleSupport flag. Supported types are boolean or RexExp.` + ); + } } module.exports = detectCSSModule; From 5bcace38ea534bc81e0cb81bc17bcceb516033f6 Mon Sep 17 00:00:00 2001 From: Divya Karippath Date: Mon, 2 Nov 2020 17:37:08 -0800 Subject: [PATCH 2/2] Fixing tests --- .../react-jest-app/src/client/components/test1.styl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/samples/react-jest-app/src/client/components/test1.styl b/samples/react-jest-app/src/client/components/test1.styl index d9eaa8883..f51b853b0 100644 --- a/samples/react-jest-app/src/client/components/test1.styl +++ b/samples/react-jest-app/src/client/components/test1.styl @@ -1,5 +1,5 @@ -pre { - display: inline; - color: orange; - margin: 0; -} +pre + display: inline + color: orange + margin: 0 +