Skip to content

Vue: Make globals reactive in decorators#33562

Merged
valentinpalkovic merged 5 commits into
nextfrom
sidnioulz/issue-23655
Jan 30, 2026
Merged

Vue: Make globals reactive in decorators#33562
valentinpalkovic merged 5 commits into
nextfrom
sidnioulz/issue-23655

Conversation

@Sidnioulz
Copy link
Copy Markdown
Member

@Sidnioulz Sidnioulz commented Jan 16, 2026

Closes #23655

What I did

  • Allowed globals to be reactive
  • Added stories and E2E tests to avoid regressions
  • Documented code patterns for Vue decorators with reactivity

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

Unit tests are very low quality. Really, we're testing the underlying update function used to update globals, rather than the whole setup. I'd want a dedicated story instead.

  • stories
  • unit tests
  • E2E tests

Manual testing

  1. Create a fresh Vue sandbox
  2. Add the code snippet below to your .storybook/preview.ts
  3. Open a component story with the mood global set
  4. Change the mood value in the toolbar
import { sb } from 'storybook/test';

sb.mock('../template-stories/core/test/ModuleMocking.utils.ts');
sb.mock('../template-stories/core/test/ModuleSpyMocking.utils.ts', { spy: true });
sb.mock('../template-stories/core/test/ModuleAutoMocking.utils.ts');
sb.mock(import('lodash-es'));
sb.mock(import('lodash-es/add'));
sb.mock(import('lodash-es/sum'));
sb.mock(import('uuid'));

import * as templateAnnotations from "../template-stories/core/preview";
import "../src/stories/renderers/vue3/preview.js";
import "../src/stories/components";
import addonDocs from "@storybook/addon-docs";
import addonA11y from "@storybook/addon-a11y";
import { definePreview } from "@storybook/vue3-vite";


import { type DecoratorFunction, type Globals } from 'storybook/internal/types';
import { computed, inject } from 'vue';

export const MOODS = [
  { color: '#eee', id: 'base', name: 'Base color' },
  { color: '#111', id: 'inverse', name: 'Inverse' },
  { color: '#56db96', id: 'emerald', name: 'Emerald' },
]

const globalTypes = {
  mood: {
    defaultValue: 'base',
    description: 'Change this global and watch the background colour change',
    name: 'mood',
    toolbar: {
      dynamicTitle: true,
      icon: 'paintbrush',
      items: MOODS.map((mood) => ({
        title: mood.name,
        value: mood.color,
      })),
      title: 'Mood',
    },
  },
}

const withMoodRootClass: DecoratorFunction = (story, { globals }) => {
  return {
    components: { story },
    inheritAttrs: false,
    setup() {
      const mood = computed(() => `width: 100%; height: 60vh; margin: -20px; padding: 20px; background-color: ${globals?.mood};`)
      return {
        globals,
        mood,
      };
    },
    template: `<div :style="mood" :data-mood="globals.mood"><story /></div>`,
  }
}

export default definePreview({
  decorators: [withMoodRootClass],
  globalTypes,
  // ...
});

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • Improvements

    • Vue 3 renderer now keeps globals reactive so UI updates immediately when args or globals change.
    • Arg-update API is more type-safe and generalized for reliable dynamic updates.
  • New Features

    • Added example stories and decorators demonstrating dynamic arg updates and reactive globals.
    • Added preview configuration examples to enable locale-aware rendering.
  • Tests

    • New unit and end-to-end tests cover reactive globals, decorator behavior, and arg-update flows.
  • Documentation

    • Added snippets and docs showing decorators that use reactive globals and update args.

✏️ Tip: You can customize this high-level summary in your review settings.

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Jan 16, 2026

View your CI Pipeline Execution ↗ for commit 75d6e5d

Command Status Duration Result
nx run-many -t compile,check,knip,test,pretty-d... ✅ Succeeded 7m 12s View ↗

☁️ Nx Cloud last updated this comment at 2026-01-21 11:50:23 UTC

@Sidnioulz Sidnioulz removed their assignment Jan 16, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 16, 2026

📝 Walkthrough

Walkthrough

Make storyContext.globals reactive and expose per-canvas reactiveGlobals; generalize updateArgs to updateArgs<T> to update reactive Args/Globals; update renderer tests and add an e2e test; add example decorator stories and preview snippets demonstrating reactive globals and updateArgs usage.

Changes

