Skip to content

Commit

Permalink
Merge pull request #7625 from webpack/bugfix/multiple-assets-same-file
Browse files Browse the repository at this point in the history
allow emitting to the same filename when hash matches
  • Loading branch information
sokra committed Jun 29, 2018
2 parents e3678aa + 522b324 commit 86370e9
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 1 deletion.
31 changes: 30 additions & 1 deletion lib/Compilation.js
Expand Up @@ -39,6 +39,7 @@ const GraphHelpers = require("./GraphHelpers");
/** @typedef {import("./Module")} Module */
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
/** @typedef {import("webpack-sources").Source} Source */

const byId = (a, b) => {
if (a.id < b.id) return -1;
Expand Down Expand Up @@ -1869,6 +1870,8 @@ class Compilation extends Tapable {
createChunkAssets() {
const outputOptions = this.outputOptions;
const cachedSourceMap = new Map();
/** @type {Map<string, {hash: string, source: Source, chunk: Chunk}>} */
const alreadyWrittenFiles = new Map();
for (let i = 0; i < this.chunks.length; i++) {
const chunk = this.chunks[i];
chunk.files = [];
Expand All @@ -1891,6 +1894,28 @@ class Compilation extends Tapable {
const cacheName = fileManifest.identifier;
const usedHash = fileManifest.hash;
filenameTemplate = fileManifest.filenameTemplate;
file = this.getPath(filenameTemplate, fileManifest.pathOptions);

// check if the same filename was already written by another chunk
const alreadyWritten = alreadyWrittenFiles.get(file);
if (alreadyWritten !== undefined) {
if (alreadyWritten.hash === usedHash) {
if (this.cache) {
this.cache[cacheName] = {
hash: usedHash,
source: alreadyWritten.source
};
}
chunk.files.push(file);
this.hooks.chunkAsset.call(chunk, file);
continue;
} else {
throw new Error(
`Conflict: Multiple chunks emit assets to the same filename ${file}` +
` (chunks ${alreadyWritten.chunk.id} and ${chunk.id})`
);
}
}
if (
this.cache &&
this.cache[cacheName] &&
Expand All @@ -1917,7 +1942,6 @@ class Compilation extends Tapable {
};
}
}
file = this.getPath(filenameTemplate, fileManifest.pathOptions);
if (this.assets[file] && this.assets[file] !== source) {
throw new Error(
`Conflict: Multiple assets emit to the same filename ${file}`
Expand All @@ -1926,6 +1950,11 @@ class Compilation extends Tapable {
this.assets[file] = source;
chunk.files.push(file);
this.hooks.chunkAsset.call(chunk, file);
alreadyWrittenFiles.set(file, {
hash: usedHash,
source,
chunk
});
}
} catch (err) {
this.errors.push(
Expand Down
13 changes: 13 additions & 0 deletions test/configCases/wasm/identical/index.js
@@ -0,0 +1,13 @@
it("should allow reference the same wasm multiple times", function() {
return import("./module").then(function(module) {
const result = module.run();
expect(result).toEqual(84);
});
});

it("should allow reference the same wasm multiple times (other chunk)", function() {
return import("./module?2").then(function(module) {
const result = module.run();
expect(result).toEqual(84);
});
});
6 changes: 6 additions & 0 deletions test/configCases/wasm/identical/module.js
@@ -0,0 +1,6 @@
import { getNumber } from "./wasm.wat?1";
import { getNumber as getNumber2 } from "./wasm.wat?2";

export function run() {
return getNumber() + getNumber2();
};
5 changes: 5 additions & 0 deletions test/configCases/wasm/identical/test.filter.js
@@ -0,0 +1,5 @@
var supportsWebAssembly = require("../../../helpers/supportsWebAssembly");

module.exports = function(config) {
return supportsWebAssembly();
};
10 changes: 10 additions & 0 deletions test/configCases/wasm/identical/wasm.wat
@@ -0,0 +1,10 @@
(module
(type $t0 (func (param i32 i32) (result i32)))
(type $t1 (func (result i32)))
(func $add (export "add") (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
(i32.add
(get_local $p0)
(get_local $p1)))
(func $getNumber (export "getNumber") (type $t1) (result i32)
(i32.const 42)))

35 changes: 35 additions & 0 deletions test/configCases/wasm/identical/webpack.config.js
@@ -0,0 +1,35 @@
const { CachedSource } = require("webpack-sources");

/** @typedef {import("../../../lib/Compilation")} Compilation */

module.exports = {
module: {
rules: [
{
test: /\.wat$/,
loader: "wast-loader",
type: "webassembly/experimental"
}
]
},
plugins: [
function() {
this.hooks.compilation.tap(
"Test",
/**
* @param {Compilation} compilation Compilation
* @returns {void}
*/
compilation => {
compilation.moduleTemplates.webassembly.hooks.package.tap(
"Test",
source => {
// this is important to make each returned value a new instance
return new CachedSource(source);
}
);
}
);
}
]
};

0 comments on commit 86370e9

Please sign in to comment.