Skip to content

Commit

Permalink
Support features.storyStoreV7 (#126)
Browse files Browse the repository at this point in the history
Closes #125
  • Loading branch information
joshwooding authored Nov 18, 2021
1 parent 98c28a4 commit 496312e
Show file tree
Hide file tree
Showing 12 changed files with 764 additions and 407 deletions.
5 changes: 4 additions & 1 deletion packages/example-react/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ module.exports = {
'../stories/**/*.stories.@(js|jsx|ts|tsx)',
],
addons: [
// "@storybook/addon-a11y", // still breaks
"@storybook/addon-a11y",
'@storybook/addon-links',
'@storybook/addon-essentials',
],
core: {
builder: 'storybook-builder-vite',
},
features: {
storyStoreV7: true,
},
async viteFinal(config, { configType }) {
// customize the Vite config here
return config;
Expand Down
8 changes: 4 additions & 4 deletions packages/example-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
"react-dom": "17.0.2"
},
"devDependencies": {
"@storybook/addon-a11y": "^v6.4.0-rc.0",
"@storybook/addon-docs": "^v6.4.0-rc.0",
"@storybook/addon-essentials": "^v6.4.0-rc.0",
"@storybook/react": "^v6.4.0-rc.0",
"@storybook/addon-a11y": "^6.4.0-rc.2",
"@storybook/addon-docs": "^6.4.0-rc.2",
"@storybook/addon-essentials": "^6.4.0-rc.2",
"@storybook/react": "^6.4.0-rc.2",
"storybook-builder-vite": "workspace:*",
"vite": "^2.4.1"
}
Expand Down
8 changes: 4 additions & 4 deletions packages/example-svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
"svelte": "^3.38.3"
},
"devDependencies": {
"@storybook/addon-actions": "^v6.4.0-rc.0",
"@storybook/addon-essentials": "^v6.4.0-rc.0",
"@storybook/addon-links": "^v6.4.0-rc.0",
"@storybook/addon-actions": "^6.4.0-rc.2",
"@storybook/addon-essentials": "^6.4.0-rc.2",
"@storybook/addon-links": "^6.4.0-rc.2",
"@storybook/addon-svelte-csf": "^1.1.0",
"@storybook/svelte": "^v6.4.0-rc.0",
"@storybook/svelte": "^6.4.0-rc.2",
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.11",
"storybook-builder-vite": "workspace:*",
"vite": "^2.4.1"
Expand Down
6 changes: 3 additions & 3 deletions packages/example-vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
"vue": "^3.1.4"
},
"devDependencies": {
"@storybook/addon-a11y": "^v6.4.0-rc.0",
"@storybook/addon-essentials": "^v6.4.0-rc.0",
"@storybook/vue3": "^v6.4.0-rc.0",
"@storybook/addon-a11y": "^6.4.0-rc.2",
"@storybook/addon-essentials": "^6.4.0-rc.2",
"@storybook/vue3": "^6.4.0-rc.2",
"@vitejs/plugin-vue": "^1.2.4",
"storybook-builder-vite": "workspace:*",
"vite": "^2.4.1"
Expand Down
28 changes: 24 additions & 4 deletions packages/storybook-builder-vite/code-generator-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ const fs = require('fs');
const path = require('path');
const { transformIframeHtml } = require('./transform-iframe-html');
const { generateIframeScriptCode } = require('./codegen-iframe-script');
const { generateModernIframeScriptCode } = require('./codegen-modern-iframe-script');
const { generateImportFnScriptCode } = require('./codegen-importfn-script');

module.exports.codeGeneratorPlugin = function codeGeneratorPlugin(options) {
const virtualFileId = '/virtual:/@storybook/builder-vite/vite-app.js';
const virtualStoriesFile = '/virtual:/@storybook/builder-vite/storybook-stories.js';
let iframeId;
return {
name: 'storybook-vite-code-generator-plugin',
Expand All @@ -14,9 +17,13 @@ module.exports.codeGeneratorPlugin = function codeGeneratorPlugin(options) {
// (this might be a little too aggressive?)
server.watcher.on('change', (e) => {
const { moduleGraph } = server;
const module = moduleGraph.getModuleById(virtualFileId);
if (module) {
server.moduleGraph.invalidateModule(module);
const appModule = moduleGraph.getModuleById(virtualFileId);
if (appModule) {
server.moduleGraph.invalidateModule(appModule);
}
const storiesModule = moduleGraph.getModuleById(virtualStoriesFile);
if(storiesModule) {
server.moduleGraph.invalidateModule(storiesModule);
}
});
},
Expand All @@ -39,12 +46,25 @@ module.exports.codeGeneratorPlugin = function codeGeneratorPlugin(options) {
return virtualFileId;
} else if (source === 'iframe.html') {
return iframeId;
} else if (source === virtualStoriesFile) {
return virtualStoriesFile;
}
},
async load(id) {
if (id === virtualStoriesFile) {
return generateImportFnScriptCode(options);
}

if (id === virtualFileId) {
return generateIframeScriptCode(options);
if (options.features && options.features.storyStoreV7) {
return generateModernIframeScriptCode(options, { storiesFilename: virtualStoriesFile });
} else {
return generateIframeScriptCode(options);
}
}



if (id === iframeId) {
return fs.readFileSync(
path.resolve(__dirname, 'input', 'iframe.html'),
Expand Down
5 changes: 4 additions & 1 deletion packages/storybook-builder-vite/codegen-iframe-script.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ function replaceCJStoESMPath(entryPath) {
module.exports.generateIframeScriptCode =
async function generateIframeScriptCode(options) {
const { presets, configDir, framework, frameworkPath } = options;

const previewEntries = (
await presets.apply('previewEntries', [], options)
).map(replaceCJStoESMPath);
Expand Down Expand Up @@ -95,6 +94,10 @@ module.exports.generateIframeScriptCode =
v[key] = value;
return addParameters(v, false);
}
case 'decorateStory':
case 'renderToDOM': {
return null; // This key is not handled directly in v6 mode.
}
default: {
// eslint-disable-next-line prefer-template
return console.log(key + ' was not supported :( !');
Expand Down
46 changes: 46 additions & 0 deletions packages/storybook-builder-vite/codegen-importfn-script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const glob = require('glob-promise');
const path = require('path');
const { normalizePath } = require('vite');

/**
* This file is largely based on https://github.com/storybookjs/storybook/blob/d1195cbd0c61687f1720fefdb772e2f490a46584/lib/core-common/src/utils/to-importFn.ts
*/

/**
* This function takes an array of stories and creates a mapping between the stories' relative paths
* to the working directory and their dynamic imports. The import is done in an asynchronous function
* to delay loading. It then creates a function, `importFn(path)`, which resolves a path to an import
* function and this is called by Storybook to fetch a story dynamically when needed.
* @param stories An array of absolute story paths.
* @returns {Promise<string>}
*/
async function toImportFn(stories) {
const objectEntries = stories
.map(file => ` './${normalizePath(path.relative(process.cwd(),file))}': async () => import('/@fs/${file}')`);
return `
const importers = {
${objectEntries.join(',\n')}
};
export async function importFn(path) {
return importers[path]();
}
`;
}

module.exports.generateImportFnScriptCode =
async function generateImportFnScriptCode(options) {
// First we need to get an array of stories and their absolute paths.
const stories = (
await Promise.all(
(
await options.presets.apply('stories', [], options)
).map((storyEntry) =>
glob(path.isAbsolute(storyEntry) ? storyEntry : path.join(options.configDir, storyEntry))
)
)
).reduce((carry, stories) => carry.concat(stories), []);

// We can then call toImportFn to create a function that can be used to load each story dynamically.
return (await toImportFn(stories)).trim();
};
70 changes: 70 additions & 0 deletions packages/storybook-builder-vite/codegen-modern-iframe-script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const { loadPreviewOrConfigFile } = require('@storybook/core-common');
const { normalizePath } = require('vite');

module.exports.generateModernIframeScriptCode =
async function generateModernIframeScriptCode(options, { storiesFilename }) {
const { presets, configDir } = options;

const previewOrConfigFile = loadPreviewOrConfigFile({ configDir });
const presetEntries = await presets.apply('config', [], options);
const configEntries = [...presetEntries, previewOrConfigFile].filter(
Boolean
).map(configEntry => `/@fs/${normalizePath(configEntry)}`);

/**
* This code is largely taken from https://github.com/storybookjs/storybook/blob/d1195cbd0c61687f1720fefdb772e2f490a46584/lib/builder-webpack4/src/preview/virtualModuleModernEntry.js.handlebars
* Some small tweaks were made to `getProjectAnnotations` (since `import()` needs to be resolved asynchronously)
* and the HMR implementation has been tweaked to work with Vite.
*/
const code = `
import fetch from 'unfetch';
import global from 'global';
import { composeConfigs, PreviewWeb } from '@storybook/preview-web';
import { ClientApi } from '@storybook/client-api';
import { addons } from '@storybook/addons';
import createPostMessageChannel from '@storybook/channel-postmessage';
import createWebSocketChannel from '@storybook/channel-websocket';
import { importFn } from '${storiesFilename}';
const { SERVER_CHANNEL_URL } = global;
const getProjectAnnotations = async () =>
composeConfigs(await Promise.all([${configEntries.map(configEntry => `import('${configEntry}')`).join(',\n')}]));
const channel = createPostMessageChannel({ page: 'preview' });
addons.setChannel(channel);
if (SERVER_CHANNEL_URL) {
const serverChannel = createWebSocketChannel({ url: SERVER_CHANNEL_URL, });
addons.setServerChannel(serverChannel);
window.__STORYBOOK_SERVER_CHANNEL__ = serverChannel;
}
const preview = new PreviewWeb();
window.__STORYBOOK_PREVIEW__ = preview;
window.__STORYBOOK_STORY_STORE__ = preview.storyStore;
window.__STORYBOOK_ADDONS_CHANNEL__ = channel;
window.__STORYBOOK_CLIENT_API__ = new ClientApi({ storyStore: preview.storyStore });
preview.initialize({ importFn, getProjectAnnotations });
if (import.meta.hot) {
import.meta.hot.accept('${storiesFilename}', (newModule) => {
// importFn has changed so we need to patch the new one in
preview.onStoriesChanged({ importFn: newModule.importFn });
});
import.meta.hot.accept(${JSON.stringify(configEntries)}, ([...newConfigEntries]) => {
const newGetProjectAnnotations = () => composeConfigs(newConfigEntries);
// getProjectAnnotations has changed so we need to patch the new one in
preview.onGetProjectAnnotationsChanged({ getProjectAnnotations: newGetProjectAnnotations });
});
}
`.trim();
return code;
};
7 changes: 7 additions & 0 deletions packages/storybook-builder-vite/input/iframe.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
<title><!-- [TITLE HERE] --></title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
window.CONFIG_TYPE = '[CONFIG_TYPE HERE]';
window.LOGLEVEL = '[LOGLEVEL HERE]';
window.FRAMEWORK_OPTIONS = '[FRAMEWORK_OPTIONS HERE]';
window.CHANNEL_OPTIONS = '[CHANNEL_OPTIONS HERE]'
window.FEATURES = '[FEATURES HERE]';
window.SERVER_CHANNEL_URL = '[SERVER_CHANNEL_URL HERE]'

// We do this so that "module && module.hot" etc. in Storybook source code
// doesn't fail (it will simply be disabled)
Expand All @@ -30,5 +33,9 @@
type="module"
src="/virtual:/@storybook/builder-vite/vite-app.js"
></script>
<script
type="module"
src="/virtual:/@storybook/builder-vite/storybook-stories.js"
></script>
</body>
</html>
5 changes: 5 additions & 0 deletions packages/storybook-builder-vite/optimizeDeps.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ module.exports.optimizeDeps = {
'@emotion/is-prop-valid',
'@emotion/styled',
'@mdx-js/react',
'@storybook/addons',
'@storybook/addon-docs',
'@storybook/channel-postmessage',
'@storybook/channel-websocket',
'@storybook/client-api',
'@storybook/client-logger',
'@storybook/core/client',
'@storybook/csf',
'@storybook/preview-web',
'@storybook/react',
'@storybook/vue3',
'acorn-jsx',
Expand Down Expand Up @@ -77,6 +81,7 @@ module.exports.optimizeDeps = {
'ts-dedent',
'util-deprecate',
'uuid-browser/v4',
'unfetch',
'vue',
'warning',
].filter((m) => {
Expand Down
8 changes: 6 additions & 2 deletions packages/storybook-builder-vite/transform-iframe-html.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
module.exports.transformIframeHtml = async function transformIframeHtml(
html,
{ title, framework, presets }
{ configType, features, framework, presets, serverChannelUrl, title }
) {
const headHtmlSnippet = await presets.apply('previewHead');
const bodyHtmlSnippet = await presets.apply('previewBody');
const logLevel = await presets.apply('logLevel', undefined);
const frameworkOptions = await presets.apply(`${framework}Options`, {});
const features = await presets.apply('features', {});
const coreOptions = await presets.apply('core');

return html
.replace('<!-- [TITLE HERE] -->', title || 'Storybook')
.replace('[CONFIG_TYPE HERE]', configType)
.replace('[LOGLEVEL HERE]', logLevel)
.replace(
`'[FRAMEWORK_OPTIONS HERE]'`,
JSON.stringify(frameworkOptions || {})
)
.replace(`'[CHANNEL_OPTIONS HERE]'`, JSON.stringify(coreOptions && coreOptions.channelOptions ? coreOptions.channelOptions : {}))
.replace(`'[FEATURES HERE]'`, JSON.stringify(features || {}))
.replace(`'[SERVER_CHANNEL_URL HERE]'`, JSON.stringify(serverChannelUrl))
.replace('<!-- [HEAD HTML SNIPPET HERE] -->', headHtmlSnippet || '')
.replace('<!-- [BODY HTML SNIPPET HERE] -->', bodyHtmlSnippet || '');
};
Loading

0 comments on commit 496312e

Please sign in to comment.