unbuild icon indicating copy to clipboard operation
unbuild copied to clipboard

How to build Vue component library?

Open itsmnthn opened this issue 3 years ago • 30 comments

As per antfu's blog we can bundle Vue components. I have followed it but getting error. here are the files I have

// package.json
{
  "name": "components",
  "private": true,
  "version": "0.0.0",
  "scripts": {
    "build": "unbuild"
  },
  "dependencies": {
    "@vueuse/core": "^8.5.0",
    "vue": "^3.2.21"
  },
  "devDependencies": {
    "typescript": "^4.6.4",
    "unbuild": "^0.7.4",
    "vue-tsc": "^0.34.15"
  }
}

// build.config.ts
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
  entries: [
    // bundling
    'src/index',
    // bundleless, or just copy assets
    { input: 'src/components/', outDir: 'dist/components' }, // this works but not generating MyComponent.vue.d.ts
  ],
  declaration: true,
})
// src/index.ts
import InputAmount from './components/InputAmount.vue'
export { InputAmount }
<!--  src/components/InputAmount.vue  -->
<script setup lang="ts">
import { watchDebounced, useMagicKeys } from '@vueuse/core'
import { computed, ref, watch } from 'vue'

const prop = defineProps({
  placeholder: { default: '0', type: [String, Number] },
  value: { default: '', type: [String, Number] },
  min: { default: '0', type: [String, Number] },
  max: { default: '', type: [String, Number] },
  step: { default: '0.01', type: String },
  readonly: { default: false, type: Boolean },
})
const emit = defineEmits({ change: (value: string) => value })

const input = ref(`${prop.value}` || '')
let wasValueChanged = false

const keys = useMagicKeys()
const shiftArrowUp = keys['Shift+ArrowUp']
const shiftCmd = keys['Shift+cmd']
const shiftCtrl = keys['Shift+Ctrl']
const arrowUp = keys['ArrowUp']
const steps = computed((): string => {
  if ((shiftCmd.value || shiftCtrl.value) && arrowUp) return '10'
  if (shiftArrowUp.value) return '1'
  return prop.step
})

const onChange = () => {
  // prevent infinite event loop
  if (wasValueChanged || input.value === prop.value) {
    wasValueChanged = false
    return
  }
  emit('change', input.value)
}

watchDebounced(input, onChange, { debounce: 700 })
watch(
  () => prop.value,
  () => {
    input.value = `${prop.value}`
    wasValueChanged = true
  }
)
</script>

<template>
  <input
    v-model="input"
    class="unstyled-input w-full h-full text-base"
    type="number"
    :placeholder="`${placeholder}`"
    :step="steps"
    :min="min"
    :max="max"
    :disabled="readonly"
  />
</template>

<style>
/* stylelint-disable selector-no-vendor-prefix */
/* stylelint-disable property-no-vendor-prefix */

/* remove default style input */
.unstyled-input {
  /* Some styles */
}

/* remove default style input */

.w-full {
  width: 100%;
}

.h-full {
  height: 100%;
}

.text-base {
  font-size: 1rem; /* 16px */
  line-height: 1.5rem; /* 24px */
}
</style>

when doing yarn build throws error

