Not so long ago I got asked at my workplace to create a Vue.js application for tourists. The requirement for the application was that it was supposed to be embeddable as a widget anywhere — using only one app.js and one app.css file — that’s how I found vue-custom-element by karol-f, which allows us to render our Vue.js application as an HTML custom element like <my-app></my-app> .

Having never done something like this before, I had a road full of Googling ahead of me — and I’m going to guide you through the process, so you don’t have to.

-Note: We’ll be working with Vue CLI 3

1. Optimize your application as an HTML custom element

This is what vue-custom-element is for. It’s a wrapper for your Vue.js application and allows you to invoke it in the source code via custom HTML element.

Imagine you initialize your application in main.js like this:

src/main.js

import Vue from 'vue'

import App from './App.vue'

import router from './router'

import store from './store/index' new Vue({

router,

store,

render: h => h(App)

}).$mount(‘#app’)

With vue-custom-element it’s almost the same

npm install vue-custom-element --save

src/main.js

import Vue from 'vue'

import App from './App.vue'

import router from './router'

import store from './store/index' import vueCustomElement from 'vue-custom-element' Vue.use(vueCustomElement) App.store = store

App.router = router

Vue.customElement('vue-widget', App)

Notice that we are not using new Vue() anymore. Calling Vue.customElement() is doing it for us — and we provide the widget name ( <vue-widget></vue-widget> in HTML) in the first argument and our App component in the second argument.

WARNING: Using a name without a dash symbol didn’t work for me.

Anything you’d like to include in new Vue() before, is supposed to be included in App as a property now, calling Vue.customElement() after all your setup is done.

2. Change your index.html

public/index.html

We should remove our <div id="app"></div> and replace it with the new <vue-widget></vue-widget> . Reload your browser tab with your app. If everything works like before, you’re all good to build!

Caution: Beware of old(er) browsers. You might need a custom polyfill in order for your HTML custom element to work. Just download it and import it in your main.js file.

npm install document-register-element --save

src/main.js

import 'document-register-element/build/document-register-element'

3. Run the build

npm run build

Building gives you your desired source — compressed, neat and small in size. But given you are developing a larger app, you may experience having dozens of .js chunk files in your dist (build) folder. And that’s so not what we wanted! We wanted a single app.js file that could be imported in other sites and not fourty-one 1–3 kB .js files..

4. Disable chunks

You can do some magic with Vue CLI 3. You can do it with the older CLI also, but it’s done in a way that won’t be shown in this article.

Create a vue.config.js file in the root of your application (next to the package.json ). This is where webpack config is written in Vue CLI 3.

We will need to install webpack to use its LimitChunkCountPlugin. We will also need to pass the config to delete split chunks.

npm install webpack --save

const webpack = require('webpack') module.exports = {

configureWebpack: {

plugins: [

new webpack.optimize.LimitChunkCountPlugin({

maxChunks: 1

})

]

},

chainWebpack:

config => {

config.optimization.delete('splitChunks')

}

}

Edit 30.03.2019 — feel free to add filenameHashing: false to the config as Maxim Manylov mentioned in the comments.

5. Run build.. again?

npm run build

If we did everything right, the build should be much better now, with only single app.js and single app.css file. Yay!

But wait. If we’re using fonts or images, they are included in the build folder ( /dist ) and bound in the style and script files. We don’t want to import all those assets in the pages where we want to embed our widget — and we don’t have to!

6. Host it and .htaccess it

Of course, you need a hosting domain for your application. This domain must include your whole built source — everything in the /dist folder. Let’s say I own a domain https://vuidget-source.danajanoskova.sk/ and a hosting for it. I’ve created a Vue.js embeddable widget, I’ve build the source and now, I’ll upload the contents of the /dist folder to my domain. All the needed assets are there and all I need to do is include my app.js and app.css file in the site where I want to display the widget.

<link href="https://vuidget-source.danajanoskova.sk/css/app.css" rel=" stylesheet" />



<vue-widget title="Vuidget live example"></vue-widget>



<script src="http://vuidget-source.danajanoskova.sk/js/app.js"></script>

However, app.js and app.css files are seeking those assets like images or fonts we’ve uploaded at our host domain ( https://vuidget-source.danajanoskova.sk/) . That’s why we have to make them accessible for external sources. I solved it by adding a .htaccess file in the root of my domain folder, next to my index.html file, with the following content:

Header add Access-Control-Allow-Origin "*"

Now my vuidget-source site is up and working like a charm, hosting the app.js and app.css files with all the assets being available for other sites.

7. Embed it

Congratulations, you’ve made it this far. All you need to do now is using the files you’ve created. Let’s say I have an another domain — https://vuidget.danajanoskova.sk/ and this site is where I want to embed my widget. All I need to do is write an index.html file in the root with a content like this

<!doctype html>

<html>

<!-- widget source css -->

<link href="

</head> widget source css https://vuidget-source.danajanoskova.sk/css/app.css " rel="stylesheet"> <body>

<vue-widget title="Vuidget live example"></vue-widget>

<script src="

</body> https://vuidget-source.danajanoskova.sk/js/app.js</a> "> </html>

and it works!