vue-cli icon indicating copy to clipboard operation
vue-cli copied to clipboard

Vue 3 Web Component target

Open ivansieder opened this issue 4 years ago • 42 comments

What problem does this feature solve?

As a vue-cli and Vue 3 user I would like to be able to build and deploy a package as a web compomponent, so that it can be built and deployed in the same way as it's currently possible with vue-cli and Vue 2

What does the proposed API look like?

i.e. vue-cli-service build --target wc --name my-web-component-name --inline-vue

ivansieder avatar Nov 04 '20 13:11 ivansieder

Hi,

I have the same require, i would like to be able to build and deploy a package as a web compomponent with vue3.

image

Is there a date to support of the web component with vue3?

Thks

y-sanchez avatar Nov 25 '20 12:11 y-sanchez

@y-sanchez we for our part didn't necessary require web components for now, so we've been ok with custom elements, too. So for now we've used custom elements, following is a (shortened) sample code that we use:

import { createApp, watch } from "vue";

import App from "./App.vue";

class CustomElement extends HTMLElement {
  constructor() {
    super();
  }

  connectedCallback() {
    const options = typeof App === "function" ? App.options : App;
    const propsList: string[] = Array.isArray(options.props) ? options.props : Object.keys(options.props || {});

    const props: { [index: string]: string} = {};
    // Validate, if all props are present
    for (const prop of propsList) {
      const propValue = process.env.NODE_ENV === "development" ? process.env[`VUE_APP_${prop.toUpperCase()}`] : this.attributes.getNamedItem(prop)?.value;

      if (!propValue) {
        console.error(`Missing attribute ${prop}`);
        return;
      }

      props[prop] = propValue;
    }

    const app = createApp(App, props);

    const wrapper = document.createElement("div");
    app.mount(wrapper);

    this.appendChild(wrapper.children[0]);
  }
}

window.customElements.define("simedia-cloud-checkin", CustomElement);

ivansieder avatar Nov 28 '20 13:11 ivansieder

@ivansieder, Thank you for your sample. I will try this.

y-sanchez avatar Nov 28 '20 21:11 y-sanchez

Does someone have proof-of-concept repo or codesandbox for a workaround on this? @y-sanchez @ivansieder Building components and publishing them in Vue 3 would be awesome.

dekadentno avatar Feb 18 '21 14:02 dekadentno

@dekadentno we've worked around by just using custom elements (instead of actually web components) with the above little wrapper: https://github.com/vuejs/vue-cli/issues/6033#issuecomment-735229344

ivansieder avatar Feb 18 '21 14:02 ivansieder

I am using defineComponent for every component the library has and then I export all in the index.ts file

File: DateRangePicker.vue
export default defineComponent({
  name: 'DateRangePicker',
...
});

File: index.ts
import DateRangePicker from '@/components/DateRangePicker.vue';
export{
DateRangePicker
....
};

Except from the triviality of having to export the components in the index.ts file, the lib is working great.

Is there anything that could go wrong with this approach? It's my first lib and I am not sure if this is the best aproach.

melenaos avatar Feb 18 '21 18:02 melenaos

Thank you for your quick replies @melenaos @ivansieder

dekadentno avatar Feb 18 '21 21:02 dekadentno

Please let me know when this will be supported

ranjanngc avatar Mar 15 '21 13:03 ranjanngc

I totally need this feature, I am trying to export some components to let users embed them into their website by just importing them via script tag. If anyone has a workaround on how to achieve this, would love to know. Thanks in advance.

Previously achived with Vue2 but needed to upgrade to Vue3, and it broke everything.

ricvillagrana avatar Apr 28 '21 22:04 ricvillagrana

@ricvillagrana if customElements is enough for you, we're currently using this workaround: https://github.com/vuejs/vue-cli/issues/6033#issuecomment-735229344

But please be aware that this implementation ist not WebComponent-compliant. It's just one part of the WebComponent spec (customElements)