ℹ Building components                                                                                                                                                                                                        21:42:02
Error building /Users/mymac/magic/app/packages/components: Error: Unexpected token (Note that you need plugins to import files that are not JavaScript)
Error: Unexpected token (Note that you need plugins to import files that are not JavaScript)
    at error (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:1829:30)
    at Module.error (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:12406:16)
    at Module.tryParse (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:12783:25)
    at Module.setSource (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:12688:24)
    at ModuleLoader.addModuleSource (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:22144:20) 
{
  code: 'PARSE_ERROR',
  parserError: SyntaxError: Unexpected token (1:0)
      at Parser.pp$4.raise (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:19573:13)
      at Parser.pp$9.unexpected (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:16867:8)
      at Parser.pp$5.parseExprAtom (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:18948:10)
      at Parser.pp$5.parseExprSubscripts (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:18740:19)
      at Parser.pp$5.parseMaybeUnary (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:18706:17)
      at Parser.pp$5.parseExprOps (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:18633:19)
      at Parser.pp$5.parseMaybeConditional (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:18616:19)
      at Parser.pp$5.parseMaybeAssign (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:18583:19)
      at Parser.pp$5.parseExpression (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:18546:19)
      at Parser.pp$8.parseStatement (file:///Users/mymac/magic/app/node_modules/rollup/dist/es/shared/rollup.js:17057:45) {
    pos: 0,
    loc: Position { line: 1, column: 0 },
    raisedAt: 1
  },
  id: '/Users/mymac/magic/app/packages/components/src/components/InputAmount.vue',
  pos: 0,
  loc: {
    column: 0,
    file: '/Users/mymac/magic/app/packages/components/src/components/InputAmount.vue',
    line: 1
  },
  frame: '1: <script setup lang="ts">\n' +
    '   ^\n' +
    "2: import { watchDebounced, useMagicKeys } from '@vueuse/core'\n" +
    "3: import { computed, ref, watch } from 'vue'",
  watchFiles: [
    '/Users/mymac/magic/app/packages/components/src/index.ts',
    '/Users/mymac/magic/app/packages/components/src/components/InputAmount.vue'
  ]
}

itsmnthn avatar May 17 '22 16:05 itsmnthn

I am also facing the same. I end up using vite.

jd-solanki avatar May 26 '22 09:05 jd-solanki

So instead building them I am using bundleless, or just copy assets/components as it is by removing src/index as below

 entries: [
    // bundling
    // 'src/index',
    // bundleless, or just copy assets
   { input: 'src/components/', outDir: 'dist/' }, // this works but not generating MyComponent.vue.d.ts
  ],

which works fine, perhaps this might help you @jd-solanki

itsmnthn avatar May 30 '22 12:05 itsmnthn

If someone still stuck with this & stubborn enougn for using this cool tool anyhow...........

Just Keep <template> block at top in your vue SFC file && it'll fix this isssue. Easy huh..!!

<!--  src/components/InputAmount.vue  -->

<template>
    ........
</template>

<script setup lang="ts">
  // 
</script>

Easy, HuH..!

who-jonson avatar Jun 09 '22 03:06 who-jonson

Still giving same error but now for templates

...
 frame: '1: <template>\n' +
    '   ^\n' +
...

itsmnthn avatar Jun 09 '22 04:06 itsmnthn

But, don't know why or how, it's working for me without giving me any error

can you please share working minimal reproduction?

itsmnthn avatar Jun 16 '22 06:06 itsmnthn

I am getting similar errror when I tried unbuild in react.js library, but it complains about .png or .svg formats, I guess I need some sort of rollup plugin for images maybe like @rollup/plugin-image, but I am not sure how to configure in unbuild

Digital-Coder avatar Jun 29 '22 14:06 Digital-Coder

I am getting similar errror when I tried unbuild in react.js library, but it complains about .png or .svg formats, I guess I need some sort of rollup plugin for images maybe like @rollup/plugin-image, but I am not sure how to configure in unbuild

@Digital-Coder you should be able to access the plugins array configuration using the rollup:options hook:

// build.config.ts
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
  // other configs
  hooks: {
    'rollup:options'(_ctx, options) {
      options.plugins.push(
       // rollup plugin... 
      )
    },
  },
})

dwightjack avatar Jul 25 '22 03:07 dwightjack

Hi @itsmnthn as I mentioned I ended up using vite. Here's how in case you want insights: https://github.com/jd-solanki/anu

jd-solanki avatar Jul 25 '22 11:07 jd-solanki

// build.config.ts
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
  // other configs
  hooks: {
    'rollup:options'(_ctx, options) {
      options.plugins.push(
       // rollup plugin... 
      )
    },
  },
})

@dwightjack I tried this trick to build .scss files but vscode isn't buying it...

Property 'push' does not exist on type 'Plugin | InputPluginOption[] | Promise<false | Plugin | NullValue | InputPluginOption[]>'.\n Property 'push' does not exist on type 'Plugin'.",

amery avatar Nov 24 '22 16:11 amery

