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/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": { diff --git a/src/index.js b/src/index.js index cef6fa8..a7ec42f 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 && 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); + }); }