Merge pull request #9304 from webpack/bugifx/issue-9296

allow filename as function at more places
This commit is contained in:
Tobias Koppers 2019-06-19 00:06:52 +02:00 committed by GitHub
commit d675f4e3ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 165 additions and 28 deletions

View File

@ -973,9 +973,11 @@ export interface OptimizationSplitChunksOptions {
minSize?: OptimizationSplitChunksSizes;
};
/**
* Sets the template for the filename for created chunks (Only works for initial chunks)
* Sets the template for the filename for created chunks
*/
filename?: string;
filename?:
| string
| ((pathData: import("../lib/Compilation").PathData) => string);
/**
* Prevents exposing path info when creating names for parts splitted by maxSize
*/
@ -1037,9 +1039,11 @@ export interface OptimizationSplitChunksCacheGroup {
*/
enforce?: boolean;
/**
* Sets the template for the filename for created chunks (Only works for initial chunks)
* Sets the template for the filename for created chunks
*/
filename?: string;
filename?:
| string
| ((pathData: import("../lib/Compilation").PathData) => string);
/**
* Sets the hint for chunk id
*/
@ -1156,7 +1160,9 @@ export interface OutputOptions {
/**
* Specifies the name of each output file on disk. You must **not** specify an absolute path here! The `output.path` option determines the location on disk the files are written to, filename is used solely for naming the individual files.
*/
filename?: string | Function;
filename?:
| string
| ((pathData: import("../lib/Compilation").PathData) => string);
/**
* An expression which is used to address the global object/scope in runtime code
*/

View File

@ -19,6 +19,7 @@ const { arrayToSetDeprecation } = require("./util/deprecation");
/** @typedef {import("./ChunkGraph").ModuleFilterPredicate} ModuleFilterPredicate */
/** @typedef {import("./ChunkGroup")} ChunkGroup */
/** @typedef {import("./Compilation")} Compilation */
/** @typedef {import("./Compilation").PathData} PathData */
/** @typedef {import("./Module")} Module */
/** @typedef {import("./ModuleGraph")} ModuleGraph */
/** @typedef {import("./util/createHash").Hash} Hash */
@ -70,7 +71,7 @@ class Chunk {
this.idNameHints = new SortableSet();
/** @type {boolean} */
this.preventIntegration = false;
/** @type {string?} */
/** @type {(string | function(PathData): string)?} */
this.filenameTemplate = undefined;
/** @private @type {SortableSet<ChunkGroup>} */
this._groups = new SortableSet(undefined, sortChunkGroupById);

View File

@ -25,6 +25,7 @@ const MinMaxSizeWarning = require("./MinMaxSizeWarning");
/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksSizes} OptimizationSplitChunksSizes */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Compilation").PathData} PathData */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
@ -60,7 +61,7 @@ const MinMaxSizeWarning = require("./MinMaxSizeWarning");
* @property {number=} minChunks
* @property {number=} maxAsyncRequests
* @property {number=} maxInitialRequests
* @property {string=} filename
* @property {(string | function(PathData): string)=} filename
* @property {string=} idHint
* @property {string} automaticNameDelimiter
* @property {boolean=} reuseExistingChunk
@ -81,7 +82,7 @@ const MinMaxSizeWarning = require("./MinMaxSizeWarning");
* @property {number=} minChunks
* @property {number=} maxAsyncRequests
* @property {number=} maxInitialRequests
* @property {string=} filename
* @property {(string | function(PathData): string)=} filename
* @property {string=} idHint
* @property {string} automaticNameDelimiter
* @property {boolean=} reuseExistingChunk
@ -127,7 +128,7 @@ const MinMaxSizeWarning = require("./MinMaxSizeWarning");
* @property {number} maxAsyncRequests
* @property {number} maxInitialRequests
* @property {boolean} hidePathInfo
* @property {string} filename
* @property {string | function(PathData): string} filename
* @property {string} automaticNameDelimiter
* @property {GetCacheGroups} getCacheGroups
* @property {GetName} getName

View File

@ -8,7 +8,22 @@ const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compilation").PathData} PathData */
/** @typedef {function(PathData): string} FilenameFunction */
class GetChunkFilenameRuntimeModule extends RuntimeModule {
/**
* @param {Compilation} compilation the compilation
* @param {Chunk} chunk the chunk
* @param {string} contentType the contentType to use the content hash for
* @param {string} name kind of filename
* @param {string} global function name to be assigned
* @param {function(Chunk): string | FilenameFunction} getFilenameForChunk functor to get the filename or function
* @param {boolean} allChunks when false, only async chunks are included
*/
constructor(
compilation,
chunk,
@ -41,9 +56,16 @@ class GetChunkFilenameRuntimeModule extends RuntimeModule {
} = this;
const mainTemplate = compilation.mainTemplate;
/** @type {Map<string | FilenameFunction, Set<Chunk>>} */
const chunkFilenames = new Map();
let maxChunks = 0;
/** @type {string} */
let dynamicFilename;
/**
* @param {Chunk} c the chunk
* @returns {void}
*/
const addChunk = c => {
const chunkFilename = getFilenameForChunk(c);
if (chunkFilename) {
@ -52,18 +74,21 @@ class GetChunkFilenameRuntimeModule extends RuntimeModule {
chunkFilenames.set(chunkFilename, (set = new Set()));
}
set.add(c);
if (set.size < maxChunks) return;
if (set.size === maxChunks) {
if (chunkFilename.length < dynamicFilename.length) return;
if (chunkFilename.length === dynamicFilename.length) {
if (chunkFilename < dynamicFilename) return;
if (typeof chunkFilename === "string") {
if (set.size < maxChunks) return;
if (set.size === maxChunks) {
if (chunkFilename.length < dynamicFilename.length) return;
if (chunkFilename.length === dynamicFilename.length) {
if (chunkFilename < dynamicFilename) return;
}
}
maxChunks = set.size;
dynamicFilename = chunkFilename;
}
maxChunks = set.size;
dynamicFilename = chunkFilename;
}
};
/** @type {string[]} */
const includedChunksMessages = [];
if (allChunks) {
includedChunksMessages.push("all chunks");
@ -88,23 +113,43 @@ class GetChunkFilenameRuntimeModule extends RuntimeModule {
}
}
/** @type {Map<string, Set<string | number>>} */
const staticUrls = new Map();
/** @type {Set<Chunk>} */
const dynamicUrlChunks = new Set();
/**
* @param {Chunk} c the chunk
* @param {string | FilenameFunction} chunkFilename the filename template for the chunk
* @returns {void}
*/
const addStaticUrl = (c, chunkFilename) => {
/**
* @param {string | number} value a value
* @returns {string} string to put in quotes
*/
const unquotedStringify = value => {
const str = `${value}`;
if (str.length >= 5 && str === `${c.id}`) {
// This is shorter and generates the same result
return `" + chunkId + "`;
return '" + chunkId + "';
}
const s = JSON.stringify(str);
return s.slice(1, s.length - 2);
};
const unquotedStringifyWithLength = value => length =>
unquotedStringify(`${value}`.slice(0, length));
const chunkFilenameValue =
typeof chunkFilename === "function"
? JSON.stringify(
chunkFilename({
chunk: c,
contentHashType: contentType
})
)
: JSON.stringify(chunkFilename);
const staticChunkFilename = mainTemplate.getAssetPath(
JSON.stringify(chunkFilename),
chunkFilenameValue,
{
hash: `" + ${RuntimeGlobals.getFullHash}() + "`,
hashWithLength: length =>
@ -141,6 +186,10 @@ class GetChunkFilenameRuntimeModule extends RuntimeModule {
}
}
/**
* @param {function(Chunk): string | number} fn function from chunk to value
* @returns {string} code with static mapping of results of fn
*/
const createMap = fn => {
const obj = {};
let useId = false;
@ -168,9 +217,19 @@ class GetChunkFilenameRuntimeModule extends RuntimeModule {
? `(${JSON.stringify(obj)}[chunkId] || chunkId)`
: `${JSON.stringify(obj)}[chunkId]`;
};
/**
* @param {function(Chunk): string | number} fn function from chunk to value
* @returns {string} code with static mapping of results of fn for including in quoted string
*/
const mapExpr = fn => {
return `" + ${createMap(fn)} + "`;
};
/**
* @param {function(Chunk): string | number} fn function from chunk to value
* @returns {function(number): string} function which generates code with static mapping of results of fn for including in quoted string for specific length
*/
const mapExprWithLength = fn => length => {
return `" + ${createMap(c => `${fn(c)}`.slice(0, length))} + "`;
};

View File

@ -642,9 +642,18 @@
"type": "boolean"
},
"filename": {
"description": "Sets the template for the filename for created chunks (Only works for initial chunks)",
"type": "string",
"minLength": 1
"description": "Sets the template for the filename for created chunks",
"anyOf": [
{
"type": "string",
"absolutePath": false,
"minLength": 1
},
{
"instanceof": "Function",
"tsType": "((pathData: import(\"../lib/Compilation\").PathData) => string)"
}
]
},
"idHint": {
"description": "Sets the hint for chunk id",
@ -858,9 +867,18 @@
}
},
"filename": {
"description": "Sets the template for the filename for created chunks (Only works for initial chunks)",
"type": "string",
"minLength": 1
"description": "Sets the template for the filename for created chunks",
"anyOf": [
{
"type": "string",
"absolutePath": false,
"minLength": 1
},
{
"instanceof": "Function",
"tsType": "((pathData: import(\"../lib/Compilation\").PathData) => string)"
}
]
},
"hidePathInfo": {
"description": "Prevents exposing path info when creating names for parts splitted by maxSize",
@ -1041,11 +1059,12 @@
"anyOf": [
{
"type": "string",
"absolutePath": false
"absolutePath": false,
"minLength": 1
},
{
"instanceof": "Function",
"tsType": "Function"
"tsType": "((pathData: import(\"../lib/Compilation\").PathData) => string)"
}
]
},

View File

@ -120,7 +120,7 @@ describe("Validation", () => {
- configuration.entry[0] should be a string.
-> A non-empty string
- configuration.output.filename should be one of these:
string | function
non-empty string | function
-> Specifies the name of each output file on disk. You must **not** specify an absolute path here! The \`output.path\` option determines the location on disk the files are written to, filename is used solely for naming the individual files.
Details:
* configuration.output.filename should be a string.
@ -147,7 +147,7 @@ describe("Validation", () => {
- configuration[0].entry[0] should be a string.
-> A non-empty string
- configuration[1].output.filename should be one of these:
string | function
non-empty string | function
-> Specifies the name of each output file on disk. You must **not** specify an absolute path here! The \`output.path\` option determines the location on disk the files are written to, filename is used solely for naming the individual files.
Details:
* configuration[1].output.filename should be a string.

View File

@ -0,0 +1,13 @@
import "./shared1";
import "./common1";
it("should be able to load the split chunk on demand (shared)", () => {
return import(/* webpackChunkName: "theName" */ "./shared2");
});
it("should be able to load the split chunk on demand (common)", () => {
return Promise.all([
import(/* webpackChunkName: "otherName1" */ "./common2"),
import(/* webpackChunkName: "otherName2" */ "./common3")
]);
});

View File

@ -0,0 +1,5 @@
import "./shared1";
import "./shared2";
import "./common1";
import "./common2";
import "./common3";

View File

@ -0,0 +1,5 @@
module.exports = {
findBundle: function(i, options) {
return ["a.js"];
}
};

View File

@ -0,0 +1,28 @@
module.exports = {
entry: {
a: "./a",
b: "./b"
},
output: {
filename: data => `${data.chunk.name || data.chunk.id}.js`,
libraryTarget: "commonjs2"
},
optimization: {
chunkIds: "named",
splitChunks: {
cacheGroups: {
shared: {
chunks: "all",
test: /shared/,
filename: data => `shared-${data.chunk.name || data.chunk.id}.js`,
enforce: true
},
common: {
chunks: "all",
test: /common/,
enforce: true
}
}
}
}
};