fix: avoid generating extra js file when using asset module as entrypoint

This commit is contained in:
Alexander Akait 2024-10-23 03:44:56 +03:00 committed by GitHub
commit 319576720c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 292 additions and 10 deletions

View File

@ -2287,11 +2287,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
this.hooks.failedEntry.call(entry, options, err);
return callback(err);
}
this.hooks.succeedEntry.call(
entry,
options,
/** @type {Module} */ (module)
);
this.hooks.succeedEntry.call(entry, options, module);
return callback(null, module);
}
);

View File

@ -5,6 +5,8 @@
"use strict";
const RawModule = require("./RawModule");
const EntryDependency = require("./dependencies/EntryDependency");
const createSchemaValidation = require("./util/create-schema-validation");
/** @typedef {import("../declarations/plugins/IgnorePlugin").IgnorePluginOptions} IgnorePluginOptions */
@ -73,7 +75,23 @@ class IgnorePlugin {
*/
apply(compiler) {
compiler.hooks.normalModuleFactory.tap("IgnorePlugin", nmf => {
nmf.hooks.beforeResolve.tap("IgnorePlugin", this.checkIgnore);
nmf.hooks.beforeResolve.tap("IgnorePlugin", resolveData => {
const result = this.checkIgnore(resolveData);
if (
result === false &&
resolveData.dependencies.length > 0 &&
resolveData.dependencies[0] instanceof EntryDependency
) {
resolveData.ignoredModule = new RawModule(
"",
"ignored-entry-module",
"(ignored-entry-module)"
);
}
return result;
});
});
compiler.hooks.contextModuleFactory.tap("IgnorePlugin", cmf => {
cmf.hooks.beforeResolve.tap("IgnorePlugin", this.checkIgnore);

View File

@ -68,6 +68,7 @@ const {
* @property {LazySet<string>} fileDependencies
* @property {LazySet<string>} missingDependencies
* @property {LazySet<string>} contextDependencies
* @property {Module=} ignoredModule
* @property {boolean} cacheable allow to use the unsafe cache
*/
@ -885,12 +886,19 @@ class NormalModuleFactory extends ModuleFactory {
// Ignored
if (result === false) {
return callback(null, {
/** @type {ModuleFactoryResult} * */
const factoryResult = {
fileDependencies,
missingDependencies,
contextDependencies,
cacheable: resolveData.cacheable
});
};
if (resolveData.ignoredModule) {
factoryResult.module = resolveData.ignoredModule;
}
return callback(null, factoryResult);
}
if (typeof result === "object")
@ -911,6 +919,7 @@ class NormalModuleFactory extends ModuleFactory {
});
}
/** @type {ModuleFactoryResult} * */
const factoryResult = {
module,
fileDependencies,

View File

@ -78,6 +78,25 @@ const chunkHasJs = (chunk, chunkGraph) => {
);
};
/**
* @param {Chunk} chunk a chunk
* @param {ChunkGraph} chunkGraph the chunk graph
* @returns {boolean} true, when a JS file is needed for this chunk
*/
const chunkHasRuntimeOrJs = (chunk, chunkGraph) => {
if (
chunkGraph.getChunkModulesIterableBySourceType(
chunk,
WEBPACK_MODULE_TYPE_RUNTIME
)
)
return true;
return Boolean(
chunkGraph.getChunkModulesIterableBySourceType(chunk, "javascript")
);
};
/**
* @param {Module} module a module
* @param {string} code the code
@ -271,13 +290,14 @@ class JavascriptModulesPlugin {
} = options;
const hotUpdateChunk = chunk instanceof HotUpdateChunk ? chunk : null;
let render;
const filenameTemplate =
JavascriptModulesPlugin.getChunkFilenameTemplate(
chunk,
outputOptions
);
let render;
if (hotUpdateChunk) {
render = () =>
this.renderChunk(
@ -293,6 +313,10 @@ class JavascriptModulesPlugin {
hooks
);
} else if (chunk.hasRuntime()) {
if (!chunkHasRuntimeOrJs(chunk, chunkGraph)) {
return result;
}
render = () =>
this.renderMain(
{
@ -1149,6 +1173,10 @@ class JavascriptModulesPlugin {
entryModule,
entrypoint
] of chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)) {
if (!chunkGraph.getModuleSourceTypes(entryModule).has("javascript")) {
i--;
continue;
}
const chunks =
/** @type {Entrypoint} */
(entrypoint).chunks.filter(c => c !== chunk);

View File

@ -438,6 +438,7 @@ const describeCases = config => {
expect,
jest,
__STATS__: jsonStats,
__STATS_I__: i,
nsObj: m => {
Object.defineProperty(m, Symbol.toStringTag, {
value: "Module"

View File

@ -0,0 +1,3 @@
.class {
background: #000;
}

View File

@ -0,0 +1,5 @@
function test() {
run();
}
test();

View File

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

View File

@ -0,0 +1,96 @@
it("should work", () => {
const stats = __STATS__.children[__STATS_I__];
const test = stats.assets.find(
a => a.name === "test.js"
);
expect(Boolean(test)).toBe(true);
const assetEntry = stats.assets.find(
a => a.info.sourceFilename === "../_images/file.png"
);
expect(Boolean(assetEntry)).toBe(true);
switch (__STATS_I__) {
case 0: {
expect(stats.assets.length).toBe(2);
break;
}
case 1: {
expect(stats.assets.length).toBe(3);
const jsEntry = stats.assets.find(
a => a.name.endsWith("js-entry.js")
);
expect(Boolean(jsEntry)).toBe(true);
break;
}
case 2: {
expect(stats.assets.length).toBe(4);
const cssEntryInJs = stats.assets.find(
a => a.name.endsWith("css-entry.js")
);
expect(Boolean(cssEntryInJs)).toBe(true);
const cssEntry = stats.assets.find(
a => a.name.endsWith("css-entry.css")
);
expect(Boolean(cssEntry)).toBe(true);
break;
}
case 3: {
expect(stats.assets.length).toBe(5);
const jsEntry = stats.assets.find(
a => a.name.endsWith("js-entry.js")
);
expect(Boolean(jsEntry)).toBe(true);
const cssEntryInJs = stats.assets.find(
a => a.name.endsWith("css-entry.js")
);
expect(Boolean(cssEntryInJs)).toBe(true);
const cssEntry = stats.assets.find(
a => a.name.endsWith("css-entry.css")
);
expect(Boolean(cssEntry)).toBe(true);
break;
}
case 4: {
expect(stats.assets.length).toBe(4);
const jsEntry = stats.assets.find(
a => a.name.endsWith("js-entry.js")
);
expect(Boolean(jsEntry)).toBe(true);
const cssEntryInJs = stats.assets.find(
a => a.name.endsWith("css-entry.js")
);
expect(Boolean(cssEntryInJs)).toBe(true);
break;
}
case 5: {
expect(stats.assets.length).toBe(3);
const jsEntry = stats.assets.find(
a => a.name.endsWith("mixed-entry.js")
);
expect(Boolean(jsEntry)).toBe(true);
break;
}
case 6: {
expect(stats.assets.length).toBe(3);
const jsEntry = stats.assets.find(
a => a.name.endsWith("mixed-entry.js")
);
expect(Boolean(jsEntry)).toBe(true);
break;
}
}
});

View File

@ -0,0 +1,120 @@
const path = require("path");
const fs = require("fs");
const webpack = require("../../../../");
/** @type {(number, any) => import("../../../../").Configuration} */
const common = (i, options) => ({
target: "web",
output: {
filename: `${i}/[name].js`,
chunkFilename: `${i}/[name].js`,
cssFilename: `${i}/[name].css`,
cssChunkFilename: `${i}/[name].css`,
assetModuleFilename: `${i}/[name][ext][query]`
},
module: {
rules: [
{
test: /\.png$/,
type: "asset"
}
]
},
experiments: {
css: true
},
plugins: [
{
apply(compiler) {
compiler.hooks.compilation.tap("Test", compilation => {
compilation.hooks.processAssets.tap(
{
name: "copy-webpack-plugin",
stage:
compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
},
() => {
const data = fs.readFileSync(
path.resolve(__dirname, "./test.js")
);
compilation.emitAsset(
"test.js",
new webpack.sources.RawSource(data)
);
}
);
});
}
}
],
...options
});
/** @type {import("../../../../").Configuration[]} */
module.exports = [
common(0, {
entry: "../_images/file.png"
}),
common(1, {
entry: {
"asset-entry": {
import: "../_images/file.png"
},
"js-entry": {
import: "./entry.js"
}
}
}),
common(2, {
entry: {
"asset-entry": {
import: "../_images/file.png"
},
"css-entry": {
import: "./entry.css"
}
}
}),
common(3, {
entry: {
"asset-entry": {
import: "../_images/file.png"
},
"js-entry": {
import: "./entry.js"
},
"css-entry": {
import: "./entry.css"
}
}
}),
common(4, {
target: "node",
entry: {
"asset-entry": {
import: "../_images/file.png"
},
"js-entry": {
import: "./entry.js"
},
"css-entry": {
import: "./entry.css"
}
}
}),
common(5, {
entry: {
"mixed-entry": {
import: ["./entry.js", "../_images/file.png"]
}
}
}),
common(6, {
entry: {
"mixed-entry": {
import: ["../_images/file.png", "./entry.js"]
}
}
})
];

1
types.d.ts vendored
View File

@ -11940,6 +11940,7 @@ declare interface ResolveData {
fileDependencies: LazySet<string>;
missingDependencies: LazySet<string>;
contextDependencies: LazySet<string>;
ignoredModule?: Module;
/**
* allow to use the unsafe cache