首页
/ Storybook 环境变量实战:在故事中使用 .env 值的跨框架写法与注入机制源码解析

Storybook 环境变量实战:在故事中使用 .env 值的跨框架写法与注入机制源码解析

2026-09-07 15:32:47作者:尤辰城Agatha

在 Storybook 中,环境变量是同一套组件在开发、预览、CI 或不同主题下呈现差异化行为的核心手段。本文围绕官方文档 environment-variables.mdx 中「Using .env files」章节所使用的示例片段 my-component-with-env-variables.md 展开,完整覆盖 STORYBOOK_ 前缀变量从命令行、.env 文件到故事 args 的完整链路,给出 Angular、Svelte、React、Vue、Web Components 等多渲染器下的可复制示例,并结合 builder-vite 的源码实现解释这些变量为何能在浏览器端被读取。

核心机制:STORYBOOK_ 前缀决定变量是否暴露到预览端

Storybook 的环境变量遵循一个简单而关键的规则:只有带 STORYBOOK_ 前缀的变量才会被注入到 Storybook 的运行环境中。使用 Webpack 时通过 process.env 访问,使用 Vite builder 时通过 import.meta.env 访问。

最基本的注入方式是直接在命令行前缀变量:

STORYBOOK_THEME=red STORYBOOK_DATA_KEY=12345 npm run storybook

此后在预览端的任意 JavaScript 代码中都可以读取这些值:

// Webpack 构建下
console.log(process.env.STORYBOOK_THEME);
console.log(process.env.STORYBOOK_DATA_KEY);
// Vite 构建下
console.log(import.meta.env.STORYBOOK_THEME);
console.log(import.meta.env.STORYBOOK_DATA_KEY);

安全警告(原文档 Callout):不要在 Storybook 中存放任何机密(如私有 API Key)或敏感信息。环境变量会被内嵌进构建产物,任何人都可以通过检查构建文件查看到它们。

主场景:通过 .env 文件向故事注入数据

在项目中添加一个 .env 文件并写入:

STORYBOOK_DATA_KEY=12345

那么即使在故事(story)内部,也能随时访问该变量。这正是本文核心文档 my-component-with-env-variables.md 的主题:将环境变量作为故事 args 的一部分,例如把一个 propertyA 属性设置为 process.env.STORYBOOK_DATA_KEY。以下按渲染器完整给出原文档中的全部示例。

通用写法(React / Preact / Qwik / Solid / Vue 等常见框架,CSF 3)

JavaScript 版本:

// MyComponent.stories.js
import { MyComponent } from './MyComponent';

export default {
  component: MyComponent,
};

export const ExampleStory = {
  args: {
    propertyA: process.env.STORYBOOK_DATA_KEY,
  },
};

TypeScript 版本(将 your-framework 替换为实际框架,如 react-vitenextjsvue3-vite):

// MyComponent.stories.ts
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';

import { MyComponent } from './MyComponent';

const meta = {
  component: MyComponent,
} satisfies Meta<typeof MyComponent>;

export default meta;
type Story = StoryObj<typeof meta>;

export const ExampleStory: Story = {
  args: {
    propertyA: process.env.STORYBOOK_DATA_KEY,
  },
};

Angular 渲染器

CSF 3 写法:

// MyComponent.stories.ts
import type { Meta, StoryObj } from '@storybook/angular';

import { MyComponent } from './my-component.component';

const meta: Meta<MyComponent> = {
  component: MyComponent,
};

export default meta;
type Story = StoryObj<MyComponent>;

export const ExampleStory: Story = {
  args: {
    propertyA: process.env.STORYBOOK_DATA_KEY,
  },
};

Svelte 渲染器

Svelte CSF(@storybook/addon-svelte-csf)写法,注意 args 在 Svelte 模板中需要用双花括号包裹对象字面量:

<!-- MyComponent.stories.svelte -->
<script module>
  import { defineMeta } from '@storybook/addon-svelte-csf';

  import MyComponent from './MyComponent.svelte';

  const { Story } = defineMeta({
    component: MyComponent,
  });
</script>

<Story
  name="ExampleStory"
  args={{
    propertyA: process.env.STORYBOOK_DATA_KEY
  }}
/>

CSF 3 的 JS / TS 写法(TS 版中 your-framework 应替换为 svelte-vitesveltekit):

// MyComponent.stories.js
import MyComponent from './MyComponent.svelte';

export default {
  component: MyComponent,
};

export const ExampleStory = {
  args: {
    propertyA: process.env.STORYBOOK_DATA_KEY,
  },
};
// MyComponent.stories.ts
// Replace your-framework with svelte-vite or sveltekit
import type { Meta, StoryObj } from '@storybook/your-framework';

import MyComponent from './MyComponent.svelte';

const meta = {
  component: MyComponent,
} satisfies Meta<typeof MyComponent>;

export default meta;
type Story = StoryObj<typeof meta>;

export const ExampleStory: Story = {
  args: {
    propertyA: process.env.STORYBOOK_DATA_KEY,
  },
};

Web Components 渲染器

Web Components 的 component 字段是标签名字符串而非组件类:

