Storybook 环境变量配置指南:在 main.js / main.ts 中通过 env 字段注入自定义环境变量
本篇以 Storybook 主配置文件(.storybook/main.js|ts)中的 env 配置项为主线,讲解如何在不依赖 .env 文件的前提下,直接在 Storybook 配置里合并、新增自定义环境变量,并说明这些变量在 story、preview-head、Vite/Webpack 构建链路中的传递原理。读完你将掌握 env 字段的完整签名、CSF 3 与 CSF Next 两种配置写法的落地姿势,以及 STORYBOOK_ 前缀变量、.env 文件与 env 函数三者之间的合并优先级。
一、env 是什么:定义于主配置文件的 API
env 是 Storybook 主配置文件(main.js|ts 配置)中的一个配置字段,其官方 API 参考位于 main-config-env.mdx。它的类型签名是:
(config: { [key: string]: string }) => { [key: string]: string }
也就是说,env 接收一个函数,入参 config 是一个字符串键值对对象,返回值同样是一个字符串键值对对象。它的作用是:定义自定义的 Storybook 环境变量——在你希望为不同组件、不同 story 提供差异化 API URL 或功能开关时,可以在这个函数中一次性声明(相关内容见官方环境变量指南)。
一个关键语义是注释里反复强调的那句话:
👇
config参数包含所有其他已有的环境变量——无论是配置在.env文件中,还是通过命令行配置的。
这决定了它的本质是一个合并函数:入参里已经躺着 Storybook 从 .env 文件和命令行里收集到的现有环境变量,你要做的是在返回的对象中把它们展开并追加自定义项。
二、基础用法:CSF 3 写法下的完整配置示例
下面分别给出 .storybook/main.js 与 .storybook/main.ts 两种格式的完整示例。使用时请把 @storybook/your-framework 替换为你实际使用的框架,例如 react-vite、nextjs、vue3-vite 等。
JavaScript(.storybook/main.js):
export default {
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
/*
* 👇 The `config` argument contains all the other existing environment variables.
* Either configured in an `.env` file or configured on the command line.
*/
env: (config) => ({
...config,
EXAMPLE_VAR: 'An environment variable configured in Storybook',
}),
};
TypeScript(.storybook/main.ts):
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { StorybookConfig } from '@storybook/your-framework';
const config: StorybookConfig = {
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
/*
* 👇 The `config` argument contains all the other existing environment variables.
* Either configured in an `.env` file or configured on the command line.
*/
env: (config) => ({
...config,
EXAMPLE_VAR: 'An environment variable configured in Storybook',
}),
};
export default config;
两点值得注意:
- 务必先展开
...config:入参config携带了.env与命令行注入的全部既有变量。如果不先展开,返回的对象将"淹没"这些既有值,可能导致原有变量(包括一些构建依赖的系统变量)丢失。 - 返回值中新增自定义键:上例的
EXAMPLE_VAR就是你在配置层面自定义的环境变量,加载完成后可在 story 代码里读取(见第四节)。
三、进阶写法:CSF Next(🧪 实验性)中的 defineMain 形式
源码仓库同时提供了基于实验性 CSF Next API defineMain 的写法。与 CSF 3 的区别在于:不再 export default 一个普通对象,而是从 @storybook/<框架>/node 导出 defineMain 并包裹整个配置。当前仓库各渲染器均支持该模式,下面是针对不同框架的完整示例。
React(TypeScript,.storybook/main.ts):
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
import { defineMain } from '@storybook/your-framework/node';
export default defineMain({
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
/*
* 👇 The `config` argument contains all the other existing environment variables.
* Either configured in an `.env` file or configured on the command line.
*/
env: (config) => ({
...config,
EXAMPLE_VAR: 'An environment variable configured in Storybook',
}),
});
React(JavaScript,.storybook/main.js):
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
import { defineMain } from '@storybook/your-framework/node';
export default defineMain({
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
/*
* 👇 The `config` argument contains all the other existing environment variables.
* Either configured in an `.env` file or configured on the command line.
*/
env: (config) => ({
...config,
EXAMPLE_VAR: 'An environment variable configured in Storybook',
}),
});
Vue 3(TypeScript,.storybook/main.ts)——需从 vue3-vite 框架入口导入:
import { defineMain } from '@storybook/vue3-vite/node';
export default defineMain({
framework: '@storybook/vue3-vite',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
/*
* 👇 The `config` argument contains all the other existing environment variables.
* Either configured in an `.env` file or configured on the command line.
*/
env: (config) => ({
...config,
EXAMPLE_VAR: 'An environment variable configured in Storybook',
}),
});
Vue 3(JavaScript,.storybook/main.js):
import { defineMain } from '@storybook/vue3-vite/node';
export default defineMain({
framework: '@storybook/vue3-vite',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
/*
* 👇 The `config` argument contains all the other existing environment variables.
* Either configured in an `.env` file or configured on the command line.
*/
env: (config) => ({
...config,
EXAMPLE_VAR: 'An environment variable configured in Storybook',
}),
});
Angular(TypeScript,.storybook/main.ts)——框架名为 @storybook/angular:
import { defineMain } from '@storybook/angular/node';
export default defineMain({
framework: '@storybook/angular',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
/*
* 👇 The `config` argument contains all the other existing environment variables.
* Either configured in an `.env` file or configured on the command line.
*/
env: (config) => ({
...config,
EXAMPLE_VAR: 'An environment variable configured in Storybook',
}),
});
Web Components(TypeScript,.storybook/main.ts):
import { defineMain } from '@storybook/web-components-vite/node';
export default defineMain({
framework: '@storybook/web-components-vite',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
/*
* 👇 The `config` argument contains all the other existing environment variables.
* Either configured in an `.env` file or configured on the command line.
*/
env: (config) => ({
...config,
EXAMPLE_VAR: 'An environment variable configured in Storybook',
}),
});
Web Components(JavaScript,.storybook/main.js):
import { defineMain } from '@storybook/web-components-vite/node';
export default defineMain({
framework: '@storybook/web-components-vite',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
/*
* 👇 The `config` argument contains all the other existing environment variables.
* Either configured in an `.env` file or configured on the command line.
*/
env: (config) => ({
...config,
EXAMPLE_VAR: 'An environment variable configured in Storybook',
}),
});
可以发现,无论哪个框架,env 字段的写法完全一致——这正是它作为主配置中"与框架无关"能力的设计意图。CSF Next 变体当前以 🧪 标识,属于仍在演进的实验性 API,生产项目中若追求稳定,建议采用第二节的 CSF 3 写法。
四、在 story 与组件中读取自定义变量
当 Storybook 启动加载后,你在 env 函数里声明的变量(如 EXAMPLE_VAR)即可在 stories 中读取,用法与使用 .env 文件时几乎一致。仓库配套示例 my-component-env-var-config.md 给出了各框架下通过 process.env.EXAMPLE_VAR 注入组件 args 的写法,例如:
// MyComponent.stories.tsx (React,CSF 3)
import type { Meta, StoryObj } from '@storybook/react-vite';
import { MyComponent } from './MyComponent';
const meta = {
component: MyComponent,
} satisfies Meta<typeof MyComponent>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Basic: Story = {
args: {
exampleProp: process.env.EXAMPLE_VAR,
},
};
读取侧的注意事项取决于你使用的构建器(详见 Vite builder 说明):
- Webpack 系构建器:以
process.env.EXAMPLE_VAR方式访问; - Vite 构建器:Vite 默认不输出
process.env这类 Node.js 全局对象,需改用import.meta.env(同时支持STORYBOOK_、VITE_前缀变量),仓库配套示例 my-component-vite-env-variables.md 演示了这一点。
如果你使用的是 Webpack 系框架(例如 Angular + Webpack)且出现 Can't find variable: process 之类的运行时错误,通常意味着某几个 STORYBOOK_ 前缀变量缺失或未正确配置——需要确认它们已通过 .env 文件、命令行参数或主配置的 env 字段注入,并给出默认值兜底。
五、与 .env 文件、命令行注入的关系
env 字段并非孤立存在,它处于 Storybook 环境变量体系的一个交汇点上。整个体系可以归纳为三层:
| 注入途径 | 示例 | 说明 |
|---|---|---|
| 命令行前缀变量 | STORYBOOK_THEME=red npm run storybook |
仅 STORYBOOK_ 前缀变量会进入前端运行时可访问的环境 |
.env / .env.development / .env.production 文件 |
STORYBOOK_DATA_KEY=12345 |
按模式拆分:开发与生产构建可应用不同值 |
主配置 env 字段 |
env: (config) => ({ ...config, EXAMPLE_VAR: '...' }) |
在配置层合并/新增自定义变量 |
关于三层之间的关系,结合本节开头引用的官方注释与环境变量文档,可以明确两件事:
env函数入参config即包含前两层收集到的全部既有环境变量,因此该函数天然具备"在保留既有变量基础上追加变量"的能力,这正是示例中...config展开的意义;.env中若使用STORYBOOK_DATA_KEY=12345,你在 stories 甚至组件代码内都可直接读取——这与env函数新增变量后的读取体验一致。两者互补:需要跟随运行模式变化的变量适合放.env,纯粹由 Storybook 配置衍生的常量适合放env函数。
此外,使用 build-storybook 构建静态站点时也可传入这些环境变量,它们会被硬编码进静态产物——因此每个部署环境的差异值必须在构建时确定。
六、源码原理:env 值如何流入预览与构建
要理解 env 字段为什么能"新增任意键"(而不像 .env 那样只放行 STORYBOOK_ 前缀),需要进入源码看合并链路。
6.1 默认 env preset:收集 dotenv 与进程变量
核心源码位于 envs.ts。其中 loadEnvs() 通过 lazy-universal-dotenv 读取 .env 文件,并拼上 process.env:
// code/core/src/common/utils/envs.ts(节选,非逐字)
const baseEnv = {
NODE_ENV: process.env.NODE_ENV || defaultNodeEnv,
STORYBOOK: process.env['STORYBOOK'] || 'true',
PUBLIC_URL: options.production ? '.' : '',
// ...
};
const envEntries = Object.fromEntries(
Object.entries({ ...process.env, ...dotenv.raw })
.filter(([name]) => /^STORYBOOK_/.test(name))
);
从源码结构看,默认收集链路对进程变量与 .env 变量做了一次仅放行 STORYBOOK_ 前缀的过滤(/^STORYBOOK_/),随后产出供构建使用的 stringified(JSON 字符串化)与 raw 两组值,并提供了把 raw 转成 process.env.X 形式键的 stringifyProcessEnvs——这正是 Webpack 环境注入时的形状。
6.2 env preset 与用户 env 字段的叠加
在 common-preset.ts 中,默认的 env preset 直接返回 loadEnvs({ production: true }) 的 raw:
export const env = async () => {
const { raw } = await loadEnvs({ production: true });
return raw;
};
而你在 main.js|ts 中提供的 env 函数,会以 preset 叠加(presets.apply)的方式作用在默认结果之上——返回对象中展开的 ...config 保留了默认 preset 的全部既有变量,新增的 EXAMPLE_VAR 则被并入同一份键值表。
6.3 preview-head / preview-body 的 %STORYBOOK_X% 插值
同一文件中,previewHead 与 previewBody 都会执行 presets.apply<Record<string, string>>('env'),把合并后的变量集交给模板渲染:
export const previewHead = async (base: any, { configDir, presets }: Options) => {
const interpolations = await presets.apply<Record<string, string>>('env');
return getPreviewHeadTemplate(configDir, interpolations);
};
这解释了自定义 <head> / <body>(即 preview-head.html、preview-body.html)中的 %STORYBOOK_X% 替换机制:%STORYBOOK_THEME% 会被替换成变量实际值,例如前面命令行示例中的 red。若把变量用作 HTML 属性值,需自行加引号,例如 <link rel="stylesheet" href="%STORYBOOK_STYLE_URL%" />,因为该值会被原样插入。
综上,env 字段产出的变量会同时流向三条通道:Webpack/Vite 构建注入(story 内 process.env/import.meta.env 读取)、preview-head.html/preview-body.html 的 %STORYBOOK_X% 插值、以及浏览器中的常量替换。
七、安全红线与实操建议
- 绝对不要存放密钥等敏感信息:环境变量会被嵌入构建产物,任何查看你静态文件的人都能读到。官方环境变量文档对此给出了明确警告。API Key、私密 token 一律走服务端环境,而不是 Storybook 的环境变量。
- 保留展开,避免覆盖:写
env函数时务必保留...config,否则会丢弃.env与命令行里已有的变量。 - 读取端注意构建器差异:Webpack 用
process.env.X,Vite 用import.meta.env.X(Vite builder)。 - 模式化配置:需要按开发/生产区分的值优先使用
.env.development与.env.production文件;需要随 Storybook 配置本身变化的值放进env字段。 - 选择稳定 API:生产项目优先使用 CSF 3 的普通导出写法;实验性的 CSF Next
defineMain形式(仓库中以 🧪 标注)适合在升级测试环境中先行验证。
八、延伸阅读
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00