EranGrin / vue-web-component-wrapper

vue3 - web component wrapper plugin
https://erangrin.github.io/vue-web-component-wrapper/
59 stars 7 forks source link
custom-elements vue3 web-component

vue-web-component-wrapper

Transforming full-fledged Vue3 applications into reusable web components

License MIT version 1.6.9 maintained yes


Introduction

vue-web-component-wrapper is a powerful Vue 3 plugin designed to transform full-fledged Vue applications into reusable web components (custom elements). These web components can be integrated into any website, enhancing flexibility and reusability.

Why Use vue-web-component-wrapper?

As of now, Vue 3 does not support the creation of full applications as web components out of the box. This plugin aims to solve this problem by providing a simple and easy-to-use solution for creating web components from Vue applications. It also provides support for Vue ecosystem plugins such as Vuex, Pinia, Vue Router, Vue I18n, and VeeValidate.

Demo

Check out these demo projects to see vue-web-component-wrapper in action:

Documentation

See the Documentation for more details.

Key Features

CSS Frameworks Examples

For more details, see the Documentation.

Installation

npm install vue-web-component-wrapper
# or
yarn add vue-web-component-wrapper
# or
pnpm add vue-web-component-wrapper

Usage

To create a web component using vue-web-component-wrapper, follow the steps below:

1. Import the Necessary Modules

In your entry file, import the required modules:

import App from './App.vue';
import tailwindStyles from './assets/tailwind.css?raw';
import { createWebHashHistory, createRouter } from 'vue-router';
import { createI18n } from 'vue-i18n';
import { createStore } from 'vuex';
import { createPinia } from 'pinia';
import { defaultRoutes } from './main.routes.js';
import { store } from './store/index.js';
import {
  defineCustomElement as VueDefineCustomElement,
  h,
  createApp,
  getCurrentInstance,
} from 'vue';
import { createWebComponent } from 'vue-web-component-wrapper';

2. Set Up the Instances and Plugins

Configure your Vuex/Pinia store, Vue Router, and other Vue plugins:

export const pluginsWrapper = {
  install(GivenVue) {
    const Vue = GivenVue;

    // Vuex
    const createdStore = createStore(store);
    Vue.use(createdStore);

    // Or Pinia
    const pinia = createPinia();
    Vue.use(pinia);

    // Vue Router
    const router = createRouter({
      history: createWebHashHistory(),
      routes: defaultRoutes,
    });
    Vue.use(router);

    // Vue I18n
    const i18n = createI18n({
      locale: 'en',
      fallbackLocale: 'en',
    });
    Vue.use(i18n);
  },
};

3. Create Your Web Component

Use createWebComponent to create your web component. Specify your root Vue component, the element name, any plugins, and CSS framework styles:

createWebComponent({
  rootComponent: App,
  elementName: 'my-web-component',
  plugins: pluginsWrapper,
  cssFrameworkStyles: tailwindStyles,
  VueDefineCustomElement,
  h,
  createApp,
  getCurrentInstance,
  disableStyleRemoval: false, // default is false
  disableShadowDOM: false,    // default is false
  replaceRootWithHost: false, // default is false
});

Options Explained

replaceRootWithHost

The replaceRootWithHost option replaces all occurrences of :root with :host in your cssFrameworkStyles. This is useful when working with CSS variables defined on :root, ensuring they are properly scoped within the Shadow DOM.

Example Usage

createWebComponent({
  rootComponent: App,
  elementName: 'my-web-component',
  plugins: pluginsWrapper,
  cssFrameworkStyles: tailwindStyles,
  VueDefineCustomElement,
  h,
  createApp,
  getCurrentInstance,
  replaceRootWithHost: true,
});

cssFrameworkStyles

The cssFrameworkStyles option imports the CSS of your CSS framework or any other global CSS styles your application needs. By setting replaceRootWithHost to true, any :root selectors in your styles will be replaced with :host, ensuring correct scoping within the web component.

4. Build Your Application

Tested bundlers to build the web-component application.

Bundler Configurations

