Skip to content

Commit 8da9df4

Browse files
committed
feat: Add initial support for webpack 4
I restored the old code paths. Sadly this makes typing tougher. The added code needs to be tested still and the old cache is missing for now.
1 parent 95eb37b commit 8da9df4

3 files changed

Lines changed: 101 additions & 32 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
},
7171
"peerDependencies": {
7272
"typescript": ">= 3.x",
73-
"webpack": ">= 5"
73+
"webpack": ">= 4"
7474
},
7575
"lint-staged": {
7676
"*.{js,css,md}": [

src/dependency.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
/* eslint-disable max-classes-per-file */
2-
import * as webpack from "webpack";
3-
42
// eslint-disable-next-line
53
// @ts-ignore: What's the right way to refer to this one?
64
import makeSerializable from "webpack/lib/util/makeSerializable.js";
75

8-
class DocGenDependency extends webpack.dependencies.NullDependency {
6+
// This import is compatible with both webpack 4 and 5
7+
// eslint-disable-next-line
8+
// @ts-ignore: Webpack 4 type
9+
import NullDependency from "webpack/lib/dependencies/NullDependency";
10+
11+
// It would be better to extend from webpack.dependencies but that works
12+
// only with webpack 5, not 4
13+
// class DocGenDependency extends webpack.dependencies.NullDependency
14+
class DocGenDependency extends NullDependency {
915
public codeBlock: string;
1016

1117
constructor(codeBlock: string) {
@@ -14,7 +20,9 @@ class DocGenDependency extends webpack.dependencies.NullDependency {
1420
this.codeBlock = codeBlock;
1521
}
1622

17-
updateHash: webpack.dependencies.NullDependency["updateHash"] = (hash) => {
23+
updateHash: NullDependency["updateHash"] = (hash: {
24+
update: (str: string) => void;
25+
}) => {
1826
hash.update(this.codeBlock);
1927
};
2028
}
@@ -24,14 +32,14 @@ makeSerializable(
2432
"react-docgen-typescript-plugin/dist/dependency"
2533
);
2634

27-
type NullDependencyTemplateType = InstanceType<
28-
typeof webpack.dependencies.NullDependency.Template
29-
>;
30-
class DocGenTemplate extends webpack.dependencies.NullDependency.Template
35+
type NullDependencyTemplateType = InstanceType<typeof NullDependency.Template>;
36+
class DocGenTemplate extends NullDependency.Template
3137
implements NullDependencyTemplateType {
38+
// eslint-disable-next-line
39+
// @ts-ignore: Webpack 4 type
3240
apply: NullDependencyTemplateType["apply"] = (
3341
dependency: DocGenDependency,
34-
source
42+
source: { insert: (a: number, b: string) => void }
3543
) => {
3644
if (dependency.codeBlock) {
3745
// Insert to the end
@@ -40,6 +48,8 @@ class DocGenTemplate extends webpack.dependencies.NullDependency.Template
4048
};
4149
}
4250

51+
// eslint-disable-next-line
52+
// @ts-ignore: Webpack 4 type
4353
DocGenDependency.Template = DocGenTemplate;
4454

4555
export default DocGenDependency;

src/plugin.ts

Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import path from "path";
22
import createDebug from "debug";
3-
import { Compiler, WebpackPluginInstance } from "webpack";
43
import ts from "typescript";
54
import * as docGen from "react-docgen-typescript";
65
import { matcher } from "micromatch";
@@ -60,16 +59,61 @@ const matchGlob = (globs?: string[]) => {
6059
Boolean(filename && matchers.find((match) => match(filename)));
6160
};
6261

62+
/** Run the docgen parser and inject the result into the output */
63+
/** This is used for webpack 4 or earlier */
64+
function processModule(
65+
parser: docGen.FileParser,
66+
webpackModule: webpack.Module,
67+
tsProgram: ts.Program,
68+
loaderOptions: Required<LoaderOptions>
69+
) {
70+
if (!webpackModule) {
71+
return;
72+
}
73+
74+
// eslint-disable-next-line
75+
// @ts-ignore: Webpack 4 type
76+
const { userRequest } = webpackModule;
77+
78+
const componentDocs = parser.parseWithProgramProvider(
79+
userRequest,
80+
() => tsProgram
81+
);
82+
83+
if (!componentDocs.length) {
84+
return;
85+
}
86+
87+
const docs = generateDocgenCodeBlock({
88+
filename: userRequest,
89+
source: userRequest,
90+
componentDocs,
91+
...loaderOptions,
92+
}).substring(userRequest.length);
93+
94+
// eslint-disable-next-line
95+
// @ts-ignore: Webpack 4 type
96+
// eslint-disable-next-line
97+
let sourceWithDocs = webpackModule._source._value;
98+
99+
sourceWithDocs += `\n${docs}\n`;
100+
101+
// eslint-disable-next-line
102+
// @ts-ignore: Webpack 4 type
103+
// eslint-disable-next-line
104+
webpackModule._source._value = sourceWithDocs;
105+
}
106+
63107
/** Inject typescript docgen information into modules at the end of a build */
64-
export default class DocgenPlugin implements WebpackPluginInstance {
108+
export default class DocgenPlugin implements webpack.WebpackPluginInstance {
65109
private name = "React Docgen Typescript Plugin";
66110
private options: PluginOptions;
67111

68112
constructor(options: PluginOptions = {}) {
69113
this.options = options;
70114
}
71115

72-
apply(compiler: Compiler): void {
116+
apply(compiler: webpack.Compiler): void {
73117
const pluginName = "DocGenPlugin";
74118
const {
75119
docgenOptions,
@@ -83,12 +127,20 @@ export default class DocgenPlugin implements WebpackPluginInstance {
83127
const { exclude = [], include = ["**/**.tsx"] } = this.options;
84128
const isExcluded = matchGlob(exclude);
85129
const isIncluded = matchGlob(include);
130+
const webpackVersion = compiler.webpack.version;
131+
const isWebpack5 = parseInt(webpackVersion.split(".")[0], 10) >= 5;
86132

87133
compiler.hooks.compilation.tap(pluginName, (compilation) => {
88-
compilation.dependencyTemplates.set(
89-
DocGenDependency,
90-
new DocGenDependency.Template()
91-
);
134+
if (isWebpack5) {
135+
compilation.dependencyTemplates.set(
136+
// eslint-disable-next-line
137+
// @ts-ignore: Webpack 4 type
138+
DocGenDependency,
139+
// eslint-disable-next-line
140+
// @ts-ignore: Webpack 4 type
141+
new DocGenDependency.Template()
142+
);
143+
}
92144

93145
compilation.hooks.seal.tap(pluginName, () => {
94146
const modulesToProcess: [string, webpack.Module][] = [];
@@ -122,21 +174,28 @@ export default class DocgenPlugin implements WebpackPluginInstance {
122174

123175
// 3. Process and parse each module and add the type information
124176
// as a dependency
125-
modulesToProcess.forEach(([name, module]) =>
126-
module.addDependency(
127-
new DocGenDependency(
128-
generateDocgenCodeBlock({
129-
filename: name,
130-
source: name,
131-
componentDocs: docGenParser.parseWithProgramProvider(
132-
name,
133-
() => tsProgram
134-
),
135-
...generateOptions,
136-
})
137-
)
138-
)
139-
);
177+
modulesToProcess.forEach(([name, module]) => {
178+
if (isWebpack5) {
179+
module.addDependency(
180+
// eslint-disable-next-line
181+
// @ts-ignore: Webpack 4 type
182+
new DocGenDependency(
183+
generateDocgenCodeBlock({
184+
filename: name,
185+
source: name,
186+
componentDocs: docGenParser.parseWithProgramProvider(
187+
name,
188+
() => tsProgram
189+
),
190+
...generateOptions,
191+
})
192+
)
193+
);
194+
} else {
195+
// Assume webpack 4 or earlier
196+
processModule(docGenParser, module, tsProgram, generateOptions);
197+
}
198+
});
140199
});
141200
});
142201
}

0 commit comments

Comments
 (0)