Cohort / File(s) Summary
Vue3 renderer core
code/renderers/vue3/src/render.ts
Add reactiveGlobals to per-canvas app state; make storyContext.globals reactive during setup; provide reactiveGlobals to apps; generalize updateArgs to updateArgs<T extends { [name: string]: unknown }>(reactive: T, next: T) and call with Args / Globals.
Renderer tests
code/renderers/vue3/src/render.test.ts
Import Globals and computed; convert updateArgs calls to generic form (updateArgs<...>) and add a test that updates reactive Globals via updateArgs<Globals> with a computed watcher.
Template stories (Vue3 starter)
code/renderers/vue3/template/stories_vue3-vite-default-ts/decorators.stories.ts
Add exported stories UpdateArgs and ReactiveGlobalDecorator demonstrating decorator-driven arg updates and locale-aware reactive globals.
End-to-end tests
code/e2e-tests/framework-vue3.spec.ts
Add Playwright e2e tests for Vue 3: verify decorator updateArgs updates preview and that decorators can consume reactive globals (toolbar locale switching).
Documentation & snippets / preview examples
docs/_snippets/decorator-with-reactive-globals.md, docs/_snippets/decorator-with-updateArgs.md, .storybook/preview.js, .storybook/preview.ts, docs/writing-stories/decorators.mdx
Add JS/TS preview examples and docs describing decorators that use Vue computed with globals, and examples using useArgs/updateArgs to update story args; add docs sections for reactive globals and Preview API hooks.

Sequence Diagram(s)

sequenceDiagram
  participant Manager as Storybook Manager
  participant Renderer as Vue3 Renderer
  participant App as Vue App (per-canvas)
  participant Component as Story Component

  Manager->>Renderer: renderCanvas(storyContext)
  Renderer->>App: create/mount app with reactiveArgs + reactiveGlobals
  App->>Component: provide('globals', reactiveGlobals)
  Component->>Component: watch reactiveArgs / reactiveGlobals -> re-render

  Manager->>Renderer: updateArgs<Args>(nextArgs)
  Renderer->>App: mutate reactiveArgs (updateArgs<T>)
  App->>Component: reactive update triggers re-render

  Manager->>Renderer: updateArgs<Globals>(nextGlobals)
  Renderer->>App: mutate reactiveGlobals (updateArgs<T>)
  App->>Component: provided globals changed -> dependent watches run -> re-render
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot Bot commented Jan 16, 2026

Package Benchmarks

Commit: 75d6e5d, ran on 21 January 2026 at 11:51:26 UTC

The following packages have significant changes to their size or dependencies:

@storybook/builder-webpack5

Before After Difference
Dependency count 192 192 0
Self size 75 KB 75 KB 🎉 -6 B 🎉
Dependency size 32.24 MB 32.25 MB 🚨 +11 KB 🚨
Bundle Size Analyzer Link Link

storybook

Before After Difference
Dependency count 49 49 0
Self size 20.30 MB 20.32 MB 🚨 +21 KB 🚨
Dependency size 16.52 MB 16.52 MB 🎉 -4 B 🎉
Bundle Size Analyzer Link Link

@storybook/ember

Before After Difference
Dependency count 196 196 0
Self size 15 KB 15 KB 🎉 -6 B 🎉
Dependency size 28.96 MB 28.97 MB 🚨 +11 KB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs

Before After Difference
Dependency count 538 538 0
Self size 646 KB 646 KB 🎉 -10 B 🎉
Dependency size 59.22 MB 59.73 MB 🚨 +505 KB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs-vite

Before After Difference
Dependency count 127 127 0
Self size 1.12 MB 1.12 MB 🎉 -22 B 🎉
Dependency size 21.82 MB 22.32 MB 🚨 +496 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-native-web-vite

Before After Difference
Dependency count 159 159 0
Self size 30 KB 30 KB 🚨 +8 B 🚨
Dependency size 23.00 MB 23.61 MB 🚨 +610 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-vite

Before After Difference
Dependency count 117 117 0
Self size 35 KB 35 KB 🎉 -26 B 🎉
Dependency size 19.62 MB 20.11 MB 🚨 +496 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 278 278 0
Self size 24 KB 24 KB 🚨 +2 B 🚨
Dependency size 44.13 MB 44.64 MB 🚨 +505 KB 🚨
Bundle Size Analyzer Link Link

@storybook/server-webpack5

Before After Difference
Dependency count 204 204 0
Self size 16 KB 16 KB 🎉 -10 B 🎉
Dependency size 33.49 MB 33.50 MB 🚨 +11 KB 🚨
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 183 183 0
Self size 775 KB 775 KB 🚨 +219 B 🚨
Dependency size 67.38 MB 67.47 MB 🚨 +92 KB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 176 176 0
Self size 30 KB 30 KB 🎉 -4 B 🎉
Dependency size 65.95 MB 66.05 MB 🚨 +92 KB 🚨
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 50 50 0
Self size 1000 KB 1000 KB 🎉 -6 B 🎉
Dependency size 36.82 MB 36.84 MB 🚨 +21 KB 🚨
Bundle Size Analyzer node node

@storybook/preset-react-webpack

Before After Difference
Dependency count 170 170 0
Self size 18 KB 18 KB 🚨 +18 B 🚨
Dependency size 31.26 MB 31.28 MB 🚨 +11 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react

Before After Difference
Dependency count 57 57 0
Self size 732 KB 1.23 MB 🚨 +494 KB 🚨
Dependency size 12.94 MB 12.94 MB 🚨 +2 KB 🚨
Bundle Size Analyzer Link Link

Copy link
Copy Markdown
Contributor

@chakAs3 chakAs3 left a comment

Choose a reason for hiding this comment

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

