From ab354dd8680487e54228ab0a94b82d9f29e60443 Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Thu, 29 Nov 2018 19:36:51 +0300 Subject: [PATCH 1/3] refactor: placeholder regex --- lib/loader.js | 54 ++++++++++++++++++++++++++++------------------- lib/processCss.js | 4 ---- lib/utils.js | 12 +++++++++-- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/lib/loader.js b/lib/loader.js index 262f751e..ca112410 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -9,6 +9,7 @@ const { getImportPrefix, placeholderImportItemReplacer, compileExports, + placholderRegExps, } = require('./utils'); module.exports = function loader(content, map) { @@ -102,7 +103,7 @@ module.exports = function loader(content, map) { .join('\n'); cssAsString = cssAsString.replace( - result.importItemRegExpG, + placholderRegExps.importItemG, placeholderImportItemReplacer(this, result, importUrlPrefix) ); @@ -119,31 +120,40 @@ module.exports = function loader(content, map) { require.resolve('./runtime/escape.js') )});\n`; - cssAsString = cssAsString.replace(result.urlItemRegExpG, (item) => { - const match = result.urlItemRegExp.exec(item); - let idx = +match[1]; - const urlItem = result.urlItems[idx]; - const { url } = urlItem; - idx = url.indexOf('?#'); - if (idx < 0) { - idx = url.indexOf('#'); - } - let urlRequest; - if (idx > 0) { - // idx === 0 is catched by isUrlRequest - // in cases like url('webfont.eot?#iefix') - urlRequest = url.substr(0, idx); + cssAsString = cssAsString.replace( + placholderRegExps.urlItemG, + (item) => { + const match = placholderRegExps.urlItem.exec(item); + let idx = +match[1]; + const urlItem = result.urlItems[idx]; + const { url } = urlItem; + + idx = url.indexOf('?#'); + + if (idx < 0) { + idx = url.indexOf('#'); + } + + let urlRequest; + + if (idx > 0) { + // idx === 0 is catched by isUrlRequest + // in cases like url('webfont.eot?#iefix') + urlRequest = url.substr(0, idx); + return `" + escape(require(${loaderUtils.stringifyRequest( + this, + urlRequest + )}) + "${url.substr(idx)}") + "`; + } + + urlRequest = url; + return `" + escape(require(${loaderUtils.stringifyRequest( this, urlRequest - )}) + "${url.substr(idx)}") + "`; + )})) + "`; } - urlRequest = url; - return `" + escape(require(${loaderUtils.stringifyRequest( - this, - urlRequest - )})) + "`; - }); + ); } if (exportJs) { diff --git a/lib/processCss.js b/lib/processCss.js index 1968d655..92db6f86 100644 --- a/lib/processCss.js +++ b/lib/processCss.js @@ -105,11 +105,7 @@ module.exports = function processCss(content, map, options, callback) { map: result.map && result.map.toJSON(), exports: parserOptions.exports, importItems: parserOptions.importItems, - importItemRegExpG: /___CSS_LOADER_IMPORT___([0-9]+)___/g, - importItemRegExp: /___CSS_LOADER_IMPORT___([0-9]+)___/, urlItems: parserOptions.urlItems, - urlItemRegExpG: /___CSS_LOADER_URL___([0-9]+)___/g, - urlItemRegExp: /___CSS_LOADER_URL___([0-9]+)___/, }); }) .catch((error) => { diff --git a/lib/utils.js b/lib/utils.js index b195e70f..10047de3 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -7,6 +7,13 @@ const path = require('path'); const camelCase = require('lodash/camelCase'); const loaderUtils = require('loader-utils'); +const placholderRegExps = { + importItemG: /___CSS_LOADER_IMPORT___([0-9]+)___/g, + importItem: /___CSS_LOADER_IMPORT___([0-9]+)___/, + urlItemG: /___CSS_LOADER_URL___([0-9]+)___/g, + urlItem: /___CSS_LOADER_URL___([0-9]+)___/, +}; + function dashesCamelCase(str) { return str.replace(/-+(\w)/g, (match, firstLetter) => firstLetter.toUpperCase() @@ -22,7 +29,7 @@ function compileExports(result, importItemMatcher, camelCaseKeys) { .reduce((res, key) => { let valueAsString = JSON.stringify(result.exports[key]); valueAsString = valueAsString.replace( - result.importItemRegExpG, + placholderRegExps.importItemG, importItemMatcher ); function addEntry(k) { @@ -117,7 +124,7 @@ function placeholderImportItemReplacer( onlyLocals = false ) { return (item) => { - const match = result.importItemRegExp.exec(item); + const match = placholderRegExps.importItem.exec(item); const idx = +match[1]; const importItem = result.importItems[idx]; const importUrl = importUrlPrefix + importItem.url; @@ -142,4 +149,5 @@ module.exports = { getImportPrefix, getLocalIdent, placeholderImportItemReplacer, + placholderRegExps, }; From b3b37ea30e2b1db6beed6aef1894fdb668db7d6b Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Thu, 29 Nov 2018 19:49:28 +0300 Subject: [PATCH 2/3] refactor: utils --- lib/loader.js | 6 +++--- lib/utils.js | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/loader.js b/lib/loader.js index ca112410..368e1c73 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -52,10 +52,10 @@ module.exports = function loader(content, map) { const importUrlPrefix = getImportPrefix(this, options); let exportJs = compileExports( - result, + result.exports, placeholderImportItemReplacer( this, - result, + result.importItems, importUrlPrefix, options.exportOnlyLocals ), @@ -104,7 +104,7 @@ module.exports = function loader(content, map) { cssAsString = cssAsString.replace( placholderRegExps.importItemG, - placeholderImportItemReplacer(this, result, importUrlPrefix) + placeholderImportItemReplacer(this, result.importItems, importUrlPrefix) ); // helper for ensuring valid CSS strings from requires diff --git a/lib/utils.js b/lib/utils.js index 10047de3..7c617d71 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -20,27 +20,31 @@ function dashesCamelCase(str) { ); } -function compileExports(result, importItemMatcher, camelCaseKeys) { - if (!result.exports || !Object.keys(result.exports).length) { +function compileExports(exports, importItemMatcher, camelCaseKeys) { + if (!exports || Object.keys(exports).length === 0) { return ''; } - const exportJs = Object.keys(result.exports) + const exportJs = Object.keys(exports) .reduce((res, key) => { - let valueAsString = JSON.stringify(result.exports[key]); + let valueAsString = JSON.stringify(exports[key]); + valueAsString = valueAsString.replace( placholderRegExps.importItemG, importItemMatcher ); + function addEntry(k) { res.push(`\t${JSON.stringify(k)}: ${valueAsString}`); } let targetKey; + switch (camelCaseKeys) { case true: addEntry(key); targetKey = camelCase(key); + if (targetKey !== key) { addEntry(targetKey); } @@ -48,6 +52,7 @@ function compileExports(result, importItemMatcher, camelCaseKeys) { case 'dashes': addEntry(key); targetKey = dashesCamelCase(key); + if (targetKey !== key) { addEntry(targetKey); } @@ -62,6 +67,7 @@ function compileExports(result, importItemMatcher, camelCaseKeys) { addEntry(key); break; } + return res; }, []) .join(',\n'); @@ -119,14 +125,14 @@ function getLocalIdent(loaderContext, localIdentName, localName, options) { function placeholderImportItemReplacer( loaderContext, - result, + importItems, importUrlPrefix, onlyLocals = false ) { return (item) => { const match = placholderRegExps.importItem.exec(item); const idx = +match[1]; - const importItem = result.importItems[idx]; + const importItem = importItems[idx]; const importUrl = importUrlPrefix + importItem.url; if (onlyLocals) { From d34887b837aa5c6e51dfaf32aae13e1c0df654cf Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Thu, 29 Nov 2018 19:54:07 +0300 Subject: [PATCH 3/3] refactor: loader --- lib/loader.js | 132 +++++++++++++++++++++++++++++++++++++--------- lib/processCss.js | 116 ---------------------------------------- 2 files changed, 108 insertions(+), 140 deletions(-) delete mode 100644 lib/processCss.js diff --git a/lib/loader.js b/lib/loader.js index 368e1c73..60a953eb 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -2,15 +2,23 @@ MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ +const postcss = require('postcss'); +const localByDefault = require('postcss-modules-local-by-default'); +const extractImports = require('postcss-modules-extract-imports'); +const modulesScope = require('postcss-modules-scope'); +const modulesValues = require('postcss-modules-values'); const loaderUtils = require('loader-utils'); -const processCss = require('./processCss'); +const { importParser, icssParser, urlParser } = require('./plugins'); const { + getLocalIdent, getImportPrefix, placeholderImportItemReplacer, compileExports, placholderRegExps, } = require('./utils'); +const Warning = require('./Warning'); +const CssSyntaxError = require('./CssSyntaxError'); module.exports = function loader(content, map) { const callback = this.async(); @@ -35,27 +43,92 @@ module.exports = function loader(content, map) { } /* eslint-enable no-param-reassign */ - processCss( - content, - map, - { - loaderContext: this, - loaderOptions: options, - sourceMap, - }, - (err, result) => { - if (err) { - return callback(err); - } + const loaderContext = this; + const localIdentName = options.localIdentName || '[hash:base64]'; + const customGetLocalIdent = options.getLocalIdent || getLocalIdent; + + const parserOptions = { + url: options.url !== false, + import: options.import !== false, + }; + + const plugins = [ + modulesValues, + localByDefault({ + mode: options.modules ? 'local' : 'global', + rewriteUrl(global, url) { + if (parserOptions.url) { + // eslint-disable-next-line no-param-reassign + url = url.trim(); + + if ( + !url.replace(/\s/g, '').length || + !loaderUtils.isUrlRequest(url) + ) { + return url; + } + if (global) { + return loaderUtils.urlToRequest(url); + } + } + return url; + }, + }), + extractImports(), + modulesScope({ + generateScopedName: function generateScopedName(exportName) { + return customGetLocalIdent(loaderContext, localIdentName, exportName, { + regExp: options.localIdentRegExp, + hashPrefix: options.hashPrefix || '', + context: options.context, + }); + }, + }), + ]; + + if (options.import !== false) { + plugins.push(importParser(parserOptions)); + } + + if (options.url !== false) { + plugins.push(urlParser(parserOptions)); + } + + plugins.push(icssParser(parserOptions)); + + postcss(plugins) + .process(content, { + // we need a prefix to avoid path rewriting of PostCSS + from: `/css-loader!${loaderUtils + .getRemainingRequest(this) + .split('!') + .pop()}`, + to: loaderUtils + .getCurrentRequest(this) + .split('!') + .pop(), + map: options.sourceMap + ? { + prev: map, + sourcesContent: true, + inline: false, + annotation: false, + } + : null, + }) + .then((result) => { + result + .warnings() + .forEach((warning) => this.emitWarning(new Warning(warning))); // for importing CSS const importUrlPrefix = getImportPrefix(this, options); let exportJs = compileExports( - result.exports, + parserOptions.exports, placeholderImportItemReplacer( this, - result.importItems, + parserOptions.importItems, importUrlPrefix, options.exportOnlyLocals ), @@ -70,10 +143,10 @@ module.exports = function loader(content, map) { return callback(null, exportJs); } - let cssAsString = JSON.stringify(result.source); + let cssAsString = JSON.stringify(result.css); const alreadyImported = {}; - const importJs = result.importItems + const importJs = parserOptions.importItems .filter((imp) => { if (!imp.media) { if (alreadyImported[imp.url]) { @@ -104,7 +177,11 @@ module.exports = function loader(content, map) { cssAsString = cssAsString.replace( placholderRegExps.importItemG, - placeholderImportItemReplacer(this, result.importItems, importUrlPrefix) + placeholderImportItemReplacer( + this, + parserOptions.importItems, + importUrlPrefix + ) ); // helper for ensuring valid CSS strings from requires @@ -112,8 +189,8 @@ module.exports = function loader(content, map) { if ( options.url !== false && - result.urlItems && - result.urlItems.length > 0 + parserOptions.urlItems && + parserOptions.urlItems.length > 0 ) { urlEscapeHelper = `var escape = require(${loaderUtils.stringifyRequest( this, @@ -125,7 +202,7 @@ module.exports = function loader(content, map) { (item) => { const match = placholderRegExps.urlItem.exec(item); let idx = +match[1]; - const urlItem = result.urlItems[idx]; + const urlItem = parserOptions.urlItems[idx]; const { url } = urlItem; idx = url.indexOf('?#'); @@ -164,7 +241,8 @@ module.exports = function loader(content, map) { if (sourceMap && result.map) { /* eslint-disable no-param-reassign */ // Add a SourceMap - ({ map } = result); + map = result.map.toJSON(); + if (map.sources) { map.sources = map.sources.map( (source) => @@ -176,12 +254,14 @@ module.exports = function loader(content, map) { ); map.sourceRoot = ''; } + map.file = map.file .split('!') .pop() .replace(/\\/g, '/'); map = JSON.stringify(map); /* eslint-enable no-param-reassign */ + moduleJs = `exports.push([module.id, ${cssAsString}, "", ${map}]);`; } else { moduleJs = `exports.push([module.id, ${cssAsString}, ""]);`; @@ -198,6 +278,10 @@ module.exports = function loader(content, map) { `// module\n${moduleJs}\n\n` + `// exports\n${exportJs}` ); - } - ); + }) + .catch((error) => { + callback( + error.name === 'CssSyntaxError' ? new CssSyntaxError(error) : error + ); + }); }; diff --git a/lib/processCss.js b/lib/processCss.js deleted file mode 100644 index 92db6f86..00000000 --- a/lib/processCss.js +++ /dev/null @@ -1,116 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ -const postcss = require('postcss'); -const loaderUtils = require('loader-utils'); - -const localByDefault = require('postcss-modules-local-by-default'); -const extractImports = require('postcss-modules-extract-imports'); -const modulesScope = require('postcss-modules-scope'); -const modulesValues = require('postcss-modules-values'); - -const { importParser, icssParser, urlParser } = require('./plugins'); - -const Warning = require('./Warning'); -const CssSyntaxError = require('./CssSyntaxError'); -const { getLocalIdent } = require('./utils'); - -module.exports = function processCss(content, map, options, callback) { - const { loaderContext, loaderOptions } = options; - const localIdentName = loaderOptions.localIdentName || '[hash:base64]'; - const customGetLocalIdent = loaderOptions.getLocalIdent || getLocalIdent; - - const parserOptions = { - url: loaderOptions.url !== false, - import: loaderOptions.import !== false, - }; - - const plugins = [ - modulesValues, - localByDefault({ - mode: loaderOptions.modules ? 'local' : 'global', - rewriteUrl(global, url) { - if (parserOptions.url) { - // eslint-disable-next-line no-param-reassign - url = url.trim(); - - if ( - !url.replace(/\s/g, '').length || - !loaderUtils.isUrlRequest(url) - ) { - return url; - } - if (global) { - return loaderUtils.urlToRequest(url); - } - } - return url; - }, - }), - extractImports(), - modulesScope({ - generateScopedName: function generateScopedName(exportName) { - return customGetLocalIdent( - options.loaderContext, - localIdentName, - exportName, - { - regExp: loaderOptions.localIdentRegExp, - hashPrefix: loaderOptions.hashPrefix || '', - context: loaderOptions.context, - } - ); - }, - }), - ]; - - if (loaderOptions.import !== false) { - plugins.push(importParser(parserOptions)); - } - - if (loaderOptions.url !== false) { - plugins.push(urlParser(parserOptions)); - } - - plugins.push(icssParser(parserOptions)); - - postcss(plugins) - .process(content, { - // we need a prefix to avoid path rewriting of PostCSS - from: `/css-loader!${loaderUtils - .getRemainingRequest(loaderContext) - .split('!') - .pop()}`, - to: loaderUtils - .getCurrentRequest(loaderContext) - .split('!') - .pop(), - map: options.sourceMap - ? { - prev: map, - sourcesContent: true, - inline: false, - annotation: false, - } - : null, - }) - .then((result) => { - result - .warnings() - .forEach((warning) => loaderContext.emitWarning(new Warning(warning))); - - callback(null, { - source: result.css, - map: result.map && result.map.toJSON(), - exports: parserOptions.exports, - importItems: parserOptions.importItems, - urlItems: parserOptions.urlItems, - }); - }) - .catch((error) => { - callback( - error.name === 'CssSyntaxError' ? new CssSyntaxError(error) : error - ); - }); -};