Skip to content

Commit 4f4551a

Browse files
Merge pull request #45 from bebraw/fix/fix-webpack-4-hmr
fix - Fix webpack 4 hmr after refresh + fix rebuild performance + fix webpack 5 serialization
2 parents ac554fd + 5513a3e commit 4f4551a

File tree

4 files changed

+183
-61
lines changed

4 files changed

+183
-61
lines changed

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@
3939
"flat-cache": "^3.0.4",
4040
"micromatch": "^4.0.2",
4141
"react-docgen-typescript": "^2.2.2",
42-
"tslib": "^2.0.0",
43-
"webpack-sources": "^2.2.0"
42+
"tslib": "^2.0.0"
4443
},
4544
"devDependencies": {
4645
"@types/debug": "^4.1.5",
@@ -50,7 +49,6 @@
5049
"@types/micromatch": "^4.0.1",
5150
"@types/node": "^14.0.12",
5251
"@types/react": "^17.0.0",
53-
"@types/webpack-sources": "^2.1.0",
5452
"@typescript-eslint/eslint-plugin": "^4.9.0",
5553
"@typescript-eslint/parser": "^4.9.0",
5654
"auto": "^10.2.3",

src/dependency.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import makeSerializable from "webpack/lib/util/makeSerializable.js";
99
// @ts-ignore: What's the right way to refer to this one?
1010
import NullDependency from "webpack/lib/dependencies/NullDependency.js";
1111

12+
// This won't be needed when only webpack 5+ can be supported. Patching for now.
13+
type Context = { write: (a: string) => void; read: () => string };
14+
1215
class DocGenDependency extends NullDependency {
1316
public codeBlock: string;
1417

@@ -18,9 +21,27 @@ class DocGenDependency extends NullDependency {
1821
this.codeBlock = codeBlock;
1922
}
2023

24+
get type(): string {
25+
return "docgen";
26+
}
27+
28+
getModuleEvaluationSideEffectsState(): boolean {
29+
return false;
30+
}
31+
2132
updateHash: webpack.dependencies.NullDependency["updateHash"] = (hash) => {
2233
hash.update(this.codeBlock);
2334
};
35+
36+
serialize(context: Context): void {
37+
const { write } = context;
38+
write(this.codeBlock);
39+
}
40+
41+
deserialize(context: Context): void {
42+
const { read } = context;
43+
this.codeBlock = read();
44+
}
2445
}
2546

2647
makeSerializable(
@@ -46,6 +67,8 @@ class DocGenTemplate extends NullDependency.Template
4667
};
4768
}
4869

70+
// eslint-disable-next-line
71+
// @ts-ignore TODO: How to type this correctly?
4972
// @ts-expect-error TODO: How to type this correctly?
5073
DocGenDependency.Template = DocGenTemplate;
5174

src/plugin.ts

Lines changed: 158 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,18 @@ export default class DocgenPlugin implements webpack.WebpackPluginInstance {
148148
}
149149

