From fef5a12a23ec60af198a21d10a4dff4ec9de9dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie?= Date: Tue, 31 May 2022 15:33:02 +0200 Subject: [PATCH] Svelte: Generate docgen (#392) --- packages/builder-vite/package.json | 1 + .../builder-vite/plugins/svelte-docgen.ts | 97 ++++++++++++++ packages/builder-vite/vite-config.ts | 3 + yarn.lock | 118 +++++++++++++++++- 4 files changed, 214 insertions(+), 5 deletions(-) create mode 100644 packages/builder-vite/plugins/svelte-docgen.ts diff --git a/packages/builder-vite/package.json b/packages/builder-vite/package.json index 31af8fe2..9ac0fd20 100644 --- a/packages/builder-vite/package.json +++ b/packages/builder-vite/package.json @@ -25,6 +25,7 @@ "magic-string": "^0.26.1", "react-docgen": "^6.0.0-alpha.0", "slash": "^3.0.0", + "sveltedoc-parser": "^4.2.1", "vite-plugin-mdx": "^3.5.6" }, "devDependencies": { diff --git a/packages/builder-vite/plugins/svelte-docgen.ts b/packages/builder-vite/plugins/svelte-docgen.ts new file mode 100644 index 00000000..8f7e41de --- /dev/null +++ b/packages/builder-vite/plugins/svelte-docgen.ts @@ -0,0 +1,97 @@ +import type { Plugin } from 'vite'; +import MagicString from 'magic-string'; +import path from 'path'; +import fs from 'fs'; +import svelteDoc from 'sveltedoc-parser'; +import type { SvelteParserOptions } from 'sveltedoc-parser'; +import { logger } from '@storybook/node-logger'; +import { preprocess } from 'svelte/compiler'; + +// Most of the code here should probably be exported by @storybook/svelte and reused here. +// See: https://github.com/storybookjs/storybook/blob/next/app/svelte/src/server/svelte-docgen-loader.ts + +// From https://github.com/sveltejs/svelte/blob/8db3e8d0297e052556f0b6dde310ef6e197b8d18/src/compiler/compile/utils/get_name_from_filename.ts +// Copied because it is not exported from the compiler +function getNameFromFilename(filename: string) { + if (!filename) return null; + + const parts = filename.split(/[/\\]/).map(encodeURI); + + if (parts.length > 1) { + const index_match = parts[parts.length - 1].match(/^index(\.\w+)/); + if (index_match) { + parts.pop(); + parts[parts.length - 1] += index_match[1]; + } + } + + const base = parts + .pop() + ?.replace(/%/g, 'u') + .replace(/\.[^.]+$/, '') + .replace(/[^a-zA-Z_$0-9]+/g, '_') + .replace(/^_/, '') + .replace(/_$/, '') + .replace(/^(\d)/, '_$1'); + + if (!base) { + throw new Error(`Could not derive component name from file ${filename}`); + } + + return base[0].toUpperCase() + base.slice(1); +} + +export function svelteDocgen(svelteOptions: Record): Plugin { + const cwd = process.cwd(); + const { preprocess: preprocessOptions, logDocgen = false } = svelteOptions; + + return { + name: 'svelte-docgen', + async transform(src: string, id: string) { + if (/\.(svelte)$/.test(id)) { + const resource = path.relative(cwd, id); + + let docOptions; + if (preprocessOptions) { + const src = fs.readFileSync(resource).toString(); + + const { code: fileContent } = await preprocess(src, preprocessOptions, { filename: resource }); + + docOptions = { + fileContent, + }; + } else { + docOptions = { filename: resource }; + } + + // set SvelteDoc options + const options: SvelteParserOptions = { + ...docOptions, + version: 3, + }; + + const s = new MagicString(src); + + try { + const componentDoc = await svelteDoc.parse(options); + // get filename for source content + const file = path.basename(resource); + + componentDoc.name = path.basename(file); + + const componentName = getNameFromFilename(resource); + s.append(`;${componentName}.__docgen = ${JSON.stringify(componentDoc)}`); + } catch (error: any) { + if (logDocgen) { + logger.error(error); + } + } + + return { + code: s.toString(), + map: s.generateMap(), + }; + } + }, + }; +} diff --git a/packages/builder-vite/vite-config.ts b/packages/builder-vite/vite-config.ts index b094e7d6..34ca9ee8 100644 --- a/packages/builder-vite/vite-config.ts +++ b/packages/builder-vite/vite-config.ts @@ -134,6 +134,9 @@ export async function pluginConfig(options: ExtendedOptions, _type: PluginConfig throw err; } } + + const { svelteDocgen } = await import('./plugins/svelte-docgen'); + plugins.push(svelteDocgen(svelteOptions)); } if (framework === 'react') { diff --git a/yarn.lock b/yarn.lock index ef3283b9..723aa7fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1762,6 +1762,23 @@ __metadata: languageName: node linkType: hard +"@eslint/eslintrc@npm:^1.0.5": + version: 1.3.0 + resolution: "@eslint/eslintrc@npm:1.3.0" + dependencies: + ajv: ^6.12.4 + debug: ^4.3.2 + espree: ^9.3.2 + globals: ^13.15.0 + ignore: ^5.2.0 + import-fresh: ^3.2.1 + js-yaml: ^4.1.0 + minimatch: ^3.1.2 + strip-json-comments: ^3.1.1 + checksum: a1e734ad31a8b5328dce9f479f185fd4fc83dd7f06c538e1fa457fd8226b89602a55cc6458cd52b29573b01cdfaf42331be8cfc1fec732570086b591f4ed6515 + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^1.2.2": version: 1.2.2 resolution: "@eslint/eslintrc@npm:1.2.2" @@ -2902,6 +2919,7 @@ __metadata: magic-string: ^0.26.1 react-docgen: ^6.0.0-alpha.0 slash: ^3.0.0 + sveltedoc-parser: ^4.2.1 vite-plugin-mdx: ^3.5.6 vue-docgen-api: ^4.40.0 peerDependencies: @@ -5106,7 +5124,7 @@ __metadata: languageName: node linkType: hard -"acorn-jsx@npm:^5.2.0, acorn-jsx@npm:^5.3.1": +"acorn-jsx@npm:^5.2.0, acorn-jsx@npm:^5.3.1, acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" peerDependencies: @@ -5158,7 +5176,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.2.4, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.7.0": +"acorn@npm:^8.2.4, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.6.0, acorn@npm:^8.7.0, acorn@npm:^8.7.1": version: 8.7.1 resolution: "acorn@npm:8.7.1" bin: @@ -8744,7 +8762,7 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^7.1.1": +"eslint-scope@npm:^7.1.0, eslint-scope@npm:^7.1.1": version: 7.1.1 resolution: "eslint-scope@npm:7.1.1" dependencies: @@ -8788,7 +8806,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.0.0, eslint-visitor-keys@npm:^3.3.0": +"eslint-visitor-keys@npm:^3.0.0, eslint-visitor-keys@npm:^3.1.0, eslint-visitor-keys@npm:^3.3.0": version: 3.3.0 resolution: "eslint-visitor-keys@npm:3.3.0" checksum: d59e68a7c5a6d0146526b0eec16ce87fbf97fe46b8281e0d41384224375c4e52f5ffb9e16d48f4ea50785cde93f766b0c898e31ab89978d88b0e1720fbfb7808 @@ -8841,6 +8859,54 @@ __metadata: languageName: node linkType: hard +"eslint@npm:8.4.1": + version: 8.4.1 + resolution: "eslint@npm:8.4.1" + dependencies: + "@eslint/eslintrc": ^1.0.5 + "@humanwhocodes/config-array": ^0.9.2 + ajv: ^6.10.0 + chalk: ^4.0.0 + cross-spawn: ^7.0.2 + debug: ^4.3.2 + doctrine: ^3.0.0 + enquirer: ^2.3.5 + escape-string-regexp: ^4.0.0 + eslint-scope: ^7.1.0 + eslint-utils: ^3.0.0 + eslint-visitor-keys: ^3.1.0 + espree: ^9.2.0 + esquery: ^1.4.0 + esutils: ^2.0.2 + fast-deep-equal: ^3.1.3 + file-entry-cache: ^6.0.1 + functional-red-black-tree: ^1.0.1 + glob-parent: ^6.0.1 + globals: ^13.6.0 + ignore: ^4.0.6 + import-fresh: ^3.0.0 + imurmurhash: ^0.1.4 + is-glob: ^4.0.0 + js-yaml: ^4.1.0 + json-stable-stringify-without-jsonify: ^1.0.1 + levn: ^0.4.1 + lodash.merge: ^4.6.2 + minimatch: ^3.0.4 + natural-compare: ^1.4.0 + optionator: ^0.9.1 + progress: ^2.0.0 + regexpp: ^3.2.0 + semver: ^7.2.1 + strip-ansi: ^6.0.1 + strip-json-comments: ^3.1.0 + text-table: ^0.2.0 + v8-compile-cache: ^2.0.3 + bin: + eslint: bin/eslint.js + checksum: d962cd7cd0f68ddc2412f47154b8992ad3af987cf47fa6e60e51a2b7d32a91f934388f7d29e2c45b16b7ac69f0d220d0a483189ec6ba43a8a480110c34f158f9 + languageName: node + linkType: hard + "eslint@npm:^8.6.0": version: 8.14.0 resolution: "eslint@npm:8.14.0" @@ -8897,6 +8963,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:9.2.0": + version: 9.2.0 + resolution: "espree@npm:9.2.0" + dependencies: + acorn: ^8.6.0 + acorn-jsx: ^5.3.1 + eslint-visitor-keys: ^3.1.0 + checksum: ae533a058036e3efeeac43a0ee39c74ab347e2a73bbe2946fba33cc0d84aca657e675bc317ed9afd95338f79d5d5a862afec2f717d2539ae13fa9f1638371761 + languageName: node + linkType: hard + "espree@npm:^7.2.0": version: 7.3.1 resolution: "espree@npm:7.3.1" @@ -8908,6 +8985,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:^9.2.0, espree@npm:^9.3.2": + version: 9.3.2 + resolution: "espree@npm:9.3.2" + dependencies: + acorn: ^8.7.1 + acorn-jsx: ^5.3.2 + eslint-visitor-keys: ^3.3.0 + checksum: 9a790d6779847051e87f70d720a0f6981899a722419e80c92ab6dee01e1ab83b8ce52d11b4dc96c2c490182efb5a4c138b8b0d569205bfe1cd4629e658e58c30 + languageName: node + linkType: hard + "espree@npm:^9.3.1": version: 9.3.1 resolution: "espree@npm:9.3.1" @@ -10235,6 +10323,15 @@ __metadata: languageName: node linkType: hard +"globals@npm:^13.15.0": + version: 13.15.0 + resolution: "globals@npm:13.15.0" + dependencies: + type-fest: ^0.20.2 + checksum: 383ade0873b2ab29ce6d143466c203ed960491575bc97406395e5c8434026fb02472ab2dfff5bc16689b8460269b18fda1047975295cd0183904385c51258bae + languageName: node + linkType: hard + "globals@npm:^13.6.0, globals@npm:^13.9.0": version: 13.13.0 resolution: "globals@npm:13.13.0" @@ -13428,7 +13525,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4": +"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -17422,6 +17519,17 @@ __metadata: languageName: node linkType: hard +"sveltedoc-parser@npm:^4.2.1": + version: 4.3.1 + resolution: "sveltedoc-parser@npm:4.3.1" + dependencies: + eslint: 8.4.1 + espree: 9.2.0 + htmlparser2-svelte: 4.1.0 + checksum: c0260161c80d1c5ec52808e98f82def379746020c4ffad6e462fec3cd299a3435430eb55a8fe4265884975575bd2aa74f9eda11617864e8aa5a4ca585b57dc51 + languageName: node + linkType: hard + "symbol-tree@npm:^3.2.4": version: 3.2.4 resolution: "symbol-tree@npm:3.2.4"