// MyComponent.stories.js
export default {
  component: 'my-component',
};

export const ExampleStory = {
  args: {
    propertyA: process.env.STORYBOOK_DATA_KEY,
  },
};
// MyComponent.stories.ts
import type { Meta, StoryObj } from '@storybook/web-components-vite';

const meta: Meta = {
  component: 'my-component',
};

export default meta;
type Story = StoryObj;

export const ExampleStory: Story = {
  args: {
    propertyA: process.env.STORYBOOK_DATA_KEY,
  },
};

实验性 CSF Next 写法

原文档同时提供了标注为实验性(CSF Next 🧪)的变体,通过 .storybook/preview 导出的 preview 对象构建 meta 与 story。以 React 为例:

// MyComponent.stories.ts
import preview from '../.storybook/preview';

import { MyComponent } from './MyComponent';

const meta = preview.meta({
  component: MyComponent,
});

export const ExampleStory = meta.story({
  args: {
    propertyA: process.env.STORYBOOK_DATA_KEY,
  },
},
);

Vue、Angular、Web Components 的 CSF Next 变体遵循同一模式,仅导入的组件与 component 字段不同(Vue 使用 import MyComponent from './MyComponent.vue';Web Components 使用 component: 'my-component'):

// MyComponent.stories.ts (web-components, CSF Next)
import preview from '../.storybook/preview';

const meta = preview.meta({
  component: 'my-component',
});

export const ExampleStory = meta.story({
  args: {
    propertyA: process.env.STORYBOOK_DATA_KEY,
  },
});

适用前提:CSF Next 写法是实验性 API(原文档中以 🧪 标记区分于 CSF 3 标签页),生产项目应优先使用 CSF 3 的稳定写法。

Vite 构建下的差异:import.meta.env 与 VITE_ 前缀

使用 Vite builder 的项目不输出 process.env 这类 Node.js 全局,需要改用 import.meta.env 访问 STORYBOOK_VITE_ 前缀的变量。以通用 TS 写法为例(引自同章节的 my-component-vite-env-variables.md):

// MyComponent.stories.ts
export const ExampleStory: Story = {
  args: {
    propertyA: import.meta.env.STORYBOOK_DATA_KEY,
    propertyB: import.meta.env.VITE_CUSTOM_VAR,
  },
};

可以看到 Vite 场景下 VITE_ 前缀的自定义变量(VITE_CUSTOM_VAR)同样可用——这一点可以从 builder-vite 的源码中得到确证。

源码佐证一:STORYBOOK_ 前缀如何进入 Vite 的 envPrefix

从源码结构看,builder-vite 内置了一个 storybook:config-plugin,其职责之一就是合并环境变量前缀。在 storybook-config-plugin.ts 中:

const existingEnvPrefix = config.envPrefix;
// If an envPrefix is specified in the user's vite config, add STORYBOOK_ to it.
// Otherwise, add both VITE_ and STORYBOOK_ so that Vite doesn't lose its default.
const mergedEnvPrefix = existingEnvPrefix
  ? Array.from(
      new Set([
        ...(Array.isArray(existingEnvPrefix) ? existingEnvPrefix : [existingEnvPrefix]),
        'STORYBOOK_',
      ])
    )
  : ['VITE_', 'STORYBOOK_'];

return {
  resolve: { ... },
  envPrefix: mergedEnvPrefix,
};

这段逻辑解释了官方文档的行为边界:如果用户的 Vite 配置已显式指定 envPrefix,Storybook 会把 STORYBOOK_ 追加进去(去重后合并);否则默认注入 ['VITE_', 'STORYBOOK_'],既保留 Vite 自身的 VITE_ 默认前缀,又让 STORYBOOK_ 变量可用。这也是排查问题章节里「框架专属前缀(如 VUE_APP_)不生效时需要自行扩展 envPrefix」的根因。

源码佐证二:import.meta.env 的字符串替换实现

Vite 通过 define 替换的方式暴露环境变量。builder-vite 中的 envs.ts 实现了定制化的 stringifyProcessEnvs

// Allowed env variables on the client
const allowedEnvVariables = [
  'STORYBOOK',
  // Vite `import.meta.env` default variables
  'BASE_URL',
  'MODE',
  'DEV',
  'PROD',
  'SSR',
];

export function stringifyProcessEnvs(raw: Builder_EnvsRaw, envPrefix: ViteConfig['envPrefix']) {
  const updatedRaw: Builder_EnvsRaw = {};
  const envs = Object.entries(raw).reduce((acc: Builder_EnvsRaw, [key, value]) => {
    // Only add allowed values OR values from array OR string started with allowed prefixes
    if (
      allowedEnvVariables.includes(key) ||
      (Array.isArray(envPrefix) && !!envPrefix.find((prefix) => key.startsWith(prefix))) ||
      (typeof envPrefix === 'string' && key.startsWith(envPrefix))
    ) {
      acc[`import.meta.env.${key}`] = JSON.stringify(value);
      updatedRaw[key] = value;
    }
    return acc;
  }, {});
  // support destructuring like
  // const { foo } = import.meta.env;
  envs['import.meta.env'] = JSON.stringify(stringifyEnvs(updatedRaw));

  return envs;
}

