Bugzilla – Attachment 172965 Details for
Bug 37911
Prototype vue islands within static pages
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 37911: Prototype vue islands within static pages
Bug-37911-Prototype-vue-islands-within-static-page.patch (text/plain), 7.89 KB, created by
Matt Blenkinsop
on 2024-10-18 12:07:04 UTC
(
hide
)
Description:
Bug 37911: Prototype vue islands within static pages
Filename:
MIME Type:
Creator:
Matt Blenkinsop
Created:
2024-10-18 12:07:04 UTC
Size:
7.89 KB
patch
obsolete
>From 39bae7201d7e22128ea12420d6088194993987e8 Mon Sep 17 00:00:00 2001 >From: Paul Derscheid <paul.derscheid@lmscloud.de> >Date: Thu, 12 Sep 2024 19:12:31 +0200 >Subject: [PATCH] Bug 37911: Prototype vue islands within static pages > >This is pretty bare bones, but might still be interesting to take a look at. > >Signed-off-by: Matt Blenkinsop <matt.blenkinsop@ptfs-europe.com> >--- > .../prog/en/modules/intranet-main.tt | 10 ++ > .../prog/js/vue/components/HelloIslands.vue | 37 ++++++++ > .../prog/js/vue/modules/islands.ts | 93 +++++++++++++++++++ > rspack.config.js | 18 ++++ > 4 files changed, 158 insertions(+) > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/HelloIslands.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/modules/islands.ts > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt >index 65d793f3190..0ef4b492172 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt >@@ -11,6 +11,7 @@ > [% t("Koha staff interface") | html %] > [% END %]</title> > [% Asset.css("css/mainpage.css") | $raw %] >+[% Asset.js("js/vue/dist/islands.js") | $raw %] > [% INCLUDE 'doc-head-close.inc' %] > </head> > >@@ -301,6 +302,11 @@ > </div> <!-- /.col-sm-9 --> > > </div> <!-- /.row --> >+ <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> >+ </div> <!-- /.row --> > > [% MACRO jsinclude BLOCK %] > <script> >@@ -309,6 +315,10 @@ > $(".news_delete").on("click", function(){ > return confirmDelete(MSG_CONFIRM_DELETE); > }); >+ >+ setTimeout(() => { >+ document.getElementById("hello-islands").dataset.props = JSON.stringify({ message: 'This is a delayed message! Props (data-props) reflect.' }); >+ }, 2000); > }); > </script> > [% END %] >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/HelloIslands.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/HelloIslands.vue >new file mode 100644 >index 00000000000..782296d5e3b >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/HelloIslands.vue >@@ -0,0 +1,37 @@ >+<template> >+ <h2>Hello from Islands!</h2> >+ <p>This component is rendered as an island in a static HTML page.</p> >+ >+ <!-- Display message prop --> >+ <p v-if="message">{{ message }}</p> >+ >+ <!-- Reactive counter example --> >+ <p>Counter: {{ count }}</p> >+ <!-- Koha's bootstrap works in here! --> >+ <button @click="incrementCounter" class="btn btn-primary"> >+ Increment Counter >+ </button> >+</template> >+ >+<script> >+import { ref } from "vue" >+ >+export default { >+ props: { >+ message: { >+ type: String, >+ default: "", >+ }, >+ }, >+ data() { >+ return { >+ count: 0, >+ } >+ }, >+ methods: { >+ incrementCounter() { >+ this.count++ >+ }, >+ }, >+} >+</script> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/modules/islands.ts b/koha-tmpl/intranet-tmpl/prog/js/vue/modules/islands.ts >new file mode 100644 >index 00000000000..379c1c954a7 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/modules/islands.ts >@@ -0,0 +1,93 @@ >+import { createApp, Component, App } from "vue"; >+ >+/** >+ * A registry for Vue components. >+ * @type {Map<string, Component>} >+ */ >+export const componentRegistry: Map<string, Component> = new Map< >+ string, >+ Component >+>(); >+ >+/** >+ * 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); >+ }); >+} >+ >+/** >+ * 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); >diff --git a/rspack.config.js b/rspack.config.js >index 6a8e40c4f6e..12b621b3fea 100644 >--- a/rspack.config.js >+++ b/rspack.config.js >@@ -13,6 +13,7 @@ module.exports = { > "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/preservation.ts", > "admin/record_sources": > "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/record_sources.ts", >+ islands: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/islands.ts", > }, > output: { > filename: "[name].js", >@@ -57,6 +58,23 @@ module.exports = { > }, > ], > }, >+ /** >+ optimization: { >+ splitChunks: { >+ chunks: "all", >+ cacheGroups: { >+ default: false, // Disable default cache groups to avoid affecting existing bundles >+ vendors: false, // Disable vendor caching >+ vue: { >+ test: /[\\/]node_modules[\\/]vue[\\/]/, >+ name: "vue", >+ chunks: "all", >+ enforce: true, >+ }, >+ }, >+ }, >+ }, >+ */ > plugins: [ > new VueLoaderPlugin(), > new rspack.DefinePlugin({ >-- >2.39.3 (Apple Git-146)
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 37911
:
171425
|
171426
|
171432
|
171439
|
171440
|
171445
|
171459
|
171517
|
171538
|
171608
|
171685
|
172962
|
172963
| 172965 |
172966
|
172967
|
172968
|
172969
|
172970
|
172971
|
172972
|
172973
|
172974
|
172975