Skip to content

Commit

Permalink
fix: resolution algorithm for CSS modules
Browse files Browse the repository at this point in the history
  • Loading branch information
cap-Bernardito authored Jul 8, 2020
1 parent f9b8ef9 commit 76f1480
Show file tree
Hide file tree
Showing 22 changed files with 299 additions and 110 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,8 @@ exports.locals = {

To import a local classname from another module.

> i We strongly recommend that you specify the extension when importing a file, since it is possible to import a file with any extension and it is not known in advance which file to use.
```css
:local(.continueButton) {
composes: button from 'library/button.css';
Expand Down
21 changes: 18 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,19 @@ export default function loader(content, map, meta) {
const preRequester = getPreRequester(this);
const urlHandler = (url) =>
stringifyRequest(this, preRequester(options.importLoaders) + url);
const icssResolver = this.getResolve({
mainFields: ['css', 'style', 'main', '...'],
mainFiles: ['index', '...'],
});

plugins.push(icssParser({ urlHandler }));
plugins.push(
icssParser({
context: this.context,
rootContext: this.rootContext,
resolver: icssResolver,
urlHandler,
})
);

if (options.import !== false && exportType === 'full') {
const resolver = this.getResolve({
Expand Down Expand Up @@ -136,8 +147,6 @@ export default function loader(content, map, meta) {
}
}

apiImports.sort((a, b) => a.index - b.index);

/*
* Order
* CSS_LOADER_ICSS_IMPORT: [],
Expand All @@ -153,6 +162,12 @@ export default function loader(content, map, meta) {
(b.index < a.index) - (a.index < b.index)
);
});
apiImports.sort((a, b) => {
return (
(b.order < a.order) - (a.order < b.order) ||
(b.index < a.index) - (a.index < b.index)
);
});

const { localsConvention } = options;
const esModule =
Expand Down
159 changes: 110 additions & 49 deletions src/plugins/postcss-icss-parser.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import postcss from 'postcss';
import { extractICSS, replaceValueSymbols, replaceSymbols } from 'icss-utils';
import { urlToRequest } from 'loader-utils';

function makeRequestableIcssImports(icssImports) {
import { normalizeUrl, resolveRequests, isUrlRequestable } from '../utils';

function makeRequestableIcssImports(icssImports, rootContext) {
return Object.keys(icssImports).reduce((accumulator, url) => {
const tokensMap = icssImports[url];
const tokens = Object.keys(tokensMap);
Expand All @@ -11,16 +12,27 @@ function makeRequestableIcssImports(icssImports) {
return accumulator;
}

const normalizedUrl = urlToRequest(url);
const isRequestable = isUrlRequestable(url);

let normalizedUrl;

if (isRequestable) {
normalizedUrl = normalizeUrl(url, true, rootContext);
}

if (!accumulator[normalizedUrl]) {
const key = typeof normalizedUrl !== 'undefined' ? normalizedUrl : url;

if (!accumulator[key]) {
// eslint-disable-next-line no-param-reassign
accumulator[normalizedUrl] = tokensMap;
accumulator[key] = { url, tokenMap: tokensMap };
} else {
// eslint-disable-next-line no-param-reassign
accumulator[normalizedUrl] = {
...accumulator[normalizedUrl],
...tokensMap,
accumulator[key] = {
url,
tokenMap: {
...accumulator[key].tokenMap,
...tokensMap,
},
};
}

Expand All @@ -31,55 +43,104 @@ function makeRequestableIcssImports(icssImports) {
export default postcss.plugin(
'postcss-icss-parser',
(options) => (css, result) => {
const importReplacements = Object.create(null);
const extractedICSS = extractICSS(css);
const icssImports = makeRequestableIcssImports(extractedICSS.icssImports);

for (const [importIndex, url] of Object.keys(icssImports).entries()) {
const importName = `___CSS_LOADER_ICSS_IMPORT_${importIndex}___`;

result.messages.push(
{
type: 'import',
value: {
// 'CSS_LOADER_ICSS_IMPORT'
order: 0,
importName,
url: options.urlHandler ? options.urlHandler(url) : url,
},
},
{
type: 'api-import',
value: { type: 'internal', importName, dedupe: true },
}
return new Promise(async (resolve, reject) => {
const importReplacements = Object.create(null);
const extractedICSS = extractICSS(css);
const icssImports = makeRequestableIcssImports(
extractedICSS.icssImports,
options.rootContext
);

const tokenMap = icssImports[url];
const tokens = Object.keys(tokenMap);

for (const [replacementIndex, token] of tokens.entries()) {
const replacementName = `___CSS_LOADER_ICSS_IMPORT_${importIndex}_REPLACEMENT_${replacementIndex}___`;
const localName = tokenMap[token];
const tasks = [];

let index = 0;

for (const [importIndex, normalizedUrl] of Object.keys(
icssImports
).entries()) {
const { url } = icssImports[normalizedUrl];

index += 1;

tasks.push(
Promise.resolve(index).then(async (currentIndex) => {
const importName = `___CSS_LOADER_ICSS_IMPORT_${importIndex}___`;
const { resolver, context } = options;

let resolvedUrl;

try {
resolvedUrl = await resolveRequests(resolver, context, [
...new Set([normalizedUrl, url]),
]);
} catch (error) {
throw error;
}

result.messages.push(
{
type: 'import',
value: {
// 'CSS_LOADER_ICSS_IMPORT'
order: 0,
importName,
url: options.urlHandler(resolvedUrl),
index: currentIndex,
},
},
{
type: 'api-import',
value: {
// 'CSS_LOADER_ICSS_IMPORT'
order: 0,
type: 'internal',
importName,
dedupe: true,
index: currentIndex,
},
}
);

const { tokenMap } = icssImports[normalizedUrl];
const tokens = Object.keys(tokenMap);

for (const [replacementIndex, token] of tokens.entries()) {
const replacementName = `___CSS_LOADER_ICSS_IMPORT_${importIndex}_REPLACEMENT_${replacementIndex}___`;
const localName = tokenMap[token];

importReplacements[token] = replacementName;

result.messages.push({
type: 'icss-replacement',
value: { replacementName, importName, localName },
});
}
})
);
}

importReplacements[token] = replacementName;
try {
await Promise.all(tasks);
} catch (error) {
reject(error);
}

result.messages.push({
type: 'icss-replacement',
value: { replacementName, importName, localName },
});
if (Object.keys(importReplacements).length > 0) {
replaceSymbols(css, importReplacements);
}
}

if (Object.keys(importReplacements).length > 0) {
replaceSymbols(css, importReplacements);
}
const { icssExports } = extractedICSS;

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

for (const name of Object.keys(icssExports)) {
const value = replaceValueSymbols(icssExports[name], importReplacements);
result.messages.push({ type: 'export', value: { name, value } });
}

result.messages.push({ type: 'export', value: { name, value } });
}
resolve();
});
}
);
15 changes: 11 additions & 4 deletions src/plugins/postcss-import-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,7 @@ export default postcss.plugin(pluginName, (options) => (css, result) => {
// 'CSS_LOADER_AT_RULE_IMPORT'
order: 1,
importName,
url: options.urlHandler
? options.urlHandler(resolvedUrl)
: resolvedUrl,
url: options.urlHandler(resolvedUrl),
index: currentIndex,
},
});
Expand All @@ -148,6 +146,8 @@ export default postcss.plugin(pluginName, (options) => (css, result) => {
result.messages.push({
type: 'api-import',
value: {
// 'CSS_LOADER_AT_RULE_IMPORT'
order: 1,
type: 'internal',
importName,
media,
Expand All @@ -161,7 +161,14 @@ export default postcss.plugin(pluginName, (options) => (css, result) => {
result.messages.push({
pluginName,
type: 'api-import',
value: { type: 'external', url, media, index: currentIndex },
value: {
// 'CSS_LOADER_AT_RULE_IMPORT'
order: 1,
type: 'external',
url,
media,
index: currentIndex,
},
});
})
);
Expand Down
9 changes: 9 additions & 0 deletions test/__snapshots__/import-option.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`"import" option should emit warning when unresolved import: errors 1`] = `
Array [
"ModuleBuildError: Module build failed (from \`replaced original path\`):
Error: Can't resolve 'unresolved-file.css' in '/test/fixtures/import'",
]
`;

exports[`"import" option should emit warning when unresolved import: warnings 1`] = `Array []`;

exports[`"import" option should keep original order: errors 1`] = `Array []`;

exports[`"import" option should keep original order: module 1`] = `
Expand Down
Loading

0 comments on commit 76f1480

Please sign in to comment.