From caf1f13334e74bdadfeff2a38e65ebe3173e69e2 Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Thu, 29 Nov 2018 20:40:44 +0300 Subject: [PATCH 1/3] refactor: `loader-utils` --- lib/loader.js | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/lib/loader.js b/lib/loader.js index 60a953eb..dcea667e 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -7,7 +7,14 @@ 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 { + getOptions, + isUrlRequest, + urlToRequest, + getRemainingRequest, + getCurrentRequest, + stringifyRequest, +} = require('loader-utils'); const { importParser, icssParser, urlParser } = require('./plugins'); const { @@ -22,7 +29,7 @@ const CssSyntaxError = require('./CssSyntaxError'); module.exports = function loader(content, map) { const callback = this.async(); - const options = loaderUtils.getOptions(this) || {}; + const options = getOptions(this) || {}; const sourceMap = options.sourceMap || false; /* eslint-disable no-param-reassign */ @@ -61,16 +68,15 @@ module.exports = function loader(content, map) { // eslint-disable-next-line no-param-reassign url = url.trim(); - if ( - !url.replace(/\s/g, '').length || - !loaderUtils.isUrlRequest(url) - ) { + if (!url.replace(/\s/g, '').length || !isUrlRequest(url)) { return url; } + if (global) { - return loaderUtils.urlToRequest(url); + return urlToRequest(url); } } + return url; }, }), @@ -99,12 +105,10 @@ module.exports = function loader(content, map) { postcss(plugins) .process(content, { // we need a prefix to avoid path rewriting of PostCSS - from: `/css-loader!${loaderUtils - .getRemainingRequest(this) + from: `/css-loader!${getRemainingRequest(this) .split('!') .pop()}`, - to: loaderUtils - .getCurrentRequest(this) + to: getCurrentRequest(this) .split('!') .pop(), map: options.sourceMap @@ -160,7 +164,7 @@ module.exports = function loader(content, map) { const { url } = imp; const media = imp.media || ''; - if (!loaderUtils.isUrlRequest(url)) { + if (!isUrlRequest(url)) { return `exports.push([module.id, ${JSON.stringify( `@import url(${url});` )}, ${JSON.stringify(media)}]);`; @@ -168,7 +172,7 @@ module.exports = function loader(content, map) { const importUrl = importUrlPrefix + url; - return `exports.i(require(${loaderUtils.stringifyRequest( + return `exports.i(require(${stringifyRequest( this, importUrl )}), ${JSON.stringify(media)});`; @@ -192,7 +196,7 @@ module.exports = function loader(content, map) { parserOptions.urlItems && parserOptions.urlItems.length > 0 ) { - urlEscapeHelper = `var escape = require(${loaderUtils.stringifyRequest( + urlEscapeHelper = `var escape = require(${stringifyRequest( this, require.resolve('./runtime/escape.js') )});\n`; @@ -217,7 +221,7 @@ module.exports = function loader(content, map) { // idx === 0 is catched by isUrlRequest // in cases like url('webfont.eot?#iefix') urlRequest = url.substr(0, idx); - return `" + escape(require(${loaderUtils.stringifyRequest( + return `" + escape(require(${stringifyRequest( this, urlRequest )}) + "${url.substr(idx)}") + "`; @@ -225,7 +229,7 @@ module.exports = function loader(content, map) { urlRequest = url; - return `" + escape(require(${loaderUtils.stringifyRequest( + return `" + escape(require(${stringifyRequest( this, urlRequest )})) + "`; @@ -270,7 +274,7 @@ module.exports = function loader(content, map) { // Embed runtime return callback( null, - `${urlEscapeHelper}exports = module.exports = require(${loaderUtils.stringifyRequest( + `${urlEscapeHelper}exports = module.exports = require(${stringifyRequest( this, require.resolve('./runtime/api.js') )})(${sourceMap});\n` + From f7946f50c2d9da0d20e52112feb89d81bacd05fe Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Thu, 29 Nov 2018 20:42:29 +0300 Subject: [PATCH 2/3] refactor: variable names --- lib/loader.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/loader.js b/lib/loader.js index dcea667e..e6a96df6 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -128,7 +128,7 @@ module.exports = function loader(content, map) { // for importing CSS const importUrlPrefix = getImportPrefix(this, options); - let exportJs = compileExports( + let exportCode = compileExports( parserOptions.exports, placeholderImportItemReplacer( this, @@ -140,17 +140,17 @@ module.exports = function loader(content, map) { ); if (options.exportOnlyLocals) { - if (exportJs) { - exportJs = `module.exports = ${exportJs};`; + if (exportCode) { + exportCode = `module.exports = ${exportCode};`; } - return callback(null, exportJs); + return callback(null, exportCode); } let cssAsString = JSON.stringify(result.css); const alreadyImported = {}; - const importJs = parserOptions.importItems + const importCode = parserOptions.importItems .filter((imp) => { if (!imp.media) { if (alreadyImported[imp.url]) { @@ -237,11 +237,11 @@ module.exports = function loader(content, map) { ); } - if (exportJs) { - exportJs = `exports.locals = ${exportJs};`; + if (exportCode) { + exportCode = `exports.locals = ${exportCode};`; } - let moduleJs; + let moduleCode; if (sourceMap && result.map) { /* eslint-disable no-param-reassign */ // Add a SourceMap @@ -266,9 +266,9 @@ module.exports = function loader(content, map) { map = JSON.stringify(map); /* eslint-enable no-param-reassign */ - moduleJs = `exports.push([module.id, ${cssAsString}, "", ${map}]);`; + moduleCode = `exports.push([module.id, ${cssAsString}, "", ${map}]);`; } else { - moduleJs = `exports.push([module.id, ${cssAsString}, ""]);`; + moduleCode = `exports.push([module.id, ${cssAsString}, ""]);`; } // Embed runtime @@ -278,9 +278,9 @@ module.exports = function loader(content, map) { this, require.resolve('./runtime/api.js') )})(${sourceMap});\n` + - `// imports\n${importJs}\n\n` + - `// module\n${moduleJs}\n\n` + - `// exports\n${exportJs}` + `// imports\n${importCode}\n\n` + + `// module\n${moduleCode}\n\n` + + `// exports\n${exportCode}` ); }) .catch((error) => { From 59e469a30e2e2361ccdcba6574e088d4b012594b Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Thu, 29 Nov 2018 20:57:27 +0300 Subject: [PATCH 3/3] refactor: code --- lib/loader.js | 129 ++++++++++++++++++++++++++------------------------ lib/utils.js | 72 +++++++++------------------- 2 files changed, 89 insertions(+), 112 deletions(-) diff --git a/lib/loader.js b/lib/loader.js index e6a96df6..a2338012 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -20,7 +20,6 @@ const { importParser, icssParser, urlParser } = require('./plugins'); const { getLocalIdent, getImportPrefix, - placeholderImportItemReplacer, compileExports, placholderRegExps, } = require('./utils'); @@ -50,21 +49,17 @@ module.exports = function loader(content, map) { } /* eslint-enable no-param-reassign */ + const parserOptions = {}; + const resolveImport = options.import !== false; + const resolveUrl = options.url !== false; 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) { + if (resolveUrl) { // eslint-disable-next-line no-param-reassign url = url.trim(); @@ -83,6 +78,9 @@ module.exports = function loader(content, map) { extractImports(), modulesScope({ generateScopedName: function generateScopedName(exportName) { + const localIdentName = options.localIdentName || '[hash:base64]'; + const customGetLocalIdent = options.getLocalIdent || getLocalIdent; + return customGetLocalIdent(loaderContext, localIdentName, exportName, { regExp: options.localIdentRegExp, hashPrefix: options.hashPrefix || '', @@ -92,11 +90,11 @@ module.exports = function loader(content, map) { }), ]; - if (options.import !== false) { + if (resolveImport) { plugins.push(importParser(parserOptions)); } - if (options.url !== false) { + if (resolveUrl) { plugins.push(urlParser(parserOptions)); } @@ -125,39 +123,52 @@ module.exports = function loader(content, map) { .warnings() .forEach((warning) => this.emitWarning(new Warning(warning))); - // for importing CSS - const importUrlPrefix = getImportPrefix(this, options); + const { camelCase, exportOnlyLocals, importLoaders } = options; + const { importItems, urlItems, exports } = parserOptions; + // Run other loader (`postcss-loader`, `sass-loader` and etc) for importing CSS + const importUrlPrefix = getImportPrefix(this, importLoaders); + // Prepare replacer to change from `___CSS_LOADER_IMPORT___INDEX___` to `require('./file.css').locals` + const importItemReplacer = (item) => { + const match = placholderRegExps.importItem.exec(item); + const idx = +match[1]; + const importItem = importItems[idx]; + const importUrl = importUrlPrefix + importItem.url; + + if (exportOnlyLocals) { + return `" + require(${stringifyRequest( + this, + importUrl + )})[${JSON.stringify(importItem.export)}] + "`; + } - let exportCode = compileExports( - parserOptions.exports, - placeholderImportItemReplacer( + return `" + require(${stringifyRequest( this, - parserOptions.importItems, - importUrlPrefix, - options.exportOnlyLocals - ), - options.camelCase - ); + importUrl + )}).locals[${JSON.stringify(importItem.export)}] + "`; + }; - if (options.exportOnlyLocals) { - if (exportCode) { - exportCode = `module.exports = ${exportCode};`; - } + let exportCode = compileExports(exports, camelCase, (valueAsString) => + valueAsString.replace(placholderRegExps.importItemG, importItemReplacer) + ); - return callback(null, exportCode); + if (exportOnlyLocals) { + return callback( + null, + exportCode ? `module.exports = ${exportCode};` : exportCode + ); } - let cssAsString = JSON.stringify(result.css); - const alreadyImported = {}; - const importCode = parserOptions.importItems + const importCode = importItems .filter((imp) => { if (!imp.media) { if (alreadyImported[imp.url]) { return false; } + alreadyImported[imp.url] = true; } + return true; }) .map((imp) => { @@ -179,24 +190,16 @@ module.exports = function loader(content, map) { }, this) .join('\n'); - cssAsString = cssAsString.replace( + let cssAsString = JSON.stringify(result.css).replace( placholderRegExps.importItemG, - placeholderImportItemReplacer( - this, - parserOptions.importItems, - importUrlPrefix - ) + importItemReplacer ); // helper for ensuring valid CSS strings from requires - let urlEscapeHelper = ''; - - if ( - options.url !== false && - parserOptions.urlItems && - parserOptions.urlItems.length > 0 - ) { - urlEscapeHelper = `var escape = require(${stringifyRequest( + let urlEscapeHelperCode = ''; + + if (resolveUrl && urlItems && urlItems.length > 0) { + urlEscapeHelperCode = `var escape = require(${stringifyRequest( this, require.resolve('./runtime/escape.js') )});\n`; @@ -206,7 +209,7 @@ module.exports = function loader(content, map) { (item) => { const match = placholderRegExps.urlItem.exec(item); let idx = +match[1]; - const urlItem = parserOptions.urlItems[idx]; + const urlItem = urlItems[idx]; const { url } = urlItem; idx = url.indexOf('?#'); @@ -241,14 +244,14 @@ module.exports = function loader(content, map) { exportCode = `exports.locals = ${exportCode};`; } - let moduleCode; - if (sourceMap && result.map) { - /* eslint-disable no-param-reassign */ + let newMap = result.map; + + if (sourceMap && newMap) { // Add a SourceMap - map = result.map.toJSON(); + newMap = newMap.toJSON(); - if (map.sources) { - map.sources = map.sources.map( + if (newMap.sources) { + newMap.sources = newMap.sources.map( (source) => source .split('!') @@ -256,28 +259,28 @@ module.exports = function loader(content, map) { .replace(/\\/g, '/'), this ); - map.sourceRoot = ''; + newMap.sourceRoot = ''; } - map.file = map.file + newMap.file = newMap.file .split('!') .pop() .replace(/\\/g, '/'); - map = JSON.stringify(map); - /* eslint-enable no-param-reassign */ - - moduleCode = `exports.push([module.id, ${cssAsString}, "", ${map}]);`; - } else { - moduleCode = `exports.push([module.id, ${cssAsString}, ""]);`; + newMap = JSON.stringify(newMap); } + const runtimeCode = `exports = module.exports = require(${stringifyRequest( + this, + require.resolve('./runtime/api') + )})(${!!sourceMap});`; + const moduleCode = `exports.push([module.id, ${cssAsString}, ""${ + newMap ? `,${newMap}` : '' + }]);`; + // Embed runtime return callback( null, - `${urlEscapeHelper}exports = module.exports = require(${stringifyRequest( - this, - require.resolve('./runtime/api.js') - )})(${sourceMap});\n` + + `${urlEscapeHelperCode}${runtimeCode}\n` + `// imports\n${importCode}\n\n` + `// module\n${moduleCode}\n\n` + `// exports\n${exportCode}` diff --git a/lib/utils.js b/lib/utils.js index 7c617d71..01808ef9 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -14,13 +14,30 @@ const placholderRegExps = { urlItem: /___CSS_LOADER_URL___([0-9]+)___/, }; +function getImportPrefix(loaderContext, importLoaders) { + if (importLoaders === false) { + return ''; + } + + const numberImportedLoaders = parseInt(importLoaders, 10) || 0; + const loadersRequest = loaderContext.loaders + .slice( + loaderContext.loaderIndex, + loaderContext.loaderIndex + 1 + numberImportedLoaders + ) + .map((x) => x.request) + .join('!'); + + return `-!${loadersRequest}!`; +} + function dashesCamelCase(str) { return str.replace(/-+(\w)/g, (match, firstLetter) => firstLetter.toUpperCase() ); } -function compileExports(exports, importItemMatcher, camelCaseKeys) { +function compileExports(exports, camelCaseKeys, valueHandler) { if (!exports || Object.keys(exports).length === 0) { return ''; } @@ -29,10 +46,7 @@ function compileExports(exports, importItemMatcher, camelCaseKeys) { .reduce((res, key) => { let valueAsString = JSON.stringify(exports[key]); - valueAsString = valueAsString.replace( - placholderRegExps.importItemG, - importItemMatcher - ); + valueAsString = valueHandler(valueAsString); function addEntry(k) { res.push(`\t${JSON.stringify(k)}: ${valueAsString}`); @@ -75,22 +89,6 @@ function compileExports(exports, importItemMatcher, camelCaseKeys) { return `{\n${exportJs}\n}`; } -function getImportPrefix(loaderContext, query) { - if (query.importLoaders === false) { - return ''; - } - - const importLoaders = parseInt(query.importLoaders, 10) || 0; - const loadersRequest = loaderContext.loaders - .slice( - loaderContext.loaderIndex, - loaderContext.loaderIndex + 1 + importLoaders - ) - .map((x) => x.request) - .join('!'); - return `-!${loadersRequest}!`; -} - function getLocalIdent(loaderContext, localIdentName, localName, options) { if (!options.context) { if (loaderContext.rootContext) { @@ -107,53 +105,29 @@ function getLocalIdent(loaderContext, localIdentName, localName, options) { options.context = loaderContext.context; } } + const request = path.relative(options.context, loaderContext.resourcePath); + // eslint-disable-next-line no-param-reassign options.content = `${options.hashPrefix + request.replace(/\\/g, '/')}+${localName}`; // eslint-disable-next-line no-param-reassign localIdentName = localIdentName.replace(/\[local\]/gi, localName); + const hash = loaderUtils.interpolateName( loaderContext, localIdentName, options ); + return hash .replace(new RegExp('[^a-zA-Z0-9\\-_\u00A0-\uFFFF]', 'g'), '-') .replace(/^((-?[0-9])|--)/, '_$1'); } -function placeholderImportItemReplacer( - loaderContext, - importItems, - importUrlPrefix, - onlyLocals = false -) { - return (item) => { - const match = placholderRegExps.importItem.exec(item); - const idx = +match[1]; - const importItem = importItems[idx]; - const importUrl = importUrlPrefix + importItem.url; - - if (onlyLocals) { - return `" + require(${loaderUtils.stringifyRequest( - loaderContext, - importUrl - )})[${JSON.stringify(importItem.export)}] + "`; - } - - return `" + require(${loaderUtils.stringifyRequest( - loaderContext, - importUrl - )}).locals[${JSON.stringify(importItem.export)}] + "`; - }; -} - module.exports = { - dashesCamelCase, compileExports, getImportPrefix, getLocalIdent, - placeholderImportItemReplacer, placholderRegExps, };