ivansieder avatar Apr 29 '21 05:04 ivansieder

@ivansieder Thank you for this workaround. If this is how we define a CustomElement, then how to build it, with the lib as --target flag? Ex. vue-cli-service build --target lib?

mrfy avatar Jun 06 '21 13:06 mrfy

@mrfy yes, we're exporting the package as library. If you need/want to include Vue itself in the package (it gets externalized by default), you need to change that in your config, too.

ivansieder avatar Jun 07 '21 11:06 ivansieder

Does anyone know if there is a roadmap for future Vue development which might give some insight into when exporting a Vue app as a web component will be implemented?

Unfortunately upgrading to Vue 3 is not an option until this is available as it is with Vue 2.

ps. Thanks to all the Vue contributors for continually making Vue amazing.

bdelate avatar Jun 15 '21 12:06 bdelate

Anyone in dev to know when this npm run build --target wc is planned out for vue3?

dsldiesel avatar Aug 27 '21 10:08 dsldiesel

Vue 3.2 added support for compiling WebComponents, but the implementation is different from Vue 2

https://v3.vuejs.org/guide/web-components.html

vikaspotluri123 avatar Aug 27 '21 15:08 vikaspotluri123

Thanks @vikaspotluri123 Although this 3.2 added feature is published, I can't see any reason to believe they won't have soon also the npm run build --target wc in the CLI. The error message today is 'it is not implemented YET'. The solution I seek, always existing in vue2, does not need a single line of coding or preparation. It takes a SFC .vue as input and does all the job: npm run build -- --target wc --name ui-leafletmap 'src/components/organisms/ui-leaflet-map/index.vue'

That's all I ask :)

dsldiesel avatar Aug 30 '21 08:08 dsldiesel

Is it available in Vue-CLI 5 Beta ? thank you to vue3 for making the creation of web component even easier but if you can't compile into a custom element target with Vue cli it is currently blocking

Tibze avatar Sep 16 '21 09:09 Tibze

For anybody that is having a bit of a hard time getting ivansieders neat and great fix to work, here is a little bit of a helping hand. I am currently using vue 3.2, with typescript and using the vue-class-component. With the vue-class-component it is not possible to use ivansieders fix in its entirity, and the options (especially the props) are not defined explicitly on the components.

Besides my main.ts file, I have created the file export-wp-plugin.ts (as I am exporting the component as a customElement to become part of a wordpress plugin). For clarification, this exports a component (in my case a subcomponent), not the entire app, as a customElement.

My code now looks like this

import { createApp } from 'vue';
import router from './router';
import store from '@/store';
import App from './components/global/Auth/Auth.vue';

class CustomElement extends HTMLElement {
    constructor() {
        super();
    }

    connectedCallback() {
        // Manually added propslist, found in 
        // ./components/global/Auth/Auth.vue, under @options decorator, .props
        const propsList: string[] = ['logoPath', 'plan_id'];

        const props: { [index: string]: string } = {};
        // Validate, if all props are present
        for (const prop of propsList) {
            const propValue = process.env.NODE_ENV === 'development' ? process.env[`VUE_APP_${prop.toUpperCase()}`] : this.attributes.getNamedItem(prop)?.value;

            if (!propValue) {
                console.error(`Missing attribute ${prop}`);
                return;
            }

            props[prop] = propValue;
        }

        const app = createApp(App, props);
        app.use(store);
        app.use(router);
 
        const wrapper = document.createElement('div');
        app.mount(wrapper);

        this.appendChild(wrapper.children[0]);
    }
}

window.customElements.define('wp-plugin-auth', CustomElement);

The key differences is that I create my app with createApp and then load all the necessary stuff, like app.use(store) that I had defined in my main.ts file before (as this is not loaded in by this script). The other difference is that I have written my props explicitly, like so: const propsList: string[] = ['logoPath', 'plan_id']; instead of getting them from the file (which is now, to my knowledge, impossible with the vue-class-component).

