From 4b1368f8835c7a311b356b53460aea76408ae279 Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Thu, 6 Dec 2018 23:28:30 +0300 Subject: [PATCH] feat: allow filtering urls --- README.md | 71 ++++-- src/index.js | 12 +- src/options.json | 9 +- src/utils.js | 22 +- test/__snapshots__/errors.test.js.snap | 2 + test/__snapshots__/url-option.test.js.snap | 269 +++++++++++++++++++++ test/errors.test.js | 1 + test/url-option.test.js | 27 +++ 8 files changed, 383 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 5f4f5a54..1a5c79ef 100644 --- a/README.md +++ b/README.md @@ -114,23 +114,41 @@ module.exports = { ## Options -| Name | Type | Default | Description | -| :-----------------------------------------: | :-----------------: | :-------------: | :------------------------------------------ | -| **[`url`](#url)** | `{Boolean}` | `true` | Enable/Disable `url()` handling | -| **[`import`](#import)** | `{Boolean}` | `true` | Enable/Disable @import handling | -| **[`modules`](#modules)** | `{Boolean\|String}` | `false` | Enable/Disable CSS Modules and setup mode | -| **[`localIdentName`](#localidentname)** | `{String}` | `[hash:base64]` | Configure the generated ident | -| **[`sourceMap`](#sourcemap)** | `{Boolean}` | `false` | Enable/Disable Sourcemaps | -| **[`camelCase`](#camelcase)** | `{Boolean\|String}` | `false` | Export Classnames in CamelCase | -| **[`importLoaders`](#importloaders)** | `{Number}` | `0` | Number of loaders applied before CSS loader | -| **[`exportOnlyLocals`](#exportonlylocals)** | `{Boolean}` | `false` | Export only locals | +| Name | Type | Default | Description | +| :-----------------------------------------: | :-------------------: | :-------------: | :------------------------------------------ | +| **[`url`](#url)** | `{Boolean\|Function}` | `true` | Enable/Disable `url()` handling | +| **[`import`](#import)** | `{Boolean}` | `true` | Enable/Disable @import handling | +| **[`modules`](#modules)** | `{Boolean\|String}` | `false` | Enable/Disable CSS Modules and setup mode | +| **[`localIdentName`](#localidentname)** | `{String}` | `[hash:base64]` | Configure the generated ident | +| **[`sourceMap`](#sourcemap)** | `{Boolean}` | `false` | Enable/Disable Sourcemaps | +| **[`camelCase`](#camelcase)** | `{Boolean\|String}` | `false` | Export Classnames in CamelCase | +| **[`importLoaders`](#importloaders)** | `{Number}` | `0` | Number of loaders applied before CSS loader | +| **[`exportOnlyLocals`](#exportonlylocals)** | `{Boolean}` | `false` | Export only locals | ### `url` -Type: `Boolean` +Type: `Boolean|Function` Default: `true` -Enable/disable `url()` resolving. Absolute `urls` are not resolving by default. +Control `url()` resolving. Absolute `urls` are not resolving by default. + +Examples resolutions: + +``` +url(image.png) => require('./image.png') +url(./image.png) => require('./image.png') +``` + +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') +url(~aliasDirectory/image.png) => require('otherDirectory/image.png') +``` + +#### `Boolean` + +Enable/disable `url()` resolving. **webpack.config.js** @@ -150,18 +168,27 @@ module.exports = { }; ``` -Examples resolutions: +#### `Function` -``` -url(image.png) => require('./image.png') -url(./image.png) => require('./image.png') -``` - -To import assets from a `node_modules` path (include `resolve.modules`) and for `alias`, prefix it with a `~`: +Allow to filter `url()`. All filtered `url()` will not be resolved. -``` -url(~module/image.png) => require('module/image.png') -url(~aliasDirectory/image.png) => require('otherDirectory/image.png') +```js +module.exports = { + module: { + rules: [ + { + test: /\.css$/, + loader: 'css-loader', + options: { + url: (url, resourcePath) => { + // `url()` with `img.png` stay untouched + return url.includes('img.png'); + }, + }, + }, + ], + }, +}; ``` ### `import` diff --git a/src/index.js b/src/index.js index bb13e4b2..2413e360 100644 --- a/src/index.js +++ b/src/index.js @@ -26,6 +26,7 @@ import { getImportPrefix, placholderRegExps, dashesCamelCase, + getFilter, } from './utils'; import Warning from './Warning'; import CssSyntaxError from './CssSyntaxError'; @@ -66,9 +67,6 @@ export default function loader(content, map, meta) { } } - const resolveImport = options.import !== false; - const resolveUrl = options.url !== false; - const plugins = []; if (options.modules) { @@ -100,14 +98,16 @@ export default function loader(content, map, meta) { ); } - if (resolveImport) { + if (options.import !== false) { plugins.push(importParser()); } - if (resolveUrl) { + if (options.url !== false) { plugins.push( urlParser({ - filter: (value) => isUrlRequest(value), + filter: getFilter(options.url, this.resourcePath, (value) => + isUrlRequest(value) + ), }) ); } diff --git a/src/options.json b/src/options.json index ba1448e4..8aa4f818 100644 --- a/src/options.json +++ b/src/options.json @@ -2,7 +2,14 @@ "additionalProperties": false, "properties": { "url": { - "type": "boolean" + "anyOf": [ + { + "type": "boolean" + }, + { + "instanceof": "Function" + } + ] }, "import": { "type": "boolean" diff --git a/src/utils.js b/src/utils.js index e07acd17..ab0b7a00 100644 --- a/src/utils.js +++ b/src/utils.js @@ -61,4 +61,24 @@ function getLocalIdent(loaderContext, localIdentName, localName, options) { .replace(/^((-?[0-9])|--)/, '_$1'); } -export { getImportPrefix, getLocalIdent, placholderRegExps, dashesCamelCase }; +function getFilter(filter, resourcePath, defaultFilter = null) { + return (content) => { + if (defaultFilter && !defaultFilter(content)) { + return false; + } + + if (typeof filter === 'function') { + return !filter(content, resourcePath); + } + + return true; + }; +} + +export { + getImportPrefix, + getLocalIdent, + placholderRegExps, + dashesCamelCase, + getFilter, +}; diff --git a/test/__snapshots__/errors.test.js.snap b/test/__snapshots__/errors.test.js.snap index 14050475..3b06b7c0 100644 --- a/test/__snapshots__/errors.test.js.snap +++ b/test/__snapshots__/errors.test.js.snap @@ -4,6 +4,8 @@ exports[`validation 1`] = ` "CSS Loader Invalid Options options.url should be boolean +options.url should pass \\"instanceof\\" keyword validation +options.url should match some schema in anyOf " `; diff --git a/test/__snapshots__/url-option.test.js.snap b/test/__snapshots__/url-option.test.js.snap index 1de17c8d..cebb3c16 100644 --- a/test/__snapshots__/url-option.test.js.snap +++ b/test/__snapshots__/url-option.test.js.snap @@ -1,5 +1,274 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`url option Function: errors 1`] = `Array []`; + +exports[`url option Function: module (evaluated) 1`] = ` +Array [ + Array [ + 2, + ".bar { + background: url(/webpack/public/path/img-from-imported.png); +} +", + "", + ], + Array [ + 1, + ".class { + background: url('./img.png'); +} + +.class { + background: url(\\"./img.png\\"); +} + +.class { + background: url(./img.png); +} + +.class { + background: url(\\"./img.png#hash\\"); +} + +.class { + background: url( + \\"./img.png\\" + ); +} + +.class { + background: green url( './img.png' ) xyz; +} + +.class { + background: green url( \\"./img.png\\" ) xyz; +} + +.class { + background: green url( ./img.png ) xyz; +} + +.class { + background: green url(~package/img.png) url(./other-img.png) xyz; +} + +.class { + background: green url( \\"./img img.png\\" ) xyz; +} + +.class { + background: green url( './img img.png' ) xyz; +} + +.class { + background: green url(/img.png) xyz; +} + +.class { + background: green url(data:image/png;base64,AAA) url(http://example.com/image.jpg) url(//example.com/image.png) xyz; +} + +.class { + background-image: url(\\"data:image/svg+xml;charset=utf-8,\\"); +} + +.class { + background-image: url(\\"data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2042%2026%27%20fill%3D%27%2523007aff%27%3E%3Crect%20width%3D%274%27%20height%3D%274%27%2F%3E%3Crect%20x%3D%278%27%20y%3D%271%27%20width%3D%2734%27%20height%3D%272%27%2F%3E%3Crect%20y%3D%2711%27%20width%3D%274%27%20height%3D%274%27%2F%3E%3Crect%20x%3D%278%27%20y%3D%2712%27%20width%3D%2734%27%20height%3D%272%27%2F%3E%3Crect%20y%3D%2722%27%20width%3D%274%27%20height%3D%274%27%2F%3E%3Crect%20x%3D%278%27%20y%3D%2723%27%20width%3D%2734%27%20height%3D%272%27%2F%3E%3C%2Fsvg%3E\\"); +} + +.class { + filter: url('data:image/svg+xml;charset=utf-8,#filter'); +} + +.class { + filter: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%5C%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%5C%22%3E%3Cfilter%20id%3D%5C%22filter%5C%22%3E%3CfeGaussianBlur%20in%3D%5C%22SourceAlpha%5C%22%20stdDeviation%3D%5C%220%5C%22%20%2F%3E%3CfeOffset%20dx%3D%5C%221%5C%22%20dy%3D%5C%222%5C%22%20result%3D%5C%22offsetblur%5C%22%20%2F%3E%3CfeFlood%20flood-color%3D%5C%22rgba(255%2C255%2C255%2C1)%5C%22%20%2F%3E%3CfeComposite%20in2%3D%5C%22offsetblur%5C%22%20operator%3D%5C%22in%5C%22%20%2F%3E%3CfeMerge%3E%3CfeMergeNode%20%2F%3E%3CfeMergeNode%20in%3D%5C%22SourceGraphic%5C%22%20%2F%3E%3C%2FfeMerge%3E%3C%2Ffilter%3E%3C%2Fsvg%3E%23filter'); +} + +.highlight { + filter: url(#highlight); +} + +.highlight { + filter: url('#line-marker'); +} + +@font-face { + src: url(/webpack/public/path/font.woff) format('woff'), + url(/webpack/public/path/font.woff2) format('woff2'), + url(/webpack/public/path/font.eot) format('eot'), + url(/webpack/public/path/font.ttf) format('truetype'), + url(\\"/webpack/public/path/font with spaces.eot\\") format(\\"embedded-opentype\\"), + url(/webpack/public/path/font.svg#svgFontName) format('svg'), + url(/webpack/public/path/font.woff2) format('woff2'), + url(/webpack/public/path/font.eot?#iefix) format('embedded-opentype'), + url(\\"/webpack/public/path/font with spaces.eot?#iefix\\") format('embedded-opentype'); +} + +@media (min-width: 500px) { + body { + background: url(\\"./img.png\\"); + } +} + +a { + content: \\"do not use url(path)\\"; +} + +b { + content: 'do not \\"use\\" url(path)'; +} + +@keyframes anim { + background: green url('./img.png') xyz; +} + +.a { + background-image: -webkit-image-set(url(/webpack/public/path/img1x.png) 1x, url(/webpack/public/path/img2x.png) 2x) +} + +.a { + background-image: image-set(url(/webpack/public/path/img1x.png) 1x, url(/webpack/public/path/img2x.png) 2x) +} + +.class { + background: green url() xyz; +} + +.class { + background: green url('') xyz; +} + +.class { + background: green url(\\"\\") xyz; +} + +.class { + background: green url(' ') xyz; +} + +.class { + background: green url( + + ) xyz; +} + +.class { + background: green url(https://raw.githubusercontent.com/webpack/media/master/logo/icon.png) xyz; +} + +.class { + background: green url(//raw.githubusercontent.com/webpack/media/master/logo/icon.png) xyz; +} + +.class { + background: url(\\"./img.png?foo\\"); +} + +.class { + background: url(\\"./img.png?foo=bar\\"); +} + +.class { + background: url(\\"./img.png?foo=bar#hash\\"); +} + +.class { + background: url(\\"./img.png?foo=bar#hash\\"); +} + +.class { + background: url(\\"./img.png?\\"); +} + +.class { + background-image: url('./img.png') url(\\"data:image/svg+xml;charset=utf-8,\\") url('./img.png'); +} + +.class { + background: ___CSS_LOADER_URL___; + background: ___CSS_LOADER_URL___INDEX___; + background: ___CSS_LOADER_URL___99999___; + background: ___CSS_LOADER_IMPORT___; + background: ___CSS_LOADER_IMPORT___INDEX___; + background: ___CSS_LOADER_IMPORT___99999___; +} + +.pure-url { + background: url(/webpack/public/path/img-simple.png); +} + +.not-resolved { + background: url('/img-simple.png'); +} + +.above-below { + background: url(/webpack/public/path/img-simple.png); +} + +.tilde { + background: url('~package/img.png'); +} + +.aliases { + background: url('~aliasesImg/img.png') ; +} +", + "", + ], +] +`; + +exports[`url option Function: module 1`] = ` +"exports = module.exports = require(\\"../../../src/runtime/api.js\\")(false); +// Imports +exports.i(require(\\"-!../../../src/index.js??ref--4-0!./imported.css\\"), \\"\\"); +var urlEscape = require(\\"../../../src/runtime/url-escape.js\\"); +var ___CSS_LOADER_URL___0___ = urlEscape(require(\\"./font.woff\\")); +var ___CSS_LOADER_URL___1___ = urlEscape(require(\\"./font.woff2\\")); +var ___CSS_LOADER_URL___2___ = urlEscape(require(\\"./font.eot\\")); +var ___CSS_LOADER_URL___3___ = urlEscape(require(\\"package/font.ttf\\")); +var ___CSS_LOADER_URL___4___ = urlEscape(require(\\"./font with spaces.eot\\")); +var ___CSS_LOADER_URL___5___ = urlEscape(require(\\"./font.svg\\") + \\"#svgFontName\\"); +var ___CSS_LOADER_URL___6___ = urlEscape(require(\\"./font.woff2?foo=bar\\")); +var ___CSS_LOADER_URL___7___ = urlEscape(require(\\"./font.eot\\") + \\"?#iefix\\"); +var ___CSS_LOADER_URL___8___ = urlEscape(require(\\"./font with spaces.eot\\") + \\"?#iefix\\"); +var ___CSS_LOADER_URL___9___ = urlEscape(require(\\"./img1x.png\\")); +var ___CSS_LOADER_URL___10___ = urlEscape(require(\\"./img2x.png\\")); +var ___CSS_LOADER_URL___11___ = urlEscape(require(\\"./img-simple.png\\")); +var ___CSS_LOADER_URL___12___ = urlEscape(require(\\"../url/img-simple.png\\")); + +// Module +exports.push([module.id, \\".class {\\\\n background: url('./img.png');\\\\n}\\\\n\\\\n.class {\\\\n background: url(\\\\\\"./img.png\\\\\\");\\\\n}\\\\n\\\\n.class {\\\\n background: url(./img.png);\\\\n}\\\\n\\\\n.class {\\\\n background: url(\\\\\\"./img.png#hash\\\\\\");\\\\n}\\\\n\\\\n.class {\\\\n background: url(\\\\n \\\\\\"./img.png\\\\\\"\\\\n );\\\\n}\\\\n\\\\n.class {\\\\n background: green url( './img.png' ) xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: green url( \\\\\\"./img.png\\\\\\" ) xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: green url( ./img.png ) xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: green url(~package/img.png) url(./other-img.png) xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: green url( \\\\\\"./img img.png\\\\\\" ) xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: green url( './img img.png' ) xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: green url(/img.png) xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: green url(data:image/png;base64,AAA) url(http://example.com/image.jpg) url(//example.com/image.png) xyz;\\\\n}\\\\n\\\\n.class {\\\\n background-image: url(\\\\\\"data:image/svg+xml;charset=utf-8,\\\\\\");\\\\n}\\\\n\\\\n.class {\\\\n background-image: url(\\\\\\"data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2042%2026%27%20fill%3D%27%2523007aff%27%3E%3Crect%20width%3D%274%27%20height%3D%274%27%2F%3E%3Crect%20x%3D%278%27%20y%3D%271%27%20width%3D%2734%27%20height%3D%272%27%2F%3E%3Crect%20y%3D%2711%27%20width%3D%274%27%20height%3D%274%27%2F%3E%3Crect%20x%3D%278%27%20y%3D%2712%27%20width%3D%2734%27%20height%3D%272%27%2F%3E%3Crect%20y%3D%2722%27%20width%3D%274%27%20height%3D%274%27%2F%3E%3Crect%20x%3D%278%27%20y%3D%2723%27%20width%3D%2734%27%20height%3D%272%27%2F%3E%3C%2Fsvg%3E\\\\\\");\\\\n}\\\\n\\\\n.class {\\\\n filter: url('data:image/svg+xml;charset=utf-8,#filter');\\\\n}\\\\n\\\\n.class {\\\\n filter: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%5C%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%5C%22%3E%3Cfilter%20id%3D%5C%22filter%5C%22%3E%3CfeGaussianBlur%20in%3D%5C%22SourceAlpha%5C%22%20stdDeviation%3D%5C%220%5C%22%20%2F%3E%3CfeOffset%20dx%3D%5C%221%5C%22%20dy%3D%5C%222%5C%22%20result%3D%5C%22offsetblur%5C%22%20%2F%3E%3CfeFlood%20flood-color%3D%5C%22rgba(255%2C255%2C255%2C1)%5C%22%20%2F%3E%3CfeComposite%20in2%3D%5C%22offsetblur%5C%22%20operator%3D%5C%22in%5C%22%20%2F%3E%3CfeMerge%3E%3CfeMergeNode%20%2F%3E%3CfeMergeNode%20in%3D%5C%22SourceGraphic%5C%22%20%2F%3E%3C%2FfeMerge%3E%3C%2Ffilter%3E%3C%2Fsvg%3E%23filter');\\\\n}\\\\n\\\\n.highlight {\\\\n filter: url(#highlight);\\\\n}\\\\n\\\\n.highlight {\\\\n filter: url('#line-marker');\\\\n}\\\\n\\\\n@font-face {\\\\n src: url(\\" + ___CSS_LOADER_URL___0___ + \\") format('woff'),\\\\n url(\\" + ___CSS_LOADER_URL___1___ + \\") format('woff2'),\\\\n url(\\" + ___CSS_LOADER_URL___2___ + \\") format('eot'),\\\\n url(\\" + ___CSS_LOADER_URL___3___ + \\") format('truetype'),\\\\n url(\\" + ___CSS_LOADER_URL___4___ + \\") format(\\\\\\"embedded-opentype\\\\\\"),\\\\n url(\\" + ___CSS_LOADER_URL___5___ + \\") format('svg'),\\\\n url(\\" + ___CSS_LOADER_URL___6___ + \\") format('woff2'),\\\\n url(\\" + ___CSS_LOADER_URL___7___ + \\") format('embedded-opentype'),\\\\n url(\\" + ___CSS_LOADER_URL___8___ + \\") format('embedded-opentype');\\\\n}\\\\n\\\\n@media (min-width: 500px) {\\\\n body {\\\\n background: url(\\\\\\"./img.png\\\\\\");\\\\n }\\\\n}\\\\n\\\\na {\\\\n content: \\\\\\"do not use url(path)\\\\\\";\\\\n}\\\\n\\\\nb {\\\\n content: 'do not \\\\\\"use\\\\\\" url(path)';\\\\n}\\\\n\\\\n@keyframes anim {\\\\n background: green url('./img.png') xyz;\\\\n}\\\\n\\\\n.a {\\\\n background-image: -webkit-image-set(url(\\" + ___CSS_LOADER_URL___9___ + \\") 1x, url(\\" + ___CSS_LOADER_URL___10___ + \\") 2x)\\\\n}\\\\n\\\\n.a {\\\\n background-image: image-set(url(\\" + ___CSS_LOADER_URL___9___ + \\") 1x, url(\\" + ___CSS_LOADER_URL___10___ + \\") 2x)\\\\n}\\\\n\\\\n.class {\\\\n background: green url() xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: green url('') xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: green url(\\\\\\"\\\\\\") xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: green url(' ') xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: green url(\\\\n \\\\n ) xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: green url(https://raw.githubusercontent.com/webpack/media/master/logo/icon.png) xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: green url(//raw.githubusercontent.com/webpack/media/master/logo/icon.png) xyz;\\\\n}\\\\n\\\\n.class {\\\\n background: url(\\\\\\"./img.png?foo\\\\\\");\\\\n}\\\\n\\\\n.class {\\\\n background: url(\\\\\\"./img.png?foo=bar\\\\\\");\\\\n}\\\\n\\\\n.class {\\\\n background: url(\\\\\\"./img.png?foo=bar#hash\\\\\\");\\\\n}\\\\n\\\\n.class {\\\\n background: url(\\\\\\"./img.png?foo=bar#hash\\\\\\");\\\\n}\\\\n\\\\n.class {\\\\n background: url(\\\\\\"./img.png?\\\\\\");\\\\n}\\\\n\\\\n.class {\\\\n background-image: url('./img.png') url(\\\\\\"data:image/svg+xml;charset=utf-8,\\\\\\") url('./img.png');\\\\n}\\\\n\\\\n.class {\\\\n background: ___CSS_LOADER_URL___;\\\\n background: ___CSS_LOADER_URL___INDEX___;\\\\n background: ___CSS_LOADER_URL___99999___;\\\\n background: ___CSS_LOADER_IMPORT___;\\\\n background: ___CSS_LOADER_IMPORT___INDEX___;\\\\n background: ___CSS_LOADER_IMPORT___99999___;\\\\n}\\\\n\\\\n.pure-url {\\\\n background: url(\\" + ___CSS_LOADER_URL___11___ + \\");\\\\n}\\\\n\\\\n.not-resolved {\\\\n background: url('/img-simple.png');\\\\n}\\\\n\\\\n.above-below {\\\\n background: url(\\" + ___CSS_LOADER_URL___12___ + \\");\\\\n}\\\\n\\\\n.tilde {\\\\n background: url('~package/img.png');\\\\n}\\\\n\\\\n.aliases {\\\\n background: url('~aliasesImg/img.png') ;\\\\n}\\\\n\\", \\"\\"]); + +" +`; + +exports[`url option Function: warnings 1`] = ` +Array [ + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(120:3) Unable to find uri in 'background: green url() xyz'", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(124:3) Unable to find uri in 'background: green url('') xyz'", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(128:3) Unable to find uri in 'background: green url(\\"\\") xyz'", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(132:3) Unable to find uri in 'background: green url(' ') xyz'", + "ModuleWarning: Module Warning (from \`replaced original path\`): +Warning + +(136:3) Unable to find uri in 'background: green url( + ) xyz'", +] +`; + exports[`url option false: errors 1`] = `Array []`; exports[`url option false: module (evaluated) 1`] = ` diff --git a/test/errors.test.js b/test/errors.test.js index 09b602f5..4a6d4a21 100644 --- a/test/errors.test.js +++ b/test/errors.test.js @@ -22,6 +22,7 @@ it('validation', () => { expect(() => validate({ url: true })).not.toThrow(); expect(() => validate({ url: false })).not.toThrow(); + expect(() => validate({ url: () => {} })).not.toThrow(); expect(() => validate({ url: 'true' })).toThrowErrorMatchingSnapshot(); expect(() => validate({ import: true })).not.toThrow(); diff --git a/test/url-option.test.js b/test/url-option.test.js index 0a556763..6893d35a 100644 --- a/test/url-option.test.js +++ b/test/url-option.test.js @@ -54,4 +54,31 @@ describe('url option', () => { ); }); }); + + it('Function', async () => { + const config = { + loader: { + options: { + url: (url, resourcePath) => { + expect(typeof resourcePath === 'string').toBe(true); + + return url.includes('img.png'); + }, + }, + }, + }; + const testId = './url/url.css'; + const stats = await webpack(testId, config); + const { modules } = stats.toJson(); + const module = modules.find((m) => m.id === testId); + + expect(module.source).toMatchSnapshot('module'); + expect(evaluated(module.source, modules)).toMatchSnapshot( + 'module (evaluated)' + ); + expect(normalizeErrors(stats.compilation.warnings)).toMatchSnapshot( + 'warnings' + ); + expect(normalizeErrors(stats.compilation.errors)).toMatchSnapshot('errors'); + }); });