Skip to content

Commit

Permalink
refactor: postcss plugins (#844)
Browse files Browse the repository at this point in the history
  • Loading branch information
evilebottnawi committed Dec 4, 2018
1 parent fdcf687 commit 8a6ea10
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 66 deletions.
22 changes: 16 additions & 6 deletions README.md
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
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
@@ -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
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
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
@@ -0,0 +1,3 @@
.alias {
color: red;
}
5 changes: 5 additions & 0 deletions test/fixtures/import/import.css
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
@@ -0,0 +1,3 @@
.relative {
color: red;
}
3 changes: 3 additions & 0 deletions test/fixtures/import/top-relative.css
@@ -0,0 +1,3 @@
.top-relative {
color: black;
}
3 changes: 2 additions & 1 deletion test/helpers.js
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

0 comments on commit 8a6ea10

Please sign in to comment.