DEV Community

Nurlan TL
Nurlan TL

Posted on

Tips to create Web components using Vue 3, TS, Vite.

Recently I had a task to create a Web Component. My favorite framework is Vue 3 and I had no intentions to use another. At first glance there are a lot of videos and articles how to do it but some of them use slightly different stack and others give only part of info I needed. Finally I managed to do it and now want to share my experience.

Install Vite with Vue latest and TS support.

Create Vue SFC component to be your web component, its name should have ce.vue extension:

SampleSimple.ce.vue

We can use it inside our App.vue as any other components. And it will help us to test and debug it on developer server:

npm run dev

You implement the logic you want inside this SampleSimple.ce.vue and place any other components inside. You don't need to name child components with ce.vue extension, just usual vue extension.

Lets move to creating final web component from you vue component. First of all we need to create second main.ts file whose job is to generate final web components js file. As you can imagine its name will be:

main.ce.ts

import { defineCustomElement } from 'vue'
import SimpleSampleComponent from './SimpleSample.ce.vue'

const SimpleSample = defineCustomElement(SimpleSampleComponent)

customElements.define('simple-sample', SimpleSample)
Enter fullscreen mode Exit fullscreen mode

Our script part of package.json needs no changes:

  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "preview": "vite preview"
  }
Enter fullscreen mode Exit fullscreen mode

The most important part is vite.config.ts:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag.includes('simple-sample')
        }
      }
    })
  ],
  build: {
    lib: {
      entry: './src/main.ce.ts',
      name: 'simple-sample',
      // the proper extensions will be added
      fileName: 'simple-sample'
    }
  },
  define: {
    'process.env': process.env
  }
})
Enter fullscreen mode Exit fullscreen mode

We have changed build part, added define for process.env, and added vue compilerOptions isCustomElement to distinguish our custom element.

Thats all. Now run:

npm run build

and that will put simple-sample.js and simple-sample.umd.cjs files into dist folder.

To use our new web component you can put simple-sample.js to any place you want. Then add

<script src="path/to/simple-sample.js"></script>

to html header and use your component like that:

<simple-sample></simple-sample>

If you defined properties inside your SimpleSample.ce.vue you add properties too:

<simple-sample property1="value"></simple-sample>

What about styles? Styles that are inside SimpleSample.se.vue are included into resulting web component:

<script setup lang="ts">
...
</script>
<template>
  ...
</template>
<style>
// our web component styles
</style>
Enter fullscreen mode Exit fullscreen mode

If you import styles inside script part of the component they will be ignored by Vite build process. So the only place is styles part of SFC. Thats ok if your styles are small but if there are a lot of lines it is not comfortable to work with. So there is a solution - scss. We can use scss import and move styles to another directory and organize them in more smart manner:

<script setup lang="ts">
...
</script>
<template>
  ...
</template>
<style lang="scss">
@import '../scss/file1';
@import '../scss/file2';
@import '../scss/file3';
</style>
Enter fullscreen mode Exit fullscreen mode

Thats all. I hope this info will be helpfull.

Top comments (15)

Collapse
 
rickgregg profile image
Rick Gregg • Edited

Excellent, well written post. You're ability to provide a simple, well thought out example with step by step explanations leaving no detail out AND especially covering the elusive build and deployment process for Vue3 Web Components lacking in all other hard to find posts on this subject is to be commended. Thank you.

Collapse
 
ericuldall profile image
Eric Uldall

Not sure why you've decided to use:

define: {
    'process.env': process.env
  }
Enter fullscreen mode Exit fullscreen mode

but I would highly recommend against that as it will result in your users entire env output being placed into the resulting .js files created at build time. This could expose private information.

Collapse
 
nurlan_tl profile image
Nurlan TL

Yes you are right in case one have sensitive info in the env file more granular approach is needed. Nice comment.

I put it like that because usually env files of js projects contain no sensitive info contrary to env files of backend applications.

Collapse
 
