Skip to content

Commit

Permalink
Vite: use vite.config.js as config base
Browse files Browse the repository at this point in the history
  • Loading branch information
IanVS committed Aug 26, 2022
1 parent d58f997 commit 8d16927
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 136 deletions.
25 changes: 6 additions & 19 deletions code/lib/builder-vite/src/build.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,19 @@
import { build as viteBuild } from 'vite';
import { stringifyProcessEnvs } from './envs';
import { commonConfig } from './vite-config';

import type { EnvsRaw, ExtendedOptions } from './types';
import type { ExtendedOptions } from './types';

export async function build(options: ExtendedOptions) {
const { presets } = options;

const baseConfig = await commonConfig(options, 'build');
const config = {
...baseConfig,
build: {
outDir: options.outputDir,
emptyOutDir: false, // do not clean before running Vite build - Storybook has already added assets in there!
sourcemap: true,
},
const config = await commonConfig(options, 'build');
config.build = {
outDir: options.outputDir,
emptyOutDir: false, // do not clean before running Vite build - Storybook has already added assets in there!
sourcemap: true,
};

const finalConfig = await presets.apply('viteFinal', config, options);

const envsRaw = await presets.apply<Promise<EnvsRaw>>('env');
// Stringify env variables after getting `envPrefix` from the final config
const envs = stringifyProcessEnvs(envsRaw, finalConfig.envPrefix);
// Update `define`
finalConfig.define = {
...finalConfig.define,
...envs,
};

await viteBuild(finalConfig);
}
3 changes: 0 additions & 3 deletions code/lib/builder-vite/src/envs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ const allowedEnvVariables = [
'SSR',
];

// Env variables starts with env prefix will be exposed to your client source code via `import.meta.env`
export const allowedEnvPrefix = ['VITE_', 'STORYBOOK_'];

/**
* Customized version of stringifyProcessEnvs from @storybook/core-common which
* uses import.meta.env instead of process.env and checks for allowed variables.
Expand Down
11 changes: 5 additions & 6 deletions code/lib/builder-vite/src/optimizeDeps.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as path from 'path';
import { normalizePath, resolveConfig, UserConfig } from 'vite';
import { normalizePath, resolveConfig } from 'vite';
import type { InlineConfig as ViteInlineConfig } from 'vite';
import { listStories } from './list-stories';

import type { ExtendedOptions } from './types';
Expand Down Expand Up @@ -101,13 +102,11 @@ const INCLUDE_CANDIDATES = [
const asyncFilter = async (arr: string[], predicate: (val: string) => Promise<boolean>) =>
Promise.all(arr.map(predicate)).then((results) => arr.filter((_v, index) => results[index]));

export async function getOptimizeDeps(
config: UserConfig & { configFile: false; root: string },
options: ExtendedOptions
) {
const { root } = config;
export async function getOptimizeDeps(config: ViteInlineConfig, options: ExtendedOptions) {
const { root = process.cwd() } = config;
const absoluteStories = await listStories(options);
const stories = absoluteStories.map((storyPath) => normalizePath(path.relative(root, storyPath)));
// TODO: check if resolveConfig takes a lot of time, possible optimizations here
const resolvedConfig = await resolveConfig(config, 'serve', 'development');

// This function converts ids which might include ` > ` to a real path, if it exists on disk.
Expand Down
140 changes: 60 additions & 80 deletions code/lib/builder-vite/src/vite-config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import * as path from 'path';
import fs from 'fs';
import { Plugin } from 'vite';
import { loadConfigFromFile, mergeConfig } from 'vite';
import type {
ConfigEnv,
InlineConfig as ViteInlineConfig,
PluginOption,
UserConfig as ViteConfig,
} from 'vite';
import viteReact from '@vitejs/plugin-react';
import type { UserConfig } from 'vite';
import { allowedEnvPrefix as envPrefix } from './envs';
import { codeGeneratorPlugin } from './code-generator-plugin';
import { stringifyProcessEnvs } from './envs';
import { injectExportOrderPlugin } from './inject-export-order-plugin';
import { mdxPlugin } from './plugins/mdx-plugin';
import { noFouc } from './plugins/no-fouc';
import type { ExtendedOptions } from './types';
import type { ExtendedOptions, EnvsRaw } from './types';

export type PluginConfigType = 'build' | 'development';

Expand All @@ -22,23 +27,58 @@ export function readPackageJson(): Record<string, any> | false {
return JSON.parse(jsonContent);
}

const configEnvServe: ConfigEnv = {
mode: 'development',
command: 'serve',
ssrBuild: false,
};

const configEnvBuild: ConfigEnv = {
mode: 'production',
command: 'build',
ssrBuild: false,
};

// Vite config that is common to development and production mode
export async function commonConfig(
options: ExtendedOptions,
_type: PluginConfigType
): Promise<UserConfig & { configFile: false; root: string }> {
return {
): Promise<ViteInlineConfig> {
const { presets } = options;
const configEnv = _type === 'development' ? configEnvServe : configEnvBuild;

const { config: userConfig = {} } = (await loadConfigFromFile(configEnv)) ?? {};

const sbConfig = {
configFile: false,
root: path.resolve(options.configDir, '..'),
cacheDir: 'node_modules/.vite-storybook',
envPrefix,
define: {},
plugins: await pluginConfig(options, _type),
root: path.resolve(options.configDir, '..'),
plugins: await pluginConfig(options),
// If an envPrefix is specified in the vite config, add STORYBOOK_ to it,
// otherwise, add VITE_ and STORYBOOK_ so that vite doesn't lose its default.
envPrefix: userConfig.envPrefix ? 'STORYBOOK_' : ['VITE_', 'STORYBOOK_'],
};

const config: ViteConfig = mergeConfig(userConfig, sbConfig);

// Sanitize environment variables if needed
const envsRaw = await presets.apply<Promise<EnvsRaw>>('env');
if (Object.keys(envsRaw).length) {
// Stringify env variables after getting `envPrefix` from the config
const envs = stringifyProcessEnvs(envsRaw, config.envPrefix);
config.define = {
...config.define,
...envs,
};
}

return config;
}

export async function pluginConfig(options: ExtendedOptions, _type: PluginConfigType) {
const { framework, presets } = options;
export async function pluginConfig(options: ExtendedOptions) {
const { presets } = options;
const framework = await presets.apply('framework', '', options);
const frameworkName: string = typeof framework === 'object' ? framework.name : framework;
const svelteOptions: Record<string, any> = await presets.apply('svelteOptions', {}, options);

const plugins = [
Expand All @@ -47,74 +87,14 @@ export async function pluginConfig(options: ExtendedOptions, _type: PluginConfig
mdxPlugin(options),
noFouc(),
injectExportOrderPlugin,
// We need the react plugin here to support MDX.
viteReact({
// Do not treat story files as HMR boundaries, storybook itself needs to handle them.
exclude: [/\.stories\.([tj])sx?$/, /node_modules/].concat(
framework === 'react' ? [] : [/\.([tj])sx?$/]
),
}),
{
name: 'vite-plugin-storybook-allow',
enforce: 'post',
config(config) {
// if there is no allow list then Vite allows anything in the root directory
// if there is an allow list then Vite allows anything in the listed directories
// add the .storybook directory only if there's an allow list so that we don't end up
// disallowing the root directory unless it's already disallowed
if (config?.server?.fs?.allow) {
config.server.fs.allow.push('.storybook');
}
},
},
] as Plugin[];
if (framework === 'svelte') {
try {
// eslint-disable-next-line import/no-extraneous-dependencies, global-require
const sveltePlugin = require('@sveltejs/vite-plugin-svelte').svelte;

// We need to create two separate svelte plugins, one for stories, and one for other svelte files
// because stories.svelte files cannot be hot-module-reloaded.
// Suggested in: https://github.com/sveltejs/vite-plugin-svelte/issues/321#issuecomment-1113205509

// First, create an array containing user exclude patterns, to combine with ours.

let userExclude = [];
if (Array.isArray(svelteOptions?.exclude)) {
userExclude = svelteOptions?.exclude;
} else if (svelteOptions?.exclude) {
userExclude = [svelteOptions?.exclude];
}
] as PluginOption[];

// These are the svelte stories we need to exclude from HMR
const storyPatterns = ['**/*.story.svelte', '**/*.stories.svelte'];
// Non-story svelte files
// Starting in 1.0.0-next.42, svelte.config.js is included by default.
// We disable that, but allow it to be overridden in svelteOptions
plugins.push(sveltePlugin({ ...svelteOptions, exclude: [...userExclude, ...storyPatterns] }));
// Svelte stories without HMR
const storySveltePlugin = sveltePlugin({
...svelteOptions,
exclude: userExclude,
include: storyPatterns,
hot: false,
});
plugins.push({
// Starting in 1.0.0-next.43, the plugin function returns an array of plugins. We only want the first one here.
...(Array.isArray(storySveltePlugin) ? storySveltePlugin[0] : storySveltePlugin),
name: 'vite-plugin-svelte-stories',
});
} catch (err) {
if ((err as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND') {
throw new Error(
'@storybook/builder-vite requires @sveltejs/vite-plugin-svelte to be installed' +
' when using @storybook/svelte.' +
' Please install it and start storybook again.'
);
}
throw err;
}
// We need the react plugin here to support MDX in non-react projects.
if (frameworkName !== '@storybook/react-vite') {
plugins.push(viteReact());
}

if (frameworkName === 'svelte') {
// eslint-disable-next-line import/no-extraneous-dependencies, global-require
const { loadSvelteConfig } = require('@sveltejs/vite-plugin-svelte');
const config = { ...loadSvelteConfig(), ...svelteOptions };
Expand All @@ -135,12 +115,12 @@ export async function pluginConfig(options: ExtendedOptions, _type: PluginConfig
plugins.push(svelteDocgen(config));
}

if (framework === 'preact') {
if (frameworkName === 'preact') {
// eslint-disable-next-line global-require
plugins.push(require('@preact/preset-vite').default());
}

if (framework === 'glimmerx') {
if (frameworkName === 'glimmerx') {
// eslint-disable-next-line global-require, import/extensions
const plugin = require('vite-plugin-glimmerx/index.cjs');
plugins.push(plugin.default());
Expand Down
46 changes: 18 additions & 28 deletions code/lib/builder-vite/src/vite-server.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,30 @@
import type { Server } from 'http';
import { createServer } from 'vite';
import { stringifyProcessEnvs } from './envs';
import { getOptimizeDeps } from './optimizeDeps';
import { commonConfig } from './vite-config';
import type { EnvsRaw, ExtendedOptions } from './types';
import type { ExtendedOptions } from './types';
import { getOptimizeDeps } from './optimizeDeps';

export async function createViteServer(options: ExtendedOptions, devServer: Server) {
const { port, presets } = options;
const { presets } = options;

const config = await commonConfig(options, 'development');

const baseConfig = await commonConfig(options, 'development');
const defaultConfig = {
...baseConfig,
server: {
middlewareMode: true,
hmr: {
port,
server: devServer,
},
fs: {
strict: true,
},
// Set up dev server
config.server = {
middlewareMode: true,
hmr: {
port: options.port,
server: devServer,
},
fs: {
strict: true,
},
appType: 'custom' as const,
optimizeDeps: await getOptimizeDeps(baseConfig, options),
};
config.appType = 'custom';

const finalConfig = await presets.apply('viteFinal', defaultConfig, options);

const envsRaw = await presets.apply<Promise<EnvsRaw>>('env');
// Stringify env variables after getting `envPrefix` from the final config
const envs = stringifyProcessEnvs(envsRaw, finalConfig.envPrefix);
// Update `define`
finalConfig.define = {
...finalConfig.define,
...envs,
};
// TODO: find a way to avoid having to do this in a separate step.
config.optimizeDeps = await getOptimizeDeps(config, options);

const finalConfig = await presets.apply('viteFinal', config, options);
return createServer(finalConfig);
}

0 comments on commit 8d16927

Please sign in to comment.