Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to build Vue component library? #80

Open
itsmnthn opened this issue May 17, 2022 · 33 comments
Open

How to build Vue component library? #80

itsmnthn opened this issue May 17, 2022 · 33 comments
Labels
discussion documentation Improvements or additions to documentation question Further information is requested

Comments

@itsmnthn
Copy link

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

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

@itsmnthn
Copy link
Author

itsmnthn commented May 30, 2022

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

who-jonson commented Jun 9, 2022

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
Copy link
Author

itsmnthn commented Jun 9, 2022

Still giving same error but now for templates

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

@itsmnthn
Copy link
Author

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

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
Copy link
Contributor

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

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

amery commented Nov 24, 2022

// 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
Copy link
Contributor

@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
Copy link

amery commented Nov 25, 2022

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
Copy link
Contributor

@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
Copy link

wobsoriano commented Dec 30, 2022

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

Supports vue 2 and 3

@lixiaofa
Copy link

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

lixiaofa commented May 29, 2023

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

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

@lixiaofa
Copy link

lixiaofa commented May 29, 2023

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

sadeghbarati commented May 29, 2023

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

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

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

@sadeghbarati
Copy link

sadeghbarati commented May 29, 2023

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

@lixiaofa
Copy link

@dwightjack
Copy link
Contributor

@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
Copy link

lixiaofa commented May 30, 2023

@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

@pi0 pi0 added documentation Improvements or additions to documentation question Further information is requested discussion labels Jul 18, 2023
@jd-solanki
Copy link

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

@jsonleex
Copy link

jsonleex commented Sep 8, 2023

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

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 Failed to resolve import #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 Error when building in stub mode #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
Copy link

nekomeowww commented Apr 8, 2024

#80 (comment)

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

nigiwen commented May 10, 2024

why?

image

image

@dwightjack
Copy link
Contributor

@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
Copy link

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

@messenjer
Copy link

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
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion documentation Improvements or additions to documentation question Further information is requested
Projects
None yet
Development

No branches or pull requests