From b743c7dfbb1e3d8b9e5b9c62442437d84b645db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juho=20Veps=C3=A4l=C3=A4inen?= Date: Mon, 14 Jun 2021 20:59:23 +0300 Subject: [PATCH 1/8] fix: Restore old webpack 4 code path to fix hmr after refresh Related to https://github.com/storybookjs/storybook/issues/15233. --- src/plugin.ts | 177 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 134 insertions(+), 43 deletions(-) diff --git a/src/plugin.ts b/src/plugin.ts index d5920e7..9331c68 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -143,6 +143,18 @@ export default class DocgenPlugin implements webpack.WebpackPluginInstance { } apply(compiler: webpack.Compiler): void { + // Property compiler.version is set only starting from webpack 5 + const webpackVersion = compiler.webpack?.version || ""; + const isWebpack5 = parseInt(webpackVersion.split(".")[0], 10) >= 5; + + if (isWebpack5) { + this.applyWebpack5(compiler); + } else { + this.applyWebpack4(compiler); + } + } + + applyWebpack5(compiler: webpack.Compiler): void { const pluginName = "DocGenPlugin"; const { docgenOptions, @@ -156,29 +168,24 @@ export default class DocgenPlugin implements webpack.WebpackPluginInstance { const { exclude = [], include = ["**/**.tsx"] } = this.options; const isExcluded = matchGlob(exclude); const isIncluded = matchGlob(include); - // Property compiler.version is set only starting from webpack 5 - const webpackVersion = compiler.webpack?.version || ""; - const isWebpack5 = parseInt(webpackVersion.split(".")[0], 10) >= 5; compiler.hooks.compilation.tap( pluginName, (compilation: webpack.Compilation) => { - if (isWebpack5) { - // Since this file is needed only for webpack 5, load it only then - // to simplify the implementation of the file. - // - // eslint-disable-next-line - const { DocGenDependency } = require("./dependency"); + // Since this file is needed only for webpack 5, load it only then + // to simplify the implementation of the file. + // + // eslint-disable-next-line + const { DocGenDependency } = require("./dependency"); - compilation.dependencyTemplates.set( - // eslint-disable-next-line - // @ts-ignore: Webpack 4 type - DocGenDependency, - // eslint-disable-next-line - // @ts-ignore: Webpack 4 type - new DocGenDependency.Template() - ); - } + compilation.dependencyTemplates.set( + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + DocGenDependency, + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + new DocGenDependency.Template() + ); compilation.hooks.seal.tap(pluginName, () => { const modulesToProcess: [string, webpack.Module][] = []; @@ -217,38 +224,122 @@ export default class DocgenPlugin implements webpack.WebpackPluginInstance { // 3. Process and parse each module and add the type information // as a dependency modulesToProcess.forEach(([name, module]) => { - if (isWebpack5) { - // Since this file is needed only for webpack 5, load it only then - // to simplify the implementation of the file. - // + // Since this file is needed only for webpack 5, load it only then + // to simplify the implementation of the file. + // + // eslint-disable-next-line + const { DocGenDependency } = require("./dependency"); + + module.addDependency( // eslint-disable-next-line - const { DocGenDependency } = require("./dependency"); - - module.addDependency( - // eslint-disable-next-line - // @ts-ignore: Webpack 4 type - new DocGenDependency( - generateDocgenCodeBlock({ - filename: name, - source: name, - componentDocs: docGenParser.parseWithProgramProvider( - name, - () => tsProgram - ), - ...generateOptions, - }).substring(name.length) - ) - ); - } else { - // Assume webpack 4 or earlier - processModule(docGenParser, module, tsProgram, generateOptions); - } + // @ts-ignore: Webpack 4 type + new DocGenDependency( + generateDocgenCodeBlock({ + filename: name, + source: name, + componentDocs: docGenParser.parseWithProgramProvider( + name, + () => tsProgram + ), + ...generateOptions, + }).substring(name.length) + ) + ); }); }); } ); } + applyWebpack4(compiler: webpack.Compiler): void { + const { docgenOptions, compilerOptions } = this.getOptions(); + const parser = docGen.withCompilerOptions(compilerOptions, docgenOptions); + const { exclude = [], include = ["**/**.tsx"] } = this.options; + const isExcluded = matchGlob(exclude); + const isIncluded = matchGlob(include); + + compiler.hooks.make.tap(this.name, (compilation) => { + compilation.hooks.seal.tap(this.name, () => { + const modulesToProcess: webpack.Module[] = []; + + compilation.modules.forEach((module: webpack.Module) => { + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + if (!module.built) { + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + debugExclude(`Ignoring un-built module: ${module.userRequest}`); + return; + } + + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + if (module.external) { + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + debugExclude(`Ignoring external module: ${module.userRequest}`); + return; + } + + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + if (!module.rawRequest) { + debugExclude( + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + `Ignoring module without "rawRequest": ${module.userRequest}` + ); + return; + } + + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + if (isExcluded(module.userRequest)) { + debugExclude( + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + `Module not matched in "exclude": ${module.userRequest}` + ); + return; + } + + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + if (!isIncluded(module.userRequest)) { + debugExclude( + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + `Module not matched in "include": ${module.userRequest}` + ); + return; + } + + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + debugInclude(module.userRequest); + modulesToProcess.push(module); + }); + + const tsProgram = ts.createProgram( + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + modulesToProcess.map((v) => v.userRequest), + compilerOptions + ); + + modulesToProcess.forEach((m) => + processModule(parser, m, tsProgram, { + docgenCollectionName: "STORYBOOK_REACT_CLASSES", + setDisplayName: true, + typePropName: "type", + }) + ); + + cache.save(); + }); + }); + } + getOptions(): { docgenOptions: docGen.ParserOptions; generateOptions: { From c2a43fb75882cf2cc4b3945da6c9e151a66c4c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juho=20Veps=C3=A4l=C3=A4inen?= Date: Mon, 14 Jun 2021 21:00:27 +0300 Subject: [PATCH 2/8] chore: Drop a redundant dependency --- package.json | 4 +--- yarn.lock | 16 +--------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 72c48bd..72a3a36 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,7 @@ "flat-cache": "^3.0.4", "micromatch": "^4.0.2", "react-docgen-typescript": "^1.22.0", - "tslib": "^2.0.0", - "webpack-sources": "^2.2.0" + "tslib": "^2.0.0" }, "devDependencies": { "@types/debug": "^4.1.5", @@ -50,7 +49,6 @@ "@types/micromatch": "^4.0.1", "@types/node": "^14.0.12", "@types/react": "^17.0.0", - "@types/webpack-sources": "^2.1.0", "@typescript-eslint/eslint-plugin": "^4.9.0", "@typescript-eslint/parser": "^4.9.0", "auto": "^10.2.3", diff --git a/yarn.lock b/yarn.lock index a269182..b77f79d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -982,25 +982,11 @@ "@types/prop-types" "*" csstype "^3.0.2" -"@types/source-list-map@*": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" - integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== - "@types/stack-utils@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== -"@types/webpack-sources@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.0.tgz#8882b0bd62d1e0ce62f183d0d01b72e6e82e8c10" - integrity sha512-LXn/oYIpBeucgP1EIJbKQ2/4ZmpvRl+dlrFdX7+94SKRUV3Evy3FsfMZY318vGhkWUS5MPhtOM3w1/hCOAOXcg== - dependencies: - "@types/node" "*" - "@types/source-list-map" "*" - source-map "^0.7.3" - "@types/yargs-parser@*": version "15.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" @@ -6377,7 +6363,7 @@ webpack-merge@^5.7.3: clone-deep "^4.0.1" wildcard "^2.0.0" -webpack-sources@^2.1.1, webpack-sources@^2.2.0: +webpack-sources@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac" integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w== From 09fe9dc63ee52dbef396894c4c160cfeb84891cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juho=20Veps=C3=A4l=C3=A4inen?= Date: Mon, 14 Jun 2021 21:08:14 +0300 Subject: [PATCH 3/8] fix: Fix a lint error --- src/dependency.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dependency.ts b/src/dependency.ts index 6d9d67a..daa67f0 100644 --- a/src/dependency.ts +++ b/src/dependency.ts @@ -46,6 +46,7 @@ class DocGenTemplate extends NullDependency.Template }; } +// eslint-disable-next-line // @ts-ignore TODO: How to type this correctly? DocGenDependency.Template = DocGenTemplate; From bb8fd0f9b9dd5af66b068444a64ea4a1bf2640a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juho=20Veps=C3=A4l=C3=A4inen?= Date: Fri, 18 Jun 2021 05:31:20 +0300 Subject: [PATCH 4/8] fix: Add missing getModuleEvaluationSideEffectsState It looks like webpack can expect this method to be defined in certain cases. Related to https://github.com/storybookjs/storybook/issues/15221. --- src/dependency.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dependency.ts b/src/dependency.ts index 6d9d67a..0e0bbe7 100644 --- a/src/dependency.ts +++ b/src/dependency.ts @@ -18,6 +18,10 @@ class DocGenDependency extends NullDependency { this.codeBlock = codeBlock; } + getModuleEvaluationSideEffectsState(): boolean { + return false; + } + updateHash: webpack.dependencies.NullDependency["updateHash"] = (hash) => { hash.update(this.codeBlock); }; From f24f46c363d88a162304c8a6ca64c6fa7a836467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juho=20Veps=C3=A4l=C3=A4inen?= Date: Fri, 18 Jun 2021 05:35:03 +0300 Subject: [PATCH 5/8] fix: Fix lint error --- src/dependency.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dependency.ts b/src/dependency.ts index 0e0bbe7..5057baf 100644 --- a/src/dependency.ts +++ b/src/dependency.ts @@ -50,6 +50,7 @@ class DocGenTemplate extends NullDependency.Template }; } +// eslint-disable-next-line // @ts-ignore TODO: How to type this correctly? DocGenDependency.Template = DocGenTemplate; From 3e13e62390a04ddefab127283f76d91e676e7b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juho=20Veps=C3=A4l=C3=A4inen?= Date: Wed, 30 Jun 2021 10:30:37 +0300 Subject: [PATCH 6/8] fix: Fix rebuild performance for webpack 5 This change adds the needed check for webpack 5 to avoid extra processing. Ported from https://github.com/hipstersmoothie/react-docgen-typescript-plugin/pull/45. --- src/plugin.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/plugin.ts b/src/plugin.ts index 9331c68..d0384cb 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -198,6 +198,30 @@ export default class DocgenPlugin implements webpack.WebpackPluginInstance { const nameForCondition = module.nameForCondition() || ""; + // Ignore already built modules for webpack 5 + if (!compilation.builtModules.has(module)) { + debugExclude(`Ignoring un-built module: ${nameForCondition}`); + return; + } + + // Ignore external modules + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + if (module.external) { + debugExclude(`Ignoring external module: ${nameForCondition}`); + return; + } + + // Ignore raw requests + // eslint-disable-next-line + // @ts-ignore: Webpack 4 type + if (!module.rawRequest) { + debugExclude( + `Ignoring module without "rawRequest": ${nameForCondition}` + ); + return; + } + if (isExcluded(nameForCondition)) { debugExclude( `Module not matched in "exclude": ${nameForCondition}` From 74bb959f468fd6dee7cbc7c8b68cc01e4bcb343c Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Mon, 29 Nov 2021 15:03:58 +0800 Subject: [PATCH 7/8] Fix CI branches --- .github/workflows/push.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 2e8f99e..4c04dfa 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -3,10 +3,12 @@ name: Node CI on: push: branches: - - master + - main + - next pull_request: branches: - - master + - main + - next jobs: build: From 1d8450811c711547432eedd242fce85293a80126 Mon Sep 17 00:00:00 2001 From: Steven Petryk Date: Thu, 27 Jan 2022 17:04:54 -0800 Subject: [PATCH 8/8] Create test case showing invalid syntax issue --- src/__tests__/__fixtures__/SubComponent.tsx | 11 +++++++ .../generateDocgenCodeBlock.test.ts.snap | 29 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/__tests__/__fixtures__/SubComponent.tsx diff --git a/src/__tests__/__fixtures__/SubComponent.tsx b/src/__tests__/__fixtures__/SubComponent.tsx new file mode 100644 index 0000000..a58358e --- /dev/null +++ b/src/__tests__/__fixtures__/SubComponent.tsx @@ -0,0 +1,11 @@ +import * as React from "react"; + +export default function Root(props: { name: string }) { + return root {props.name}; +} + +function Sub(props: { name: string }) { + return sub {props.name}; +} + +Root.Sub = Sub; diff --git a/src/__tests__/__snapshots__/generateDocgenCodeBlock.test.ts.snap b/src/__tests__/__snapshots__/generateDocgenCodeBlock.test.ts.snap index 316ff3d..7dc4fc1 100644 --- a/src/__tests__/__snapshots__/generateDocgenCodeBlock.test.ts.snap +++ b/src/__tests__/__snapshots__/generateDocgenCodeBlock.test.ts.snap @@ -209,6 +209,35 @@ try { catch (__react_docgen_typescript_loader_error) { }" `; +exports[`component fixture SubComponent.tsx has code block generated 1`] = ` +"import * as React from \\"react\\"; + +export default function Root(props: { name: string }) { + return root {props.name}; +} + +function Sub(props: { name: string }) { + return sub {props.name}; +} + +Root.Sub = Sub; + +try { + // @ts-ignore + SubComponent.displayName = \\"SubComponent\\"; + // @ts-ignore + SubComponent.__docgenInfo = { \\"description\\": \\"\\", \\"displayName\\": \\"SubComponent\\", \\"props\\": { \\"name\\": { \\"defaultValue\\": null, \\"description\\": \\"\\", \\"name\\": \\"name\\", \\"required\\": true, \\"type\\": { \\"name\\": \\"string\\" } } } }; +} +catch (__react_docgen_typescript_loader_error) { } +try { + // @ts-ignore + default.Sub.displayName = \\"default.Sub\\"; + // @ts-ignore + default.Sub.__docgenInfo = { \\"description\\": \\"\\", \\"displayName\\": \\"default.Sub\\", \\"props\\": { \\"name\\": { \\"defaultValue\\": null, \\"description\\": \\"\\", \\"name\\": \\"name\\", \\"required\\": true, \\"type\\": { \\"name\\": \\"string\\" } } } }; +} +catch (__react_docgen_typescript_loader_error) { }" +`; + exports[`component fixture TextOnlyComponent.tsx has code block generated 1`] = ` "import * as React from \\"react\\";