@amery I guess that's an issue with rollup typing because in the source code plugins is always an array:

  • https://github.com/unjs/unbuild/blob/main/src/builder/rollup.ts#L112
  • https://github.com/unjs/unbuild/blob/main/src/builder/rollup.ts#L190

The solution, in this case, could be to wrap the code in a type guard condition:

if (Array.isArray(options.plugins)) {
  options.plugins.push(
    // ...
  )
}

dwightjack avatar Nov 25 '22 08:11 dwightjack

The solution, in this case, could be to wrap the code in a type guard condition:

if (Array.isArray(options.plugins)) {
  options.plugins.push(
    // ...
  )
}

thank you @dwightjack, that did the trick. wouldn't this make a case for adding a plugins list to unbuild's RollupBuildOptions ? The amount of boilerplate just to add a rollup plugin

amery avatar Nov 25 '22 14:11 amery

@amery I guess so. @pi0 What do you think about it?

From a more general point of view, I think that a plugin interface similar to the one provided by vite could help improve the extensibility of the tool. Something like:

// unbuild-plugin-myplugin
export default function MyPlugin({
  hooks: {
    'rollup:options'(_ctx, options) {
      options.plugins.push(
       // rollup plugin... 
      )
    },
  },
})

// 
// build.config.ts
import { defineBuildConfig } from 'unbuild'
import MyPlugin 'unbuild-plugin-myplugin'

export default defineBuildConfig({
  plugins: [MyPlugin()],
})

dwightjack avatar Nov 28 '22 02:11 dwightjack

If you're still looking for one... https://github.com/wobsoriano/vue-sfc-unbuild

Supports vue 2 and 3

wobsoriano avatar Dec 30 '22 05:12 wobsoriano

how did you solve it? I am also facing the same. image Just add this code import 'vant/es/popup/style' will report this error: image build.config.ts: image @itsmnthn @who-jonson @wobsoriano @amery @dwightjack

lixiaofa avatar May 29 '23 10:05 lixiaofa

thank you @dwightjack, that did the trick. wouldn't this make a case for adding a plugins list to unbuild's RollupBuildOptions ? The amount of boilerplate just to add a rollup plugin

it doesn't seem to work @amery

lixiaofa avatar May 29 '23 10:05 lixiaofa

Are those css files? What happens if you add the extension? @lixiaofa

wobsoriano avatar May 29 '23 14:05 wobsoriano

Are those css files? What happens if you add the extension? @lixiaofa

image To be precise, it is a. mjs file , It is a CSS for a third-party component library, but the file format is. mjs image

Packaging error, The error message is as follows: image

@wobsoriano

lixiaofa avatar May 29 '23 14:05 lixiaofa

unbuild does not process SFC files like .vue or .svelte

I think you have to use a compiler/transformer within the build tool, something like

  • https://github.com/sxzz/unplugin-vue

Or only use mkdist

antfu:

I would suggest directly shipping SFC to npm and let userland plugin to compile it.

sadeghbarati avatar May 29 '23 15:05 sadeghbarati

unbuild does not process SFC files like .vue or .svelte

I think you have to use a compiler/transformer within the build tool, something like

  • https://github.com/sxzz/unplugin-vue

Or only use mkdist

antfu:

I would suggest directly shipping SFC to npm and let userland plugin to compile it.

image I tried, but still reported the same error @sadeghbarati

lixiaofa avatar May 29 '23 15:05 lixiaofa

The best is to create a basic repro so userland can play with it @lixiaofa

wobsoriano avatar May 29 '23 16:05 wobsoriano

unbuild already covered those plugins no need to install them again

options.plugins.push(
- commonjs(),
- nodeResolve(),
- externals(),
  postcss({
    plugins: [],
  })
)

This may help but this is not the way

  • unbuild + @vitejs/plugin-vue
  • use vue-tsc as dts file generator

unbuild-vue-transform.zip ( still not working when you add CSS content in the style section 😭 )


From what I understand unbuild only works well when it's all about JS stuff Something like @sveltejs/package is missing in the Vue ecosystem

sadeghbarati avatar May 29 '23 16:05 sadeghbarati

The best is to create a basic repro so userland can play with it @lixiaofa

repro: https://github.com/lixiaofa/fast-plus

