Skip to content

Commit

Permalink
Merge 76ca07d into 4de2478
Browse files Browse the repository at this point in the history
  • Loading branch information
Dschungelabenteuer committed Jan 25, 2023
2 parents 4de2478 + 76ca07d commit 9152417
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 28 deletions.
3 changes: 1 addition & 2 deletions code/lib/builder-vite/package.json
Expand Up @@ -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",
Expand Down
31 changes: 31 additions & 0 deletions 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<false>(input, caseGlobals, casePackage)).toStrictEqual(output);
});
});
112 changes: 112 additions & 0 deletions code/lib/builder-vite/src/plugins/externals-plugin.ts
@@ -0,0 +1,112 @@
import { join } from 'node:path';
import { init, parse } from 'es-module-lexer';
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 SingleGlobalName = keyof typeof globals;
type Globals = typeof globals & Record<string, string>;

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
* 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 simple 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;
}
const newAlias = mergeAlias([], config.resolve?.alias) as Alias[];

const cachePath = join(process.cwd(), 'node_modules', '.cache', 'vite-plugin-externals');
await ensureDir(cachePath);
await emptyDir(cachePath);
await Promise.all(
(Object.keys(globals) as Array<keyof typeof globals>).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: {
alias: newAlias,
},
};
},
// 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;

const [imports] = parse(code);
const src = new MagicString(code);
imports.forEach(({ n: path, ss: startPosition, se: endPosition }) => {
const packageName = path as SingleGlobalName | undefined;
if (packageName && globalsList.includes(packageName)) {
const importStatement = src.slice(startPosition, endPosition);
const transformedImport = rewriteImport(importStatement, globals, packageName);
src.update(startPosition, endPosition, transformedImport);
}
});

return {
code: src.toString(),
map: src.generateMap({
source: id,
includeContent: true,
hires: true,
}),
};
},
} satisfies Plugin;
}

export function rewriteImport<T = true>(
importStatement: string,
globs: T extends true ? Globals : Record<string, string>,
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]
);
}
6 changes: 3 additions & 3 deletions code/lib/builder-vite/src/vite-config.ts
Expand Up @@ -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,
Expand All @@ -18,6 +16,8 @@ import {
mdxPlugin,
stripStoryHMRBoundary,
} from './plugins';

import { externalsPlugin } from './plugins/externals-plugin';
import type { BuilderOptions } from './types';

export type PluginConfigType = 'build' | 'development';
Expand Down Expand Up @@ -93,7 +93,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
Expand Down
24 changes: 1 addition & 23 deletions code/yarn.lock
Expand Up @@ -5766,7 +5766,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"
Expand Down Expand Up @@ -9658,7 +9657,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:
Expand Down Expand Up @@ -13997,13 +13996,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"
Expand Down Expand Up @@ -28063,20 +28055,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"
Expand Down

0 comments on commit 9152417

Please sign in to comment.