Vite Configuration ### Vite.js Configuration Here's a sample Vite configuration. Vite.js handles asset files like `.css` and `.scss`, and media files, importing them as usual. Vue files are parsed using the official [@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue). ```javascript import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ build: { sourcemap: 'inline', }, plugins: [ vue({ customElement: true, }), ], }); ``` #### `main.js/ts` In your main file, import the CSS framework with `?inline`: ```javascript // Fonts are not loaded with ?inline; import font CSS in App.vue import style from './style.css?inline'; ``` #### `App.vue` Workaround for fonts: ```html ```
Webpack Configuration ### Webpack Configuration Here's a sample webpack configuration to handle `.vue`, `.css`, and `.scss` files: ```javascript const path = require('path'); const { VueLoaderPlugin } = require('vue-loader'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode: 'production', entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'my-web-component.js', }, module: { rules: [ { test: /\.(vue|ce\.vue)$/, loader: 'vue-loader', options: { customElement: true, }, }, { test: /\.(css|scss)$/, oneOf: [ { resourceQuery: /raw/, use: [ 'to-string-loader', 'css-loader', 'postcss-loader', { loader: 'sass-loader', options: { sassOptions: { indentedSyntax: false, }, }, }, ], }, { use: [ 'style-loader', 'css-loader', 'postcss-loader', { loader: 'sass-loader', options: { sassOptions: { indentedSyntax: false, }, }, }, ], }, ], }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'file-loader', options: { name: 'assets/[name].[hash:7].[ext]', }, }, ], }, plugins: [ new VueLoaderPlugin(), new HtmlWebpackPlugin({ template: './public/index.html', }), ], resolve: { alias: { vue$: 'vue/dist/vue.esm-bundler.js', }, extensions: ['.js', '.vue', '.json'], }, }; ``` #### `main.js/ts` Import the CSS framework with `?raw`: ```javascript import style from './style.css?raw'; ```
Vite + Rollup Configuration ### Vite + Rollup Configuration This configuration provides enhanced build options using Vite with Rollup: ```typescript import { defineConfig, UserConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; export default defineConfig(({ mode }): UserConfig => { return { esbuild: { // Remove debugger statements in production drop: mode === 'production' ? ['debugger'] : [], }, build: { emptyOutDir: true, target: 'ES2020', rollupOptions: { output: { // Maintain original file names entryFileNames: '[name].js', }, }, // Disable CSS code splitting cssCodeSplit: false, }, plugins: [ vue({ template: { compilerOptions: { // Define custom elements starting with 'app-element' isCustomElement: (tag) => tag.startsWith('app-element'), }, }, customElement: true, }), { // Hot reload fix for Vue components name: 'force-reload', handleHotUpdate({ file, server }) { if (file.endsWith('.vue')) { server.ws.send({ type: 'full-reload' }); return []; } }, }, ], }; }); ``` **Features:** - Custom element support for tags starting with 'app-element'. - Disabled CSS code splitting for better web component compatibility. - Hot reload improvements for Vue components. - Rollup output configuration to maintain file names.

Web Component Without Shadow DOM

To create a web component without Shadow DOM, set the disableShadowDOM option to true in the createWebComponent function:

createWebComponent({
  // ...other options
  disableShadowDOM: true,
});

This feature uses a patch to the Vue source code, which may lead to issues with future versions of Vue. Please report any issues in the repository.

Demo Without Shadow DOM

Demo Link

SFC as Custom Element

Enhance the functionality of Single File Components (SFC) as Custom Elements using defineCustomElement with two new features:

  1. Nested Components: Use nested components with styles, sharing base components between multiple custom elements.
  2. Shadow DOM Option: Disable Shadow DOM for the SFC custom element.

Usage

// main.js
import { defineCustomElementSFC } from 'vue-web-component-wrapper';
const MyComponentElement = defineCustomElementSFC(MyComponent, { shadowRoot: false });
customElements.define('my-component', MyComponentElement);

Demo SFC Custom Element

Demo Link

Tips

Future Plans

  1. TypeScript Support: Adding proper strict types.

Contributing

Contributions are welcome! To contribute:

Please follow the code style and conventions used in the project.

If you find a bug or have a feature request, please open an issue.

License

This project is licensed under the MIT License.