unjs / unbuild

📦 A unified JavaScript build system
MIT License
2.37k stars 92 forks source link

How to build Vue component library? #80

Open itsmnthn opened 2 years ago

itsmnthn commented 2 years ago

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'
  ]
}
jd-solanki commented 2 years ago

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

itsmnthn commented 2 years ago

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

who-jonson commented 2 years ago

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..!

itsmnthn commented 2 years ago

Still giving same error but now for templates

...
 frame: '1: <template>\n' +
    '   ^\n' +
...
itsmnthn commented 2 years ago

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

can you please share working minimal reproduction?

Digital-Coder commented 2 years ago

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

dwightjack commented 2 years ago

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... 
      )
    },
  },
})
jd-solanki commented 2 years ago

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

amery commented 2 years ago
// 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'.",

dwightjack commented 2 years ago

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

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

if (Array.isArray(options.plugins)) {
  options.plugins.push(
    // ...
  )
}
amery commented 2 years ago

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

dwightjack commented 1 year ago

@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()],
})
wobsoriano commented 1 year ago

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

Supports vue 2 and 3

lixiaofa commented 1 year ago

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 commented 1 year ago

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

wobsoriano commented 1 year ago

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

lixiaofa commented 1 year ago

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

sadeghbarati commented 1 year ago

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


Or only use mkdist

antfu:

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

lixiaofa commented 1 year ago

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

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

wobsoriano commented 1 year ago

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

sadeghbarati commented 1 year ago

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-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

lixiaofa commented 1 year ago

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

dwightjack commented 1 year ago

@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.

lixiaofa commented 1 year ago

@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

jd-solanki commented 1 year ago

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

jsonleex commented 1 year ago

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

nekomeowww commented 8 months ago

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:

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 commented 7 months ago

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

nigiwen commented 6 months ago

why?

image

image

dwightjack commented 6 months ago

@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

typed-sigterm commented 3 weeks ago

@nekomeowww @dwightjack Thanks for the amazing workaround :+1: But I have a question, is it a good practice to directly put .vue to the dist dir rather than compiling it?

messenjer commented 3 weeks ago

An article "The Simplest Method to Create a Vue.js Component Library" with unbuild https://soubiran.dev/posts/the-simplest-method-to-create-a-vue-js-component-library

dwightjack commented 3 weeks ago

@nekomeowww @dwightjack Thanks for the amazing workaround 👍 But I have a question, is it a good practice to directly put .vue to the dist dir rather than compiling it?

I think it depends on the use case you expect. If you pulbish your components as .vue files then the compilation will be performed by the end user (Vite, Nuxt, ...). Probably the main advantage is that the end result will be more tailored to the user setup. On the other hand, it might make the compilation slightly slower (depending on how many components you have) and your components might not be usable without a build tool.