Skip to content
This repository has been archived by the owner on Sep 28, 2023. It is now read-only.

fix: wait for nuxt to finish before initializing storybook #21

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 74 additions & 72 deletions src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,15 @@ import type { StorybookConfig } from '@storybook/vue3-vite';
import { fileURLToPath } from 'node:url';
import { join } from 'node:path';
import { mergeConfig } from 'vite';
import vuePlugin from '@vitejs/plugin-vue';
import viteJsxPlugin from '@vitejs/plugin-vue-jsx';

const runtimeDir = fileURLToPath(new URL('../runtime', import.meta.url));

export const viteFinal: StorybookConfig['viteFinal'] = async (config) => {
const nuxtViteConfig = await useNuxtViteConfig();
const { viteConfig } = nuxtViteConfig;
const { viteConfig } = await initNuxt();

// https://github.com/storybookjs/storybook/issues/20817
if (config.plugins) {
config.plugins = config.plugins.filter((plugin) => {
if (
plugin !== null &&
typeof plugin === 'object' &&
'name' in plugin &&
plugin.name === 'vite:vue'
) {
return false;
}
return true;
return (plugin as any).name !== 'vite:vue';
});
}

Expand All @@ -36,13 +26,9 @@ export const viteFinal: StorybookConfig['viteFinal'] = async (config) => {
);
};

const vuePlugins = {
'vite:vue': [vuePlugin, 'vue'],
'vite:vue-jsx': [viteJsxPlugin, 'vueJsx'],
} as const;

async function useNuxtViteConfig() {
async function initNuxt() {
const { loadNuxt, buildNuxt } = await import('@nuxt/kit');

const nuxt = await loadNuxt({
// cwd: process.cwd(),
ready: false,
Expand All @@ -53,75 +39,91 @@ async function useNuxtViteConfig() {
rootId: 'nuxt-test',
},
pages: false,
// debug: true,
},
});

if ((nuxt.options.builder as string) !== '@nuxt/vite-builder') {
throw new Error(
`Storybook addon Nuxt only supports Vite bundler, but Nuxt builder is currently set to '${nuxt.options.builder}'.`
);
}
const runtimeDir = fileURLToPath(new URL('../runtime', import.meta.url));

nuxt.options.build.templates.push(
{ src: join(runtimeDir, 'composables.mjs'), filename: 'storybook/composables.mjs' },
{ src: join(runtimeDir, 'components.mjs'), filename: 'storybook/components.mjs' }
);

nuxt.hook('app:templates', (app) => {
app.templates = app.templates.filter((template) => template.filename !== 'app-component.mjs');
app.templates.push({
src: join(runtimeDir, 'app-component.mjs'),
filename: 'app-component.mjs',
stubApp();
stubComposables();
stubComponents();

const [viteConfig] = await Promise.all([waitForConfig(), waitForNuxt()]);

function stubApp() {
nuxt.hook('app:templates', (app) => {
app.templates = app.templates.filter((template) => template.filename !== 'app-component.mjs');
app.templates.push({
src: join(runtimeDir, 'app-component.mjs'),
filename: 'app-component.mjs',
});
});
});
}

nuxt.hook('imports:sources', (presets) => {
const stubbedComposables = ['useNuxtApp'];
const appPreset = presets.find((p) => p.from === '#app');
if (appPreset) {
appPreset.imports = appPreset.imports.filter(
(i) => typeof i !== 'string' || !stubbedComposables.includes(i)
);
}
presets.push({
from: '#build/storybook/composables.mjs',
imports: stubbedComposables,
function stubComposables() {
nuxt.hook('imports:sources', (presets) => {
const stubbedComposables = ['useNuxtApp'];
const appPreset = presets.find((p) => p.from === '#app');
if (appPreset) {
appPreset.imports = appPreset.imports.filter(
(i) => typeof i !== 'string' || !stubbedComposables.includes(i)
);
}
presets.push({
from: '#build/storybook/composables.mjs',
imports: stubbedComposables,
});
});
});
}

return {
viteConfig: await new Promise<ViteConfig>((resolve, reject) => {
nuxt.hook('modules:done', () => {
nuxt.hook('components:extend', (components) => {
for (const name of ['NuxtLink']) {
Object.assign(components.find((c) => c.pascalName === name) || {}, {
export: name,
filePath: '#build/storybook/components.mjs',
});
}
});
nuxt.hook('vite:extendConfig', (config, { isClient }) => {
if (isClient) {
for (const name in vuePlugins) {
if (!config.plugins?.some((p) => (p as any)?.name === name)) {
const [plugin, key] = vuePlugins[name as keyof typeof vuePlugins];
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
config.plugins.push(plugin(config[key]));
}
}
resolve({ ...config });
}
});
function stubComponents() {
nuxt.hook('components:extend', (components) => {
for (const name of ['NuxtLink']) {
const component = components.find((c) => c.pascalName === name);

if (component) {
Object.assign(component, {
export: name,
filePath: '#build/storybook/components.mjs',
});
}
}
});
}

async function waitForConfig() {
return new Promise<ViteConfig>((resolve) => {
nuxt.hook('vite:extendConfig', (config, { isClient }) => {
if (isClient) {
resolve(config);
}
});
nuxt
.ready()
.then(() => buildNuxt(nuxt))
.catch((err) => {
if (!err.toString().includes('_stop_')) {
reject(err);
}
});
}),
});
}

async function waitForNuxt() {
try {
await nuxt.ready();
await buildNuxt(nuxt);
} catch (err) {
if (!err.toString().includes('_stop_')) {
throw err;
}
}
}

return {
viteConfig,
nuxt,
};
}