Under the hood
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/8165207/Guillaume.jpeg)
Guillaume Chau
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/3405366/twitterbird_RGB.png)
@Akryum
Vue.js Core Team
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/10210168/frame.png)
@Akryum@m.webtoo.ls
A story is a small number of components mounted in an isolated environment
Story:
Why stories?
Organize and document components for other developers
Showcase features and components
Develop components in isolation
Tests components
Visual regression
⚡ Dynamic source
🍱 Variant grids
📖 Markdown docs
🌔 Dark theme
📱 Responsive testing
🎹 Flexible controls
📷 Visual regression testing
🎨 Automatic design tokens
🔍 Fast fuzzy/full-text search
More to come...
Packed with features!
Writing stories
![](https://media4.giphy.com/media/lJNoBCvQYp7nq/giphy.gif)
Writing stories
should look similar to writing component code
<!-- Cars.story.vue -->
<template>
<Story title="Cars">
<Variant title="default">
🚗
</Variant>
<Variant title="Fast">
🏎️
</Variant>
<Variant title="Slow">
🚜
</Variant>
</Story>
</template>
How Vite powers Histoire
Vite Native
Reuse the same build pipeline
Less time and effort setting up
Fast boot and instant HMR
![](https://media3.giphy.com/media/BzyTuYCmvSORqs1ABM/giphy.gif)
Virtual modules
histoire dev
Story FS watcher
.md FS watcher
Vite dev server
(HTTP server)
vite-node server
Generate docs-only stories
<!-- Cars.story.vue -->
<template>
<Story title="Cars">
<Variant title="default">
🚗
</Variant>
<Variant title="Fast">
🏎️
</Variant>
<Variant title="Slow">
🚜
</Variant>
</Story>
</template>
-
Docs of Story 1
-
Docs page 1
-
Docs page 2
-
Story 1
-
Story 2
-
Story 3
-
...
Collect
Story FS watcher
.md FS watcher
Vite dev server
(HTTP server)
vite-node server
-
Story 1
-
Story 2
-
Story 3
-
...
-
Docs of Story 1
-
Docs page 1
-
Docs page 2
Vite dev server
(HTTP server)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/9609834/Screenshot_2022-06-02_at_00.56.37.png)
Virtual modules
Browser
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/9847477/pasted-from-clipboard.png)
import {
files, tree, onUpdate
} from 'virtual:$histoire-stories'
export let files = [{ ... }]
export let tree = { ... }
const handlers = []
export function onUpdate (cb) {
handlers.push(cb)
}
if (import.meta.hot) {
import.meta.hot.accept(newModule => {
files = newModule.files
tree = newModule.tree
handlers.forEach(h => {
h(newModule.files, newModule.tree)
newModule.onUpdate(h)
})
})
}
HMR
async resolveId (id) {
if (id === 'virtual:$histoire-stories') {
return '\0virtual:$histoire-stories'
}
}
async load (id) {
if (id === '\0virtual:$histoire-stories') {
return `export let files = [${JSON.stringify(files)}]
export let tree = ${JSON.stringify(tree)}
const handlers = []
export function onUpdate (cb) {
handlers.push(cb)
}
if (import.meta.hot) {
// Handle HMR...
}`
}
}
Vite dev server
(HTTP server)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/9609834/Screenshot_2022-06-02_at_00.56.37.png)
Virtual modules
Browser
HMR
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/9847477/pasted-from-clipboard.png)
stories data & tree
resolved config
theme CSS variables
setup code
support plugins
markdown files
full-text search indexes
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/7080221/svelte.png)
Vite dev server
(HTTP server)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/9609834/Screenshot_2022-06-02_at_00.56.37.png)
Browser
View: Story 1
stories data & tree
setup code
support plugin
markdown files
Mount: Story 1
Story1.story.vue
Render: Story 1: Content
Render: Story 1: Controls
support plugin
support plugin
Load
- Props
- Slots
- Render functions
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/9609834/Screenshot_2022-06-02_at_00.56.37.png)
Browser
View: Story 1
stories data & tree
support plugin
Mount: Story 1
Story1Component.vue
Load
Render: Story 1: Content
Render: Story 1: Controls
support plugin
support plugin
Sync state
can be in an iframe
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/9609834/Screenshot_2022-06-02_at_00.56.37.png)
Browser
View: Story 1
stories data & tree
support plugin
Mount: Story 1
Story1Component.vue
Load
Render: Story 1: Content
support plugin
Auto-CodeGen: Story 1
support plugin
slot
<button
color="primary"
disabled
>
Click me!
</button>
Sync state
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/9609834/Screenshot_2022-06-02_at_00.56.37.png)
Browser
Virtual modules
stories data & tree
resolved config
theme CSS variables
setup code
support plugins
markdown files
full-text search indexes
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/7080221/svelte.png)
Static code
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/9847898/purfect.gif)
Vite build
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/9847896/pasted-from-clipboard.png)
![](https://media0.giphy.com/media/heIX5HfWgEYlW/giphy.gif)
HMR API
export const count = 1
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
if (newModule) {
// newModule is undefined when SyntaxError happened
console.log('updated: count is now ', newModule.count)
}
})
}
Accept an update for current module
import { foo } from './foo.js'
foo()
if (import.meta.hot) {
import.meta.hot.accept('./foo.js', (newFoo) => {
// the callback receives the updated './foo.js' module
newFoo?.foo()
})
}
Accept an update for a dependency
if (import.meta.hot) {
// When current module is replaced
import.meta.hot.dispose((data) => {
// cleanup side effect
})
// when the module is no longer used in the page
import.meta.hot.prune((data) => {
// cleanup side effect
})
}
Cleanup module side-effects
if (import.meta.hot) {
import.meta.hot.accept((module) => {
// You may use the new module instance
// to decide whether to invalidate.
if (cannotHandleUpdate(module)) {
import.meta.hot.invalidate()
}
})
}
Bail out of HMR and propagate the update in parent modules
Client-Dev server communication
Using HMR API
vitePlugins.push({
name: 'histoire:example',
configureServer (server) {
server.ws.on('some-event', (payload) => {
server.ws.send('some-other-event', somePayload)
})
},
})
if (import.meta.hot) {
import.meta.hot.send('some-event', { message: 'Hello Amsterdam' })
import.meta.hot.on('some-other-event', (somePayload) => {
console.log(somePayload)
})
}
Browser
Vite server
![](https://media0.giphy.com/media/heIX5HfWgEYlW/giphy.gif)
Thank you!
![](https://s3.amazonaws.com/media-p.slid.es/uploads/638176/images/10210168/frame.png)
Histoire: A deep dive (Vue Amsterdam 2023)
By Guillaume Chau
Histoire: A deep dive (Vue Amsterdam 2023)
A look at the internals of Histoire, a tool to generate component stories, and how Vite makes it possible.
- 2,193