diff --git a/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts b/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts index 7ec671bddf62..54ac30cbb9dd 100644 --- a/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts +++ b/code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts @@ -5,8 +5,8 @@ import path from 'path'; import type { PluginOption } from 'vite'; import { TypeMeta, - createComponentMetaChecker, - createComponentMetaCheckerByJsonConfig, + createChecker, + createCheckerByJson, type ComponentMeta, type MetaCheckerOptions, } from 'vue-component-meta'; @@ -19,7 +19,7 @@ type MetaSource = { } & ComponentMeta & MetaCheckerOptions['schema']; -export async function vueComponentMeta(): Promise { +export async function vueComponentMeta(tsconfigPath = 'tsconfig.json'): Promise { const { createFilter } = await import('vite'); // exclude stories, virtual modules and storybook internals @@ -28,7 +28,7 @@ export async function vueComponentMeta(): Promise { const include = /\.(vue|ts|js|tsx|jsx)$/; const filter = createFilter(include, exclude); - const checker = await createChecker(); + const checker = await createVueComponentMetaChecker(tsconfigPath); return { name: 'storybook:vue-component-meta-plugin', @@ -126,9 +126,10 @@ export async function vueComponentMeta(): Promise { } /** - * Creates the vue-component-meta checker to use for extracting component meta/docs. + * Creates the `vue-component-meta` checker to use for extracting component meta/docs. + * Considers the given tsconfig file (will use a fallback checker if it does not exist or is not supported). */ -async function createChecker() { +async function createVueComponentMetaChecker(tsconfigPath = 'tsconfig.json') { const checkerOptions: MetaCheckerOptions = { forceUseTs: true, noDeclarations: true, @@ -136,26 +137,18 @@ async function createChecker() { }; const projectRoot = getProjectRoot(); - const projectTsConfigPath = path.join(projectRoot, 'tsconfig.json'); + const projectTsConfigPath = path.join(projectRoot, tsconfigPath); - const defaultChecker = createComponentMetaCheckerByJsonConfig( - projectRoot, - { include: ['**/*'] }, - checkerOptions - ); + const defaultChecker = createCheckerByJson(projectRoot, { include: ['**/*'] }, checkerOptions); // prefer the tsconfig.json file of the project to support alias resolution etc. if (await fileExists(projectTsConfigPath)) { - // tsconfig that uses references is currently not supported by vue-component-meta - // see: https://github.com/vuejs/language-tools/issues/3896 - // so we return the no-tsconfig defaultChecker if tsconfig references are found - // remove this workaround once the above issue is fixed + // vue-component-meta does currently not resolve tsconfig references (see https://github.com/vuejs/language-tools/issues/3896) + // so we will return the defaultChecker if references are used. + // Otherwise vue-component-meta might not work at all for the Storybook docgen. const references = await getTsConfigReferences(projectTsConfigPath); - if (references.length > 0) { - // TODO: paths/aliases are not resolvable, find workaround for this - return defaultChecker; - } - return createComponentMetaChecker(projectTsConfigPath, checkerOptions); + if (references.length > 0) return defaultChecker; + return createChecker(projectTsConfigPath, checkerOptions); } return defaultChecker; diff --git a/code/frameworks/vue3-vite/src/preset.ts b/code/frameworks/vue3-vite/src/preset.ts index 69173e6e767c..01b1d5f1de8d 100644 --- a/code/frameworks/vue3-vite/src/preset.ts +++ b/code/frameworks/vue3-vite/src/preset.ts @@ -3,7 +3,7 @@ import { dirname, join } from 'path'; import type { PluginOption } from 'vite'; import { vueComponentMeta } from './plugins/vue-component-meta'; import { vueDocgen } from './plugins/vue-docgen'; -import type { FrameworkOptions, StorybookConfig } from './types'; +import type { FrameworkOptions, StorybookConfig, VueDocgenPlugin } from './types'; const getAbsolutePath = (input: I): I => dirname(require.resolve(join(input, 'package.json'))) as any; @@ -20,11 +20,11 @@ export const viteFinal: StorybookConfig['viteFinal'] = async (config, options) = const frameworkOptions: FrameworkOptions = typeof framework === 'string' ? {} : framework.options ?? {}; - const docgenPlugin = frameworkOptions.docgen ?? 'vue-docgen-api'; + const docgen = resolveDocgenOptions(frameworkOptions.docgen); // add docgen plugin depending on framework option - if (docgenPlugin === 'vue-component-meta') { - plugins.push(await vueComponentMeta()); + if (docgen.plugin === 'vue-component-meta') { + plugins.push(await vueComponentMeta(docgen.tsconfig)); } else { plugins.push(await vueDocgen()); } @@ -39,3 +39,14 @@ export const viteFinal: StorybookConfig['viteFinal'] = async (config, options) = }, }); }; + +/** + * Resolves the docgen framework option. + */ +const resolveDocgenOptions = ( + docgen?: FrameworkOptions['docgen'] +): { plugin: VueDocgenPlugin; tsconfig?: string } => { + if (!docgen) return { plugin: 'vue-docgen-api' }; + if (typeof docgen === 'string') return { plugin: docgen }; + return docgen; +}; diff --git a/code/frameworks/vue3-vite/src/types.ts b/code/frameworks/vue3-vite/src/types.ts index 4b77d28e7a94..c75fee101de8 100644 --- a/code/frameworks/vue3-vite/src/types.ts +++ b/code/frameworks/vue3-vite/src/types.ts @@ -21,7 +21,21 @@ export type FrameworkOptions = { * "vue-component-meta" will become the new default in the future and "vue-docgen-api" will be removed. * @default "vue-docgen-api" */ - docgen?: VueDocgenPlugin; + docgen?: + | VueDocgenPlugin + | { + plugin: 'vue-component-meta'; + /** + * Tsconfig filename to use. Should be set if your main `tsconfig.json` includes references to other tsconfig files + * like `tsconfig.app.json`. + * Otherwise docgen might not be generated correctly (e.g. import aliases are not resolved). + * + * For further information, see our [docs](https://storybook.js.org/docs/get-started/vue3-vite#override-the-default-configuration). + * + * @default "tsconfig.json" + */ + tsconfig: `tsconfig${string}.json`; + }; }; type StorybookConfigFramework = { diff --git a/code/lib/cli/src/upgrade.ts b/code/lib/cli/src/upgrade.ts index 4483bdb0f2e3..a49b6730bca8 100644 --- a/code/lib/cli/src/upgrade.ts +++ b/code/lib/cli/src/upgrade.ts @@ -20,6 +20,7 @@ import { getStorybookInfo, loadMainConfig, JsPackageManagerFactory, + getCoercedStorybookVersion, } from '@storybook/core-common'; import { automigrate } from './automigrate/index'; import { autoblock } from './autoblock/index'; @@ -146,10 +147,11 @@ export const doUpgrade = async ({ throw new UpgradeStorybookToSameVersionError({ beforeVersion }); } - const [latestVersion, packageJson] = await Promise.all([ + const [latestVersion, packageJson, storybookVersion] = await Promise.all([ // packageManager.latestVersion('@storybook/cli'), packageManager.retrievePackageJson(), + getCoercedStorybookVersion(packageManager), ]); const isOutdated = lt(currentVersion, latestVersion); @@ -193,7 +195,7 @@ export const doUpgrade = async ({ const mainConfig = await loadMainConfig({ configDir }); // GUARDS - if (!beforeVersion) { + if (!storybookVersion) { throw new UpgradeStorybookUnknownCurrentVersionError(); } diff --git a/code/lib/preview-api/src/modules/store/csf/composeConfigs.test.ts b/code/lib/preview-api/src/modules/store/csf/composeConfigs.test.ts index ba9ead34d119..9f663834f324 100644 --- a/code/lib/preview-api/src/modules/store/csf/composeConfigs.test.ts +++ b/code/lib/preview-api/src/modules/store/csf/composeConfigs.test.ts @@ -218,6 +218,34 @@ describe('composeConfigs', () => { }); }); + it('allows single array to be written without array', () => { + expect( + composeConfigs([ + { + argsEnhancers: ['1', '2'], + argTypesEnhancers: ['1', '2'], + loaders: '1', + }, + { + argsEnhancers: '3', + argTypesEnhancers: '3', + loaders: ['2', '3'], + }, + ]) + ).toEqual({ + parameters: {}, + decorators: [], + args: {}, + argsEnhancers: ['1', '2', '3'], + argTypes: {}, + argTypesEnhancers: ['1', '2', '3'], + globals: {}, + globalTypes: {}, + loaders: ['1', '2', '3'], + runStep: expect.any(Function), + }); + }); + it('combines decorators in reverse file order', () => { expect( composeConfigs([ diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/component-meta/DefineModel.stories.ts b/code/renderers/vue3/template/stories_vue3-vite-default-ts/component-meta/DefineModel.stories.ts new file mode 100644 index 000000000000..be2f612287a5 --- /dev/null +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/component-meta/DefineModel.stories.ts @@ -0,0 +1,16 @@ +import type { Meta, StoryObj } from '@storybook/vue3'; +import Component from './define-model/component.vue'; + +const meta = { + component: Component, + tags: ['autodocs'], +} satisfies Meta; + +type Story = StoryObj; +export default meta; + +export const Default: Story = { + args: { + modelValue: 'Test value', + }, +}; diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/component-meta/DefineSlots.stories.ts b/code/renderers/vue3/template/stories_vue3-vite-default-ts/component-meta/DefineSlots.stories.ts new file mode 100644 index 000000000000..1a06ce6bb504 --- /dev/null +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/component-meta/DefineSlots.stories.ts @@ -0,0 +1,18 @@ +import type { Meta, StoryObj } from '@storybook/vue3'; +import Component from './define-slots/component.vue'; + +const meta = { + component: Component, + tags: ['autodocs'], +} satisfies Meta; + +type Story = StoryObj; +export default meta; + +export const Default: Story = { + args: { + default: ({ num }) => `Default slot { num=${num} }`, + named: ({ str }) => `Named slot { str=${str} }`, + vbind: ({ num, str }) => `Named v-bind slot { num=${num}, str=${str} }`, + }, +}; diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/component-meta/define-model/component.vue b/code/renderers/vue3/template/stories_vue3-vite-default-ts/component-meta/define-model/component.vue new file mode 100644 index 000000000000..0b1ea4b3a3d6 --- /dev/null +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/component-meta/define-model/component.vue @@ -0,0 +1,7 @@ + + + diff --git a/code/renderers/vue3/template/stories_vue3-vite-default-ts/component-meta/define-slots/component.vue b/code/renderers/vue3/template/stories_vue3-vite-default-ts/component-meta/define-slots/component.vue new file mode 100644 index 000000000000..94533f010bdb --- /dev/null +++ b/code/renderers/vue3/template/stories_vue3-vite-default-ts/component-meta/define-slots/component.vue @@ -0,0 +1,22 @@ + + + diff --git a/docs/get-started/vue3-vite.md b/docs/get-started/vue3-vite.md index 776281caf29b..da31627b2677 100644 --- a/docs/get-started/vue3-vite.md +++ b/docs/get-started/vue3-vite.md @@ -275,16 +275,37 @@ The definition above will generate the following controls: ![Controls generated from exposed properties and methods](./vue-component-meta-exposed-types-controls.png) -### Limitations +### Override the default configuration -`vue-component-meta` cannot currently reference types from an import alias. You will need to replace any aliased imports with relative ones, as in the example below. See [this issue](https://github.com/vuejs/language-tools/issues/3896) for more information. +If you're working with a project that relies on [`tsconfig references`](https://www.typescriptlang.org/docs/handbook/project-references.html) to link to other existing configuration files (e.g. `tsconfig.app.json`, `tsconfig.node.json`), we recommend that you update your [`.storybook/main.js|ts`](../configure/index.md) configuration file and add the following: ```ts -// YourComponent.ts -import type { MyProps } from '@/types'; // ❌ Cannot be resolved -import type { MyProps } from '../types'; // ✅ Can be resolved +// .storybook/main.ts +import type { StorybookConfig } from '@storybook/vue3-vite'; + +const config: StorybookConfig = { + framework: { + name: '@storybook/vue3-vite', + options: { + docgen: { + plugin: 'vue-component-meta', + tsconfig: 'tsconfig.app.json', + }, + }, + }, +}; + +export default config; ``` + + +This is not a limitation of Storybook, but instead how `vue-component-meta` works. For more information, refer to the appropriate [GitHub issue](https://github.com/vuejs/language-tools/issues/3896). + + + +Otherwise, you might face missing component types/descriptions or unresolvable import aliases like `@/some/import`. + ## Troubleshooting ### Storybook doesn't work with my Vue 2 project diff --git a/scripts/prepare/addon-bundle.ts b/scripts/prepare/addon-bundle.ts index d4163609d5bf..39e070e8c4d0 100755 --- a/scripts/prepare/addon-bundle.ts +++ b/scripts/prepare/addon-bundle.ts @@ -126,8 +126,10 @@ const run = async ({ cwd, flags }: { cwd: string; flags: string[] }) => { platform: 'neutral', external: [...commonExternals, ...globalManagerPackages, ...globalPreviewPackages], esbuildOptions: (options) => { + /* eslint-disable no-param-reassign */ options.platform = 'neutral'; Object.assign(options, getESBuildOptions(optimized)); + /* eslint-enable no-param-reassign */ }, }) );