Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
refactor: code
  • Loading branch information
alexander-akait committed Nov 30, 2018
commit 59e469a30e2e2361ccdcba6574e088d4b012594b
129 changes: 66 additions & 63 deletions lib/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const { importParser, icssParser, urlParser } = require('./plugins');
const {
getLocalIdent,
getImportPrefix,
placeholderImportItemReplacer,
compileExports,
placholderRegExps,
} = require('./utils');
Expand Down Expand Up @@ -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();

Expand All @@ -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 || '',
Expand All @@ -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));
}

Expand Down Expand Up @@ -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) => {
Expand All @@ -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`;
Expand All @@ -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('?#');
Expand Down Expand Up @@ -241,43 +244,43 @@ 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('!')
.pop()
.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}`
Expand Down
72 changes: 23 additions & 49 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 '';
}
Expand All @@ -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}`);
Expand Down Expand Up @@ -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) {
Expand All @@ -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,
};