Skip to content

feat: support absolute URLs and DataURI #519

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ type sources =

Default: `true`

By default every loadable attribute (for example - `<img src="image.png">`) is imported (`const img = require('./image.png')` or `import img from "./image.png""`).
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)`).
You may need to specify loaders for images in your configuration (recommended [`asset modules`](https://webpack.js.org/guides/asset-modules/)).

Supported tags and attributes:
Expand Down
14 changes: 14 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,23 @@ export default async function loader(content) {
const imports = [];
const replacements = [];

let isSupportAbsoluteURL = false;

// TODO enable by default in the next major release
if (
this._compilation &&
this._compilation.options &&
this._compilation.options.experiments &&
this._compilation.options.experiments.buildHttp
) {
isSupportAbsoluteURL = true;
}

if (options.sources) {
plugins.push(
sourcesPlugin({
isSupportAbsoluteURL,
isSupportDataURL: options.esModule,
sources: options.sources,
resourcePath: this.resourcePath,
context: this.context,
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/sources-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ export default (options) =>
attributeStartOffset: sourceCodeLocation.attrs[name].startOffset,
attributeEndOffset: sourceCodeLocation.attrs[name].endOffset,
value: attribute.value,
isSupportAbsoluteURL: options.isSupportAbsoluteURL,
isSupportDataURL: options.isSupportDataURL,
isValueQuoted,
valueEndOffset,
valueStartOffset,
Expand Down
91 changes: 40 additions & 51 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,15 @@ export function parseSrc(input) {

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

export function isUrlRequestable(url) {
function isDataUrl(url) {
if (/^data:/i.test(url)) {
return true;
}

return false;
}

export function isURLRequestable(url, options = {}) {
// Protocol-relative URLs
if (/^\/\//.test(url)) {
return false;
Expand All @@ -389,8 +397,26 @@ export function isUrlRequestable(url) {
return true;
}

if (isDataUrl(url) && options.isSupportDataURL) {
try {
decodeURIComponent(url);
} catch (ignoreError) {
return false;
}

return true;
}

if (/^file:/i.test(url)) {
return true;
}

// Absolute URLs
if (/^[a-z][a-z0-9+.-]*:/i.test(url) && !WINDOWS_ABS_PATH_REGEXP.test(url)) {
if (/^https?:/i.test(url)) {
return options.isSupportAbsoluteURL;
}

return false;
}

Expand Down Expand Up @@ -483,7 +509,7 @@ export function requestify(context, request) {
return newRequest;
}

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

Expand Down Expand Up @@ -710,7 +736,12 @@ export function srcType(options) {
);
}

if (!isUrlRequestable(source.value)) {
if (
!isURLRequestable(source.value, {
isSupportDataURL: options.isSupportDataURL,
isSupportAbsoluteURL: options.isSupportAbsoluteURL,
})
) {
return [];
}

Expand Down Expand Up @@ -750,7 +781,12 @@ export function srcsetType(options) {
);
}

if (!isUrlRequestable(source.value)) {
if (
!isURLRequestable(source.value, {
isSupportDataURL: options.isSupportDataURL,
isSupportAbsoluteURL: options.isSupportAbsoluteURL,
})
) {
return false;
}

Expand Down Expand Up @@ -832,53 +868,6 @@ function metaContentType(options) {
return srcType(options);
}

// function webpackImportType(options) {
// let source;
//
// try {
// source = trimASCIIWhitespace(options.value);
// } catch (error) {
// throw new HtmlSourceError(
// `Bad value for attribute "${options.attribute}" on element "${options.tag}": ${error.message}`,
// options.attributeStartOffset,
// options.attributeEndOffset,
// options.html
// );
// }
//
// try {
// source = c0ControlCodesExclude(source);
// } catch (error) {
// throw new HtmlSourceError(
// `Bad value for attribute "${options.attribute}" on element "${options.tag}": ${error.message}`,
// options.attributeStartOffset,
// options.attributeEndOffset,
// options.html
// );
// }
//
// if (!isUrlRequestable(source.value)) {
// return [];
// }
//
// const { startOffset } = options.startTag;
// let { endOffset } = options.startTag;
//
// if (options.endTag) {
// ({ endOffset } = options.endTag);
// }
//
// return [
// {
// format: 'import',
// runtime: false,
// value: source.value,
// startOffset,
// endOffset,
// },
// ];
// }

const defaultSources = new Map([
[
"audio",
Expand Down
132 changes: 70 additions & 62 deletions test/__snapshots__/esModule-option.test.js.snap

Large diffs are not rendered by default.

596 changes: 566 additions & 30 deletions test/__snapshots__/loader.test.js.snap

Large diffs are not rendered by default.

374 changes: 193 additions & 181 deletions test/__snapshots__/minimize-option.test.js.snap

Large diffs are not rendered by default.

656 changes: 354 additions & 302 deletions test/__snapshots__/sources-option.test.js.snap

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion test/fixtures/simple.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ <h2>An Ordered HTML List</h2>
<input type="image" src="image.png" alt="Submit">
</form>

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

<audio controls>
<source src="example.ogg" type="audio/ogg">
Expand Down Expand Up @@ -452,3 +452,5 @@ <h2>An Ordered HTML List</h2>
<noscript>
<img src="./noscript.png" alt="noscript content" />
</noscript>

<img src="https://github.com/raw/webpack-contrib/html-loader/master/test/fixtures/image.png">
27 changes: 27 additions & 0 deletions test/loader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,31 @@ describe("loader", () => {
expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it("should work with `experiments.buildHttp`", async () => {
const compiler = getCompiler(
"simple.js",
{},
{
experiments: {
buildHttp: {
allowedUris: [() => true],
lockfileLocation: path.resolve(
__dirname,
"./lock-files/url/lock.json",
),
cacheLocation: path.resolve(__dirname, "./lock-files/url"),
},
},
},
);
const stats = await compile(compiler);

expect(getModuleSource("./simple.html", stats)).toMatchSnapshot("module");
expect(
execute(readAsset("main.bundle.js", compiler, stats)),
).toMatchSnapshot("result");
expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions test/lock-files/url/lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"https://github.com/webpack-contrib/css-loader/blob/master/src/index.js": {
"integrity": "sha512-1wkV5mn4eBNNjlMgq1+MyEUf8ucfKfJBsq+0oZelJZ7Y5L/ksHDSBojxLj6YbgrWNQfWfp3/SxCYLd/jS+u17g==",
"contentType": "text/html; charset=utf-8"
},
"https://github.com/raw/webpack-contrib/html-loader/master/test/fixtures/image.png": {
"integrity": "sha512-bHqIPBYwzPsVLYcTDqJzwgvIaxLjmezufiCVXAMI0Naelf3eWVdydMA40hXbSuB0dZCGjCepuGaI7Ze8kLM+Ew==",
"contentType": "image/png"
},
"version": 1
}
Loading