Skip to content

refactor: postcss plugins #844

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

Merged
merged 1 commit into from
Dec 4, 2018
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ url(image.png) => require('./image.png')
url(./image.png) => require('./image.png')
```

To import styles from a `node_modules` path (include `resolve.modules`) and for `alias`, prefix it with a `~`:
To import assets from a `node_modules` path (include `resolve.modules`) and for `alias`, prefix it with a `~`:

```
url(~module/image.png) => require('module/image.png')
Expand All @@ -130,14 +130,24 @@ url(~aliasDirectory/image.png) => require('otherDirectory/image.png')

### `import`

To disable `@import` resolving by `css-loader` set the option to `false`.
Enable/disable `@import` resolving. Absolute urls are not resolving.

Examples resolutions:

Absolute urls are not resolving.
```
@import 'style.css' => require('./style.css')
@import url(style.css) => require('./style.css')
@import url('style.css') => require('./style.css')
@import './style.css' => require('./style.css')
@import url(./style.css) => require('./style.css')
@import url('./style.css') => require('./style.css')
```

To import styles from a node module path, prefix it with a `~`:
To import styles from a `node_modules` path (include `resolve.modules`) and for `alias`, prefix it with a `~`:

```css
@import '~module/styles.css';
```
@import url(~module/style.css) => require('module/style.css')
@import url(~aliasDirectory/style.css) => require('otherDirectory/style.css')
```

### [`modules`](https://github.com/css-modules/css-modules)
Expand Down
2 changes: 1 addition & 1 deletion lib/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ module.exports = function loader(content, map, meta) {
)}, ${JSON.stringify(media)}]);`;
}

const importUrl = importUrlPrefix + url;
const importUrl = importUrlPrefix + urlToRequest(url);

return `exports.i(require(${stringifyRequest(
this,
Expand Down
114 changes: 59 additions & 55 deletions lib/plugins/postcss-import-parser.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const postcss = require('postcss');
const valueParser = require('postcss-value-parser');
const loaderUtils = require('loader-utils');

const pluginName = 'postcss-import-parser';

Expand All @@ -19,7 +18,7 @@ function getUrl(node) {
return node.value;
}

return '';
return null;
}

function parseImport(params) {
Expand All @@ -31,7 +30,7 @@ function parseImport(params) {

const url = getUrl(nodes[0]);

if (url.trim().length === 0) {
if (!url || url.trim().length === 0) {
return null;
}

Expand All @@ -44,61 +43,66 @@ function parseImport(params) {
};
}

function walkAtRules(css, result, filter) {
const items = [];

css.walkAtRules(/^import$/i, (atRule) => {
// Convert only top-level @import
if (atRule.parent.type !== 'root') {
return;
}

if (atRule.nodes) {
result.warn(
"It looks like you didn't end your @import statement correctly. " +
'Child nodes are attached to it.',
{ node: atRule }
);
return;
}

const parsed = parseImport(atRule.params);

if (!parsed) {
// eslint-disable-next-line consistent-return
return result.warn(`Unable to find uri in '${atRule.toString()}'`, {
node: atRule,
});
}

if (filter && !filter(parsed)) {
return;
}

atRule.remove();

const { url, media } = parsed;

items.push({ url, media });
});

return items;
}

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

module.exports = postcss.plugin(
pluginName,
() =>
(options = {}) =>
function process(css, result) {
css.walkAtRules(/^import$/i, (atRule) => {
// Convert only top-level @import
if (atRule.parent.type !== 'root') {
return;
}

if (atRule.nodes) {
result.warn(
"It looks like you didn't end your @import statement correctly. " +
'Child nodes are attached to it.',
{ node: atRule }
);
return;
}

const parsed = parseImport(atRule.params);

if (!parsed) {
// eslint-disable-next-line consistent-return
return result.warn(`Unable to find uri in '${atRule.toString()}'`, {
node: atRule,
});
}

atRule.remove();

const { media } = parsed;
let { url } = parsed;
const isUrlRequest = loaderUtils.isUrlRequest(url);

if (isUrlRequest) {
url = loaderUtils.urlToRequest(url);
}

const alreadyIncluded = result.messages.find(
(message) =>
message.pluginName === pluginName &&
message.type === 'import' &&
message.item.url === url &&
message.item.media === media
);

if (alreadyIncluded) {
return;
}

result.messages.push({
pluginName,
type: 'import',
item: { url, media },
});
const traversed = walkAtRules(css, result, options.filter);
const paths = uniq(traversed);

paths.forEach((item) => {
result.messages.push({ pluginName, type: 'import', item });
});
}
);
5 changes: 3 additions & 2 deletions lib/plugins/postcss-url-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function filterUrls(parsed, result, decl, filter) {
return;
}

if (!filter(url)) {
if (filter && !filter(url)) {
return;
}

Expand Down Expand Up @@ -96,7 +96,7 @@ function uniq(array) {

module.exports = postcss.plugin(
pluginName,
(options) =>
(options = {}) =>
function process(css, result) {
const traversed = walkDeclsWithUrl(css, result, options.filter);
const paths = uniq(flatten(traversed.map((item) => item.values)));
Expand All @@ -121,6 +121,7 @@ module.exports = postcss.plugin(

traversed.forEach((item) => {
mapUrls(item.parsed, (value) => urls[value]);

// eslint-disable-next-line no-param-reassign
item.decl.value = item.parsed.toString();
});
Expand Down
43 changes: 42 additions & 1 deletion test/__snapshots__/import-option.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,11 @@ Array [
.foo {
@import 'path.css';
}

@import url('./relative.css');
@import url('../import/top-relative.css');
@import url(~package/tilde.css);
@import url(~aliasesImport/alias.css);
",
"",
],
Expand All @@ -355,7 +360,7 @@ exports[`import option false: module 1`] = `


