From cc5d2c570f6f4d8e83c1f4aef5495a7d64f7c4b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Kvasnic=CC=8Ca=CC=81k?= Date: Sat, 11 Mar 2017 14:59:07 +0100 Subject: [PATCH 1/3] :racehorse: :bug: optimize plugin to initalize options only once Also fixes problems with circular dependencies when used with babel-node and babel-register, #35 Also this provides functionality to use watch mode, only thing a developer has to do is to enable devMode, #33 --- README.md | 27 --------- src/index.js | 113 ++++++++++++++++++++++-------------- src/utils/extractCssFile.js | 53 +++++++---------- 3 files changed, 90 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index e0ce862..1f9e302 100644 --- a/README.md +++ b/README.md @@ -175,33 +175,6 @@ To extract all files in a single directory, give an object: Note that `relativeRoot` is used to resolve relative directory names, available as `[path]` in `filename` pattern. -## Using a `babel-register` - -Make sure you set `ignore` option of `babel-register` to ignore all files used by css-modules-require-hook to process your css files. - -**Require `babel-register` only once otherwise it will fail** -**Be aware, you need to explicitly ignore `node_modules` if you set `ignore` option** - -```js -require('babel-register')({ - ignore: /(processCss\.js|node_modules)/ // regex matching all files used by css-modules-require-hook to process your css files -}) -``` - -## Using in mocha - -Create a js file with content - -**Be aware, you need to explicitly ignore `node_modules` if you set `ignore` option** - -```js -require('babel-register')({ - ignore: /(processCss\.js|node_modules)/ // regex matching all files used by css-modules-require-hook to process your css files -}) -``` - -and then set this file as a compiler `--compilers js:.js` - ## Alternatives - [babel-plugin-transform-postcss](https://github.com/wbyoung/babel-plugin-transform-postcss) - which supports async plugins and does not depend on `css-modules-require-hook`. diff --git a/src/index.js b/src/index.js index cef6fa8..3346442 100644 --- a/src/index.js +++ b/src/index.js @@ -43,6 +43,10 @@ export default function transformCssModules({ types: t }) { // is css modules require hook initialized? let initialized = false; + // are we requiring a module for preprocessCss, processCss, etc? + // we don't want them to be transformed using this plugin + // because it will cause circular dependency in babel-node and babel-register process + let inProcessingFunction = false; let matchExtensions = /\.css$/i; @@ -63,61 +67,80 @@ export default function transformCssModules({ types: t }) { ); } - return { - visitor: { - Program(path, state) { - if (initialized) { - return; - } - - const currentConfig = { ...defaultOptions, ...state.opts }; - // this is not a css-require-ook config - delete currentConfig.extractCss; - - // match file extensions, speeds up transform by creating one - // RegExp ahead of execution time - matchExtensions = matcher(currentConfig.extensions); + const cssMap = new Map(); + let thisPluginOptions = null; - // Add a space in current state for css filenames - state.$$css = { - styles: new Map() - }; - - const pushStylesCreator = (toWrap) => (css, filepath) => { - let processed; + const pluginApi = { + manipulateOptions(options) { + if (initialized || inProcessingFunction) { + return options; + } - if (typeof toWrap === 'function') { - processed = toWrap(css, filepath); - } + // find options for this plugin + // we have to use this hack because plugin.key does not have to be 'css-modules-transform' + // so we will identify it by comparing manipulateOptions + thisPluginOptions = options.plugins.filter( + ([plugin]) => plugin.manipulateOptions === pluginApi.manipulateOptions + )[0][1]; - if (typeof processed !== 'string') processed = css; + const currentConfig = { ...defaultOptions, ...thisPluginOptions }; + // this is not a css-require-ook config + delete currentConfig.extractCss; - if (!state.$$css.styles.has(filepath)) { - state.$$css.styles.set(filepath, processed); - extractCssFile(process.cwd(), filepath, processed, state); - } + // match file extensions, speeds up transform by creating one + // RegExp ahead of execution time + matchExtensions = matcher(currentConfig.extensions); - return processed; - }; + const pushStylesCreator = (toWrap) => (css, filepath) => { + let processed; - // resolve options - Object.keys(requireHooksOptions).forEach(key => { - // skip undefined options - if (currentConfig[key] === undefined) { - return; - } + if (typeof toWrap === 'function') { + processed = toWrap(css, filepath); + } - currentConfig[key] = requireHooksOptions[key](currentConfig[key], currentConfig); - }); + if (typeof processed !== 'string') processed = css; - // wrap or define processCss function that collect generated css - currentConfig.processCss = pushStylesCreator(currentConfig.processCss); + // set css content only if is new + if (!cssMap.has(filepath) || cssMap.get(filepath) !== processed) { + cssMap.set(filepath, processed); + } - require('css-modules-require-hook')(currentConfig); + return processed; + }; - initialized = true; - }, + // resolve options + Object.keys(requireHooksOptions).forEach(key => { + // skip undefined options + if (currentConfig[key] === undefined) { + return; + } + inProcessingFunction = true; + currentConfig[key] = requireHooksOptions[key](currentConfig[key], currentConfig); + inProcessingFunction = false; + }); + + // wrap or define processCss function that collect generated css + currentConfig.processCss = pushStylesCreator(currentConfig.processCss); + + require('css-modules-require-hook')(currentConfig); + + initialized = true; + + return options; + }, + post() { + // extract css only if is this option set + if (thisPluginOptions.extractCss) { + // always rewrite file :-/ + extractCssFile( + process.cwd(), + cssMap, + thisPluginOptions.extractCss + ); + } + }, + visitor: { // import styles from './style.css'; ImportDefaultSpecifier(path, { file }) { const { value } = path.parentPath.node.source; @@ -162,4 +185,6 @@ export default function transformCssModules({ types: t }) { } } }; + + return pluginApi; } diff --git a/src/utils/extractCssFile.js b/src/utils/extractCssFile.js index bb2c381..b1a6312 100644 --- a/src/utils/extractCssFile.js +++ b/src/utils/extractCssFile.js @@ -7,18 +7,11 @@ export const PATH_VARIABLES = ['[path]', '[name]']; * Extracts CSS to file * * @param {String} cwd - * @param {String} filepath - * @param {String} css - * @param {Object} state + * @param {Map} cssMap + * @param {String|Object} extractCss * @returns {null} */ -export default function extractCssFile(cwd, filepath, css, state) { - const { extractCss = null } = state.opts; - - if (!extractCss) { - return null; - } - +export default function extractCssFile(cwd, cssMap, extractCss) { // this is the case where a single extractCss is requested if (typeof(extractCss) === 'string') { // check if extractCss contains some from pattern variables, if yes throw! @@ -28,15 +21,9 @@ export default function extractCssFile(cwd, filepath, css, state) { } }); - // If this is the first file, then we should replace - // old content - if (state.$$css.styles.size === 1) { - return writeCssFile(extractCss, css); - } + const css = Array.from(cssMap.values()).join(''); - // this should output in a single file. - // Let's append the new file content. - return writeCssFile(extractCss, css, true); + return writeCssFile(extractCss, css); } // This is the case where each css file is written in @@ -52,18 +39,20 @@ export default function extractCssFile(cwd, filepath, css, state) { throw new Error('[name] variable has to be used in extractCss.filename option'); } - // Make css file name relative to relativeRoot - const relativePath = relative( - resolve(cwd, relativeRoot), - filepath - ); - - const destination = join( - resolve(cwd, dir), - filename - ) - .replace(/\[name]/, basename(filepath, extname(filepath))) - .replace(/\[path]/, dirname(relativePath)); - - writeCssFile(destination, css); + cssMap.forEach((css, filepath) => { + // Make css file name relative to relativeRoot + const relativePath = relative( + resolve(cwd, relativeRoot), + filepath + ); + + const destination = join( + resolve(cwd, dir), + filename + ) + .replace(/\[name]/, basename(filepath, extname(filepath))) + .replace(/\[path]/, dirname(relativePath)); + + writeCssFile(destination, css); + }); } From 6b9cfac2f12d4fb7a02fb7d0c07103d1af66ede6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Kvasnic=CC=8Ca=CC=81k?= Date: Sat, 11 Mar 2017 15:10:07 +0100 Subject: [PATCH 2/3] Version 1.2.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 263b13c..5979933 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "babel-plugin-css-modules-transform", - "version": "1.2.1", + "version": "1.2.3", "description": "Transform required css modules so one can use generated class names.", "main": "build/index.js", "scripts": { From 5de9916d418fb760d03fce383dd07b51b0a28118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Kvasnic=CC=8Ca=CC=81k?= Date: Sat, 18 Mar 2017 18:45:49 +0100 Subject: [PATCH 3/3] :bug: fix checking for extractCss config option --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 3346442..a7ec42f 100644 --- a/src/index.js +++ b/src/index.js @@ -131,7 +131,7 @@ export default function transformCssModules({ types: t }) { }, post() { // extract css only if is this option set - if (thisPluginOptions.extractCss) { + if (thisPluginOptions && thisPluginOptions.extractCss) { // always rewrite file :-/ extractCssFile( process.cwd(),