diff --git a/README.md b/README.md index b68189a6..c0c5eef2 100644 --- a/README.md +++ b/README.md @@ -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') @@ -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) diff --git a/lib/loader.js b/lib/loader.js index 8ca549eb..cec68723 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -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, diff --git a/lib/plugins/postcss-import-parser.js b/lib/plugins/postcss-import-parser.js index ea537f86..3cf31d88 100644 --- a/lib/plugins/postcss-import-parser.js +++ b/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'; @@ -19,7 +18,7 @@ function getUrl(node) { return node.value; } - return ''; + return null; } function parseImport(params) { @@ -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; } @@ -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 }); }); } ); diff --git a/lib/plugins/postcss-url-parser.js b/lib/plugins/postcss-url-parser.js index 1dbcf84a..1fd26816 100644 --- a/lib/plugins/postcss-url-parser.js +++ b/lib/plugins/postcss-url-parser.js @@ -39,7 +39,7 @@ function filterUrls(parsed, result, decl, filter) { return; } - if (!filter(url)) { + if (filter && !filter(url)) { return; } @@ -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))); @@ -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(); }); diff --git a/test/__snapshots__/import-option.test.js.snap b/test/__snapshots__/import-option.test.js.snap index 5562fd6d..aabcb7fd 100644 --- a/test/__snapshots__/import-option.test.js.snap +++ b/test/__snapshots__/import-option.test.js.snap @@ -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); ", "", ], @@ -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 " @@ -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(); @@ -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\\", \\"\\"]); diff --git a/test/fixtures/import/alias.css b/test/fixtures/import/alias.css new file mode 100644 index 00000000..17fd95e0 --- /dev/null +++ b/test/fixtures/import/alias.css @@ -0,0 +1,3 @@ +.alias { + color: red; +} diff --git a/test/fixtures/import/import.css b/test/fixtures/import/import.css index 7064c505..f1167b87 100644 --- a/test/fixtures/import/import.css +++ b/test/fixtures/import/import.css @@ -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); diff --git a/test/fixtures/import/node_modules/package/tilde.css b/test/fixtures/import/node_modules/package/tilde.css new file mode 100644 index 00000000..c895f358 --- /dev/null +++ b/test/fixtures/import/node_modules/package/tilde.css @@ -0,0 +1,3 @@ +.tilde { + color: yellow; +} diff --git a/test/fixtures/import/relative.css b/test/fixtures/import/relative.css new file mode 100644 index 00000000..73597627 --- /dev/null +++ b/test/fixtures/import/relative.css @@ -0,0 +1,3 @@ +.relative { + color: red; +} diff --git a/test/fixtures/import/top-relative.css b/test/fixtures/import/top-relative.css new file mode 100644 index 00000000..13a954d0 --- /dev/null +++ b/test/fixtures/import/top-relative.css @@ -0,0 +1,3 @@ +.top-relative { + color: black; +} diff --git a/test/helpers.js b/test/helpers.js index c973d3e1..354176e7 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -55,7 +55,7 @@ function evaluated(output, modules, moduleId = 1) { path.resolve( __dirname, `./fixtures/${importedPath}`, - module.replace('aliasesImg/', '') + module.replace('aliasesImg/', '').replace('aliasesImport/', '') ) ); @@ -173,6 +173,7 @@ function compile(fixture, config = {}, options = {}) { resolve: { alias: { aliasesImg: path.resolve(__dirname, 'fixtures/url'), + aliasesImport: path.resolve(__dirname, 'fixtures/import'), }, }, };