Files
stack/v1/packages/mosaic/framework/skills/tsdown/references/recipe-vue.md
T

6.3 KiB

Vue Support

Build Vue component libraries with tsdown.

Overview

tsdown provides first-class support for Vue libraries through integration with unplugin-vue and rolldown-plugin-dts for type generation.

Quick Start

Use Starter Template

npx create-tsdown@latest -t vue

Basic Configuration

Install Dependencies

pnpm add -D unplugin-vue vue-tsc

Minimal Setup

// tsdown.config.ts
import { defineConfig } from 'tsdown';
import Vue from 'unplugin-vue/rolldown';

export default defineConfig({
  entry: ['./src/index.ts'],
  format: ['esm', 'cjs'],
  platform: 'neutral',
  external: ['vue'],
  plugins: [Vue({ isProduction: true })],
  dts: {
    vue: true, // Enable Vue type generation
  },
});

How It Works

unplugin-vue

Compiles .vue single-file components:

  • Transforms template to render functions
  • Handles scoped styles
  • Processes script setup

vue-tsc

Generates TypeScript declarations:

  • Type-checks Vue components
  • Creates .d.ts files
  • Preserves component props types
  • Exports component types

Component Example

Single File Component

<!-- src/Button.vue -->
<script setup lang="ts">
interface Props {
  type?: 'primary' | 'secondary';
  disabled?: boolean;
}

defineProps<Props>();
defineEmits<{
  click: [];
}>();
</script>

<template>
  <button :class="['btn', `btn-${type}`]" :disabled="disabled" @click="$emit('click')">
    <slot />
  </button>
</template>

<style scoped>
.btn {
  padding: 8px 16px;
  border-radius: 4px;
}

.btn-primary {
  background: blue;
  color: white;
}
</style>

Export Components

// src/index.ts
export { default as Button } from './Button.vue';
export { default as Input } from './Input.vue';
export { default as Modal } from './Modal.vue';

// Re-export types
export type { ButtonProps } from './Button.vue';

Common Patterns

Component Library

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm', 'cjs'],
  platform: 'neutral',
  external: ['vue'],
  plugins: [
    Vue({
      isProduction: true,
      style: {
        trim: true,
      },
    }),
  ],
  dts: {
    vue: true,
  },
  clean: true,
});

Multiple Components

export default defineConfig({
  entry: {
    index: 'src/index.ts',
    Button: 'src/Button.vue',
    Input: 'src/Input.vue',
    Modal: 'src/Modal.vue',
  },
  format: ['esm', 'cjs'],
  external: ['vue'],
  plugins: [Vue({ isProduction: true })],
  dts: { vue: true },
});

With Composition Utilities

// src/composables/useCounter.ts
import { ref } from 'vue';

export function useCounter(initial = 0) {
  const count = ref(initial);
  const increment = () => count.value++;
  const decrement = () => count.value--;
  return { count, increment, decrement };
}
export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm', 'cjs'],
  external: ['vue'],
  plugins: [Vue({ isProduction: true })],
  dts: { vue: true },
});

TypeScript Configuration

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "jsx": "preserve",
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "strict": true,
    "isolatedDeclarations": true,
    "skipLibCheck": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

Package.json Configuration

{
  "name": "my-vue-library",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  },
  "files": ["dist"],
  "peerDependencies": {
    "vue": "^3.0.0"
  },
  "devDependencies": {
    "tsdown": "^0.9.0",
    "typescript": "^5.0.0",
    "unplugin-vue": "^5.0.0",
    "vue": "^3.4.0",
    "vue-tsc": "^2.0.0"
  }
}

Advanced Patterns

With Vite Plugins

Some Vite Vue plugins may work:

import Vue from 'unplugin-vue/rolldown';
import Components from 'unplugin-vue-components/rolldown';

export default defineConfig({
  entry: ['src/index.ts'],
  external: ['vue'],
  plugins: [
    Vue({ isProduction: true }),
    Components({
      dts: 'src/components.d.ts',
    }),
  ],
  dts: { vue: true },
});

JSX Support

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm', 'cjs'],
  external: ['vue'],
  plugins: [
    Vue({
      isProduction: true,
      script: {
        propsDestructure: true,
      },
    }),
  ],
  inputOptions: {
    transform: {
      jsx: 'automatic',
      jsxImportSource: 'vue',
    },
  },
  dts: { vue: true },
});

Monorepo Vue Packages

export default defineConfig({
  workspace: 'packages/*',
  entry: ['src/index.ts'],
  format: ['esm', 'cjs'],
  external: ['vue', /^@mycompany\//],
  plugins: [Vue({ isProduction: true })],
  dts: { vue: true },
});

Plugin Options

unplugin-vue Options

Vue({
  isProduction: true,
  script: {
    defineModel: true,
    propsDestructure: true,
  },
  style: {
    trim: true,
  },
  template: {
    compilerOptions: {
      isCustomElement: (tag) => tag.startsWith('custom-'),
    },
  },
});

Tips

  1. Always externalize Vue - Don't bundle Vue itself
  2. Enable vue: true in dts - For proper type generation
  3. Use platform: 'neutral' - Maximum compatibility
  4. Install vue-tsc - Required for type generation
  5. Set isProduction: true - Optimize for production
  6. Add peer dependency - Vue as peer dependency

Troubleshooting

Type Generation Fails

Ensure vue-tsc is installed:

pnpm add -D vue-tsc

Enable in config:

dts: {
  vue: true;
}

Component Types Missing

Check TypeScript config:

{
  "compilerOptions": {
    "jsx": "preserve",
    "moduleResolution": "bundler"
  }
}

Vue Not Externalized

Add to external:

external: ['vue'];

SFC Compilation Errors

Check unplugin-vue version:

pnpm add -D unplugin-vue@latest