enrichit profile image
Richard Vanbergen

One important thing to note. The SFC component you want to export needs to have either:

<script>
export default {}
</script>
Enter fullscreen mode Exit fullscreen mode

Or

<script setup>
</script>
Enter fullscreen mode Exit fullscreen mode

Otherwise it wont be able to find the output TS file and will fail to build.

Collapse
 
nurlan_tl profile image
Nurlan TL

Right you are.

Collapse
 
kcko profile image
Kcko

Nice approach, but what a final size of our new web builded component?
It includes compiled Vue and final size is too huge (my vue hello-world component has about 1kB) and after build a and convert to web component has about 100kBs (because vue is included).

How to solve it?

Collapse
 
ruthrapathyr profile image
R.Ruthrapathy

in vite.config.js file under rollupOptions add "external: ["vue"]" this will not include vue in your build file.

Collapse
 
notpatreese profile image
Pat

I know im suuuuper late to the party but i figured i'd ask a quick question in the event anyone is still paying attention to this.

(also thanks for writing this, its great)

  • my application builds and runs properly. everything looks good when testing locally
  • ive imported the component into a simple html file. it doesnt 404 and following the link shows what was generated in dist/web-component.js during build
  • however, the <web-component></web-component> DOM object is empty and has no functionality

i added some more info to stack overflow as it seemed appropriate

thanks!

Collapse
 
nurlan_tl profile image
Nurlan TL

Your configs are fine.

Folder dist is usually used for build. Normally you put components to public/js. Check another js files to test your path is working. And also use your component in another project not in the one you building it.

Collapse
 
herpster profile image
herpster

Thanks for this article however how do I build multiple components?

Collapse
 
nurlan_tl profile image
Nurlan TL

What do you mean?

Collapse
 
herpster profile image
herpster • Edited

I have a folder ./src/web-components with multiple components inside i.e. prefix-component-a.vue, prefix-component-b.vue and I want to build both, running "npm run build". How do I nee to modify my vite.config file etc. in order to build multiple components at the same time?

vite.config.mjs

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag.includes('prefix-'),
        },
      },
    }),
  ],
  build: {
    lib: {
      entry: './src/component-a.ce.ts',
      name: 'prefix-component-a',
      fileName: 'prefix-component-a',
      formats: ['es'],
    },
  },
  define: {
    'process.env': process.env,
  },
  resolve: {
    alias: {
      vue: 'vue/dist/vue.esm-bundler.js',
      '@': resolve(__dirname, 'src'),
      composables: resolve(__dirname, 'composables'),
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

component-a.ce.ts

import { defineCustomElement } from 'vue'
import prefixComponentA from './components/ds-web-components/prefix-component-a.ce.vue'

const componentA = defineCustomElement(prefixComponentA)

customElements.define('prefix-component-a', componentA)

// register global typings
declare module 'vue' {
  export interface GlobalComponents {
    'prefix-component-a': typeof componentA
  }
}
Enter fullscreen mode Exit fullscreen mode

I tried the following:
vite.config.mjs

[...]
  build: {
    emptyOutDir: true,
    outDir: 'dist',
    lib: {
      entry: {
        componentA: './src/componentA.ce.ts',
        componentB: './src/componentB.ce.ts',
      },
      formats: ['es'],
    },
}
[...]
Enter fullscreen mode Exit fullscreen mode

However this is generating a "_plugin-vue_export-helper-DQzczt30.js"
which is then imported in the generated .js files which is not what I need.

So any advice on that?

Thread Thread
 
nurlan_tl profile image
Nurlan TL

My guess is that is impossible. I would simply create multiple projects with one component in each. But who knows, maybe you will succeed. Anyway you have now better expertise in that matter than me.

Collapse
 
amirtbi profile image
Amirhosein

is it possible to use vuetify library inside the webcomponent? I tried , but vuetify instance could not be found!

Collapse
 
nurlan_tl profile image
Nurlan TL

I can't help. Never used Vuetify.