From 76f1480b14265369ac5dc8dbbce467cfb8e814c5 Mon Sep 17 00:00:00 2001 From: Alexey Lavinsky Date: Wed, 8 Jul 2020 16:10:22 +0300 Subject: [PATCH] fix: resolution algorithm for CSS modules --- README.md | 2 + src/index.js | 21 ++- src/plugins/postcss-icss-parser.js | 159 ++++++++++++------ src/plugins/postcss-import-parser.js | 15 +- test/__snapshots__/import-option.test.js.snap | 9 + .../__snapshots__/modules-option.test.js.snap | 88 ++++++---- .../onlyLocals-option.test.js.snap | 4 +- test/fixtures/import/unresolved.css | 1 + test/fixtures/import/unresolved.js | 5 + .../composes/composes-preprocessors.css | 6 - test/fixtures/modules/composes/composes.css | 4 +- test/fixtures/modules/composes/sass-file.sass | 8 - test/fixtures/modules/issue-914/source.css | 8 + test/fixtures/modules/issue-914/source.js | 5 + .../modules/node_modules/test/index.css | 2 + .../modules/node_modules/test/package.json | 9 + test/fixtures/modules/unresolved/source.css | 6 + test/fixtures/modules/unresolved/source.js | 5 + test/helpers/getErrors.js | 4 +- test/helpers/normalizeErrors.js | 14 +- test/import-option.test.js | 8 + test/modules-option.test.js | 26 +++ 22 files changed, 299 insertions(+), 110 deletions(-) create mode 100644 test/fixtures/import/unresolved.css create mode 100644 test/fixtures/import/unresolved.js delete mode 100644 test/fixtures/modules/composes/sass-file.sass create mode 100644 test/fixtures/modules/issue-914/source.css create mode 100644 test/fixtures/modules/issue-914/source.js create mode 100644 test/fixtures/modules/node_modules/test/index.css create mode 100644 test/fixtures/modules/node_modules/test/package.json create mode 100644 test/fixtures/modules/unresolved/source.css create mode 100644 test/fixtures/modules/unresolved/source.js diff --git a/README.md b/README.md index 64fe2707..2e3da598 100644 --- a/README.md +++ b/README.md @@ -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'; diff --git a/src/index.js b/src/index.js index 2d21d153..1910ea27 100644 --- a/src/index.js +++ b/src/index.js @@ -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({ @@ -136,8 +147,6 @@ export default function loader(content, map, meta) { } } - apiImports.sort((a, b) => a.index - b.index); - /* * Order * CSS_LOADER_ICSS_IMPORT: [], @@ -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 = diff --git a/src/plugins/postcss-icss-parser.js b/src/plugins/postcss-icss-parser.js index 53bca320..f2de17ba 100644 --- a/src/plugins/postcss-icss-parser.js +++ b/src/plugins/postcss-icss-parser.js @@ -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); @@ -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, + }, }; } @@ -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(); + }); } ); diff --git a/src/plugins/postcss-import-parser.js b/src/plugins/postcss-import-parser.js index 9bd43124..94b1f10c 100644 --- a/src/plugins/postcss-import-parser.js +++ b/src/plugins/postcss-import-parser.js @@ -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, }, }); @@ -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, @@ -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, + }, }); }) ); diff --git a/test/__snapshots__/import-option.test.js.snap b/test/__snapshots__/import-option.test.js.snap index 24a57a0f..5107c86a 100644 --- a/test/__snapshots__/import-option.test.js.snap +++ b/test/__snapshots__/import-option.test.js.snap @@ -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`] = ` diff --git a/test/__snapshots__/modules-option.test.js.snap b/test/__snapshots__/modules-option.test.js.snap index c5d10730..65b3c375 100644 --- a/test/__snapshots__/modules-option.test.js.snap +++ b/test/__snapshots__/modules-option.test.js.snap @@ -668,6 +668,51 @@ Array [ exports[`"modules" option should keep order: warnings 1`] = `Array []`; +exports[`"modules" option should resolve package from node_modules with and without tilde: errors 1`] = `Array []`; + +exports[`"modules" option should resolve package from node_modules with and without tilde: module 1`] = ` +"// Imports +var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../../src/runtime/api.js\\"); +var ___CSS_LOADER_ICSS_IMPORT_0___ = require(\\"-!../../../../src/index.js??[ident]!../node_modules/test/index.css\\"); +var ___CSS_LOADER_ICSS_IMPORT_1___ = require(\\"-!../../../../src/index.js??[ident]!../node_modules/test/index.css\\"); +exports = ___CSS_LOADER_API_IMPORT___(false); +exports.i(___CSS_LOADER_ICSS_IMPORT_0___, \\"\\", true); +exports.i(___CSS_LOADER_ICSS_IMPORT_1___, \\"\\", true); +// Module +exports.push([module.id, \\"._1AcMgd5TrMMVc761JDuYuc {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0___.locals[\\"foo\\"] + \\";\\\\n background: \\" + ___CSS_LOADER_ICSS_IMPORT_1___.locals[\\"bar\\"] + \\";\\\\n}\\\\n\\\\n\\", \\"\\"]); +// Exports +exports.locals = { + \\"foo\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0___.locals[\\"foo\\"] + \\"\\", + \\"bar\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_1___.locals[\\"bar\\"] + \\"\\", + \\"className\\": \\"_1AcMgd5TrMMVc761JDuYuc\\" +}; +module.exports = exports; +" +`; + +exports[`"modules" option should resolve package from node_modules with and without tilde: result 1`] = ` +Array [ + Array [ + "../../src/index.js?[ident]!./modules/node_modules/test/index.css", + " +", + "", + ], + Array [ + "./modules/issue-914/source.css", + "._1AcMgd5TrMMVc761JDuYuc { + color: red; + background: green; +} + +", + "", + ], +] +`; + +exports[`"modules" option should resolve package from node_modules with and without tilde: warnings 1`] = `Array []`; + exports[`"modules" option should should work with two leading hyphens: errors 1`] = `Array []`; exports[`"modules" option should should work with two leading hyphens: module 1`] = ` @@ -1050,25 +1095,21 @@ var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../../src/runtime/api.js\\"); var ___CSS_LOADER_ICSS_IMPORT_0___ = require(\\"-!../../../../src/index.js??[ident]!./values.css\\"); var ___CSS_LOADER_ICSS_IMPORT_1___ = require(\\"-!../../../../src/index.js??[ident]!./less-file.less\\"); var ___CSS_LOADER_ICSS_IMPORT_2___ = require(\\"-!../../../../src/index.js??[ident]!./scss-file.scss\\"); -var ___CSS_LOADER_ICSS_IMPORT_3___ = require(\\"-!../../../../src/index.js??[ident]!./sass-file.sass\\"); exports = ___CSS_LOADER_API_IMPORT___(false); exports.i(___CSS_LOADER_ICSS_IMPORT_0___, \\"\\", true); exports.i(___CSS_LOADER_ICSS_IMPORT_1___, \\"\\", true); exports.i(___CSS_LOADER_ICSS_IMPORT_2___, \\"\\", true); -exports.i(___CSS_LOADER_ICSS_IMPORT_3___, \\"\\", true); // Module -exports.push([module.id, \\".globalClassName {\\\\n color: orange;\\\\n}\\\\n\\\\n._2vZioV5tEV-AlqaI8c_mtN {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0___.locals[\\"v-def\\"] + \\";\\\\n}\\\\n\\\\n.YX1POP6L7ESwR7pdtyRna {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_1___.locals[\\"v-foo\\"] + \\";\\\\n}\\\\n\\\\n._1578mz8iEiOG2qNa19R11c {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_2___.locals[\\"v-bar\\"] + \\";\\\\n}\\\\n\\\\n._1yWiOyfH6HuDl7TOzpnWkK {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_3___.locals[\\"v-baz\\"] + \\";\\\\n}\\\\n\\\\n.OVj95yO5B9JEdIo3xDMtO {\\\\n background: #000;\\\\n}\\\\n\\", \\"\\"]); +exports.push([module.id, \\".globalClassName {\\\\n color: orange;\\\\n}\\\\n\\\\n._2vZioV5tEV-AlqaI8c_mtN {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_0___.locals[\\"v-def\\"] + \\";\\\\n}\\\\n\\\\n.YX1POP6L7ESwR7pdtyRna {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_1___.locals[\\"v-foo\\"] + \\";\\\\n}\\\\n\\\\n._1578mz8iEiOG2qNa19R11c {\\\\n color: \\" + ___CSS_LOADER_ICSS_IMPORT_2___.locals[\\"v-bar\\"] + \\";\\\\n}\\\\n\\\\n.OVj95yO5B9JEdIo3xDMtO {\\\\n background: #000;\\\\n}\\\\n\\", \\"\\"]); // Exports exports.locals = { \\"v-def\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0___.locals[\\"v-def\\"] + \\"\\", \\"v-foo\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_1___.locals[\\"v-foo\\"] + \\"\\", \\"v-bar\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_2___.locals[\\"v-bar\\"] + \\"\\", - \\"v-baz\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_3___.locals[\\"v-baz\\"] + \\"\\", \\"globalClassName\\": \\"globalClassName\\", \\"ghi\\": \\"_2vZioV5tEV-AlqaI8c_mtN\\", \\"class\\": \\"YX1POP6L7ESwR7pdtyRna \\" + ___CSS_LOADER_ICSS_IMPORT_1___.locals[\\"lessClass\\"] + \\"\\", \\"other\\": \\"_1578mz8iEiOG2qNa19R11c \\" + ___CSS_LOADER_ICSS_IMPORT_2___.locals[\\"scssClass\\"] + \\"\\", - \\"last\\": \\"_1yWiOyfH6HuDl7TOzpnWkK \\" + ___CSS_LOADER_ICSS_IMPORT_3___.locals[\\"sassClass\\"] + \\"\\", \\"otherClassName\\": \\"OVj95yO5B9JEdIo3xDMtO globalClassName\\" }; module.exports = exports; @@ -1096,13 +1137,6 @@ Array [ "._228B9eI0iy8oD-AJowJJdF { padding: 15px; } -", - "", - ], - Array [ - "../../src/index.js?[ident]!./modules/composes/sass-file.sass", - " - ", "", ], @@ -1124,10 +1158,6 @@ Array [ color: white; } -._1yWiOyfH6HuDl7TOzpnWkK { - color: undefined; -} - .OVj95yO5B9JEdIo3xDMtO { background: #000; } @@ -1137,20 +1167,7 @@ Array [ ] `; -exports[`"modules" option should support resolving in composes preprocessor files with extensions: warnings 1`] = ` -Array [ - "ModuleWarning: Module Warning (from \`replaced original path\`): -Warning - -Invalid value definition: v-def: red -@value v-foo: green -@value v-bar: white -@value v-baz: coral - -.sassClass - padding: 10px", -] -`; +exports[`"modules" option should support resolving in composes preprocessor files with extensions: warnings 1`] = `Array []`; exports[`"modules" option should support resolving in composes: errors 1`] = `Array []`; @@ -1163,7 +1180,7 @@ var ___CSS_LOADER_ICSS_IMPORT_2___ = require(\\"-!../../../../src/index.js??[ide var ___CSS_LOADER_ICSS_IMPORT_3___ = require(\\"-!../../../../src/index.js??[ident]!./relative.css\\"); var ___CSS_LOADER_ICSS_IMPORT_4___ = require(\\"-!../../../../src/index.js??[ident]!./top-relative.css\\"); var ___CSS_LOADER_ICSS_IMPORT_5___ = require(\\"-!../../../../src/index.js??[ident]!../issue-861/node_modules/package/style.css\\"); -var ___CSS_LOADER_ICSS_IMPORT_6___ = require(\\"-!../../../../src/index.js??[ident]!aliasesComposes/alias.css\\"); +var ___CSS_LOADER_ICSS_IMPORT_6___ = require(\\"-!../../../../src/index.js??[ident]!./alias.css\\"); var ___CSS_LOADER_AT_RULE_IMPORT_0___ = require(\\"-!../../../../src/index.js??[ident]!./test-other.css\\"); var ___CSS_LOADER_GET_URL_IMPORT___ = require(\\"../../../../src/runtime/getUrl.js\\"); var ___CSS_LOADER_URL_IMPORT_0___ = require(\\"../../url/img.png\\"); @@ -1413,6 +1430,15 @@ a { exports[`"modules" option should support resolving in composes: warnings 1`] = `Array []`; +exports[`"modules" option should throw error when unresolved import: errors 1`] = ` +Array [ + "ModuleBuildError: Module build failed (from \`replaced original path\`): +Error: Can't resolve '//exammple.com/unresolved.css' in '/test/fixtures/modules/unresolved'", +] +`; + +exports[`"modules" option should throw error when unresolved import: warnings 1`] = `Array []`; + exports[`"modules" option should work and correctly replace escaped symbols: errors 1`] = `Array []`; exports[`"modules" option should work and correctly replace escaped symbols: module 1`] = ` diff --git a/test/__snapshots__/onlyLocals-option.test.js.snap b/test/__snapshots__/onlyLocals-option.test.js.snap index 82845d42..b8ffcd8b 100644 --- a/test/__snapshots__/onlyLocals-option.test.js.snap +++ b/test/__snapshots__/onlyLocals-option.test.js.snap @@ -10,7 +10,7 @@ import ___CSS_LOADER_ICSS_IMPORT_2___ from \\"-!../../../../src/index.js??[ident import ___CSS_LOADER_ICSS_IMPORT_3___ from \\"-!../../../../src/index.js??[ident]!./relative.css\\"; import ___CSS_LOADER_ICSS_IMPORT_4___ from \\"-!../../../../src/index.js??[ident]!./top-relative.css\\"; import ___CSS_LOADER_ICSS_IMPORT_5___ from \\"-!../../../../src/index.js??[ident]!../issue-861/node_modules/package/style.css\\"; -import ___CSS_LOADER_ICSS_IMPORT_6___ from \\"-!../../../../src/index.js??[ident]!aliasesComposes/alias.css\\"; +import ___CSS_LOADER_ICSS_IMPORT_6___ from \\"-!../../../../src/index.js??[ident]!./alias.css\\"; // Exports export default { \\"v-def\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0___[\\"v-def\\"] + \\"\\", @@ -122,7 +122,7 @@ var ___CSS_LOADER_ICSS_IMPORT_2___ = require(\\"-!../../../../src/index.js??[ide var ___CSS_LOADER_ICSS_IMPORT_3___ = require(\\"-!../../../../src/index.js??[ident]!./relative.css\\"); var ___CSS_LOADER_ICSS_IMPORT_4___ = require(\\"-!../../../../src/index.js??[ident]!./top-relative.css\\"); var ___CSS_LOADER_ICSS_IMPORT_5___ = require(\\"-!../../../../src/index.js??[ident]!../issue-861/node_modules/package/style.css\\"); -var ___CSS_LOADER_ICSS_IMPORT_6___ = require(\\"-!../../../../src/index.js??[ident]!aliasesComposes/alias.css\\"); +var ___CSS_LOADER_ICSS_IMPORT_6___ = require(\\"-!../../../../src/index.js??[ident]!./alias.css\\"); // Exports module.exports = { \\"v-def\\": \\"\\" + ___CSS_LOADER_ICSS_IMPORT_0___[\\"v-def\\"] + \\"\\", diff --git a/test/fixtures/import/unresolved.css b/test/fixtures/import/unresolved.css new file mode 100644 index 00000000..6cd410c8 --- /dev/null +++ b/test/fixtures/import/unresolved.css @@ -0,0 +1 @@ +@import "unresolved-file.css"; diff --git a/test/fixtures/import/unresolved.js b/test/fixtures/import/unresolved.js new file mode 100644 index 00000000..884bd80c --- /dev/null +++ b/test/fixtures/import/unresolved.js @@ -0,0 +1,5 @@ +import css from './unresolved.css'; + +__export__ = css; + +export default css; diff --git a/test/fixtures/modules/composes/composes-preprocessors.css b/test/fixtures/modules/composes/composes-preprocessors.css index f7250729..9b178528 100644 --- a/test/fixtures/modules/composes/composes-preprocessors.css +++ b/test/fixtures/modules/composes/composes-preprocessors.css @@ -1,7 +1,6 @@ @value v-def from './values.css'; @value v-foo from './less-file.less'; @value v-bar from './scss-file.scss'; -@value v-baz from './sass-file.sass'; :global(.globalClassName) { color: orange; @@ -21,11 +20,6 @@ composes: scssClass from "scss-file.scss"; } -.last { - color: v-baz; - composes: sassClass from "sass-file.sass"; -} - .otherClassName { composes: globalClassName from global; background: #000; diff --git a/test/fixtures/modules/composes/composes.css b/test/fixtures/modules/composes/composes.css index e5680fef..53e137c0 100644 --- a/test/fixtures/modules/composes/composes.css +++ b/test/fixtures/modules/composes/composes.css @@ -1,6 +1,8 @@ @import url(./test-other.css) (min-width: 100px); -@value v-def from './values.css'; +@value v-def from './values\ +\ +.css'; @value v-other from './values.css'; @value v-other from './values.css'; @value s-white from './values.css'; diff --git a/test/fixtures/modules/composes/sass-file.sass b/test/fixtures/modules/composes/sass-file.sass deleted file mode 100644 index 50ea4705..00000000 --- a/test/fixtures/modules/composes/sass-file.sass +++ /dev/null @@ -1,8 +0,0 @@ -@value v-def: red -@value v-foo: green -@value v-bar: white -@value v-baz: coral - -.sassClass - padding: 10px - diff --git a/test/fixtures/modules/issue-914/source.css b/test/fixtures/modules/issue-914/source.css new file mode 100644 index 00000000..9a606d7c --- /dev/null +++ b/test/fixtures/modules/issue-914/source.css @@ -0,0 +1,8 @@ +@value foo from '~test'; +@value bar from 'test'; + +.className { + color: foo; + background: bar; +} + diff --git a/test/fixtures/modules/issue-914/source.js b/test/fixtures/modules/issue-914/source.js new file mode 100644 index 00000000..1996779e --- /dev/null +++ b/test/fixtures/modules/issue-914/source.js @@ -0,0 +1,5 @@ +import css from './source.css'; + +__export__ = css; + +export default css; diff --git a/test/fixtures/modules/node_modules/test/index.css b/test/fixtures/modules/node_modules/test/index.css new file mode 100644 index 00000000..f4592cc2 --- /dev/null +++ b/test/fixtures/modules/node_modules/test/index.css @@ -0,0 +1,2 @@ +@value foo: red; +@value bar: green; diff --git a/test/fixtures/modules/node_modules/test/package.json b/test/fixtures/modules/node_modules/test/package.json new file mode 100644 index 00000000..6fd9679f --- /dev/null +++ b/test/fixtures/modules/node_modules/test/package.json @@ -0,0 +1,9 @@ +{ + "author": { + "name": "test" + }, + "description": "test", + "main": "index.css", + "name": "test", + "version": "1.0.0" +} diff --git a/test/fixtures/modules/unresolved/source.css b/test/fixtures/modules/unresolved/source.css new file mode 100644 index 00000000..079eb8b4 --- /dev/null +++ b/test/fixtures/modules/unresolved/source.css @@ -0,0 +1,6 @@ +@value foo from '//exammple.com/unresolved.css'; + +.className { + color: foo; +} + diff --git a/test/fixtures/modules/unresolved/source.js b/test/fixtures/modules/unresolved/source.js new file mode 100644 index 00000000..1996779e --- /dev/null +++ b/test/fixtures/modules/unresolved/source.js @@ -0,0 +1,5 @@ +import css from './source.css'; + +__export__ = css; + +export default css; diff --git a/test/helpers/getErrors.js b/test/helpers/getErrors.js index 085bae5b..03339e1e 100644 --- a/test/helpers/getErrors.js +++ b/test/helpers/getErrors.js @@ -1,5 +1,5 @@ import normalizeErrors from './normalizeErrors'; -export default (stats) => { - return normalizeErrors(stats.compilation.errors).sort(); +export default (stats, shortError) => { + return normalizeErrors(stats.compilation.errors, shortError).sort(); }; diff --git a/test/helpers/normalizeErrors.js b/test/helpers/normalizeErrors.js index 7b3ededc..5660a66a 100644 --- a/test/helpers/normalizeErrors.js +++ b/test/helpers/normalizeErrors.js @@ -16,8 +16,14 @@ function removeCWD(str) { .replace(new RegExp(cwd, 'g'), ''); } -export default (errors) => { - return errors.map((error) => - removeCWD(error.toString().split('\n').slice(0, 12).join('\n')) - ); +export default (errors, shortError) => { + return errors.map((error) => { + let errorMessage = error.toString(); + + if (shortError) { + errorMessage = errorMessage.split('\n').slice(0, 2).join('\n'); + } + + return removeCWD(errorMessage.split('\n').slice(0, 12).join('\n')); + }); }; diff --git a/test/import-option.test.js b/test/import-option.test.js index 88533af3..7c172bfb 100644 --- a/test/import-option.test.js +++ b/test/import-option.test.js @@ -152,4 +152,12 @@ describe('"import" option', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); }); + + it('should emit warning when unresolved import', async () => { + const compiler = getCompiler('./import/unresolved.js'); + const stats = await compile(compiler); + + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats, true)).toMatchSnapshot('errors'); + }); }); diff --git a/test/modules-option.test.js b/test/modules-option.test.js index 1bcc8160..5b5e3600 100644 --- a/test/modules-option.test.js +++ b/test/modules-option.test.js @@ -818,4 +818,30 @@ describe('"modules" option', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); }); + + it('should resolve package from node_modules with and without tilde', async () => { + const compiler = getCompiler('./modules/issue-914/source.js', { + modules: true, + }); + const stats = await compile(compiler); + + expect( + getModuleSource('./modules/issue-914/source.css', stats) + ).toMatchSnapshot('module'); + expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot( + 'result' + ); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should throw error when unresolved import', async () => { + const compiler = getCompiler('./modules/unresolved/source.js', { + modules: true, + }); + const stats = await compile(compiler); + + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats, true)).toMatchSnapshot('errors'); + }); });