build.config.ts: https://github.com/lixiaofa/fast-plus/blob/master/internal/build/build.config.ts

import 'vant/es/popup/style': https://github.com/lixiaofa/fast-plus/blob/master/packages/components/sku/src/sku.vue

@wobsoriano @sadeghbarati Thank you

lixiaofa avatar May 29 '23 17:05 lixiaofa

@lixiaofa I think the reproduction repo is too large and complex to understand the issue. From what I can understand from the screenshots, the task that is failing is buildModules which is calling rollup and not unbuild: https://github.com/lixiaofa/fast-plus/blob/4bee41cbb55bd1250422096cb89dda02e70466e1/internal/build/src/tasks/modules.ts#L16 is this correct?

My advice is to create a minimal reproduction to isolate the unbuild setup from all the other things going on in the project.

dwightjack avatar May 30 '23 00:05 dwightjack

@lixiaofa I think the reproduction repo is too large and complex to understand the issue. From what I can understand from the screenshots, the task that is failing is buildModules which is calling rollup and not unbuild: https://github.com/lixiaofa/fast-plus/blob/4bee41cbb55bd1250422096cb89dda02e70466e1/internal/build/src/tasks/modules.ts#L16 is this correct?

My advice is to create a minimal reproduction to isolate the unbuild setup from all the other things going on in the project.

I have conducted tests and found that importing 'vant/es/popup/style' without it can be packaged normally. If it is added, the above error will be reported.

You seem to be right @dwightjack

lixiaofa avatar May 30 '23 01:05 lixiaofa

Hey @pi0 can we have a example/template in this repo for building vue component library?

jd-solanki avatar Aug 01 '23 10:08 jd-solanki

I create a demo for building vue component library with unbuild + mkdist

And I have a component library (@leex/components) which also use unbuild + mkdist

jsonleex avatar Sep 08 '23 11:09 jsonleex

When working with monorepo, how to stub the .vue files with mkdist while still ask rollup builder of unbuild to generate jiti wrapped modules for other modules to import, test and develop?

I am currently having questions when packages have multiple entries:

  • packages/some-vitepress-plugin/src/client/index: where the entry for Vue plugin that installs and export InjectionKey for .vue SFC components. (I use Vue plugin here since VitePress requires me to register all the components for App instance of Vue). I tried to use rollup to transpile them all to dist/client/index.mjs.
    • packages/some-vitepress-plugin/src/client/components: where all .vue files live in. I tried to use mkdist to transpile them all to dist/client/components.
  • packages/some-vitepress-plugin/src/vite: where the Vite plugin to inject extra build time data (as virtual module), and transform markdown files located at. I tried to use rollup to transpile them all to dist/vite/index.mjs.

