Bug 37911: (follow-up) Update to vue ^3.5.4, use vue's web component implementation instead

- This would require us to update vue to a new minor version.
	- We could then use the defineCustomElement function with the shadowRoot option set to false (this is important).
- The current implementation in islands.ts is naive and would result in an intolerable bundle size.
	- The right way to do code splitting here is to use dynamic imports.

Signed-off-by: Matt Blenkinsop <matt.blenkinsop@ptfs-europe.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
This commit is contained in:
Paul Derscheid 2024-09-13 01:38:35 +02:00 committed by Katrin Fischer
parent b2459bb9fc
commit 4c40cf8f9f
No known key found for this signature in database
GPG Key ID: 0EF6E2C03357A834
5 changed files with 27 additions and 96 deletions

View File

@ -13,7 +13,7 @@
[% END %]</title
>
[% Asset.css("css/mainpage.css") | $raw %]
[% Asset.js("js/vue/dist/islands.js") | $raw %]
[% Asset.js("js/vue/dist/islands.js", "defer" => 1, async => "1") | $raw %]
[% INCLUDE 'doc-head-close.inc' %]
</head>
@ -335,7 +335,7 @@
<div class="row w-25 m-auto p-4 border border-4 border-warning br-4 rounded text-center">
<h1>🚧 Static Page with Vue Components 🚧</h1>
<div id="hello-islands" data-component="HelloIslands" data-props='{"message": "Hello from props!"}'></div>
<hello-islands message="This is an attribute on the web component, that's bound to a prop in the Vue component. Cool, right?" color="dodgerblue"></hello-islands>
</div>
<!-- /.row -->
@ -348,7 +348,8 @@
});
setTimeout(() => {
document.getElementById("hello-islands").dataset.props = JSON.stringify({ message: "This is a delayed message! Changes to data-props currently unfortunately reset the internal state." });
const [firstHelloIslandsElement] = document.getElementsByTagName("hello-islands");
firstHelloIslandsElement.message = "This is a delayed update to the message attribute of the web commponent.";
}, 2000);
});
</script>

View File

@ -1,5 +1,5 @@
<template>
<h2>Hello from Islands!</h2>
<h2 :style="{ color }">Hello from Islands!</h2>
<p>This component is rendered as an island in a static HTML page.</p>
<!-- Display message prop -->
@ -20,7 +20,11 @@ export default {
props: {
message: {
type: String,
default: "",
default: "No content",
},
color: {
type: String,
default: "crimson",
},
},
data() {

View File

@ -1,93 +1,20 @@
import { createApp, Component, App } from "vue";
import { Component, defineCustomElement } from "vue";
import HelloIslands from "../components/HelloIslands.vue";
/**
* A registry for Vue components.
* @type {Map<string, Component>}
* @type {Map<string, string>}
*/
export const componentRegistry: Map<string, Component> = new Map<
string,
Component
>();
export const componentRegistry: Map<string, () => Promise<Component>> = new Map(
[["hello-islands", HelloIslands]]
);
/**
* Registers a Vue component with a name.
* @param {string} name - The name of the component.
* @param {Component} component - The Vue component to register.
* @returns {void}
*/
export function registerComponent(name: string, component: Component): void {
componentRegistry.set(name, component);
}
/**
* Mounts Vue components to DOM elements based on the `data-component` attribute.
* Components are created with props parsed from the `data-props` attribute.
* Watches for changes in props and updates the component accordingly.
* @returns {void}
*/
export function mountComponents(): void {
console.log("Mounting components");
const elements: NodeListOf<Element> =
document.querySelectorAll("[data-component]");
elements.forEach((element: Element) => {
const componentName: string | null =
element.getAttribute("data-component");
if (!componentName) {
console.warn("No data-component attribute found.");
return;
}
const component: Component | undefined =
componentRegistry.get(componentName);
if (!component) {
console.warn(`Component ${componentName} not found.`);
return;
}
const props: string | null = element.getAttribute("data-props");
const parsedProps: Record<string, any> = props ? JSON.parse(props) : {};
// Create and mount the Vue component
const app: App = createApp(component, parsedProps);
app.mount(element);
// Watch for updates to props
watchProps(element, app, component);
// Register and define custom elements
window.requestIdleCallback(() => {
componentRegistry.forEach((component, name) => {
customElements.define(
name,
defineCustomElement(component as any, { shadowRoot: false })
);
});
}
/**
* Watches for changes in props and updates the component accordingly.
* @param {Element} element - The DOM element where the component is mounted.
* @param {App} app - The Vue application instance.
* @param {Component} component - The Vue component.
* @returns {void}
*/
function watchProps(element: Element, app: App, component: Component): void {
const propsAttr: string | null = element.getAttribute("data-props");
let prevProps: Record<string, any> = propsAttr ? JSON.parse(propsAttr) : {};
const observer = new MutationObserver(() => {
const newPropsAttr: string | null = element.getAttribute("data-props");
if (newPropsAttr) {
const newProps: Record<string, any> = JSON.parse(newPropsAttr);
if (JSON.stringify(newProps) !== JSON.stringify(prevProps)) {
prevProps = newProps;
app.unmount(); // Unmount existing component
createApp(component, newProps).mount(element); // Mount with new props
}
}
});
observer.observe(element, {
attributes: true,
attributeFilter: ["data-props"],
});
}
import HelloIslands from "../components/HelloIslands.vue";
registerComponent("HelloIslands", HelloIslands);
// Automatically mount components when the DOM is fully loaded
document.addEventListener("DOMContentLoaded", mountComponents);
});

View File

@ -38,7 +38,7 @@
"pinia": "^2.0.13",
"sass": "^1.58.1",
"style-loader": "^3.3.1",
"vue": "^3.2.31",
"vue": "^3.5.4",
"vue-flatpickr-component": "^9",
"vue-router": "^4.0.14",
"vue-select": "4.0.0-beta.3",
@ -69,8 +69,6 @@
"author": "",
"license": "GPL-3.0",
"devDependencies": {
"@babel/core": "^7.26.8",
"@koha-community/prettier-plugin-template-toolkit": "^2.0.2",
"@rspack/cli": "^1.0.1",
"@rspack/core": "^1.0.1",
"@vue/compiler-sfc": "^3.2.31",
@ -101,4 +99,4 @@
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4"
}
}
}

View File

@ -88,6 +88,7 @@ module.exports = {
new rspack.DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false,
}),
],
externals: {