Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor loader #956

Merged
merged 6 commits into from Jun 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
80 changes: 44 additions & 36 deletions src/index.js
Expand Up @@ -19,12 +19,12 @@ import {
normalizeSourceMap,
getModulesPlugins,
getImportPrefix,
getIcssItemReplacer,
getFilter,
getRuntimeCode,
getApiCode,
getImportCode,
getModuleCode,
getExportCode,
prepareCode,
} from './utils';
import Warning from './Warning';
import CssSyntaxError from './CssSyntaxError';
Expand All @@ -37,14 +37,9 @@ export default function loader(content, map, meta) {
const callback = this.async();
const sourceMap = options.sourceMap || false;

if (sourceMap && map) {
// eslint-disable-next-line no-param-reassign
map = normalizeSourceMap(map);
} else {
// Some loaders (example `"postcss-loader": "1.x.x"`) always generates source map, we should remove it
// eslint-disable-next-line no-param-reassign
map = null;
}
// Some loaders (example `"postcss-loader": "1.x.x"`) always generates source map, we should remove it
// eslint-disable-next-line no-param-reassign
map = sourceMap && map ? normalizeSourceMap(map) : null;

// Reuse CSS AST (PostCSS AST e.g 'postcss-loader') to avoid reparsing
if (meta) {
Expand All @@ -62,11 +57,22 @@ export default function loader(content, map, meta) {
plugins.push(...getModulesPlugins(options, this));
}

plugins.push(icssParser());
// Run other loader (`postcss-loader`, `sass-loader` and etc) for importing CSS
const importPrefix = getImportPrefix(this, options.importLoaders);

plugins.push(
icssParser({
loaderContext: this,
importPrefix,
exportLocalsStyle: options.exportLocalsStyle,
})
);

if (options.import !== false) {
plugins.push(
importParser({
loaderContext: this,
importPrefix,
filter: getFilter(options.import, this.resourcePath),
})
);
Expand All @@ -75,6 +81,7 @@ export default function loader(content, map, meta) {
if (options.url !== false) {
plugins.push(
urlParser({
loaderContext: this,
filter: getFilter(options.url, this.resourcePath, (value) =>
isUrlRequest(value)
),
Expand Down Expand Up @@ -108,36 +115,37 @@ export default function loader(content, map, meta) {
result.messages = [];
}

const {
exportOnlyLocals: onlyLocals,
exportLocalsStyle: localsStyle,
} = options;
// Run other loader (`postcss-loader`, `sass-loader` and etc) for importing CSS
const importPrefix = getImportPrefix(this, options.importLoaders);
// Prepare replacer to change from `___CSS_LOADER_IMPORT___INDEX___` to `require('./file.css').locals`
const replacer = getIcssItemReplacer(
result,
this,
importPrefix,
onlyLocals
);
const { exportOnlyLocals: onlyLocals } = options;

// eslint-disable-next-line no-param-reassign
result.cssLoaderBuildInfo = {
onlyLocals,
localsStyle,
importPrefix,
replacer,
};
const importItems = result.messages
.filter((message) => (message.type === 'import' ? message : false))
.reduce((accumulator, currentValue) => {
accumulator.push(currentValue.import);

return accumulator;
}, []);
const exportItems = result.messages
.filter((message) => (message.type === 'export' ? message : false))
.reduce((accumulator, currentValue) => {
accumulator.push(currentValue.export);

return accumulator;
}, []);

const runtimeCode = getRuntimeCode(result, this, sourceMap);
const importCode = getImportCode(result, this);
const moduleCode = getModuleCode(result);
const exportsCode = getExportCode(result);
const importCode = getImportCode(importItems, onlyLocals);
const moduleCode = getModuleCode(result, sourceMap, onlyLocals);
const exportCode = getExportCode(exportItems, onlyLocals);
const apiCode = getApiCode(this, sourceMap, onlyLocals);

return callback(
null,
runtimeCode + importCode + moduleCode + exportsCode
prepareCode(
{ apiCode, importCode, moduleCode, exportCode },
result.messages,
this,
importPrefix,
onlyLocals
)
);
})
.catch((error) => {
Expand Down
29 changes: 20 additions & 9 deletions src/plugins/postcss-icss-parser.js
Expand Up @@ -2,6 +2,8 @@ import postcss from 'postcss';
import { extractICSS, replaceValueSymbols, replaceSymbols } from 'icss-utils';
import loaderUtils from 'loader-utils';

import { getExportItemCode, getImportItemCode } from '../utils';

const pluginName = 'postcss-icss-parser';

function hasImportMessage(messages, url) {
Expand All @@ -16,7 +18,7 @@ function hasImportMessage(messages, url) {

export default postcss.plugin(
pluginName,
() =>
(options = {}) =>
function process(css, result) {
const importReplacements = Object.create(null);
const { icssImports, icssExports } = extractICSS(css);
Expand All @@ -37,10 +39,18 @@ export default postcss.plugin(
});

if (!hasImportMessage(result.messages, url)) {
const media = '';
const { loaderContext, importPrefix } = options;

result.messages.push({
pluginName,
type: 'import',
item: { url, media: '' },
import: getImportItemCode(
{ url, media },
loaderContext,
importPrefix
),
item: { url, media },
});
}
}
Expand All @@ -49,16 +59,17 @@ export default postcss.plugin(
replaceSymbols(css, importReplacements);

for (const exportName of Object.keys(icssExports)) {
const name = exportName;
const value = replaceValueSymbols(
icssExports[name],
importReplacements
);

result.messages.push({
pluginName,
export: getExportItemCode(name, value, options.exportLocalsStyle),
type: 'export',
item: {
key: exportName,
value: replaceValueSymbols(
icssExports[exportName],
importReplacements
),
},
item: { name, value },
});
}
}
Expand Down
27 changes: 15 additions & 12 deletions src/plugins/postcss-import-parser.js
@@ -1,6 +1,8 @@
import postcss from 'postcss';
import valueParser from 'postcss-value-parser';

import { uniqWith, getImportItemCode } from '../utils';

const pluginName = 'postcss-import-parser';

function getArg(nodes) {
Expand Down Expand Up @@ -84,25 +86,26 @@ function walkAtRules(css, result, filter) {
return items;
}

function uniq(array) {
return array.reduce(
(acc, d) =>
!acc.find((el) => el.url === d.url && el.media === d.media)
? [...acc, d]
: acc,
[]
);
}

export default postcss.plugin(
pluginName,
(options = {}) =>
function process(css, result) {
const traversed = walkAtRules(css, result, options.filter);
const paths = uniq(traversed);
const paths = uniqWith(
traversed,
(value, other) => value.url === other.url && value.media === other.media
);

paths.forEach((item) => {
result.messages.push({ pluginName, type: 'import', item });
result.messages.push({
pluginName,
type: 'import',
import: getImportItemCode(
item,
options.loaderContext,
options.importPrefix
),
});
});
}
);
43 changes: 25 additions & 18 deletions src/plugins/postcss-url-parser.js
@@ -1,6 +1,8 @@
import postcss from 'postcss';
import valueParser from 'postcss-value-parser';

import { uniqWith, flatten, getUrlHelperCode, getUrlItemCode } from '../utils';

const pluginName = 'postcss-url-parser';

const isUrlFunc = /url/i;
Expand Down Expand Up @@ -102,29 +104,15 @@ function walkDeclsWithUrl(css, result, filter) {
return items;
}

function uniqWith(array, comparator) {
return array.reduce(
(acc, d) => (!acc.some((item) => comparator(d, item)) ? [...acc, d] : acc),
[]
);
}

function flatten(array) {
return array.reduce((a, b) => a.concat(b), []);
}

function isEqual(value, other) {
return value.url === other.url && value.needQuotes === other.needQuotes;
}

export default postcss.plugin(
pluginName,
(options = {}) =>
function process(css, result) {
const traversed = walkDeclsWithUrl(css, result, options.filter);
const paths = uniqWith(
flatten(traversed.map((item) => item.urls)),
isEqual
(value, other) =>
value.url === other.url && value.needQuotes === other.needQuotes
);

if (paths.length === 0) {
Expand All @@ -133,16 +121,35 @@ export default postcss.plugin(

const placeholders = [];

let hasUrlHelper = false;

paths.forEach((path, index) => {
const { loaderContext } = options;
const placeholder = `___CSS_LOADER_URL___${index}___`;
const { url, needQuotes } = path;

placeholders.push({ placeholder, path });

if (!hasUrlHelper) {
result.messages.push({
pluginName,
type: 'import',
import: getUrlHelperCode(loaderContext),
});

// eslint-disable-next-line no-param-reassign
hasUrlHelper = true;
}

result.messages.push({
pluginName,
type: 'url',
item: { url, placeholder, needQuotes },
type: 'import',
import: getUrlItemCode(
{ url, placeholder, needQuotes },
loaderContext
),
importType: 'url',
placeholder,
});
});

Expand Down
2 changes: 1 addition & 1 deletion src/runtime/get-url.js → src/runtime/getUrl.js
@@ -1,4 +1,4 @@
module.exports = function escape(url, needQuotes) {
module.exports = (url, needQuotes) => {
if (typeof url !== 'string') {
return url;
}
Expand Down