Skip to content

Commit f67c05a

Browse files
fix: hmr crash in some situations (#1140)
1 parent ba231e2 commit f67c05a

File tree

8 files changed

+170
-144
lines changed

8 files changed

+170
-144
lines changed

src/hmr/hotModuleReplacement.js

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
func-names
66
*/
77

8-
// eslint-disable-next-line jsdoc/no-restricted-syntax
9-
/** @typedef {any} TODO */
10-
118
const normalizeUrl = require("./normalize-url");
129

1310
const srcByModuleId = Object.create(null);
@@ -47,9 +44,11 @@ function debounce(fn, time) {
4744
*/
4845
function noop() {}
4946

47+
/** @typedef {(filename?: string) => string[]} GetScriptSrc */
48+
5049
/**
5150
* @param {string | number} moduleId a module id
52-
* @returns {TODO} current script url
51+
* @returns {GetScriptSrc} current script url
5352
*/
5453
function getCurrentScriptUrl(moduleId) {
5554
let src = srcByModuleId[moduleId];
@@ -69,13 +68,10 @@ function getCurrentScriptUrl(moduleId) {
6968
srcByModuleId[moduleId] = src;
7069
}
7170

72-
/**
73-
* @param {string} fileMap file map
74-
* @returns {null | string[]} normalized files
75-
*/
71+
/** @type {GetScriptSrc} */
7672
return function (fileMap) {
7773
if (!src) {
78-
return null;
74+
return [];
7975
}
8076

8177
const splitResult = src.split(/([^\\/]+)\.js$/);
@@ -114,8 +110,10 @@ function isUrlRequest(url) {
114110
return true;
115111
}
116112

113+
/** @typedef {HTMLLinkElement & { isLoaded: boolean, visited: boolean }} HotHTMLLinkElement */
114+
117115
/**
118-
* @param {TODO} el html link element
116+
* @param {HotHTMLLinkElement} el html link element
119117
* @param {string=} url a URL
120118
*/
121119
function updateCss(el, url) {
@@ -145,7 +143,9 @@ function updateCss(el, url) {
145143

146144
el.visited = true;
147145

148-
const newEl = el.cloneNode();
146+
const newEl =
147+
/** @type {HotHTMLLinkElement} */
148+
(el.cloneNode());
149149

150150
newEl.isLoaded = false;
151151

@@ -155,7 +155,10 @@ function updateCss(el, url) {
155155
}
156156

157157
newEl.isLoaded = true;
158-
el.parentNode.removeChild(el);
158+
159+
if (el.parentNode) {
160+
el.parentNode.removeChild(el);
161+
}
159162
});
160163

161164
newEl.addEventListener("error", () => {
@@ -164,21 +167,26 @@ function updateCss(el, url) {
164167
}
165168

166169
newEl.isLoaded = true;
167-
el.parentNode.removeChild(el);
170+
171+
if (el.parentNode) {
172+
el.parentNode.removeChild(el);
173+
}
168174
});
169175

170176
newEl.href = `${url}?${Date.now()}`;
171177

172-
if (el.nextSibling) {
173-
el.parentNode.insertBefore(newEl, el.nextSibling);
174-
} else {
175-
el.parentNode.appendChild(newEl);
178+
if (el.parentNode) {
179+
if (el.nextSibling) {
180+
el.parentNode.insertBefore(newEl, el.nextSibling);
181+
} else {
182+
el.parentNode.appendChild(newEl);
183+
}
176184
}
177185
}
178186

179187
/**
180188
* @param {string} href href
181-
* @param {TODO} src src
189+
* @param {string[]} src src
182190
* @returns {undefined | string} a reload url
183191
*/
184192
function getReloadUrl(href, src) {
@@ -192,6 +200,7 @@ function getReloadUrl(href, src) {
192200
*/
193201
// eslint-disable-next-line array-callback-return
194202
(url) => {
203+
// @ts-expect-error fix me in the next major release
195204
// eslint-disable-next-line unicorn/prefer-includes
196205
if (href.indexOf(src) > -1) {
197206
ret = url;
@@ -203,14 +212,10 @@ function getReloadUrl(href, src) {
203212
}
204213

205214
/**
206-
* @param {string=} src source
215+
* @param {string[]} src source
207216
* @returns {boolean} true when loaded, otherwise false
208217
*/
209218
function reloadStyle(src) {
210-
if (!src) {
211-
return false;
212-
}
213-
214219
const elements = document.querySelectorAll("link");
215220
let loaded = false;
216221

@@ -256,8 +261,8 @@ function reloadAll() {
256261

257262
/**
258263
* @param {number | string} moduleId a module id
259-
* @param {TODO} options options
260-
* @returns {TODO} wrapper function
264+
* @param {{ filename?: string, locals?: boolean }} options options
265+
* @returns {() => void} wrapper function
261266
*/
262267
module.exports = function (moduleId, options) {
263268
if (noDocument) {

src/index.js

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const {
2121
/** @typedef {import("webpack").Compilation} Compilation */
2222
/** @typedef {import("webpack").ChunkGraph} ChunkGraph */
2323
/** @typedef {import("webpack").Chunk} Chunk */
24-
/** @typedef {Parameters<import("webpack").Chunk["isInGroup"]>[0]} ChunkGroup */
24+
/** @typedef {import("webpack").ChunkGroup} ChunkGroup */
2525
/** @typedef {import("webpack").Module} Module */
2626
/** @typedef {import("webpack").Dependency} Dependency */
2727
/** @typedef {import("webpack").sources.Source} Source */
@@ -30,6 +30,9 @@ const {
3030
/** @typedef {import("webpack").AssetInfo} AssetInfo */
3131
/** @typedef {import("./loader.js").Dependency} LoaderDependency */
3232

33+
/** @typedef {NonNullable<Required<Configuration>['output']['filename']>} Filename */
34+
/** @typedef {NonNullable<Required<Configuration>['output']['chunkFilename']>} ChunkFilename */
35+
3336
/**
3437
* @typedef {object} LoaderOptions
3538
* @property {string | ((resourcePath: string, rootContext: string) => string)=} publicPath public path
@@ -41,8 +44,8 @@ const {
4144

4245
/**
4346
* @typedef {object} PluginOptions
44-
* @property {Required<Configuration>['output']['filename']=} filename filename
45-
* @property {Required<Configuration>['output']['chunkFilename']=} chunkFilename chunk filename
47+
* @property {Filename=} filename filename
48+
* @property {ChunkFilename=} chunkFilename chunk filename
4649
* @property {boolean=} ignoreOrder true when need to ignore order, otherwise false
4750
* @property {string | ((linkTag: HTMLLinkElement) => void)=} insert link insert place or a custom insert function
4851
* @property {Record<string, string>=} attributes link attributes
@@ -53,8 +56,8 @@ const {
5356

5457
/**
5558
* @typedef {object} NormalizedPluginOptions
56-
* @property {Required<Configuration>['output']['filename']} filename filename
57-
* @property {Required<Configuration>['output']['chunkFilename']=} chunkFilename chunk filename
59+
* @property {Filename} filename filename
60+
* @property {ChunkFilename=} chunkFilename chunk filename
5861
* @property {boolean} ignoreOrder true when need to ignore order, otherwise false
5962
* @property {string | ((linkTag: HTMLLinkElement) => void)=} insert a link insert place or a custom insert function
6063
* @property {Record<string, string>=} attributes link attributes
@@ -70,9 +73,6 @@ const {
7073
* @property {Record<string, string>=} attributes link attributes
7174
*/
7275

73-
// eslint-disable-next-line jsdoc/no-restricted-syntax
74-
/** @typedef {any} TODO */
75-
7676
const pluginName = "mini-css-extract-plugin";
7777
const pluginSymbol = Symbol(pluginName);
7878

@@ -89,8 +89,9 @@ const CODE_GENERATION_RESULT = {
8989
runtimeRequirements: new Set(),
9090
};
9191

92-
/** @typedef {Module & { content: Buffer, media?: string, sourceMap?: Buffer, supports?: string, layer?: string, assets?: { [key: string]: TODO }, assetsInfo?: Map<string, AssetInfo> }} CssModule */
93-
/** @typedef {{ context: string | null, identifier: string, identifierIndex: number, content: Buffer, sourceMap?: Buffer, media?: string, supports?: string, layer?: TODO, assetsInfo?: Map<string, AssetInfo>, assets?: { [key: string]: TODO }}} CssModuleDependency */
92+
// eslint-disable-next-line jsdoc/no-restricted-syntax
93+
/** @typedef {{ context: string | null, identifier: string, identifierIndex: number, content: Buffer, sourceMap?: Buffer, media?: string, supports?: string, layer?: any, assetsInfo?: Map<string, AssetInfo>, assets?: { [key: string]: Source }}} CssModuleDependency */
94+
/** @typedef {Module & { content: Buffer, media?: string, sourceMap?: Buffer, supports?: string, layer?: string, assets?: { [key: string]: Source }, assetsInfo?: Map<string, AssetInfo> }} CssModule */
9495
/** @typedef {{ new(dependency: CssModuleDependency): CssModule }} CssModuleConstructor */
9596
/** @typedef {Dependency & CssModuleDependency} CssDependency */
9697
/** @typedef {Omit<LoaderDependency, "context">} CssDependencyOptions */
@@ -437,10 +438,8 @@ class MiniCssExtractPlugin {
437438
this.sourceMap = sourceMap;
438439
this.context = context;
439440
/** @type {{ [key: string]: Source } | undefined}} */
440-
441441
this.assets = undefined;
442442
/** @type {Map<string, AssetInfo> | undefined} */
443-
444443
this.assetsInfo = undefined;
445444
}
446445

@@ -486,7 +485,6 @@ class MiniCssExtractPlugin {
486485
}
487486
}
488487

489-
// @ts-expect-error
490488
cssDependencyCache.set(webpack, CssDependency);
491489

492490
webpack.util.serialization.register(
@@ -525,7 +523,6 @@ class MiniCssExtractPlugin {
525523
},
526524
);
527525

528-
// @ts-expect-error
529526
return CssDependency;
530527
}
531528

@@ -574,7 +571,6 @@ class MiniCssExtractPlugin {
574571
filename: DEFAULT_FILENAME,
575572
ignoreOrder: false,
576573
// TODO remove in the next major release
577-
578574
experimentalUseImportModule: undefined,
579575
runtime: true,
580576
...options,
@@ -639,7 +635,7 @@ class MiniCssExtractPlugin {
639635
) {
640636
/** @type {Compiler["options"]["experiments"] & { executeModule?: boolean }} */
641637

642-
// @ts-expect-error
638+
// @ts-expect-error TODO remove in the next major release
643639
compiler.options.experiments.executeModule = true;
644640
}
645641

@@ -692,7 +688,7 @@ class MiniCssExtractPlugin {
692688
class CssModuleFactory {
693689
/**
694690
* @param {{ dependencies: Dependency[] }} dependencies
695-
* @param {(arg0?: Error, arg1?: TODO) => void} callback
691+
* @param {(err?: null | Error, result?: CssModule) => void} callback
696692
*/
697693

698694
create({ dependencies: [dependency] }, callback) {
@@ -705,6 +701,7 @@ class MiniCssExtractPlugin {
705701

706702
compilation.dependencyFactories.set(
707703
CssDependency,
704+
// @ts-expect-error TODO fix in the next major release and fix using `CssModuleFactory extends webpack.ModuleFactory`
708705
new CssModuleFactory(),
709706
);
710707

@@ -722,7 +719,7 @@ class MiniCssExtractPlugin {
722719
/**
723720
* @param {ReturnType<Compilation["getRenderManifest"]>} result result
724721
* @param {Parameters<Compilation["getRenderManifest"]>[0]} chunk chunk
725-
* @returns {TODO} a rendered manifest
722+
* @returns {ReturnType<Compilation["getRenderManifest"]>} a rendered manifest
726723
*/
727724
(result, { chunk }) => {
728725
const { chunkGraph } = compilation;
@@ -731,7 +728,7 @@ class MiniCssExtractPlugin {
731728
// We don't need hot update chunks for css
732729
// We will use the real asset instead to update
733730
if (chunk instanceof HotUpdateChunk) {
734-
return;
731+
return result;
735732
}
736733

737734
const renderedModules =
@@ -774,6 +771,8 @@ class MiniCssExtractPlugin {
774771
hash: chunk.contentHash[MODULE_TYPE],
775772
});
776773
}
774+
775+
return result;
777776
},
778777
);
779778

@@ -1232,16 +1231,16 @@ class MiniCssExtractPlugin {
12321231
`${RuntimeGlobals.require}.miniCssF`,
12331232
/**
12341233
* @param {Chunk} referencedChunk a referenced chunk
1235-
* @returns {TODO} a template value
1234+
* @returns {ReturnType<import("webpack").runtime.GetChunkFilenameRuntimeModule["getFilenameForChunk"]>} a template value
12361235
*/
12371236
(referencedChunk) => {
12381237
if (!referencedChunk.contentHash[MODULE_TYPE]) {
12391238
return false;
12401239
}
12411240

12421241
return referencedChunk.canBeInitial()
1243-
? this.options.filename
1244-
: this.options.chunkFilename;
1242+
? /** @type {Filename} */ (this.options.filename)
1243+
: /** @type {ChunkFilename} */ (this.options.chunkFilename);
12451244
},
12461245
set.has(RuntimeGlobals.hmrDownloadUpdateHandlers),
12471246
),

src/loader.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ const MiniCssExtractPlugin = require("./index");
2828
/** @typedef {{[key: string]: string | Function }} Locals */
2929

3030
// eslint-disable-next-line jsdoc/no-restricted-syntax
31-
/** @typedef {any} TODO */
31+
/** @typedef {any} EXPECTED_ANY */
3232

3333
/**
3434
* @typedef {object} Dependency
3535
* @property {string} identifier identifier
3636
* @property {string | null} context context
37-
* @property {Buffer=} content content
37+
* @property {Buffer} content content
3838
* @property {string=} media media
3939
* @property {string=} supports supports
4040
* @property {string=} layer layer
@@ -104,9 +104,9 @@ function pitch(request) {
104104
const options = this.getOptions(/** @type {Schema} */ (schema));
105105
const emit = typeof options.emit !== "undefined" ? options.emit : true;
106106
const callback = this.async();
107-
const optionsFromPlugin = /** @type {TODO} */ (this)[
108-
MiniCssExtractPlugin.pluginSymbol
109-
];
107+
const optionsFromPlugin =
108+
// @ts-expect-error
109+
this[MiniCssExtractPlugin.pluginSymbol];
110110

111111
if (!optionsFromPlugin) {
112112
callback(
@@ -121,7 +121,7 @@ function pitch(request) {
121121
const { webpack } = /** @type {Compiler} */ (this._compiler);
122122

123123
/**
124-
* @param {TODO} originalExports original exports
124+
* @param {EXPECTED_ANY} originalExports original exports
125125
* @param {Compilation=} compilation compilation
126126
* @param {{ [name: string]: Source }=} assets assets
127127
* @param {Map<string, AssetInfo>=} assetsInfo assets info
@@ -201,14 +201,15 @@ function pitch(request) {
201201
locals = {};
202202
}
203203

204-
/** @type {Locals} */ (locals)[key] = originalExports[key];
204+
/** @type {Locals} */
205+
(locals)[key] = originalExports[key];
205206
}
206207
}
207208
} else {
208209
locals = exports && exports.locals;
209210
}
210211

211-
/** @type {Dependency[] | [null, TODO][]} */
212+
/** @type {Dependency[] | [null, Record<string, string>][]} */
212213
let dependencies;
213214

214215
if (!Array.isArray(exports)) {

0 commit comments

Comments
 (0)