From 9642d04f28ec38f48cbd89a4f70a48f6890d1c0b Mon Sep 17 00:00:00 2001 From: "charles.gruenais" Date: Thu, 19 Jan 2023 23:57:34 +0100 Subject: [PATCH 01/13] Naive implementation of local vite plugin to replace vite-plugin-externals --- .../src/plugins/externals-plugin.ts | 38 +++++++++++++++++++ code/lib/builder-vite/src/vite-config.ts | 3 +- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 code/lib/builder-vite/src/plugins/externals-plugin.ts diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.ts b/code/lib/builder-vite/src/plugins/externals-plugin.ts new file mode 100644 index 000000000000..574ffc1365fb --- /dev/null +++ b/code/lib/builder-vite/src/plugins/externals-plugin.ts @@ -0,0 +1,38 @@ +import { init, parse } from 'es-module-lexer'; +import MagicString from 'magic-string'; +import { globals } from '@storybook/preview/globals'; + +type SingleGlobalName = keyof typeof globals; + +export async function externalsPlugin() { + await init; + return { + name: 'storybook:externals-plugin', + enforce: 'post', + async transform(code: string, id: string) { + const globalsList = Object.keys(globals) as SingleGlobalName[]; + if (globalsList.every((glob) => !code.includes(glob))) return undefined; + + const [imports] = parse(code); + const src = new MagicString(code); + imports.forEach(({ n: path, ss: startPosition, se: endPosition }) => { + const packageName = path as SingleGlobalName | undefined; + const packageAndDelimiters = new RegExp(`.${packageName}.`); + if (packageName && globalsList.includes(packageName)) { + src.update( + startPosition, + endPosition, + src + .slice(startPosition, endPosition) + .replace('import ', 'const ') + .replace(' as ', ': ') + .replace(' from ', ' = ') + .replace(packageAndDelimiters, globals[packageName]) + ); + } + }); + + return src.toString(); + }, + }; +} diff --git a/code/lib/builder-vite/src/vite-config.ts b/code/lib/builder-vite/src/vite-config.ts index 316d970189a0..aa1b2572fc72 100644 --- a/code/lib/builder-vite/src/vite-config.ts +++ b/code/lib/builder-vite/src/vite-config.ts @@ -18,6 +18,7 @@ import { mdxPlugin, stripStoryHMRBoundary, } from './plugins'; +import { externalsPlugin } from './plugins/externals-plugin'; export type PluginConfigType = 'build' | 'development'; @@ -91,7 +92,7 @@ export async function pluginConfig(options: Options) { } }, }, - viteExternalsPlugin(globals, { useWindow: false, disableInServe: true }), + await externalsPlugin(), ] as PluginOption[]; // TODO: framework doesn't exist, should move into framework when/if built From b49050af0a25ef54ff60ca55a78b653dcced37ae Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Sun, 22 Jan 2023 19:47:18 -0500 Subject: [PATCH 02/13] Remove unused imports --- code/lib/builder-vite/src/vite-config.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/lib/builder-vite/src/vite-config.ts b/code/lib/builder-vite/src/vite-config.ts index b17f10222297..b216455c7f6c 100644 --- a/code/lib/builder-vite/src/vite-config.ts +++ b/code/lib/builder-vite/src/vite-config.ts @@ -7,9 +7,7 @@ import type { UserConfig as ViteConfig, InlineConfig, } from 'vite'; -import { viteExternalsPlugin } from 'vite-plugin-externals'; import { isPreservingSymlinks, getFrameworkName, getBuilderOptions } from '@storybook/core-common'; -import { globals } from '@storybook/preview/globals'; import type { Options } from '@storybook/types'; import { codeGeneratorPlugin, From 61acc22f7facd4b9675257bc256499d9e87b2f16 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Sun, 22 Jan 2023 19:49:12 -0500 Subject: [PATCH 03/13] Remove vite-plugin-externals --- code/lib/builder-vite/package.json | 3 +-- code/yarn.lock | 24 +----------------------- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/code/lib/builder-vite/package.json b/code/lib/builder-vite/package.json index deb9221b9880..952eaa47c04d 100644 --- a/code/lib/builder-vite/package.json +++ b/code/lib/builder-vite/package.json @@ -61,8 +61,7 @@ "glob-promise": "^4.2.0", "magic-string": "^0.26.1", "rollup": "^2.25.0 || ^3.3.0", - "slash": "^3.0.0", - "vite-plugin-externals": "^0.5.1" + "slash": "^3.0.0" }, "devDependencies": { "@types/express": "^4.17.13", diff --git a/code/yarn.lock b/code/yarn.lock index 9b67de8df997..bf5f3ba77288 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5808,7 +5808,6 @@ __metadata: slash: ^3.0.0 typescript: ~4.9.3 vite: ^4.0.4 - vite-plugin-externals: ^0.5.1 peerDependencies: "@preact/preset-vite": "*" typescript: ">= 4.3.x" @@ -9681,7 +9680,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.1.0, acorn@npm:^8.4.0, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.6.0, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.1": +"acorn@npm:^8.1.0, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.6.0, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.1": version: 8.8.1 resolution: "acorn@npm:8.8.1" bin: @@ -14054,13 +14053,6 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^0.4.1": - version: 0.4.1 - resolution: "es-module-lexer@npm:0.4.1" - checksum: 6463778f04367979d7770cefb1969b6bfc277319e8437a39718b3516df16b1b496b725ceec96a2d24975837a15cf4d56838f16d9c8c7640ad13ad9c8f93ad6fc - languageName: node - linkType: hard - "es-module-lexer@npm:^0.9.0, es-module-lexer@npm:^0.9.3": version: 0.9.3 resolution: "es-module-lexer@npm:0.9.3" @@ -28114,20 +28106,6 @@ __metadata: languageName: node linkType: hard -"vite-plugin-externals@npm:^0.5.1": - version: 0.5.1 - resolution: "vite-plugin-externals@npm:0.5.1" - dependencies: - acorn: ^8.4.0 - es-module-lexer: ^0.4.1 - fs-extra: ^10.0.0 - magic-string: ^0.25.7 - peerDependencies: - vite: ">=2.0.0" - checksum: a8b07fc911efb0a0ed47e12c6dc8f71280c40d222ae9b9ffa5c238aa5427bfda1b13444b378bdb649734057a53574f362f4e9af3ef96180be8901af18cab2f78 - languageName: node - linkType: hard - "vite-plugin-turbosnap@npm:^1.0.1": version: 1.0.1 resolution: "vite-plugin-turbosnap@npm:1.0.1" From 69f23e4b56a8200d89f9640311c59655dd15acca Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Sun, 22 Jan 2023 20:37:25 -0500 Subject: [PATCH 04/13] Replace all ` as ` in import specifiers with colons --- code/lib/builder-vite/src/plugins/externals-plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.ts b/code/lib/builder-vite/src/plugins/externals-plugin.ts index 574ffc1365fb..7555f9b70e21 100644 --- a/code/lib/builder-vite/src/plugins/externals-plugin.ts +++ b/code/lib/builder-vite/src/plugins/externals-plugin.ts @@ -25,7 +25,7 @@ export async function externalsPlugin() { src .slice(startPosition, endPosition) .replace('import ', 'const ') - .replace(' as ', ': ') + .replaceAll(' as ', ': ') .replace(' from ', ' = ') .replace(packageAndDelimiters, globals[packageName]) ); From 22af7fe51247c8522b778aa90054c1de62030dd7 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Sun, 22 Jan 2023 20:39:46 -0500 Subject: [PATCH 05/13] Return sourcemap --- code/lib/builder-vite/src/plugins/externals-plugin.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.ts b/code/lib/builder-vite/src/plugins/externals-plugin.ts index 7555f9b70e21..c6c2df2d9a4c 100644 --- a/code/lib/builder-vite/src/plugins/externals-plugin.ts +++ b/code/lib/builder-vite/src/plugins/externals-plugin.ts @@ -32,7 +32,14 @@ export async function externalsPlugin() { } }); - return src.toString(); + return { + code: src.toString(), + map: src.generateMap({ + source: id, + includeContent: true, + hires: true, + }), + }; }, }; } From b78244e652e72f470295b39715b61d7ecad93990 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Sun, 22 Jan 2023 22:15:26 -0500 Subject: [PATCH 06/13] Create aliases for dev --- .../src/plugins/externals-plugin.ts | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.ts b/code/lib/builder-vite/src/plugins/externals-plugin.ts index c6c2df2d9a4c..bbb8e26e75b2 100644 --- a/code/lib/builder-vite/src/plugins/externals-plugin.ts +++ b/code/lib/builder-vite/src/plugins/externals-plugin.ts @@ -1,5 +1,9 @@ +import { join } from 'node:path'; import { init, parse } from 'es-module-lexer'; import MagicString from 'magic-string'; +import { emptyDirSync, ensureDir, ensureFile, writeFile } from 'fs-extra'; +import { mergeAlias } from 'vite'; +import type { Alias, Plugin } from 'vite'; import { globals } from '@storybook/preview/globals'; type SingleGlobalName = keyof typeof globals; @@ -9,6 +13,30 @@ export async function externalsPlugin() { return { name: 'storybook:externals-plugin', enforce: 'post', + async config(config, { command }) { + if (command !== 'serve') { + return undefined; + } + const newAlias = mergeAlias([], config.resolve?.alias) as Alias[]; + + const cachePath = join(process.cwd(), 'node_modules', '.cache', 'vite-plugin-externals'); + await ensureDir(cachePath); + await emptyDirSync(cachePath); + + // eslint-disable-next-line no-restricted-syntax + for await (const externalKey of Object.keys(globals) as Array) { + const externalCachePath = join(cachePath, `${externalKey}.js`); + newAlias.push({ find: new RegExp(`^${externalKey}$`), replacement: externalCachePath }); + await ensureFile(externalCachePath); + await writeFile(externalCachePath, `module.exports = ${globals[externalKey]};`); + } + + return { + resolve: { + alias: newAlias, + }, + }; + }, async transform(code: string, id: string) { const globalsList = Object.keys(globals) as SingleGlobalName[]; if (globalsList.every((glob) => !code.includes(glob))) return undefined; @@ -41,5 +69,5 @@ export async function externalsPlugin() { }), }; }, - }; + } satisfies Plugin; } From 156178506db61edec41dc2d7fdf2c4c9abd2e5b6 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Sun, 22 Jan 2023 22:23:04 -0500 Subject: [PATCH 07/13] Add a few docblocks --- .../src/plugins/externals-plugin.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.ts b/code/lib/builder-vite/src/plugins/externals-plugin.ts index bbb8e26e75b2..3897c3cf3c79 100644 --- a/code/lib/builder-vite/src/plugins/externals-plugin.ts +++ b/code/lib/builder-vite/src/plugins/externals-plugin.ts @@ -8,11 +8,31 @@ import { globals } from '@storybook/preview/globals'; type SingleGlobalName = keyof typeof globals; +/** + * This plugin swaps out imports of pre-bundled storybook preview modules for destructures from global + * variables that are added in runtime.mjs. + * + * For instance: + * + * ```js + * import { useMemo as useMemo2, useEffect as useEffect2 } from "@storybook/preview-api"; + * ``` + * + * becomes + * + * ```js + * const { useMemo: useMemo2, useEffect: useEffect2 } = __STORYBOOK_MODULE_PREVIEW_API__; + * ``` + * + * It is based on existing plugins like https://github.com/crcong/vite-plugin-externals + * and https://github.com/eight04/rollup-plugin-external-globals, but simplified to meet our meager needs. + */ export async function externalsPlugin() { await init; return { name: 'storybook:externals-plugin', enforce: 'post', + // In dev (serve), we set up aliases to files that we write into node_modules/.cache. async config(config, { command }) { if (command !== 'serve') { return undefined; @@ -37,6 +57,7 @@ export async function externalsPlugin() { }, }; }, + // Replace imports with variables destructured from global scope async transform(code: string, id: string) { const globalsList = Object.keys(globals) as SingleGlobalName[]; if (globalsList.every((glob) => !code.includes(glob))) return undefined; From 563fa6c1f6660a332cbd3a6e0aba9058dd32f706 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Sun, 22 Jan 2023 23:44:09 -0500 Subject: [PATCH 08/13] Handle minified code --- code/lib/builder-vite/src/plugins/externals-plugin.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.ts b/code/lib/builder-vite/src/plugins/externals-plugin.ts index 3897c3cf3c79..de5032f9d1bd 100644 --- a/code/lib/builder-vite/src/plugins/externals-plugin.ts +++ b/code/lib/builder-vite/src/plugins/externals-plugin.ts @@ -74,8 +74,10 @@ export async function externalsPlugin() { src .slice(startPosition, endPosition) .replace('import ', 'const ') + .replace('import{', 'const {') .replaceAll(' as ', ': ') .replace(' from ', ' = ') + .replace('}from', '} = ') .replace(packageAndDelimiters, globals[packageName]) ); } From 76ca07d4ad238cdb5dec9e9b2caee5c9dec1ad69 Mon Sep 17 00:00:00 2001 From: Charles GRUENAIS Date: Mon, 23 Jan 2023 23:35:35 +0100 Subject: [PATCH 09/13] Parallel cache creation, grouped all replaces as a single regex, added basic tests --- .../src/plugins/externals-plugin.test.ts | 31 +++++++++ .../src/plugins/externals-plugin.ts | 64 ++++++++++++------- 2 files changed, 71 insertions(+), 24 deletions(-) create mode 100644 code/lib/builder-vite/src/plugins/externals-plugin.test.ts diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.test.ts b/code/lib/builder-vite/src/plugins/externals-plugin.test.ts new file mode 100644 index 000000000000..3eb2bb8a4dc8 --- /dev/null +++ b/code/lib/builder-vite/src/plugins/externals-plugin.test.ts @@ -0,0 +1,31 @@ +import { rewriteImport } from './externals-plugin'; + +const packageName = '@storybook/package'; +const globals = { [packageName]: '_STORYBOOK_PACKAGE_' }; + +const cases = [ + { + globals, + packageName, + input: `import { Rain, Jour as Day, Nuit as Night, Sun } from "${packageName}"`, + output: `const { Rain, Jour: Day, Nuit: Night, Sun } = ${globals[packageName]}`, + }, + { + globals, + packageName, + input: `import{Rain,Jour as Day,Nuit as Night,Sun}from '${packageName}'`, + output: `const {Rain,Jour: Day,Nuit: Night,Sun} = ${globals[packageName]}`, + }, + { + globals, + packageName, + input: `const { Afternoon } = await import('${packageName}')`, + output: `const { Afternoon } = ${globals[packageName]}`, + }, +]; + +test('rewriteImport', () => { + cases.forEach(({ input, output, globals: caseGlobals, packageName: casePackage }) => { + expect(rewriteImport(input, caseGlobals, casePackage)).toStrictEqual(output); + }); +}); diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.ts b/code/lib/builder-vite/src/plugins/externals-plugin.ts index de5032f9d1bd..62631313b958 100644 --- a/code/lib/builder-vite/src/plugins/externals-plugin.ts +++ b/code/lib/builder-vite/src/plugins/externals-plugin.ts @@ -1,12 +1,21 @@ import { join } from 'node:path'; import { init, parse } from 'es-module-lexer'; import MagicString from 'magic-string'; -import { emptyDirSync, ensureDir, ensureFile, writeFile } from 'fs-extra'; +import { emptyDir, ensureDir, ensureFile, writeFile } from 'fs-extra'; import { mergeAlias } from 'vite'; import type { Alias, Plugin } from 'vite'; import { globals } from '@storybook/preview/globals'; type SingleGlobalName = keyof typeof globals; +type Globals = typeof globals & Record; + +const replacementMap = new Map([ + ['import ', 'const '], + ['import{', 'const {'], + [' as ', ': '], + [' from ', ' = '], + ['}from', '} ='], +]); /** * This plugin swaps out imports of pre-bundled storybook preview modules for destructures from global @@ -25,7 +34,7 @@ type SingleGlobalName = keyof typeof globals; * ``` * * It is based on existing plugins like https://github.com/crcong/vite-plugin-externals - * and https://github.com/eight04/rollup-plugin-external-globals, but simplified to meet our meager needs. + * and https://github.com/eight04/rollup-plugin-external-globals, but simplified to meet our simple needs. */ export async function externalsPlugin() { await init; @@ -41,15 +50,15 @@ export async function externalsPlugin() { const cachePath = join(process.cwd(), 'node_modules', '.cache', 'vite-plugin-externals'); await ensureDir(cachePath); - await emptyDirSync(cachePath); - - // eslint-disable-next-line no-restricted-syntax - for await (const externalKey of Object.keys(globals) as Array) { - const externalCachePath = join(cachePath, `${externalKey}.js`); - newAlias.push({ find: new RegExp(`^${externalKey}$`), replacement: externalCachePath }); - await ensureFile(externalCachePath); - await writeFile(externalCachePath, `module.exports = ${globals[externalKey]};`); - } + await emptyDir(cachePath); + await Promise.all( + (Object.keys(globals) as Array).map(async (externalKey) => { + const externalCachePath = join(cachePath, `${externalKey}.js`); + newAlias.push({ find: new RegExp(`^${externalKey}$`), replacement: externalCachePath }); + await ensureFile(externalCachePath); + await writeFile(externalCachePath, `module.exports = ${globals[externalKey]};`); + }) + ); return { resolve: { @@ -66,20 +75,10 @@ export async function externalsPlugin() { const src = new MagicString(code); imports.forEach(({ n: path, ss: startPosition, se: endPosition }) => { const packageName = path as SingleGlobalName | undefined; - const packageAndDelimiters = new RegExp(`.${packageName}.`); if (packageName && globalsList.includes(packageName)) { - src.update( - startPosition, - endPosition, - src - .slice(startPosition, endPosition) - .replace('import ', 'const ') - .replace('import{', 'const {') - .replaceAll(' as ', ': ') - .replace(' from ', ' = ') - .replace('}from', '} = ') - .replace(packageAndDelimiters, globals[packageName]) - ); + const importStatement = src.slice(startPosition, endPosition); + const transformedImport = rewriteImport(importStatement, globals, packageName); + src.update(startPosition, endPosition, transformedImport); } }); @@ -94,3 +93,20 @@ export async function externalsPlugin() { }, } satisfies Plugin; } + +export function rewriteImport( + importStatement: string, + globs: T extends true ? Globals : Record, + packageName: keyof typeof globs & string +): string { + const lookup = [ + ...replacementMap.keys(), + `.${packageName}.`, + `await import\\(.${packageName}.\\)`, + ]; + const search = new RegExp(`(${lookup.join('|')})`, 'g'); + return importStatement.replace( + search, + (match) => replacementMap.get(match) ?? globs[packageName] + ); +} From 71c3cc2c68806652046bee7bc224d938b0f777be Mon Sep 17 00:00:00 2001 From: Charles GRUENAIS Date: Wed, 25 Jan 2023 18:59:13 +0100 Subject: [PATCH 10/13] Cleaned up pointless types --- code/lib/builder-vite/src/plugins/externals-plugin.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.ts b/code/lib/builder-vite/src/plugins/externals-plugin.ts index 62631313b958..ea1fb6373186 100644 --- a/code/lib/builder-vite/src/plugins/externals-plugin.ts +++ b/code/lib/builder-vite/src/plugins/externals-plugin.ts @@ -6,7 +6,6 @@ import { mergeAlias } from 'vite'; import type { Alias, Plugin } from 'vite'; import { globals } from '@storybook/preview/globals'; -type SingleGlobalName = keyof typeof globals; type Globals = typeof globals & Record; const replacementMap = new Map([ @@ -39,7 +38,7 @@ const replacementMap = new Map([ export async function externalsPlugin() { await init; return { - name: 'storybook:externals-plugin', + name: 'storybook:external-globals-plugin', enforce: 'post', // In dev (serve), we set up aliases to files that we write into node_modules/.cache. async config(config, { command }) { @@ -68,13 +67,13 @@ export async function externalsPlugin() { }, // Replace imports with variables destructured from global scope async transform(code: string, id: string) { - const globalsList = Object.keys(globals) as SingleGlobalName[]; + const globalsList = Object.keys(globals); if (globalsList.every((glob) => !code.includes(glob))) return undefined; const [imports] = parse(code); const src = new MagicString(code); imports.forEach(({ n: path, ss: startPosition, se: endPosition }) => { - const packageName = path as SingleGlobalName | undefined; + const packageName = path; if (packageName && globalsList.includes(packageName)) { const importStatement = src.slice(startPosition, endPosition); const transformedImport = rewriteImport(importStatement, globals, packageName); From f86ff03826971add05203f3eaa8fc70585be2422 Mon Sep 17 00:00:00 2001 From: Charles GRUENAIS Date: Thu, 26 Jan 2023 14:22:32 +0100 Subject: [PATCH 11/13] Fixed default imports, added support for import namespace specifiers, added more tests --- .../src/plugins/externals-plugin.test.ts | 16 ++++++++++-- .../src/plugins/externals-plugin.ts | 25 +++++++++++++------ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.test.ts b/code/lib/builder-vite/src/plugins/externals-plugin.test.ts index 3eb2bb8a4dc8..0a6b6d84944b 100644 --- a/code/lib/builder-vite/src/plugins/externals-plugin.test.ts +++ b/code/lib/builder-vite/src/plugins/externals-plugin.test.ts @@ -13,8 +13,20 @@ const cases = [ { globals, packageName, - input: `import{Rain,Jour as Day,Nuit as Night,Sun}from '${packageName}'`, - output: `const {Rain,Jour: Day,Nuit: Night,Sun} = ${globals[packageName]}`, + input: `import * as Foo from "${packageName}"`, + output: `const Foo = ${globals[packageName]}`, + }, + { + globals, + packageName, + input: `import Foo from "${packageName}"`, + output: `const {default: Foo} = ${globals[packageName]}`, + }, + { + globals, + packageName, + input: `import{Rain,Jour as Day,Nuit as Night,Sun}from'${packageName}'`, + output: `const {Rain,Jour: Day,Nuit: Night,Sun} =${globals[packageName]}`, }, { globals, diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.ts b/code/lib/builder-vite/src/plugins/externals-plugin.ts index ea1fb6373186..28194a3df4a9 100644 --- a/code/lib/builder-vite/src/plugins/externals-plugin.ts +++ b/code/lib/builder-vite/src/plugins/externals-plugin.ts @@ -8,9 +8,12 @@ import { globals } from '@storybook/preview/globals'; type Globals = typeof globals & Record; +const escapeKeys = (key: string) => key.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); +const defaultImportRegExp = 'import ([^*{}]+) from'; const replacementMap = new Map([ ['import ', 'const '], ['import{', 'const {'], + ['* as ', ''], [' as ', ': '], [' from ', ' = '], ['}from', '} ='], @@ -93,19 +96,27 @@ export async function externalsPlugin() { } satisfies Plugin; } +function getDefaultImportReplacement(match: string) { + const matched = match.match(defaultImportRegExp); + return matched && `const {default: ${matched[1]}} =`; +} + +function getSearchRegExp(packageName: string) { + const staticKeys = [...replacementMap.keys()].map(escapeKeys); + const packageNameLiteral = `.${packageName}.`; + const dynamicImportExpression = `await import\\(.${packageName}.\\)`; + const lookup = [defaultImportRegExp, ...staticKeys, packageNameLiteral, dynamicImportExpression]; + return new RegExp(`(${lookup.join('|')})`, 'g'); +} + export function rewriteImport( importStatement: string, globs: T extends true ? Globals : Record, packageName: keyof typeof globs & string ): string { - const lookup = [ - ...replacementMap.keys(), - `.${packageName}.`, - `await import\\(.${packageName}.\\)`, - ]; - const search = new RegExp(`(${lookup.join('|')})`, 'g'); + const search = getSearchRegExp(packageName); return importStatement.replace( search, - (match) => replacementMap.get(match) ?? globs[packageName] + (match) => replacementMap.get(match) ?? getDefaultImportReplacement(match) ?? globs[packageName] ); } From e9592b247e88701c7a0c7210811848f5d4f91fd6 Mon Sep 17 00:00:00 2001 From: Charles GRUENAIS Date: Thu, 26 Jan 2023 16:15:37 +0100 Subject: [PATCH 12/13] Pass globals definition through plugin's arguments --- .../src/plugins/externals-plugin.test.ts | 2 +- .../src/plugins/externals-plugin.ts | 19 ++++++++----------- code/lib/builder-vite/src/vite-config.ts | 3 ++- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.test.ts b/code/lib/builder-vite/src/plugins/externals-plugin.test.ts index 0a6b6d84944b..290569e28d5b 100644 --- a/code/lib/builder-vite/src/plugins/externals-plugin.test.ts +++ b/code/lib/builder-vite/src/plugins/externals-plugin.test.ts @@ -38,6 +38,6 @@ const cases = [ test('rewriteImport', () => { cases.forEach(({ input, output, globals: caseGlobals, packageName: casePackage }) => { - expect(rewriteImport(input, caseGlobals, casePackage)).toStrictEqual(output); + expect(rewriteImport(input, caseGlobals, casePackage)).toStrictEqual(output); }); }); diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.ts b/code/lib/builder-vite/src/plugins/externals-plugin.ts index 28194a3df4a9..e23e869d07be 100644 --- a/code/lib/builder-vite/src/plugins/externals-plugin.ts +++ b/code/lib/builder-vite/src/plugins/externals-plugin.ts @@ -4,9 +4,6 @@ import MagicString from 'magic-string'; import { emptyDir, ensureDir, ensureFile, writeFile } from 'fs-extra'; import { mergeAlias } from 'vite'; import type { Alias, Plugin } from 'vite'; -import { globals } from '@storybook/preview/globals'; - -type Globals = typeof globals & Record; const escapeKeys = (key: string) => key.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); const defaultImportRegExp = 'import ([^*{}]+) from'; @@ -38,7 +35,7 @@ const replacementMap = new Map([ * It is based on existing plugins like https://github.com/crcong/vite-plugin-externals * and https://github.com/eight04/rollup-plugin-external-globals, but simplified to meet our simple needs. */ -export async function externalsPlugin() { +export async function externalsPlugin(externals: Record) { await init; return { name: 'storybook:external-globals-plugin', @@ -54,11 +51,11 @@ export async function externalsPlugin() { await ensureDir(cachePath); await emptyDir(cachePath); await Promise.all( - (Object.keys(globals) as Array).map(async (externalKey) => { + (Object.keys(externals) as Array).map(async (externalKey) => { const externalCachePath = join(cachePath, `${externalKey}.js`); newAlias.push({ find: new RegExp(`^${externalKey}$`), replacement: externalCachePath }); await ensureFile(externalCachePath); - await writeFile(externalCachePath, `module.exports = ${globals[externalKey]};`); + await writeFile(externalCachePath, `module.exports = ${externals[externalKey]};`); }) ); @@ -70,7 +67,7 @@ export async function externalsPlugin() { }, // Replace imports with variables destructured from global scope async transform(code: string, id: string) { - const globalsList = Object.keys(globals); + const globalsList = Object.keys(externals); if (globalsList.every((glob) => !code.includes(glob))) return undefined; const [imports] = parse(code); @@ -79,7 +76,7 @@ export async function externalsPlugin() { const packageName = path; if (packageName && globalsList.includes(packageName)) { const importStatement = src.slice(startPosition, endPosition); - const transformedImport = rewriteImport(importStatement, globals, packageName); + const transformedImport = rewriteImport(importStatement, externals, packageName); src.update(startPosition, endPosition, transformedImport); } }); @@ -109,10 +106,10 @@ function getSearchRegExp(packageName: string) { return new RegExp(`(${lookup.join('|')})`, 'g'); } -export function rewriteImport( +export function rewriteImport( importStatement: string, - globs: T extends true ? Globals : Record, - packageName: keyof typeof globs & string + globs: Record, + packageName: string ): string { const search = getSearchRegExp(packageName); return importStatement.replace( diff --git a/code/lib/builder-vite/src/vite-config.ts b/code/lib/builder-vite/src/vite-config.ts index b216455c7f6c..8eeac9ddbabb 100644 --- a/code/lib/builder-vite/src/vite-config.ts +++ b/code/lib/builder-vite/src/vite-config.ts @@ -8,6 +8,7 @@ import type { InlineConfig, } from 'vite'; import { isPreservingSymlinks, getFrameworkName, getBuilderOptions } from '@storybook/core-common'; +import { globals } from '@storybook/preview/globals'; import type { Options } from '@storybook/types'; import { codeGeneratorPlugin, @@ -93,7 +94,7 @@ export async function pluginConfig(options: Options) { } }, }, - await externalsPlugin(), + await externalsPlugin(globals), ] as PluginOption[]; // TODO: framework doesn't exist, should move into framework when/if built From 759047eb4ea5239902647c0a1ddfac8854f7a0e1 Mon Sep 17 00:00:00 2001 From: Charles GRUENAIS Date: Fri, 27 Jan 2023 17:12:32 +0100 Subject: [PATCH 13/13] Made names more consistent --- ...ternals-plugin.test.ts => external-globals-plugin.test.ts} | 2 +- .../{externals-plugin.ts => external-globals-plugin.ts} | 2 +- code/lib/builder-vite/src/plugins/index.ts | 1 + code/lib/builder-vite/src/vite-config.ts | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) rename code/lib/builder-vite/src/plugins/{externals-plugin.test.ts => external-globals-plugin.test.ts} (95%) rename code/lib/builder-vite/src/plugins/{externals-plugin.ts => external-globals-plugin.ts} (98%) diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.test.ts b/code/lib/builder-vite/src/plugins/external-globals-plugin.test.ts similarity index 95% rename from code/lib/builder-vite/src/plugins/externals-plugin.test.ts rename to code/lib/builder-vite/src/plugins/external-globals-plugin.test.ts index 290569e28d5b..2bd540ee9c29 100644 --- a/code/lib/builder-vite/src/plugins/externals-plugin.test.ts +++ b/code/lib/builder-vite/src/plugins/external-globals-plugin.test.ts @@ -1,4 +1,4 @@ -import { rewriteImport } from './externals-plugin'; +import { rewriteImport } from './external-globals-plugin'; const packageName = '@storybook/package'; const globals = { [packageName]: '_STORYBOOK_PACKAGE_' }; diff --git a/code/lib/builder-vite/src/plugins/externals-plugin.ts b/code/lib/builder-vite/src/plugins/external-globals-plugin.ts similarity index 98% rename from code/lib/builder-vite/src/plugins/externals-plugin.ts rename to code/lib/builder-vite/src/plugins/external-globals-plugin.ts index e23e869d07be..60841e22bc97 100644 --- a/code/lib/builder-vite/src/plugins/externals-plugin.ts +++ b/code/lib/builder-vite/src/plugins/external-globals-plugin.ts @@ -35,7 +35,7 @@ const replacementMap = new Map([ * It is based on existing plugins like https://github.com/crcong/vite-plugin-externals * and https://github.com/eight04/rollup-plugin-external-globals, but simplified to meet our simple needs. */ -export async function externalsPlugin(externals: Record) { +export async function externalGlobalsPlugin(externals: Record) { await init; return { name: 'storybook:external-globals-plugin', diff --git a/code/lib/builder-vite/src/plugins/index.ts b/code/lib/builder-vite/src/plugins/index.ts index 114a09191365..bccebbdb4833 100644 --- a/code/lib/builder-vite/src/plugins/index.ts +++ b/code/lib/builder-vite/src/plugins/index.ts @@ -3,3 +3,4 @@ export * from './mdx-plugin'; export * from './strip-story-hmr-boundaries'; export * from './code-generator-plugin'; export * from './csf-plugin'; +export * from './external-globals-plugin'; diff --git a/code/lib/builder-vite/src/vite-config.ts b/code/lib/builder-vite/src/vite-config.ts index 8eeac9ddbabb..066abda9e4ea 100644 --- a/code/lib/builder-vite/src/vite-config.ts +++ b/code/lib/builder-vite/src/vite-config.ts @@ -16,9 +16,9 @@ import { injectExportOrderPlugin, mdxPlugin, stripStoryHMRBoundary, + externalGlobalsPlugin, } from './plugins'; -import { externalsPlugin } from './plugins/externals-plugin'; import type { BuilderOptions } from './types'; export type PluginConfigType = 'build' | 'development'; @@ -94,7 +94,7 @@ export async function pluginConfig(options: Options) { } }, }, - await externalsPlugin(globals), + await externalGlobalsPlugin(globals), ] as PluginOption[]; // TODO: framework doesn't exist, should move into framework when/if built