Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vite: Replace vite-plugin-externals with custom plugin #20698

Merged
merged 18 commits into from
Jan 27, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions code/lib/builder-vite/package.json
Original file line number Diff line number Diff line change
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
96 changes: 96 additions & 0 deletions code/lib/builder-vite/src/plugins/externals-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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;

/**
* 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.
Dschungelabenteuer marked this conversation as resolved.
Show resolved Hide resolved
*/
export async function externalsPlugin() {
Dschungelabenteuer marked this conversation as resolved.
Show resolved Hide resolved
await init;
return {
name: 'storybook:externals-plugin',
Dschungelabenteuer marked this conversation as resolved.
Show resolved Hide resolved
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 emptyDirSync(cachePath);

// eslint-disable-next-line no-restricted-syntax
for await (const externalKey of Object.keys(globals) as Array<keyof typeof globals>) {
const externalCachePath = join(cachePath, `${externalKey}.js`);
newAlias.push({ find: new RegExp(`^${externalKey}$`), replacement: externalCachePath });
await ensureFile(externalCachePath);
await writeFile(externalCachePath, `module.exports = ${globals[externalKey]};`);
}
Dschungelabenteuer marked this conversation as resolved.
Show resolved Hide resolved

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[];
Dschungelabenteuer marked this conversation as resolved.
Show resolved Hide resolved
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;
Dschungelabenteuer marked this conversation as resolved.
Show resolved Hide resolved
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])
);
}
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am scared, without a proper test-suite. ... we have tests for this?

We should extract this code into a function and test the flip out of it.

I would also recommend writing a single regex + a single replace, that's easier to grok than 6 separate replace commands.?


return {
code: src.toString(),
map: src.generateMap({
source: id,
includeContent: true,
hires: true,
}),
};
},
} satisfies Plugin;
}
6 changes: 3 additions & 3 deletions code/lib/builder-vite/src/vite-config.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down