However, when working with such setup:

  1. I would encounter problems where the wrapped modules over jiti would result in Failed to resolve import error when Vite tries to resolve dependencies for the needed modules. (others have mentioned such errors in this issue https://github.com/unjs/unbuild/issues/248)
  2. I could switch to mkdist for them all where both https://github.com/wobsoriano/vue-sfc-unbuild and https://github.com/jsonleex/demo-mkdist suggests. However, according to what https://github.com/unjs/unbuild/issues/182 has explained, using mkdist as builder will never generate jiti wrapper for TypeScript modules. How can I ask mkdist to generate modules as .mjs and .js correspond to package.json? In another word, how to stub .vue modules, while generate working jiti wrapped modules?
  3. When using mkdist with rollup, even though I have set patterns and inputDir, the rollup build would still fail due to the missing modules to parse .vue modules.

I understand that I can ship and include all the .vue to npm registry in traditional project setup, since there would be any problem with dual entries for components, and the development used vitepress and vite server will never to read and resolve modules with the values in exports fields in package.json.

The workflow seems broken when it comes to monorepo, how does everyone build and stub, even develop their UI compoents in multi-entries and monorepo packages? Does anyone have examples for me to take a look at?

nekomeowww avatar Mar 23 '24 10:03 nekomeowww

https://github.com/unjs/unbuild/issues/80#issuecomment-2016442847

I finally came up an all-in-one unified solution for both developing, previewing, bundling for mixed project that has Vue components Library and Vite plugin all together. I pushed the limits even further to make it possible to allow opt-in for unocss, i18n module bundling (with i18n-ally compatiblities), check it out on our project https://github.com/nolebase/integrations for VitePress plugins.

Configure for unbuild

export default defineBuildConfig({
  entries: [
    // Thanks to https://github.com/wobsoriano/vue-sfc-unbuild
    // and https://github.com/jsonleex/demo-mkdist
    // and all the discussions in https://github.com/unjs/unbuild/issues/80
    // for the following configuration.

    // Thanks to una-ui https://github.com/una-ui/una-ui/blob/main/packages/nuxt/package.json
    // and the great examples of https://github.com/nuxt/module-builder/blob/5f34de12f934dd3c5f9b97bd919c4303736f2fc5/src/commands/build.ts#L41-L67
    // excellent explanation in unjs/unbuild https://github.com/unjs/unbuild/issues/182
    // for me to understand which entry points to use.

    { builder: 'mkdist', input: './src/client', outDir: './dist/client', pattern: ['**/*.vue'], loaders: ['vue'] },
    { builder: 'mkdist', input: './src/client', outDir: './dist/client', pattern: ['**/*.ts'], format: 'cjs', loaders: ['js'] },
    { builder: 'mkdist', input: './src/client', outDir: './dist/client', pattern: ['**/*.ts'], format: 'esm', loaders: ['js'] },
    { builder: 'rollup', input: './src/vite/index', outDir: './dist/vite' },
    { builder: 'rollup', input: './src/vite/index', outDir: './dist/vite' },
  ],
  clean: true,
  sourcemap: true,
  declaration: true,
  externals: [
    'vite',
    // builtins
    ...builtins,
  ],
  rollup: {
    emitCJS: true,
  },
})

This configuration will:

  1. Use mkdist to transpile all the sources under ./src/client to ./dist/client.
  2. Use rollup to bundle all the sources under ./src/vite to ./dist/vite, while still be able to stub by using jiti for all the sources under ./src/vite to ./dist/vite.

Configure for tsconfig.json and vite.config.(m)ts

And since the ./dist/client is bundled by file-to-file transpile, we have to configure both the tsconfig.json and vite.config.ts at the end user's side (not the "end users" who will install the released version of packages, but the VitePress docs dir or Vite's index.html located under root dir that lives in the monorepo itself).

Configure tsconfig.json

Add more paths to help tsc to redirect package resolve to the relative path:

{
  // ...
  "paths": {
      "@scope/packagename/client/*": [
        "./packages/packagename/src/client/*"
      ],
    },
  // ...
}

Configure vite.config.(m)ts

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

export default defineConfig({
  resolve: {
    alias: {
      '@scope/packagename/client': resolve(__dirname, '../packages/packagename/src/client'),
    },
  },
})

Details of package.json

{
  "name": "@scope/packagename",
  "type": "module",
  "version": "1.0.0",
  // other fields
  "sideEffects": false,
  "exports": {
    "./vite": {
      "types": "./dist/vite/index.d.ts",
      "import": "./dist/vite/index.mjs",
      "require": "./dist/vite/index.cjs"
    },
    "./client": {
      "types": "./dist/client/index.d.ts",
      "import": "./dist/client/index.mjs",
      "require": "./dist/client/index.js"
    },
  },
  "main": "./dist/vite/index.cjs",
  "module": "./dist/vite/index.mjs",
  "types": "./dist/vite/index.d.ts",
  "files": [
    "README.md",
    "dist",
    "package.json"
  ],
  "scripts": {
    "dev": "unbuild --stub",
    "stub": "unbuild --stub",
    "build": "unbuild",
  },
}

More examples can be found at the project I work on at https://github.com/nolebase/integrations

nekomeowww avatar Apr 08 '24 02:04 nekomeowww

why?

image

image

nigiwen avatar May 10 '24 07:05 nigiwen

@wen403 The error is saying that, probably, you have a reference to a dist/index.cjs in your package.json file, while the bundler generates dist/index.js

dwightjack avatar May 10 '24 08:05 dwightjack