150150
apply(compiler: webpack.Compiler): void {
151+
// Property compiler.version is set only starting from webpack 5
152+
const webpackVersion = compiler.webpack?.version || "";
153+
const isWebpack5 = parseInt(webpackVersion.split(".")[0], 10) >= 5;
154+
155+
if (isWebpack5) {
156+
this.applyWebpack5(compiler);
157+
} else {
158+
this.applyWebpack4(compiler);
159+
}
160+
}
161+
162+
applyWebpack5(compiler: webpack.Compiler): void {
151163
const pluginName = "DocGenPlugin";
152164
const {
153165
docgenOptions,
@@ -161,29 +173,24 @@ export default class DocgenPlugin implements webpack.WebpackPluginInstance {
161173
const { exclude = [], include = ["**/**.tsx"] } = this.options;
162174
const isExcluded = matchGlob(exclude);
163175
const isIncluded = matchGlob(include);
164-
// Property compiler.version is set only starting from webpack 5
165-
const webpackVersion = compiler.webpack?.version || "";
166-
const isWebpack5 = parseInt(webpackVersion.split(".")[0], 10) >= 5;
167176

168177
compiler.hooks.compilation.tap(
169178
pluginName,
170179
(compilation: webpack.Compilation) => {
171-
if (isWebpack5) {
172-
// Since this file is needed only for webpack 5, load it only then
173-
// to simplify the implementation of the file.
174-
//
175-
// eslint-disable-next-line
176-
const { DocGenDependency } = require("./dependency");
180+
// Since this file is needed only for webpack 5, load it only then
181+
// to simplify the implementation of the file.
182+
//
183+
// eslint-disable-next-line
184+
const { DocGenDependency } = require("./dependency");
177185

178-
compilation.dependencyTemplates.set(
179-
// eslint-disable-next-line
180-
// @ts-ignore: Webpack 4 type
181-
DocGenDependency,
182-
// eslint-disable-next-line
183-
// @ts-ignore: Webpack 4 type
184-
new DocGenDependency.Template()
185-
);
186-
}
186+
compilation.dependencyTemplates.set(
187+
// eslint-disable-next-line
188+
// @ts-ignore: Webpack 4 type
189+
DocGenDependency,
190+
// eslint-disable-next-line
191+
// @ts-ignore: Webpack 4 type
192+
new DocGenDependency.Template()
193+
);
187194

188195
compilation.hooks.seal.tap(pluginName, () => {
189196
const modulesToProcess: [string, webpack.Module][] = [];
@@ -196,6 +203,30 @@ export default class DocgenPlugin implements webpack.WebpackPluginInstance {
196203

197204
const nameForCondition = module.nameForCondition() || "";
198205

206+
// Ignore modules that haven't been built yet for webpack 5
207+
if (!compilation.builtModules.has(module)) {
208+
debugExclude(`Ignoring un-built module: ${nameForCondition}`);
209+
return;
210+
}
211+
212+
// Ignore external modules
213+
// eslint-disable-next-line
214+
// @ts-ignore: Webpack 4 type
215+
if (module.external) {
216+
debugExclude(`Ignoring external module: ${nameForCondition}`);
217+
return;
218+
}
219+
220+
// Ignore raw requests
221+
// eslint-disable-next-line
222+
// @ts-ignore: Webpack 4 type
223+
if (!module.rawRequest) {
224+
debugExclude(
225+
`Ignoring module without "rawRequest": ${nameForCondition}`
226+
);
227+
return;
228+
}
229+
199230
if (isExcluded(nameForCondition)) {
200231
debugExclude(
201232
`Module not matched in "exclude": ${nameForCondition}`
@@ -222,38 +253,122 @@ export default class DocgenPlugin implements webpack.WebpackPluginInstance {
222253
// 3. Process and parse each module and add the type information
223254
// as a dependency
224255
modulesToProcess.forEach(([name, module]) => {
225-
if (isWebpack5) {
226-
// Since this file is needed only for webpack 5, load it only then
227-
// to simplify the implementation of the file.
228-
//
256+
// Since this file is needed only for webpack 5, load it only then
257+
// to simplify the implementation of the file.
258+
//
259+
// eslint-disable-next-line
260+
const { DocGenDependency } = require("./dependency");
261+
262+
module.addDependency(
229263
// eslint-disable-next-line
230-
const { DocGenDependency } = require("./dependency");
231-
232-
module.addDependency(
233-
// eslint-disable-next-line
234-
// @ts-ignore: Webpack 4 type
235-
new DocGenDependency(
236-
generateDocgenCodeBlock({
237-
filename: name,
238-
source: name,
239-
componentDocs: docGenParser.parseWithProgramProvider(
240-
name,
241-
() => tsProgram
242-
),
243-
...generateOptions,
244-
}).substring(name.length)
245-
)
246-
);
247-
} else {
248-
// Assume webpack 4 or earlier
249-
processModule(docGenParser, module, tsProgram, generateOptions);
250-
}
264+
// @ts-ignore: Webpack 4 type
265+
new DocGenDependency(
266+
generateDocgenCodeBlock({
267+
filename: name,
268+
source: name,
269+
componentDocs: docGenParser.parseWithProgramProvider(
270+
name,
271+
() => tsProgram
272+
),
273+
...generateOptions,
274+
}).substring(name.length)
275+
)
276+
);
251277
});
252278
});
253279
}
254280
);
255281
}
256282

283+
applyWebpack4(compiler: webpack.Compiler): void {
284+
const { docgenOptions, compilerOptions } = this.getOptions();
285+
const parser = docGen.withCompilerOptions(compilerOptions, docgenOptions);
286+
const { exclude = [], include = ["**/**.tsx"] } = this.options;
287+
const isExcluded = matchGlob(exclude);
288+
const isIncluded = matchGlob(include);
289+
290+
compiler.hooks.make.tap(this.name, (compilation) => {
291+
compilation.hooks.seal.tap(this.name, () => {
292+
const modulesToProcess: webpack.Module[] = [];
293+
294+
compilation.modules.forEach((module: webpack.Module) => {
295+
// eslint-disable-next-line
296+
// @ts-ignore: Webpack 4 type
297+
if (!module.built) {
298+
// eslint-disable-next-line
299+
// @ts-ignore: Webpack 4 type
300+
debugExclude(`Ignoring un-built module: ${module.userRequest}`);
301+
return;
302+
}
303+
304+
// eslint-disable-next-line
305+
// @ts-ignore: Webpack 4 type
306+
if (module.external) {
307+
// eslint-disable-next-line
308+
// @ts-ignore: Webpack 4 type
309+
debugExclude(`Ignoring external module: ${module.userRequest}`);
310+
return;
311+
}
312+
313+
// eslint-disable-next-line
314+
// @ts-ignore: Webpack 4 type
315+
if (!module.rawRequest) {
316+
debugExclude(
317+
// eslint-disable-next-line
318+
// @ts-ignore: Webpack 4 type
319+
`Ignoring module without "rawRequest": ${module.userRequest}`
320+
);
321+
return;
322+
}
323+
324+
// eslint-disable-next-line
325+
// @ts-ignore: Webpack 4 type
326+
if (isExcluded(module.userRequest)) {
327+
debugExclude(
328+
// eslint-disable-next-line
329+
// @ts-ignore: Webpack 4 type
330+
`Module not matched in "exclude": ${module.userRequest}`
331+
);
332+
return;
333+
}
334+
335+
// eslint-disable-next-line
336+
// @ts-ignore: Webpack 4 type
337+
if (!isIncluded(module.userRequest)) {
338+
debugExclude(
339+
// eslint-disable-next-line
340+
// @ts-ignore: Webpack 4 type
341+
`Module not matched in "include": ${module.userRequest}`
342+
);
343+
return;
344+
}
345+
346+
// eslint-disable-next-line
347+
// @ts-ignore: Webpack 4 type
348+
debugInclude(module.userRequest);
349+
modulesToProcess.push(module);
350+
});
351+
352+
const tsProgram = ts.createProgram(
353+
// eslint-disable-next-line
354+
// @ts-ignore: Webpack 4 type
355+
modulesToProcess.map((v) => v.userRequest),
356+
compilerOptions
357+
);
358+
359+
modulesToProcess.forEach((m) =>
360+
processModule(parser, m, tsProgram, {
361+
docgenCollectionName: "STORYBOOK_REACT_CLASSES",
362+
setDisplayName: true,
363+
typePropName: "type",
364+
})
365+
);
366+
367+
cache.save();
368+
});
369+
});
370+
}
371+
257372
getOptions(): {
258373
docgenOptions: docGen.ParserOptions;
259374
generateOptions: {

yarn.lock

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -982,25 +982,11 @@
982982
"@types/prop-types" "*"
983983
csstype "^3.0.2"
984984

985-
"@types/source-list-map@*":
986-
version "0.1.2"
987-
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
988-
integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==
989-
990985
"@types/stack-utils@^2.0.0":
991986
version "2.0.0"
992987
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
993988
integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==
994989

995-
"@types/webpack-sources@^2.1.0":
996-
version "2.1.0"
997-
resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.0.tgz#8882b0bd62d1e0ce62f183d0d01b72e6e82e8c10"
998-
integrity sha512-LXn/oYIpBeucgP1EIJbKQ2/4ZmpvRl+dlrFdX7+94SKRUV3Evy3FsfMZY318vGhkWUS5MPhtOM3w1/hCOAOXcg==
999-
dependencies:
1000-
"@types/node" "*"
1001-
"@types/source-list-map" "*"
1002-
source-map "^0.7.3"
1003-
1004990
"@types/yargs-parser@*":
1005991
version "15.0.0"
1006992
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
@@ -6377,7 +6363,7 @@ webpack-merge@^5.7.3:
63776363
clone-deep "^4.0.1"
63786364
wildcard "^2.0.0"
63796365

6380-
webpack-sources@^2.1.1, webpack-sources@^2.2.0:
6366+
webpack-sources@^2.1.1:
63816367
version "2.2.0"
63826368
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac"
63836369
integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==

0 commit comments

Comments
 (0)