Merge pull request #15015 from Knagis/feature/assetModules/outputPath

Add 'outputPath' configuration option for resource asset modules
This commit is contained in:
Tobias Koppers 2022-01-19 14:30:48 +01:00 committed by GitHub
commit d631d14116
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 128 additions and 10 deletions

View File

@ -692,6 +692,15 @@ export type AssetGeneratorDataUrlFunction = (
*/
export type AssetGeneratorOptions = AssetInlineGeneratorOptions &
AssetResourceGeneratorOptions;
/**
* Emit the asset in the specified folder relative to 'output.path'. This should only be needed when custom 'publicPath' is specified to match the folder structure there.
*/
export type AssetModuleOutputPath =
| string
| ((
pathData: import("../lib/Compilation").PathData,
assetInfo?: import("../lib/Compilation").AssetInfo
) => string);
/**
* Function that executes for module and should return whenever asset should be inlined as DataUrl.
*/
@ -2705,6 +2714,10 @@ export interface AssetResourceGeneratorOptions {
* Specifies the filename template of output files on disk. You must **not** specify an absolute path here, but the path may contain folders separated by '/'! The specified path is joined with the value of the 'output.path' option to determine the location on disk.
*/
filename?: FilenameTemplate;
/**
* Emit the asset in the specified folder relative to 'output.path'. This should only be needed when custom 'publicPath' is specified to match the folder structure there.
*/
outputPath?: AssetModuleOutputPath;
/**
* The 'publicPath' specifies the public URL address of the output files when referenced in a browser.
*/

View File

@ -15,6 +15,7 @@ const { makePathsRelative } = require("../util/identifier");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorOptions} AssetGeneratorOptions */
/** @typedef {import("../../declarations/WebpackOptions").AssetModuleOutputPath} AssetModuleOutputPath */
/** @typedef {import("../../declarations/WebpackOptions").RawPublicPath} RawPublicPath */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compiler")} Compiler */
@ -117,13 +118,15 @@ class AssetGenerator extends Generator {
* @param {AssetGeneratorOptions["dataUrl"]=} dataUrlOptions the options for the data url
* @param {string=} filename override for output.assetModuleFilename
* @param {RawPublicPath=} publicPath override for output.assetModulePublicPath
* @param {AssetModuleOutputPath=} outputPath the output path for the emitted file which is not included in the runtime import
* @param {boolean=} emit generate output asset
*/
constructor(dataUrlOptions, filename, publicPath, emit) {
constructor(dataUrlOptions, filename, publicPath, outputPath, emit) {
super();
this.dataUrlOptions = dataUrlOptions;
this.filename = filename;
this.publicPath = publicPath;
this.outputPath = outputPath;
this.emit = emit;
}
@ -276,6 +279,21 @@ class AssetGenerator extends Generator {
sourceFilename,
...assetInfo
};
if (this.outputPath) {
const { path: outputPath, info } =
runtimeTemplate.compilation.getAssetPathWithInfo(
this.outputPath,
{
module,
runtime,
filename: sourceFilename,
chunkGraph,
contentHash
}
);
assetInfo = mergeAssetInfo(assetInfo, info);
filename = path.posix.join(outputPath, filename);
}
module.buildInfo.filename = filename;
module.buildInfo.assetInfo = assetInfo;
if (getData) {

View File

@ -137,9 +137,11 @@ class AssetModulesPlugin {
let filename = undefined;
let publicPath = undefined;
let outputPath = undefined;
if (type !== "asset/inline") {
filename = generatorOptions.filename;
publicPath = generatorOptions.publicPath;
outputPath = generatorOptions.outputPath;
}
const AssetGenerator = getAssetGenerator();
@ -148,6 +150,7 @@ class AssetModulesPlugin {
dataUrl,
filename,
publicPath,
outputPath,
generatorOptions.emit !== false
);
});

File diff suppressed because one or more lines are too long

View File

@ -108,6 +108,9 @@
"filename": {
"$ref": "#/definitions/FilenameTemplate"
},
"outputPath": {
"$ref": "#/definitions/AssetModuleOutputPath"
},
"publicPath": {
"$ref": "#/definitions/RawPublicPath"
}
@ -136,6 +139,19 @@
}
]
},
"AssetModuleOutputPath": {
"description": "Emit the asset in the specified folder relative to 'output.path'. This should only be needed when custom 'publicPath' is specified to match the folder structure there.",
"anyOf": [
{
"type": "string",
"absolutePath": false
},
{
"instanceof": "Function",
"tsType": "((pathData: import(\"../lib/Compilation\").PathData, assetInfo?: import(\"../lib/Compilation\").AssetInfo) => string)"
}
]
},
"AssetParserDataUrlFunction": {
"description": "Function that executes for module and should return whenever asset should be inlined as DataUrl.",
"instanceof": "Function",
@ -182,6 +198,9 @@
"filename": {
"$ref": "#/definitions/FilenameTemplate"
},
"outputPath": {
"$ref": "#/definitions/AssetModuleOutputPath"
},
"publicPath": {
"$ref": "#/definitions/RawPublicPath"
}

View File

@ -3,4 +3,4 @@
* DO NOT MODIFY BY HAND.
* Run `yarn special-lint-fix` to update
*/
const t=/^(?:[A-Za-z]:[\\/]|\\\\|\/)/;function n(t,{instancePath:r="",parentData:e,parentDataProperty:a,rootData:s=t}={}){let o=null,l=0;const i=l;let p=!1;const c=l;if(l==l)if(t&&"object"==typeof t&&!Array.isArray(t)){const n=l;for(const n in t)if("encoding"!==n&&"mimetype"!==n){const t={params:{additionalProperty:n}};null===o?o=[t]:o.push(t),l++;break}if(n===l){if(void 0!==t.encoding){let n=t.encoding;const r=l;if(!1!==n&&"base64"!==n){const t={params:{}};null===o?o=[t]:o.push(t),l++}var u=r===l}else u=!0;if(u)if(void 0!==t.mimetype){const n=l;if("string"!=typeof t.mimetype){const t={params:{type:"string"}};null===o?o=[t]:o.push(t),l++}u=n===l}else u=!0}}else{const t={params:{type:"object"}};null===o?o=[t]:o.push(t),l++}var f=c===l;if(p=p||f,!p){const n=l;if(!(t instanceof Function)){const t={params:{}};null===o?o=[t]:o.push(t),l++}f=n===l,p=p||f}if(!p){const t={params:{}};return null===o?o=[t]:o.push(t),l++,n.errors=o,!1}return l=i,null!==o&&(i?o.length=i:o=null),n.errors=o,0===l}function r(e,{instancePath:a="",parentData:s,parentDataProperty:o,rootData:l=e}={}){let i=null,p=0;if(0===p){if(!e||"object"!=typeof e||Array.isArray(e))return r.errors=[{params:{type:"object"}}],!1;{const s=p;for(const t in e)if("dataUrl"!==t&&"emit"!==t&&"filename"!==t&&"publicPath"!==t)return r.errors=[{params:{additionalProperty:t}}],!1;if(s===p){if(void 0!==e.dataUrl){const t=p;n(e.dataUrl,{instancePath:a+"/dataUrl",parentData:e,parentDataProperty:"dataUrl",rootData:l})||(i=null===i?n.errors:i.concat(n.errors),p=i.length);var c=t===p}else c=!0;if(c){if(void 0!==e.emit){const t=p;if("boolean"!=typeof e.emit)return r.errors=[{params:{type:"boolean"}}],!1;c=t===p}else c=!0;if(c){if(void 0!==e.filename){let n=e.filename;const a=p,s=p;let o=!1;const l=p;if(p===l)if("string"==typeof n){if(n.includes("!")||!1!==t.test(n)){const t={params:{}};null===i?i=[t]:i.push(t),p++}else if(n.length<1){const t={params:{}};null===i?i=[t]:i.push(t),p++}}else{const t={params:{type:"string"}};null===i?i=[t]:i.push(t),p++}var u=l===p;if(o=o||u,!o){const t=p;if(!(n instanceof Function)){const t={params:{}};null===i?i=[t]:i.push(t),p++}u=t===p,o=o||u}if(!o){const t={params:{}};return null===i?i=[t]:i.push(t),p++,r.errors=i,!1}p=s,null!==i&&(s?i.length=s:i=null),c=a===p}else c=!0;if(c)if(void 0!==e.publicPath){let t=e.publicPath;const n=p,a=p;let s=!1;const o=p;if("string"!=typeof t){const t={params:{type:"string"}};null===i?i=[t]:i.push(t),p++}var f=o===p;if(s=s||f,!s){const n=p;if(!(t instanceof Function)){const t={params:{}};null===i?i=[t]:i.push(t),p++}f=n===p,s=s||f}if(!s){const t={params:{}};return null===i?i=[t]:i.push(t),p++,r.errors=i,!1}p=a,null!==i&&(a?i.length=a:i=null),c=n===p}else c=!0}}}}}return r.errors=i,0===p}function e(t,{instancePath:n="",parentData:a,parentDataProperty:s,rootData:o=t}={}){let l=null,i=0;return r(t,{instancePath:n,parentData:a,parentDataProperty:s,rootData:o})||(l=null===l?r.errors:l.concat(r.errors),i=l.length),e.errors=l,0===i}module.exports=e,module.exports.default=e;
const t=/^(?:[A-Za-z]:[\\/]|\\\\|\/)/;function n(t,{instancePath:r="",parentData:e,parentDataProperty:a,rootData:s=t}={}){let o=null,l=0;const i=l;let p=!1;const u=l;if(l==l)if(t&&"object"==typeof t&&!Array.isArray(t)){const n=l;for(const n in t)if("encoding"!==n&&"mimetype"!==n){const t={params:{additionalProperty:n}};null===o?o=[t]:o.push(t),l++;break}if(n===l){if(void 0!==t.encoding){let n=t.encoding;const r=l;if(!1!==n&&"base64"!==n){const t={params:{}};null===o?o=[t]:o.push(t),l++}var c=r===l}else c=!0;if(c)if(void 0!==t.mimetype){const n=l;if("string"!=typeof t.mimetype){const t={params:{type:"string"}};null===o?o=[t]:o.push(t),l++}c=n===l}else c=!0}}else{const t={params:{type:"object"}};null===o?o=[t]:o.push(t),l++}var f=u===l;if(p=p||f,!p){const n=l;if(!(t instanceof Function)){const t={params:{}};null===o?o=[t]:o.push(t),l++}f=n===l,p=p||f}if(!p){const t={params:{}};return null===o?o=[t]:o.push(t),l++,n.errors=o,!1}return l=i,null!==o&&(i?o.length=i:o=null),n.errors=o,0===l}function r(e,{instancePath:a="",parentData:s,parentDataProperty:o,rootData:l=e}={}){let i=null,p=0;if(0===p){if(!e||"object"!=typeof e||Array.isArray(e))return r.errors=[{params:{type:"object"}}],!1;{const s=p;for(const t in e)if("dataUrl"!==t&&"emit"!==t&&"filename"!==t&&"outputPath"!==t&&"publicPath"!==t)return r.errors=[{params:{additionalProperty:t}}],!1;if(s===p){if(void 0!==e.dataUrl){const t=p;n(e.dataUrl,{instancePath:a+"/dataUrl",parentData:e,parentDataProperty:"dataUrl",rootData:l})||(i=null===i?n.errors:i.concat(n.errors),p=i.length);var u=t===p}else u=!0;if(u){if(void 0!==e.emit){const t=p;if("boolean"!=typeof e.emit)return r.errors=[{params:{type:"boolean"}}],!1;u=t===p}else u=!0;if(u){if(void 0!==e.filename){let n=e.filename;const a=p,s=p;let o=!1;const l=p;if(p===l)if("string"==typeof n){if(n.includes("!")||!1!==t.test(n)){const t={params:{}};null===i?i=[t]:i.push(t),p++}else if(n.length<1){const t={params:{}};null===i?i=[t]:i.push(t),p++}}else{const t={params:{type:"string"}};null===i?i=[t]:i.push(t),p++}var c=l===p;if(o=o||c,!o){const t=p;if(!(n instanceof Function)){const t={params:{}};null===i?i=[t]:i.push(t),p++}c=t===p,o=o||c}if(!o){const t={params:{}};return null===i?i=[t]:i.push(t),p++,r.errors=i,!1}p=s,null!==i&&(s?i.length=s:i=null),u=a===p}else u=!0;if(u){if(void 0!==e.outputPath){let n=e.outputPath;const a=p,s=p;let o=!1;const l=p;if(p===l)if("string"==typeof n){if(n.includes("!")||!1!==t.test(n)){const t={params:{}};null===i?i=[t]:i.push(t),p++}}else{const t={params:{type:"string"}};null===i?i=[t]:i.push(t),p++}var f=l===p;if(o=o||f,!o){const t=p;if(!(n instanceof Function)){const t={params:{}};null===i?i=[t]:i.push(t),p++}f=t===p,o=o||f}if(!o){const t={params:{}};return null===i?i=[t]:i.push(t),p++,r.errors=i,!1}p=s,null!==i&&(s?i.length=s:i=null),u=a===p}else u=!0;if(u)if(void 0!==e.publicPath){let t=e.publicPath;const n=p,a=p;let s=!1;const o=p;if("string"!=typeof t){const t={params:{type:"string"}};null===i?i=[t]:i.push(t),p++}var h=o===p;if(s=s||h,!s){const n=p;if(!(t instanceof Function)){const t={params:{}};null===i?i=[t]:i.push(t),p++}h=n===p,s=s||h}if(!s){const t={params:{}};return null===i?i=[t]:i.push(t),p++,r.errors=i,!1}p=a,null!==i&&(a?i.length=a:i=null),u=n===p}else u=!0}}}}}}return r.errors=i,0===p}function e(t,{instancePath:n="",parentData:a,parentDataProperty:s,rootData:o=t}={}){let l=null,i=0;return r(t,{instancePath:n,parentData:a,parentDataProperty:s,rootData:o})||(l=null===l?r.errors:l.concat(r.errors),i=l.length),e.errors=l,0===i}module.exports=e,module.exports.default=e;

View File

@ -3,4 +3,4 @@
* DO NOT MODIFY BY HAND.
* Run `yarn special-lint-fix` to update
*/
const t=/^(?:[A-Za-z]:[\\/]|\\\\|\/)/;function r(n,{instancePath:e="",parentData:a,parentDataProperty:s,rootData:o=n}={}){let l=null,i=0;if(0===i){if(!n||"object"!=typeof n||Array.isArray(n))return r.errors=[{params:{type:"object"}}],!1;{const e=i;for(const t in n)if("emit"!==t&&"filename"!==t&&"publicPath"!==t)return r.errors=[{params:{additionalProperty:t}}],!1;if(e===i){if(void 0!==n.emit){const t=i;if("boolean"!=typeof n.emit)return r.errors=[{params:{type:"boolean"}}],!1;var p=t===i}else p=!0;if(p){if(void 0!==n.filename){let e=n.filename;const a=i,s=i;let o=!1;const c=i;if(i===c)if("string"==typeof e){if(e.includes("!")||!1!==t.test(e)){const t={params:{}};null===l?l=[t]:l.push(t),i++}else if(e.length<1){const t={params:{}};null===l?l=[t]:l.push(t),i++}}else{const t={params:{type:"string"}};null===l?l=[t]:l.push(t),i++}var u=c===i;if(o=o||u,!o){const t=i;if(!(e instanceof Function)){const t={params:{}};null===l?l=[t]:l.push(t),i++}u=t===i,o=o||u}if(!o){const t={params:{}};return null===l?l=[t]:l.push(t),i++,r.errors=l,!1}i=s,null!==l&&(s?l.length=s:l=null),p=a===i}else p=!0;if(p)if(void 0!==n.publicPath){let t=n.publicPath;const e=i,a=i;let s=!1;const o=i;if("string"!=typeof t){const t={params:{type:"string"}};null===l?l=[t]:l.push(t),i++}var c=o===i;if(s=s||c,!s){const r=i;if(!(t instanceof Function)){const t={params:{}};null===l?l=[t]:l.push(t),i++}c=r===i,s=s||c}if(!s){const t={params:{}};return null===l?l=[t]:l.push(t),i++,r.errors=l,!1}i=a,null!==l&&(a?l.length=a:l=null),p=e===i}else p=!0}}}}return r.errors=l,0===i}function n(t,{instancePath:e="",parentData:a,parentDataProperty:s,rootData:o=t}={}){let l=null,i=0;return r(t,{instancePath:e,parentData:a,parentDataProperty:s,rootData:o})||(l=null===l?r.errors:l.concat(r.errors),i=l.length),n.errors=l,0===i}module.exports=n,module.exports.default=n;
const t=/^(?:[A-Za-z]:[\\/]|\\\\|\/)/;function n(r,{instancePath:e="",parentData:s,parentDataProperty:a,rootData:o=r}={}){let l=null,i=0;if(0===i){if(!r||"object"!=typeof r||Array.isArray(r))return n.errors=[{params:{type:"object"}}],!1;{const e=i;for(const t in r)if("emit"!==t&&"filename"!==t&&"outputPath"!==t&&"publicPath"!==t)return n.errors=[{params:{additionalProperty:t}}],!1;if(e===i){if(void 0!==r.emit){const t=i;if("boolean"!=typeof r.emit)return n.errors=[{params:{type:"boolean"}}],!1;var u=t===i}else u=!0;if(u){if(void 0!==r.filename){let e=r.filename;const s=i,a=i;let o=!1;const c=i;if(i===c)if("string"==typeof e){if(e.includes("!")||!1!==t.test(e)){const t={params:{}};null===l?l=[t]:l.push(t),i++}else if(e.length<1){const t={params:{}};null===l?l=[t]:l.push(t),i++}}else{const t={params:{type:"string"}};null===l?l=[t]:l.push(t),i++}var p=c===i;if(o=o||p,!o){const t=i;if(!(e instanceof Function)){const t={params:{}};null===l?l=[t]:l.push(t),i++}p=t===i,o=o||p}if(!o){const t={params:{}};return null===l?l=[t]:l.push(t),i++,n.errors=l,!1}i=a,null!==l&&(a?l.length=a:l=null),u=s===i}else u=!0;if(u){if(void 0!==r.outputPath){let e=r.outputPath;const s=i,a=i;let o=!1;const p=i;if(i===p)if("string"==typeof e){if(e.includes("!")||!1!==t.test(e)){const t={params:{}};null===l?l=[t]:l.push(t),i++}}else{const t={params:{type:"string"}};null===l?l=[t]:l.push(t),i++}var c=p===i;if(o=o||c,!o){const t=i;if(!(e instanceof Function)){const t={params:{}};null===l?l=[t]:l.push(t),i++}c=t===i,o=o||c}if(!o){const t={params:{}};return null===l?l=[t]:l.push(t),i++,n.errors=l,!1}i=a,null!==l&&(a?l.length=a:l=null),u=s===i}else u=!0;if(u)if(void 0!==r.publicPath){let t=r.publicPath;const e=i,s=i;let a=!1;const o=i;if("string"!=typeof t){const t={params:{type:"string"}};null===l?l=[t]:l.push(t),i++}var f=o===i;if(a=a||f,!a){const n=i;if(!(t instanceof Function)){const t={params:{}};null===l?l=[t]:l.push(t),i++}f=n===i,a=a||f}if(!a){const t={params:{}};return null===l?l=[t]:l.push(t),i++,n.errors=l,!1}i=s,null!==l&&(s?l.length=s:l=null),u=e===i}else u=!0}}}}}return n.errors=l,0===i}function r(t,{instancePath:e="",parentData:s,parentDataProperty:a,rootData:o=t}={}){let l=null,i=0;return n(t,{instancePath:e,parentData:s,parentDataProperty:a,rootData:o})||(l=null===l?n.errors:l.concat(n.errors),i=l.length),r.errors=l,0===i}module.exports=r,module.exports.default=r;

View File

@ -1283,6 +1283,19 @@ Object {
"multiple": false,
"simpleType": "string",
},
"module-generator-asset-output-path": Object {
"configs": Array [
Object {
"description": "Emit the asset in the specified folder relative to 'output.path'. This should only be needed when custom 'publicPath' is specified to match the folder structure there.",
"multiple": false,
"path": "module.generator.asset.outputPath",
"type": "string",
},
],
"description": "Emit the asset in the specified folder relative to 'output.path'. This should only be needed when custom 'publicPath' is specified to match the folder structure there.",
"multiple": false,
"simpleType": "string",
},
"module-generator-asset-public-path": Object {
"configs": Array [
Object {
@ -1322,6 +1335,19 @@ Object {
"multiple": false,
"simpleType": "string",
},
"module-generator-asset-resource-output-path": Object {
"configs": Array [
Object {
"description": "Emit the asset in the specified folder relative to 'output.path'. This should only be needed when custom 'publicPath' is specified to match the folder structure there.",
"multiple": false,
"path": "module.generator.asset/resource.outputPath",
"type": "string",
},
],
"description": "Emit the asset in the specified folder relative to 'output.path'. This should only be needed when custom 'publicPath' is specified to match the folder structure there.",
"multiple": false,
"simpleType": "string",
},
"module-generator-asset-resource-public-path": Object {
"configs": Array [
Object {

View File

@ -1,11 +1,12 @@
import url from "../_images/file.png";
import url2 from "../_images/file.jpg";
import fs from "fs";
import path from "path";
it("should output asset with path", () => {
expect(url).toEqual("images/file.png");
expect(() => fs.statSync(url)).toThrowError(
expect.objectContaining({
code: "ENOENT"
})
);
expect(url2).toEqual("images/file.jpg");
expect(fs.existsSync(path.join(__STATS__.outputPath, url))).toBe(false);
expect(fs.existsSync(path.join(__STATS__.outputPath, url2))).toBe(true);
});

View File

@ -8,10 +8,14 @@ module.exports = {
rules: [
{
test: /\.png$/,
type: "asset",
type: "asset/resource",
generator: {
emit: false
}
},
{
test: /\.jpg$/,
type: "asset/resource"
}
]
}

View File

@ -0,0 +1,10 @@
import url from "../_images/file.png";
import fs from "fs";
import path from "path";
it("should emit asset with module.generator.asset.outputPath", () => {
expect(url).toEqual("https://cdn/assets/file.png");
const emitPath = path.join(__STATS__.outputPath, "cdn-assets/file.png")
expect(fs.existsSync(emitPath)).toBe(true);
});

View File

@ -0,0 +1,19 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
mode: "development",
output: {
assetModuleFilename: "file[ext]"
},
module: {
rules: [
{
test: /\.png$/,
type: "asset/resource",
generator: {
publicPath: "https://cdn/assets/",
outputPath: "cdn-assets/"
}
}
]
}
};

5
types.d.ts vendored
View File

@ -321,6 +321,11 @@ declare interface AssetResourceGeneratorOptions {
*/
filename?: string | ((pathData: PathData, assetInfo?: AssetInfo) => string);
/**
* Emit the asset in the specified folder relative to 'output.path'. This should only be needed when custom 'publicPath' is specified to match the folder structure there.
*/
outputPath?: string | ((pathData: PathData, assetInfo?: AssetInfo) => string);
/**
* The 'publicPath' specifies the public URL address of the output files when referenced in a browser.
*/