@sahil1711 What have you tried?
How to create an vue embeddable widget ?
I was stuck with creating an embeddable widget using Vue, Laravel , vite , tailwind.
@martinbean Hey martin, my stack is vue laravel and vite and inertia. For creating the web component , here is what I have done. I created a new ts file other than app.ts for the component and also updated vite config ts for exporting that bundle. Here is my vite config.
import vue from "@vitejs/plugin-vue";
import tsconfigPaths from "vite-tsconfig-paths";
import path from 'path';
import { fileURLToPath, URL } from 'node:url'
import laravel from "laravel-vite-plugin";
const __dirname = fileURLToPath(new URL('.', import.meta.url));
export default defineConfig({
server: {
hmr: {
host: "localhost",
},
},
plugins: [
tsconfigPaths(),
laravel({
input: "resources/js/app.ts",
refresh: true,
}),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
compilerOptions: {
isCustomElement: tag => tag.startsWith('sf-')
},
customElement:true
},
}),
],
build: {
lib: {
entry: path.resolve(__dirname, 'src/app.ts'),
name: 'BoardView',
formats: ['es', 'umd'],
fileName: (format) => `board-view.${format}.js`,
target:"esnext",
minify: false
},
rollupOptions: {
// Externalize deps that shouldn't be bundled into the library.
external: ['vue'],
output: {
// Provide global variables to use in the UMD build for externalized deps.
globals: {
vue: 'Vue'
}
}
}
},
optimizeDeps: {
include: ['vue']
},
});
I am getting styles etc in the component since I have scoped it , however as mentioned , my current site is giving me different errors. Here's is the link : https://kanbancast.com/. It's an app and I wanted to export this for my users as a functionality to embed in their site.
Currently i am getting these error. Uncaught TypeError: Cannot read properties of undefined (reading 'ref') at board-view.umd.js:81:24867 at board-view.umd.js:1:181 at board-view.umd.js:1:193 kanbancast.com/:1 Uncaught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".
@sahil1711 Did you import ref from vue ?
import { ref } from 'vue';
Can you also share your app.ts file
@gych Here's the app.ts
import "./bootstrap";
import "../css/app.css";
import {type Component, DefineComponent, createApp, h, ref} from "vue";
import {createInertiaApp, Link, router} from "@inertiajs/vue3";
import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";
import { ZiggyVue } from "../../vendor/tightenco/ziggy/dist";
import EmptyComponent from "@/Jetstream/EmptyComponent.vue";
import { bootstrapVueApp } from './bootstrap';
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
const appName = import.meta.env.VITE_APP_NAME || "KanbanCast";
const initialProps = {
projectId: "1",
statuses: [
{
"id": 1,
"title": "Pending",
"slug": "pending",
"order": 3,
"tasks": []
},
{
"id": 2,
"title": "To Do",
"slug": "to-do",
"order": 1,
"tasks": [
{
"id": 1,
"title": "Task 1",
"description": "Description of Task 1",
"order": 1,
"votes":1,
"status_id": 1
},
{
"id": 2,
"title": "Task 2",
"description": "Description of Task 2",
"order": 2,
"votes":1,
"status_id": 1
}
]
},
]
};
const customLink: Component = {
name: 'CustomLink',
props: ['item'],
render() {
return h(this.item.href ? Link : EmptyComponent, {
class: window.location.href.startsWith(this.item.href) && 'vsm--link_active',
}, this.$slots.default);
},
mounted: function () {
this.onRouteChange();
router.on('start', () => {
this.onRouteChange();
});
},
inject: ['onRouteChange']
};
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) =>
resolvePageComponent(
`./Pages/${name}.vue`,
import.meta.glob<DefineComponent>("./Pages/**/*.vue")
),
setup({ el, App, props, plugin }) {
const app = createApp({ render: () => h(App, props) });
app.component("CustomLink", customLink).use(plugin)
.use(ZiggyVue, (window as Window).Ziggy)
.mount(el);
return app;
},
progress: {
color: "#4B5563",
showSpinner: true,
includeCSS: true,
},
}).then();
document.addEventListener('DOMContentLoaded', () => {
bootstrapVueApp('board-view', initialProps);
});
The bootstrap function comes from bootstrap.ts
import BoardView from '@/components/SFBoardView.ce.vue';
export function bootstrapVueApp(elementId: string, props: any) {
// Define the custom element from the Vue component
const BoardViewElement = defineCustomElement(BoardView);
// Register the custom element if it's not already registered
if (!customElements.get(elementId)) {
customElements.define(elementId, BoardViewElement);
}
// Create an instance of the custom element
const elementInstance = new BoardViewElement(props);
// Append the new custom element instance to the document body or another container
document.body.appendChild(elementInstance);
}
Creating an embeddable Vue widget that can be integrated into various websites involves several steps, particularly when using a stack that includes Vue, Laravel, Vite, and Tailwind CSS. Below, I'll outline a basic approach to building such a widget.
Step 1: Set Up Your Project
First, ensure you have Node.js, npm, and Composer installed on your machine. Then, create a new Laravel project and set up Vue and Vite within this project.
composer create-project laravel/laravel vue-widget
cd vue-widget
npm install
npm install vue@next vue-loader@next vite tailwindcss --save-dev
Set up Tailwind CSS by following the official documentation, which typically involves creating a tailwind.config.js and adding Tailwind to your CSS.
Step 2: Configure Vite
Create a vite.config.js file in your project root if it doesn't already exist, and configure it to work with Vue:
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
build: {
outDir: 'path/to/public/widget',
lib: {
entry: 'resources/js/widget.js',
name: 'MyWidget',
formats: ['umd'],
fileName: (format) => `my-widget.${format}.js`
}
}
});
Step 3: Create Your Vue Component
Create a new Vue component that will act as your widget. For example, create resources/js/Widget.vue:
<template>
<div class="widget">
<h1 class="text-2xl font-bold">Hello from My Widget!</h1>
</div>
</template>
<script>
export default {
name: 'Widget'
}
</script>
<style scoped>
.widget {
padding: 1rem;
background-color: #f0f0f0;
border: 1px solid #ccc;
}
</style>
Step 4: Create an Entry Point
Create an entry point for your widget in resources/js/widget.js:
import { createApp } from 'vue';
import Widget from './Widget.vue';
const mountWidget = (selector) => {
const app = createApp(Widget);
app.mount(selector);
};
window.mountWidget = mountWidget;
Step 5: Build the Widget
Run Vite to build your widget:
npm run build
Step 6: Embedding the Widget
To embed the widget into any HTML page, include the script and initialize the widget:
<div id="my-widget"></div>
<script src="http://your-laravel-app-url/path/to/public/widget/my-widget.umd.js"></script>
<script>
mountWidget('#my-widget');
</script>
Step 7: Serve and Test
Make sure your Laravel application is accessible from the URL you used in the script tag, and test the widget in different environments.
This basic example sets up a simple Vue widget that can be embedded into any site. You can expand upon this by adding more complex interactions, styles, and functionalities according to your needs.
Please or to participate in this conversation.