I can build this by running: vue-cli-service build --target lib --inline-vue ./src/export-wp-plugin.ts Now, in my dist folder, I can simply add the following index.html file (derived from the demo.html in the same folder):

<meta charset="utf-8">
<title>auth demo</title>
<script src="./auth.umd.js"></script>

<link rel="stylesheet" href="./auth.css">
<wp-plugin-auth logoPath="myLogoPath.png" plan_id="testplan"></wp-plugin-auth>

I hope this can be of help to somebody. sadly, I could not get proper WC working with vue 3.2 yet :(

TobiasFP avatar Dec 15 '21 10:12 TobiasFP

Okay I'm confused - so I jumped onto vue3 / typescript / composition API / Web Component band wagon after reading multiple blogs about developing web components with the Vue and also read https://cli.vuejs.org/guide/build-targets.html#web-component

I have my tiny web component running in dev mode on my local and now I'm finding out that it's not possible to build it with the 'wc' target? Or am I doing something wrong ?

Why this is not mentioned anywhere in the documentation? Why we are saying Vue 3.2 release has support for defineCustomElement when (if I understand correctly) is not possible to build it?

Sorry but I'm completely confused now...

nztomas avatar Jan 02 '22 10:01 nztomas

I'm not sure we are looking at this correctly. Do we even NEED a web component build target?

The purpose of defineCustomElement appears to be to create a registrable web component. I converted my main.ts to look like this:

(notice ALL the Vue bootstrapping is gone)

import { defineCustomElement } from 'vue';
import SwFullName from './components/SwFullName.ce.vue';

const FullNameElement = defineCustomElement(SwFullName);
customElements.define('sw-full-name', FullNameElement);

And my public/index.html is now:

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div>
        <sw-full-name></sw-full-name>
    </div>
    <!-- built files will be auto injected -->
  </body>
</html>

This works GREAT in both npm run serve AND npm run build.

dparish avatar Jan 11 '22 16:01 dparish

Proper building as WC is definitely needed asap for Vue 3 adoption.

leonheess avatar Jan 13 '22 23:01 leonheess

This works GREAT in both npm run serve AND npm run build.

I thought I can get arround that web component error as well by using custom element but as soon as you want to build something bigger you're going to come accross:

  • vuejs/core#4662

Makoehle avatar Jan 18 '22 22:01 Makoehle

@dparish Follow up question though: If you simply run build, it would build a whole vue application correct? Including the index.html etc.

Isn't the idea, to get the webcomponent only (including everything that is needed for the webcomponent to function)? I mean the idea is usually to publish the webcomponent somewhere, for other to use with minimal integration effort.

Or in other words: How do I use the result of the build as a webcomponent?

telion2 avatar Jan 28 '22 15:01 telion2

Also, what is the status? Is it work in Progress? Blocked by something? Is there some estimate when it will be implemented?

telion2 avatar Jan 28 '22 15:01 telion2

@telion2

It will generate the .js file you need to include to run your web component. I modified my index.html to use my web component tags instead of bootstrap the vue app and it just worked.

There should be enough in my example above for you to be able to bootstrap something on your own. If there's not let me know and I'll see what I can do.

dparish avatar Jan 28 '22 15:01 dparish

@dparish Well npm run serve works for me too. But if I run the normal build then I get Screenshot from 2022-01-28 16-35-55 Say I publish that to npm and hit npm install on it: How would someone use it? I admit I am somewhat new to WC, but I would expect:

  1. that the user of the WC has to add <sw-full-name></sw-full-name> somewhere
  2. that the user of the WC has to import some js somewhere. This is where I got lost. One way would be to do something like this: <script src="pathOrLinkToWC" /> or something like import swFullName from "swFullName"; //and continue integrating the wc depending on the framework, doesn't have to be Vue

Edit: Upon Rereading the Docs: Screenshot from 2022-01-28 17-02-56

So the main question is the part with <script src="path/to/my-element.js"></script> as I don't know what the user should provide into 'src'.

telion2 avatar Jan 28 '22 15:01 telion2

Ok I managed to pull it of somehow:

so my final command is: "build:wc": "vue-cli-service build --target lib --name myElement src/main.ts --inline-vue", This results into this dist folder: Screenshot from 2022-01-28 18-18-56

Now the demo was the part I was interested in:

<!DOCTYPE HTML>
<meta charset="utf-8" /> 
<title>myElement demo</title>
<script src="./myElement.umd.js"></script>
<link rel="stylesheet" href="./myElement.css" />
<my-element></my-element>
<script>
  console.log(myElement);
</script>

However, I had to manually add the line <my-element></my-element>

//main.ts
import { defineCustomElement } from "vue";
import App from "./App.ce.vue";              

customElements.define("my-element", defineCustomElement(App));

To get around https://github.com/vuejs/core/issues/4662 and while I was at dealing with https://stackoverflow.com/questions/70878556/how-do-i-use-primevue-in-customelements as well, I did this:

// App.ce.vue 
<template>
  <someOtherComponent msg="name" />
</template>

<script lang="ts">
import { defineComponent } from "vue";

import someOtherComponent from "@/components/someOtherComponent.ce.vue";
export default defineComponent({
  name: "my-element",    //I think this should be the same string as the one used in customElements.define() but not sure
  components: { someOtherComponent },
});
</script>

<style>
@import url("https://unpkg.com/primevue/resources/primevue.min.css");
@import url("https://unpkg.com/[email protected]/primeflex.css");
@import url("https://unpkg.com/primevue/resources/themes/tailwind-light/theme.css");
</style>
<style lang="scss" src="@/assets/scss/globals.scss"></style>
/*//globals.scss*/
/* Global Styles here:  */
.someClass { color: red; }
/* Component Style should be imported here till https://github.com/vuejs/core/issues/4662 is resolved */
@import "@/components/someOtherComponent.scss";

Apparently, there are Issues with scoped (s)css so I added them on the "root" wc component. Then I created a global .scss file where I instruct everyone in the team to import the scss code from their child components as shown. Additionally, they should add them as they normally would. This way, when the styles Issue is resolved the workaround can be deleted rather easily. However the team has to add a prefix to each css rule. e.g. `someOtherComponent-colorText{ color: blue; }

//vue.config.js
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
  transpileDependencies: true,
  chainWebpack: (config) => {
    config.module
      .rule("vue")
      .use("vue-loader")
      .tap((options) => {
        options.customElement = true;
        return options;
      });
  },
});

I'm am not sure if the change in vue.config.js is necessary, but I just report what I changed in my project to get where I am. So for anyone trying to build WC with Vue3: Its possible, but wait until they fix it properly, if you can.

telion2 avatar Jan 28 '22 17:01 telion2

wait until they fix it properly, if you can.

@telion2, that's exactly the conclusion I came to. Do you think it will be fixed?

leonheess avatar Jan 31 '22 14:01 leonheess

@leonheess I have no Idea. I am just a Developer of which a client asked to create a Web Component. After Planning, Comparing SPA-Frameworks, Reading Documentation and evaluating multiple aspects of a project, we decided to use Vue 3, just to find out, it only works with serious research and workarounds. That is not to take away from the otherwise superb library and Framework, that Vue and Vue CLI usually is. So me sounding rather annoyed about the documentation should not overshadow the fact that I see great value and quality in Vue 3 and Vue CLI.

The Problem with Vue 2 is that you are forced to use Libraries that don't look like they are actively maintained:

  • "@vue/web-component-wrapper" last commit 12 months old
  • vue-custom-element last commit 11 months old.

Meaning, that for a new project you risk the lack of support or fixes in that area.

telion2 avatar Jan 31 '22 16:01 telion2

@sodatea do you know the current progress of this feature? it will be present in vue-cli 5 ?

tunessofia avatar Feb 18 '22 12:02 tunessofia