这段实现有两个值得注意的细节:

  1. 白名单 + 前缀双重过滤:只有命中内置白名单(STORYBOOKBASE_URLMODEDEVPRODSSR)或满足 envPrefix 前缀(即上文合并出的 VITE_/STORYBOOK_)的变量才会生成 import.meta.env.KEY 的替换规则。这就是「只有带前缀的变量才暴露到浏览器端」的底层执行点;
  2. 支持解构写法:函数最后额外输出了完整的 import.meta.env 对象替换(envs['import.meta.env'] = JSON.stringify(...)),使得 const { foo } = import.meta.env 这类解构语法同样可用。

源码佐证三:main.js 的 env 字段

.env 文件外,还可以扩展 Storybook 配置(.storybook/main.js|.ts)新增 env 字段来定义变量。该字段在类型定义 core-common.ts 中声明为 env?: PresetValue<StorybookConfigRaw['env']>(注释为 "Modify or return env config."),因此支持函数形式的预设合并。典型写法(引自 main-config-env.md):

// .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',
  }),
};

这里的关键点是 env 接收的 config 参数已经包含了 .env 文件与命令行配置的全部既有环境变量,函数在展开 ...config 的基础上追加或覆盖新变量。CSF Next 体系下则使用框架 node 导出(如 @storybook/react-vite/node)的 defineMainenv 写法完全一致。配置生效后,故事中以与 .env 相同的方式访问即可:

export const Basic: Story = {
  args: {
    exampleProp: process.env.EXAMPLE_VAR,
  },
};

其他注入渠道与衍生用法

.env 文件的模式区分

除了通用的 .env,还可以为不同模式提供专属文件:添加 .env.development.env.production 来为环境变量应用不同的值。

构建时硬编码

运行 build-storybook 时同样可以传入这些环境变量(例如 STORYBOOK_DATA_KEY=12345 npx build-storybook),它们会被硬编码进静态产物,随静态版 Storybook 一起分发,构建后不再可变。

自定义 head/body 模板中的占位替换

在自定义 <head>/<body> 中可使用 %STORYBOOK_X% 占位符,例如 %STORYBOOK_THEME% 会被替换为 red。注意:如果占位符用在 JavaScript 的属性或值位置,由于值是原样插入的,可能需要自行补加引号,例如 <link rel="stylesheet" href="%STORYBOOK_STYLE_URL%" />

用环境变量选择预览浏览器

Browser Example
Safari BROWSER="safari"
Firefox BROWSER="firefox"
Chromium BROWSER="chromium"

默认情况下 Storybook 启动时会打开一个新的 Chrome 窗口;若本机没有 Chrome,需显式指定上述选项或调整系统默认浏览器。此外可通过 BROWSER_ARGS 向浏览器传参,例如 BROWSER_ARGS="--incognito" 以无痕模式打开 Chrome——该选项仅在同时显式设置了 BROWSER 时才生效。

故障排查

Vite 场景下框架专属前缀不生效:如果你试图使用框架专属的环境变量(例如 VUE_APP_),可能因为 Storybook 与框架各自的配置无法互相识别而失效。此时需要扩展框架配置让其识别目标前缀——对 Vite 系框架,即在上文 storybook-config-plugin.ts 所合并的配置基础上显式设置 envPrefix 选项;其他框架可能需要类似的调整。

Webpack 场景下 Can't find variable: process:若使用基于 Webpack 的框架(如 Angular with Webpack),引用 STORYBOOK_ 前缀变量时出现该运行时错误,通常意味着一个或多个环境变量缺失或未正确配置。修复方式是确保它们通过 .env 文件、命令行参数配置,并按「main.js 的 env 字段」一节提供默认值。

能力速查

能力 写法 适用前提
命令行注入 STORYBOOK_DATA_KEY=12345 npm run storybook 任意构建
.env 文件 STORYBOOK_DATA_KEY=12345 写入项目根 .env 任意构建
模式区分 .env.development / .env.production 任意构建
配置注入 main.jsenv: (config) => ({ ...config, EXAMPLE_VAR }) 任意构建
读取(Webpack) process.env.STORYBOOK_DATA_KEY webpack5 / Angular 等
读取(Vite) import.meta.env.STORYBOOK_DATA_KEYimport.meta.env.VITE_CUSTOM_VAR Vite 系 builder
head/body 占位 %STORYBOOK_X% 自定义模板
静态构建硬编码 build-storybook 时传入 STORYBOOK_ 变量 静态发布
浏览器选择 BROWSER="firefox",可选 BROWSER_ARGS 开发预览

整体来看,STORYBOOK_ 前缀是 Storybook 环境变量体系的单一入口:命令行、.envmain.jsenv 三条注入路径最终汇聚到同一套机制,再由 Webpack 的 process.env 或 Vite builder 中 envs.ts 的前缀过滤与 import.meta.env 替换落地到浏览器端,故事 args 因此成为承载环境差异的最自然落点。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388