this looks a straight forward and clean, would be worth to add some test story to showcase this beside the unit tests ?

@Sidnioulz
Copy link
Copy Markdown
Member Author

this looks a straight forward and clean, would be worth to add some test story to showcase this beside the unit tests ?

I don't think we can call updateGlobals from within a play function. I'll check with the team if we can have framework-specific E2E tests though.

@chakAs3
Copy link
Copy Markdown
Contributor

chakAs3 commented Jan 19, 2026

By the way, something feels off here. I remember this globals update working correctly when I worked on it back in v7. At that time, changing globals from the toolbar and triggering decorators caused a full re-render of the component tree outside of Vue.

This looks like a regression introduced by a later commit

@Sidnioulz
Copy link
Copy Markdown
Member Author

By the way, something feels off here. I remember this globals update working correctly when I worked on it back in v7. At that time, changing globals from the toolbar and triggering decorators caused a full re-render of the component tree outside of Vue.

This looks like a regression introduced by a later commit

I remember it worked for a brief time around 7.0, and then broke. Reactivity does occur in the decorator function, but the object being returned by the decorator isn't being transformed into a new component. I haven't tried to figure out why/when it broke. The code for args was straightforward enough to copy and I have you to thank for that :)

@Sidnioulz Sidnioulz force-pushed the sidnioulz/issue-23655 branch from 5b33799 to 1aae330 Compare January 21, 2026 10:43
@Sidnioulz
Copy link
Copy Markdown
Member Author

@chakAs3 I've managed to find a code pattern without provide/inject so I've simplified the code a bit. I added Vue framework stories for reactive globals (and reactive updateArgs) alongside E2E tests and documentation changes.

@Sidnioulz Sidnioulz changed the title Vue: Make globals reactive and Provide them Vue: Make globals reactive in decorators Jan 21, 2026
@Sidnioulz Sidnioulz force-pushed the sidnioulz/issue-23655 branch from 1aae330 to 12e7f63 Compare January 21, 2026 10:48
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@docs/_snippets/decorator-with-reactive-globals.md`:
- Around line 15-16: The template uses invalid Vue binding syntax
"lang={{globals?.locale || 'en'}}" which will render literally; update the
attribute to use Vue v-bind syntax by replacing the raw mustache binding with a
bound expression (i.e., use :lang with the same expression globals?.locale ||
'en') in both occurrences (the shown snippet around the div with lang and the
other occurrence at lines ~41-42) so the lang attribute is evaluated at runtime
rather than printed as text.
🧹 Nitpick comments (1)
code/e2e-tests/framework-vue3.spec.ts (1)

17-17: Remove stale TODO.

The updateArgs test exists below, so this TODO is now misleading.

🧹 Proposed cleanup
-  // TODO: add test to prove updateArgs works too

Comment thread docs/_snippets/decorator-with-reactive-globals.md Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@code/renderers/vue3/template/stories_vue3-vite-default-ts/decorators.stories.ts`:
- Around line 87-107: The decorator localeDecorator currently declares inject:
['globals'] which is unnecessary and causes Vue dev warnings; remove the inject
property from the returned object in localeDecorator (leaving components, setup,
template intact) so that globals from the decorator context is used directly in
setup (where ctxGreeting is computed using
getCaptionForLocale(globals?.locale)), and verify no other code relies on Vue's
provide/inject for globals.

Comment thread docs/_snippets/decorator-with-reactive-globals.md Outdated
Comment thread docs/_snippets/decorator-with-reactive-globals.md Outdated
Comment thread code/renderers/vue3/template/stories_vue3-vite-default-ts/decorators.stories.ts Outdated
Comment thread code/e2e-tests/framework-vue3.spec.ts Outdated
@valentinpalkovic valentinpalkovic merged commit 2669c84 into next Jan 30, 2026
124 checks passed
@valentinpalkovic valentinpalkovic deleted the sidnioulz/issue-23655 branch January 30, 2026 10:20
@github-actions github-actions Bot mentioned this pull request Jan 30, 2026
18 tasks
@valentinpalkovic
Copy link
Copy Markdown
Contributor

@chakAs3, @Sidnioulz

I need to revert this PR because vue-v3--vite--javascript---dev is failing, which was caught by our ci:daily job:
https://app.circleci.com/pipelines/github/storybookjs/storybook/113971/workflows/39ce261b-2953-48ab-9588-b829b448f716/jobs/1065651/tests.

Please reopen another PR and set ci:daily to ensure that JavaScript Vue3 sandboxes are not broken!

@github-actions github-actions Bot mentioned this pull request Feb 3, 2026
18 tasks
@Sidnioulz Sidnioulz added the needs qa Indicates that this needs manual QA during the upcoming minor/major release label Mar 11, 2026
@yannbf yannbf removed the needs qa Indicates that this needs manual QA during the upcoming minor/major release label Mar 13, 2026
@coderabbitai coderabbitai Bot mentioned this pull request May 13, 2026
8 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Bug]: Changes to globals don't cause the story to refresh in canvas mode

5 participants