// module
exports.push([module.id, \\"@import url(test.css);\\\\n@import url('test.css');\\\\n@import url(\\\\\\"test.css\\\\\\");\\\\n@IMPORT url(test.css);\\\\n@import URL(test.css);\\\\n@import url(test.css );\\\\n@import url( test.css);\\\\n@import url( test.css );\\\\n@import url(\\\\n test.css\\\\n);\\\\n@import url();\\\\n@import url('');\\\\n@import url(\\\\\\"\\\\\\");\\\\n@import \\\\\\"test.css\\\\\\";\\\\n@import 'test.css';\\\\n@import '';\\\\n@import \\\\\\"\\\\\\";\\\\n@import \\\\\\" \\\\\\";\\\\n@import \\\\\\"\\\\n\\\\\\";\\\\n@import url();\\\\n@import url('');\\\\n@import url(\\\\\\"\\\\\\");\\\\n@import url(test.css) screen and print;\\\\n@import url(test.css) SCREEN AND PRINT;\\\\n@import url(test.css)screen and print;\\\\n@import url(test.css) screen and print;\\\\n@import url(test-media.css) screen and print;\\\\n@import url(test-other.css) (min-width: 100px);\\\\n@import url(http://example.com/style.css);\\\\n@import url(http://example.com/style.css);\\\\n@import url(http://example.com/style.css#hash);\\\\n@import url(http://example.com/style.css?#hash);\\\\n@import url(http://example.com/style.css?foo=bar#hash);\\\\n@import url(http://example.com/other-style.css) screen and print;\\\\n@import url(http://example.com/other-style.css) screen and print;\\\\n@import url(\\\\\\"//example.com/style.css\\\\\\");\\\\n@import url(~package/test.css);\\\\n@import ;\\\\n@import foo-bar;\\\\n@import-normalize;\\\\n@import url('http://') :root {}\\\\n@import url('query.css?foo=1&bar=1');\\\\n@import url('other-query.css?foo=1&bar=1#hash');\\\\n@import url('other-query.css?foo=1&bar=1#hash') screen and print;\\\\n@import url('https://fonts.googleapis.com/css?family=Roboto');\\\\n@import url('https://fonts.googleapis.com/css?family=Noto+Sans+TC');\\\\n@import url('https://fonts.googleapis.com/css?family=Noto+Sans+TC|Roboto');\\\\n\\\\n.class {\\\\n a: b c d;\\\\n}\\\\n\\\\n.foo {\\\\n @import 'path.css';\\\\n}\\\\n\\", \\"\\"]);
exports.push([module.id, \\"@import url(test.css);\\\\n@import url('test.css');\\\\n@import url(\\\\\\"test.css\\\\\\");\\\\n@IMPORT url(test.css);\\\\n@import URL(test.css);\\\\n@import url(test.css );\\\\n@import url( test.css);\\\\n@import url( test.css );\\\\n@import url(\\\\n test.css\\\\n);\\\\n@import url();\\\\n@import url('');\\\\n@import url(\\\\\\"\\\\\\");\\\\n@import \\\\\\"test.css\\\\\\";\\\\n@import 'test.css';\\\\n@import '';\\\\n@import \\\\\\"\\\\\\";\\\\n@import \\\\\\" \\\\\\";\\\\n@import \\\\\\"\\\\n\\\\\\";\\\\n@import url();\\\\n@import url('');\\\\n@import url(\\\\\\"\\\\\\");\\\\n@import url(test.css) screen and print;\\\\n@import url(test.css) SCREEN AND PRINT;\\\\n@import url(test.css)screen and print;\\\\n@import url(test.css) screen and print;\\\\n@import url(test-media.css) screen and print;\\\\n@import url(test-other.css) (min-width: 100px);\\\\n@import url(http://example.com/style.css);\\\\n@import url(http://example.com/style.css);\\\\n@import url(http://example.com/style.css#hash);\\\\n@import url(http://example.com/style.css?#hash);\\\\n@import url(http://example.com/style.css?foo=bar#hash);\\\\n@import url(http://example.com/other-style.css) screen and print;\\\\n@import url(http://example.com/other-style.css) screen and print;\\\\n@import url(\\\\\\"//example.com/style.css\\\\\\");\\\\n@import url(~package/test.css);\\\\n@import ;\\\\n@import foo-bar;\\\\n@import-normalize;\\\\n@import url('http://') :root {}\\\\n@import url('query.css?foo=1&bar=1');\\\\n@import url('other-query.css?foo=1&bar=1#hash');\\\\n@import url('other-query.css?foo=1&bar=1#hash') screen and print;\\\\n@import url('https://fonts.googleapis.com/css?family=Roboto');\\\\n@import url('https://fonts.googleapis.com/css?family=Noto+Sans+TC');\\\\n@import url('https://fonts.googleapis.com/css?family=Noto+Sans+TC|Roboto');\\\\n\\\\n.class {\\\\n a: b c d;\\\\n}\\\\n\\\\n.foo {\\\\n @import 'path.css';\\\\n}\\\\n\\\\n@import url('./relative.css');\\\\n@import url('../import/top-relative.css');\\\\n@import url(~package/tilde.css);\\\\n@import url(~aliasesImport/alias.css);\\\\n\\", \\"\\"]);

// exports
"
Expand Down Expand Up @@ -476,6 +481,38 @@ Array [
"@import url(https://fonts.googleapis.com/css?family=Noto+Sans+TC|Roboto);",
"",
],
Array [
10,
".relative {
color: red;
}
",
"",
],
Array [
11,
".top-relative {
color: black;
}
",
"",
],
Array [
12,
".tilde {
color: yellow;
}
",
"",
],
Array [
13,
".alias {
color: red;
}
",
"",
],
Array [
1,
"@import url();
Expand Down Expand Up @@ -527,6 +564,10 @@ exports.i(require(\\"-!../../../index.js??ref--4-0!./other-query.css?foo=1&bar=1
exports.push([module.id, \\"@import url(https://fonts.googleapis.com/css?family=Roboto);\\", \\"\\"]);
exports.push([module.id, \\"@import url(https://fonts.googleapis.com/css?family=Noto+Sans+TC);\\", \\"\\"]);
exports.push([module.id, \\"@import url(https://fonts.googleapis.com/css?family=Noto+Sans+TC|Roboto);\\", \\"\\"]);
exports.i(require(\\"-!../../../index.js??ref--4-0!./relative.css\\"), \\"\\");
exports.i(require(\\"-!../../../index.js??ref--4-0!../import/top-relative.css\\"), \\"\\");
exports.i(require(\\"-!../../../index.js??ref--4-0!package/tilde.css\\"), \\"\\");
exports.i(require(\\"-!../../../index.js??ref--4-0!aliasesImport/alias.css\\"), \\"\\");

// module
exports.push([module.id, \\"@import url();\\\\n@import url('');\\\\n@import url(\\\\\\"\\\\\\");\\\\n@import '';\\\\n@import \\\\\\"\\\\\\";\\\\n@import \\\\\\" \\\\\\";\\\\n@import \\\\\\"\\\\n\\\\\\";\\\\n@import url();\\\\n@import url('');\\\\n@import url(\\\\\\"\\\\\\");\\\\n@import ;\\\\n@import foo-bar;\\\\n@import-normalize;\\\\n@import url('http://') :root {}\\\\n\\\\n.class {\\\\n a: b c d;\\\\n}\\\\n\\\\n.foo {\\\\n @import 'path.css';\\\\n}\\\\n\\", \\"\\"]);
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/import/alias.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.alias {
color: red;
}
5 changes: 5 additions & 0 deletions test/fixtures/import/import.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,8 @@
.foo {
@import 'path.css';
}

@import url('./relative.css');
@import url('../import/top-relative.css');
@import url(~package/tilde.css);
@import url(~aliasesImport/alias.css);
3 changes: 3 additions & 0 deletions test/fixtures/import/node_modules/package/tilde.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions test/fixtures/import/relative.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.relative {
color: red;
}
3 changes: 3 additions & 0 deletions test/fixtures/import/top-relative.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.top-relative {
color: black;
}
3 changes: 2 additions & 1 deletion test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function evaluated(output, modules, moduleId = 1) {
path.resolve(
__dirname,
`./fixtures/${importedPath}`,
module.replace('aliasesImg/', '')
module.replace('aliasesImg/', '').replace('aliasesImport/', '')
)
);

Expand Down Expand Up @@ -173,6 +173,7 @@ function compile(fixture, config = {}, options = {}) {
resolve: {
alias: {
aliasesImg: path.resolve(__dirname, 'fixtures/url'),
aliasesImport: path.resolve(__dirname, 'fixtures/import'),
},
},
};
Expand Down