265 lines
7.4 KiB
JavaScript
265 lines
7.4 KiB
JavaScript
/*
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
Author Tobias Koppers @sokra
|
|
*/
|
|
var formatCodeFrame = require("babel-code-frame");
|
|
var Tokenizer = require("css-selector-tokenizer");
|
|
var postcss = require("postcss");
|
|
var loaderUtils = require("loader-utils");
|
|
var assign = require("object-assign");
|
|
var getLocalIdent = require("./getLocalIdent");
|
|
|
|
var icssUtils = require('icss-utils');
|
|
var localByDefault = require("postcss-modules-local-by-default");
|
|
var extractImports = require("postcss-modules-extract-imports");
|
|
var modulesScope = require("postcss-modules-scope");
|
|
var modulesValues = require("postcss-modules-values");
|
|
var valueParser = require('postcss-value-parser');
|
|
|
|
var parserPlugin = postcss.plugin("css-loader-parser", function(options) {
|
|
return function(css) {
|
|
var imports = {};
|
|
var exports = {};
|
|
var importItems = [];
|
|
var urlItems = [];
|
|
|
|
function replaceImportsInString(str) {
|
|
if(options.import) {
|
|
var tokens = valueParser(str);
|
|
tokens.walk(function (node) {
|
|
if (node.type !== 'word') {
|
|
return;
|
|
}
|
|
var token = node.value;
|
|
var importIndex = imports["$" + token];
|
|
if(typeof importIndex === "number") {
|
|
node.value = "___CSS_LOADER_IMPORT___" + importIndex + "___";
|
|
}
|
|
})
|
|
return tokens.toString();
|
|
}
|
|
return str;
|
|
}
|
|
|
|
if(options.import) {
|
|
css.walkAtRules(/^import$/i, function(rule) {
|
|
var values = Tokenizer.parseValues(rule.params);
|
|
var url = values.nodes[0].nodes[0];
|
|
if(url && url.type === "url") {
|
|
url = url.url;
|
|
} else if(url && url.type === "string") {
|
|
url = url.value;
|
|
} else throw rule.error("Unexpected format " + rule.params);
|
|
if (!url.replace(/\s/g, '').length) {
|
|
return;
|
|
}
|
|
values.nodes[0].nodes.shift();
|
|
var mediaQuery = Tokenizer.stringifyValues(values);
|
|
|
|
if(loaderUtils.isUrlRequest(url, options.root)) {
|
|
url = loaderUtils.urlToRequest(url, options.root);
|
|
}
|
|
|
|
importItems.push({
|
|
url: url,
|
|
mediaQuery: mediaQuery
|
|
});
|
|
rule.remove();
|
|
});
|
|
}
|
|
|
|
var icss = icssUtils.extractICSS(css);
|
|
exports = icss.icssExports;
|
|
Object.keys(icss.icssImports).forEach(function(key) {
|
|
var url = loaderUtils.parseString(key);
|
|
Object.keys(icss.icssImports[key]).forEach(function(prop) {
|
|
imports["$" + prop] = importItems.length;
|
|
importItems.push({
|
|
url: url,
|
|
export: icss.icssImports[key][prop]
|
|
});
|
|
})
|
|
});
|
|
|
|
Object.keys(exports).forEach(function(exportName) {
|
|
exports[exportName] = replaceImportsInString(exports[exportName]);
|
|
});
|
|
|
|
function isAlias(url) {
|
|
// Handle alias starting by / and root disabled
|
|
return url !== options.resolve(url)
|
|
}
|
|
|
|
function processNode(item) {
|
|
switch (item.type) {
|
|
case "value":
|
|
item.nodes.forEach(processNode);
|
|
break;
|
|
case "nested-item":
|
|
item.nodes.forEach(processNode);
|
|
break;
|
|
case "item":
|
|
var importIndex = imports["$" + item.name];
|
|
if (typeof importIndex === "number") {
|
|
item.name = "___CSS_LOADER_IMPORT___" + importIndex + "___";
|
|
}
|
|
break;
|
|
case "url":
|
|
if (options.url && item.url.replace(/\s/g, '').length && !/^#/.test(item.url) && (isAlias(item.url) || loaderUtils.isUrlRequest(item.url, options.root))) {
|
|
// Strip quotes, they will be re-added if the module needs them
|
|
item.stringType = "";
|
|
delete item.innerSpacingBefore;
|
|
delete item.innerSpacingAfter;
|
|
var url = item.url;
|
|
item.url = "___CSS_LOADER_URL___" + urlItems.length + "___";
|
|
urlItems.push({
|
|
url: url
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
css.walkDecls(function(decl) {
|
|
var values = Tokenizer.parseValues(decl.value);
|
|
values.nodes.forEach(function(value) {
|
|
value.nodes.forEach(processNode);
|
|
});
|
|
decl.value = Tokenizer.stringifyValues(values);
|
|
});
|
|
css.walkAtRules(function(atrule) {
|
|
if(typeof atrule.params === "string") {
|
|
atrule.params = replaceImportsInString(atrule.params);
|
|
}
|
|
});
|
|
|
|
options.importItems = importItems;
|
|
options.urlItems = urlItems;
|
|
options.exports = exports;
|
|
};
|
|
});
|
|
|
|
module.exports = function processCss(inputSource, inputMap, options, callback) {
|
|
var query = options.query;
|
|
var root = query.root && query.root.length > 0 ? query.root.replace(/\/$/, "") : query.root;
|
|
var context = query.context;
|
|
var localIdentName = query.localIdentName || "[hash:base64]";
|
|
var localIdentRegExp = query.localIdentRegExp;
|
|
var forceMinimize = query.minimize;
|
|
var minimize = typeof forceMinimize !== "undefined" ? !!forceMinimize : options.minimize;
|
|
|
|
var customGetLocalIdent = query.getLocalIdent || getLocalIdent;
|
|
|
|
var parserOptions = {
|
|
root: root,
|
|
mode: options.mode,
|
|
url: query.url !== false,
|
|
import: query.import !== false,
|
|
resolve: options.resolve
|
|
};
|
|
|
|
var pipeline = postcss([
|
|
modulesValues,
|
|
localByDefault({
|
|
mode: options.mode,
|
|
rewriteUrl: function(global, url) {
|
|
if(parserOptions.url){
|
|
url = url.trim();
|
|
|
|
if(!url.replace(/\s/g, '').length || !loaderUtils.isUrlRequest(url, root)) {
|
|
return url;
|
|
}
|
|
if(global) {
|
|
return loaderUtils.urlToRequest(url, root);
|
|
}
|
|
}
|
|
return url;
|
|
}
|
|
}),
|
|
extractImports(),
|
|
modulesScope({
|
|
generateScopedName: function generateScopedName (exportName) {
|
|
return customGetLocalIdent(options.loaderContext, localIdentName, exportName, {
|
|
regExp: localIdentRegExp,
|
|
hashPrefix: query.hashPrefix || "",
|
|
context: context
|
|
});
|
|
}
|
|
}),
|
|
parserPlugin(parserOptions)
|
|
]);
|
|
|
|
if(minimize) {
|
|
var cssnano = require("cssnano");
|
|
var minimizeOptions = assign({}, query.minimize);
|
|
["zindex", "normalizeUrl", "discardUnused", "mergeIdents", "reduceIdents", "autoprefixer"].forEach(function(name) {
|
|
if(typeof minimizeOptions[name] === "undefined")
|
|
minimizeOptions[name] = false;
|
|
});
|
|
pipeline.use(cssnano(minimizeOptions));
|
|
}
|
|
|
|
pipeline.process(inputSource, {
|
|
// we need a prefix to avoid path rewriting of PostCSS
|
|
from: "/css-loader!" + options.from,
|
|
to: options.to,
|
|
map: options.sourceMap ? {
|
|
prev: inputMap,
|
|
sourcesContent: true,
|
|
inline: false,
|
|
annotation: false
|
|
} : null
|
|
}).then(function(result) {
|
|
callback(null, {
|
|
source: result.css,
|
|
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(function(err) {
|
|
if (err.name === 'CssSyntaxError') {
|
|
var wrappedError = new CSSLoaderError(
|
|
'Syntax Error',
|
|
err.reason,
|
|
err.line != null && err.column != null
|
|
? {line: err.line, column: err.column}
|
|
: null,
|
|
err.input.source
|
|
);
|
|
callback(wrappedError);
|
|
} else {
|
|
callback(err);
|
|
}
|
|
});
|
|
};
|
|
|
|
function formatMessage(message, loc, source) {
|
|
var formatted = message;
|
|
if (loc) {
|
|
formatted = formatted
|
|
+ ' (' + loc.line + ':' + loc.column + ')';
|
|
}
|
|
if (loc && source) {
|
|
formatted = formatted
|
|
+ '\n\n' + formatCodeFrame(source, loc.line, loc.column) + '\n';
|
|
}
|
|
return formatted;
|
|
}
|
|
|
|
function CSSLoaderError(name, message, loc, source, error) {
|
|
Error.call(this);
|
|
Error.captureStackTrace(this, CSSLoaderError);
|
|
this.name = name;
|
|
this.error = error;
|
|
this.message = formatMessage(message, loc, source);
|
|
this.hideStack = true;
|
|
}
|
|
|
|
CSSLoaderError.prototype = Object.create(Error.prototype);
|
|
CSSLoaderError.prototype.constructor = CSSLoaderError;
|