Skip to content

Commit cc34b06

Browse files
feat: support absolute URLs and DataURI (#519)
1 parent 536a204 commit cc34b06

12 files changed

+1281
-628
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ type sources =
9797

9898
Default: `true`
9999

100-
By default every loadable attribute (for example - `<img src="image.png">`) is imported (`const img = require('./image.png')` or `import img from "./image.png""`).
100+
By default every loadable attribute (for example - `<img src="image.png">`) is imported (`const img = require('./image.png')` or `new URL("./image.png", import.meta.url)`).
101101
You may need to specify loaders for images in your configuration (recommended [`asset modules`](https://webpack.js.org/guides/asset-modules/)).
102102

103103
Supported tags and attributes:

src/index.js

+14
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,23 @@ export default async function loader(content) {
2424
const imports = [];
2525
const replacements = [];
2626

27+
let isSupportAbsoluteURL = false;
28+
29+
// TODO enable by default in the next major release
30+
if (
31+
this._compilation &&
32+
this._compilation.options &&
33+
this._compilation.options.experiments &&
34+
this._compilation.options.experiments.buildHttp
35+
) {
36+
isSupportAbsoluteURL = true;
37+
}
38+
2739
if (options.sources) {
2840
plugins.push(
2941
sourcesPlugin({
42+
isSupportAbsoluteURL,
43+
isSupportDataURL: options.esModule,
3044
sources: options.sources,
3145
resourcePath: this.resourcePath,
3246
context: this.context,

src/plugins/sources-plugin.js

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ export default (options) =>
9898
attributeStartOffset: sourceCodeLocation.attrs[name].startOffset,
9999
attributeEndOffset: sourceCodeLocation.attrs[name].endOffset,
100100
value: attribute.value,
101+
isSupportAbsoluteURL: options.isSupportAbsoluteURL,
102+
isSupportDataURL: options.isSupportDataURL,
101103
isValueQuoted,
102104
valueEndOffset,
103105
valueStartOffset,

src/utils.js

+40-51
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,15 @@ export function parseSrc(input) {
378378

379379
const WINDOWS_ABS_PATH_REGEXP = /^[a-zA-Z]:[\\/]|^\\\\/;
380380

381-
export function isUrlRequestable(url) {
381+
function isDataUrl(url) {
382+
if (/^data:/i.test(url)) {
383+
return true;
384+
}
385+
386+
return false;
387+
}
388+
389+
export function isURLRequestable(url, options = {}) {
382390
// Protocol-relative URLs
383391
if (/^\/\//.test(url)) {
384392
return false;
@@ -389,8 +397,26 @@ export function isUrlRequestable(url) {
389397
return true;
390398
}
391399

400+
if (isDataUrl(url) && options.isSupportDataURL) {
401+
try {
402+
decodeURIComponent(url);
403+
} catch (ignoreError) {
404+
return false;
405+
}
406+
407+
return true;
408+
}
409+
410+
if (/^file:/i.test(url)) {
411+
return true;
412+
}
413+
392414
// Absolute URLs
393415
if (/^[a-z][a-z0-9+.-]*:/i.test(url) && !WINDOWS_ABS_PATH_REGEXP.test(url)) {
416+
if (/^https?:/i.test(url)) {
417+
return options.isSupportAbsoluteURL;
418+
}
419+
394420
return false;
395421
}
396422

@@ -483,7 +509,7 @@ export function requestify(context, request) {
483509
return newRequest;
484510
}
485511

486-
if (/^file:/i.test(newRequest)) {
512+
if (/^[a-z]+:/i.test(newRequest)) {
487513
return newRequest;
488514
}
489515

@@ -710,7 +736,12 @@ export function srcType(options) {
710736
);
711737
}
712738

713-
if (!isUrlRequestable(source.value)) {
739+
if (
740+
!isURLRequestable(source.value, {
741+
isSupportDataURL: options.isSupportDataURL,
742+
isSupportAbsoluteURL: options.isSupportAbsoluteURL,
743+
})
744+
) {
714745
return [];
715746
}
716747

@@ -750,7 +781,12 @@ export function srcsetType(options) {
750781
);
751782
}
752783

753-
if (!isUrlRequestable(source.value)) {
784+
if (
785+
!isURLRequestable(source.value, {
786+
isSupportDataURL: options.isSupportDataURL,
787+
isSupportAbsoluteURL: options.isSupportAbsoluteURL,
788+
})
789+
) {
754790
return false;
755791
}
756792

@@ -832,53 +868,6 @@ function metaContentType(options) {
832868
return srcType(options);
833869
}
834870

835-
// function webpackImportType(options) {
836-
// let source;
837-
//
838-
// try {
839-
// source = trimASCIIWhitespace(options.value);
840-
// } catch (error) {
841-
// throw new HtmlSourceError(
842-
// `Bad value for attribute "${options.attribute}" on element "${options.tag}": ${error.message}`,
843-
// options.attributeStartOffset,
844-
// options.attributeEndOffset,
845-
// options.html
846-
// );
847-
// }
848-
//
849-
// try {
850-
// source = c0ControlCodesExclude(source);
851-
// } catch (error) {
852-
// throw new HtmlSourceError(
853-
// `Bad value for attribute "${options.attribute}" on element "${options.tag}": ${error.message}`,
854-
// options.attributeStartOffset,
855-
// options.attributeEndOffset,
856-
// options.html
857-
// );
858-
// }
859-
//
860-
// if (!isUrlRequestable(source.value)) {
861-
// return [];
862-
// }
863-
//
864-
// const { startOffset } = options.startTag;
865-
// let { endOffset } = options.startTag;
866-
//
867-
// if (options.endTag) {
868-
// ({ endOffset } = options.endTag);
869-
// }
870-
//
871-
// return [
872-
// {
873-
// format: 'import',
874-
// runtime: false,
875-
// value: source.value,
876-
// startOffset,
877-
// endOffset,
878-
// },
879-
// ];
880-
// }
881-
882871
const defaultSources = new Map([
883872
[
884873
"audio",

test/__snapshots__/esModule-option.test.js.snap

+70-62
Large diffs are not rendered by default.

test/__snapshots__/loader.test.js.snap

+566-30
Large diffs are not rendered by default.

test/__snapshots__/minimize-option.test.js.snap

+193-181
Large diffs are not rendered by default.

test/__snapshots__/sources-option.test.js.snap

+354-302
Large diffs are not rendered by default.

test/fixtures/simple.html

+3-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ <h2>An Ordered HTML List</h2>
9898
<input type="image" src="image.png" alt="Submit">
9999
</form>
100100

101-
<script src="https://domain.org/script.js"></script>
101+
<script src="https://github.com/webpack-contrib/css-loader/blob/master/src/index.js"></script>
102102

103103
<audio controls>
104104
<source src="example.ogg" type="audio/ogg">
@@ -452,3 +452,5 @@ <h2>An Ordered HTML List</h2>
452452
<noscript>
453453
<img src="./noscript.png" alt="noscript content" />
454454
</noscript>
455+
456+
<img src="https://raw.githubusercontent.com/webpack-contrib/html-loader/master/test/fixtures/image.png">

test/loader.test.js

+27
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,31 @@ describe("loader", () => {
218218
expect(getWarnings(stats)).toMatchSnapshot("warnings");
219219
expect(getErrors(stats)).toMatchSnapshot("errors");
220220
});
221+
222+
it("should work with `experiments.buildHttp`", async () => {
223+
const compiler = getCompiler(
224+
"simple.js",
225+
{},
226+
{
227+
experiments: {
228+
buildHttp: {
229+
allowedUris: [() => true],
230+
lockfileLocation: path.resolve(
231+
__dirname,
232+
"./lock-files/url/lock.json",
233+
),
234+
cacheLocation: path.resolve(__dirname, "./lock-files/url"),
235+
},
236+
},
237+
},
238+
);
239+
const stats = await compile(compiler);
240+
241+
expect(getModuleSource("./simple.html", stats)).toMatchSnapshot("module");
242+
expect(
243+
execute(readAsset("main.bundle.js", compiler, stats)),
244+
).toMatchSnapshot("result");
245+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
246+
expect(getErrors(stats)).toMatchSnapshot("errors");
247+
});
221248
});

test/lock-files/url/lock.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"https://github.com/webpack-contrib/css-loader/blob/master/src/index.js": {
3+
"integrity": "sha512-1wkV5mn4eBNNjlMgq1+MyEUf8ucfKfJBsq+0oZelJZ7Y5L/ksHDSBojxLj6YbgrWNQfWfp3/SxCYLd/jS+u17g==",
4+
"contentType": "text/html; charset=utf-8"
5+
},
6+
"https://raw.githubusercontent.com/webpack-contrib/html-loader/master/test/fixtures/image.png": {
7+
"integrity": "sha512-bHqIPBYwzPsVLYcTDqJzwgvIaxLjmezufiCVXAMI0Naelf3eWVdydMA40hXbSuB0dZCGjCepuGaI7Ze8kLM+Ew==",
8+
"contentType": "image/png"
9+
},
10+
"version": 1
11+
}

0 commit comments

Comments
 (0)