Rendering a Vue app in Shadow DOM using Custom Element - javascript

So I have a full featured (Routing etc) Vue 3 app that I need to embed on random page somewhere, but I don't want to use an iframe for various reasons so I'm looking to achieve this using Shadow DOM and Custom Element.
The way I thought it would work:
✅ Build the Vue App
✅ Publish the dist folder
✅ Create a Custom Element
✅ Create Shadow DOM in the constructor
✅ Load dist/index.html
❌ Vue App renders in Shadow DOM
... except it doesn't. Everything technically works but the Vue app doesn't render at all.
What am I missing?
My attempt: https://stackblitz.com/edit/javascript-7trm5i?devtoolsheight=33&file=index.html
Edited to add:
Same result even if I deploy the app to a service like Vercel and use it as src.

This is not a right way to do it. You are using fetch to get the HTML page. The HTML page has following script and style tags:
<!DOCTYPE html>
<html lang="en" data-theme="bumblebee">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue Cart</title>
<script type="module" crossorigin src="/assets/index.0f5a3d92.js"></script>
<link rel="modulepreload" href="/assets/vendor.ab0eb239.js">
<link rel="stylesheet" href="/assets/index.8fd7686d.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
Look at this script: <script type="module" crossorigin src="/assets/index.0f5a3d92.js">.
When this tag is added via innerHTML, the browser will try to find the index.0f5a3d92.js file using the domain from which you are serving the main page. It is not asking the domain from where you are getting this HTML. Ideally, it should be this:
https://vue-cart-ayoc0w7nv-eozzy.vercel.app/assets/index.0f5a3d92.js
But it is making call to this:
https://stackblitz.com/assets/index.0f5a3d92.js
You will have to rewrite the src attribute before you add it to HTML.
On a side note, this is an anti-pattern on what you are trying to do. Web component is not a real substitute for iFrame. Plus, a page should have only one top level head tag. You are introducing multiple head and body tags here. Browser will silently handle it but it is still anti-pattern.
The right way to do this is to bundle your Vue application with custom element definition. Then upload this bundled script to some CDN. From there, add it as a script on the page and the element should render when browser encounters the custom element.

You can not have multiple html, body, head tags in single document, so the html parser ignores the html, head and body tags of the Vue app.
Before:
<!DOCTYPE html>
<html lang="en" data-theme="bumblebee">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue Cart</title>
<script type="module" crossorigin src="https://vue-cart-ayoc0w7nv-eozzy.vercel.app/assets/index.0f5a3d92.js"></script>
<link rel="modulepreload" href="https://vue-cart-ayoc0w7nv-eozzy.vercel.app/assets/vendor.ab0eb239.js">
<link rel="stylesheet" href="https://vue-cart-ayoc0w7nv-eozzy.vercel.app/assets/index.8fd7686d.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
After parsing:
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue Cart</title>
<script type="module" crossorigin src="https://vue-cart-ayoc0w7nv-eozzy.vercel.app/assets/index.0f5a3d92.js"></script>
<link rel="modulepreload" href="https://vue-cart-ayoc0w7nv-eozzy.vercel.app/assets/vendor.ab0eb239.js">
<link rel="stylesheet" href="https://vue-cart-ayoc0w7nv-eozzy.vercel.app/assets/index.8fd7686d.css">
<div id="app"></div>
So ultimately the html structure changes from the original one which Vue Virtual DOM can not work with.
So I don't think you can use Shadow DOM like an iframe.

Related

Why sometimes my javascript doesn't get applied on my php

I have a lot of scripts and styles in head tag and sometimes they don't load properly. Here's how my head looks like:
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="w3.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD#48,500,1,0" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"
integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!--<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">-->
<script src="index.js"></script>
<title>NCA</title>
</head>
However when I reload my page, everything works properly and my js gets applied on my php exactly the way I want...
Can you tell what's going on and why the first time I load my page, my js doesn't work properly?
Note that: I can't do $(document).ready(function () { ... }); as there are many functions in my index.js file and also I want to force the body load after head is loaded... Is there any way to do it?
It is a good practice to put the script tags after the body tag, to ensure that the body its the first thing to load, it doesn't work that way with the css because you want the css to load first
I had a similar problem tha I solved using defer after the script src, example:
<script src="demo_defer.js" defer></script>
https://www.w3schools.com/tags/att_script_defer.asp

How can I define build path in Svelte?

How can I set deployment path in Svelte?
I would like to use it on the webserver in different path like
https://example.com/somewhere-else/index.html
But now right after i run npm build, I must use it like:
https://example.com/index.html
It must be something in the rollup.config.js but I cannot find it out.
I imagine you are using the standard svelte template?
https://github.com/sveltejs/template
The bundles it creates are not restricted to a specific path. You just need to put them in the appropriate path and update the link and script tags in the index.html file appropriately:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Svelte app</title>
<link rel='icon' type='image/png' href='/favicon.png'><!-- change this -->
<link rel='stylesheet' href='/global.css'><!-- change this -->
<link rel='stylesheet' href='/build/bundle.css'><!-- change this -->
<script defer src='/build/bundle.js'></script><!-- change this -->
</head>
<body>
</body>
</html>
Does this answer your question, or is your problem more complicated?

Favicon is not been rendered in IE11 for Angular 7 application

In IE11: Icon shows up in home page on load but when I navigate to other pages, rendered icon is replaced by default IE icon.
In Chrome: It works fine and icon is rendered on all routes.
Here are some of approaches I tried by referring to articles in Medium, stack overflow etc., which wasn’t not helpful.
I tried to keep absolute path(to test this in local), server name at different instances in index.html but it didn’t worked.
Also, I replaced base64 code of image and server url in above href attribute so it can access/locate favicon.
I added paths in assets array of angular.json file
"assets": [
"src/assets",
"src/locale",
"src/assets/favicon32x32.png",
"src/assets/favicon16x16.png",
"src/assets/favicon32x32.ico",
"src/assets/favicon16x16.ico"
]
I tried to modify base url in index.html and CLI didn’t build. Also, I tried modify polyfill.ts and installed npm package.
I inspected other angular apps/website by opening in IE11(e.g., angular.io or gmail.com). here too problem exists.
Kindly please help/guide me in resolving above issue. Also, please let me know which approach should I take or refer to, to get to the solution.
Here is my html metadata inside head tag inside index.html
<!DOCTYPE html>
<script></script>
<html>
<head>
<base href="/" />
<title>AB CORP</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<link rel="icon" href="/src/assets/images/favicons/favicon-16x16.ico" sizes="16x16" type="image/x-icon" media="all" />
<link rel="icon" href="/src/assets/images/favicons/favicon-32x32.ico" sizes="32x32" type="image/x-icon" media="all" />
</head>
<body class="ls">
<app>
<div class="lf-splash">
<div class="lf-message">Loading, please wait...</div>
<i class="fa fa-spinner fa-spin"></i>
</div>
</app>
</body>
</html>

Thymeleaf loads CSS but not Javascript

I have a Spring boot app with a Thymeleaf template, that is supposed to load a css file and a javascript file.
The CSS file loads, but the Javascript file doesnt, it does not even try to get it from the server. There is no HTTP request for the Javascript, but the requests for the CSS are visible.
Where is my mistake?
I already tried putting the link for the javascript in the header next to the css but that made no difference.
My folder structure is as follows:
resources
-- static
---- bootstrap
------ css
---- js
Here the HTML:
<html xmlns:th="https://thymeleaf.org" lang="en" style="height: 100%;">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Website Title</title>
<link rel="stylesheet" type="text/css" th:href="#{/bootstrap/css/bootstrap_custom.css}" />
</head>
<body class="d-flex flex-column h-100">
<script th:href="#{/js/main.js}" type="text/javascript"></script>
</body>
</html>
There is no error message, the tag for the javascript is simply ignored.
Your script end tag is incomplete. It should be:
<script th:src="#{/js/main.js}" type="text/javascript"></script>
The tag should read
<script th:src[...]>
instead of
<script th:href[...]>

Accents in `<component-name>.vue.html` are shown as �

I am using asp dotnet core for the backend.
This is how asp renders the page (notice how everything with meta etc seems correct, and my tests confirm this):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Website</title>
<base href="/" />
<link rel="stylesheet" href="/dist/vendor.css?v=nmP2oJlipQWfe1Q9dKUoaAj31lKk6_m4owNSV9fEeqA" />
</head>
<body>
<!-- test from _Layout.cshtml to see if it is its fault -->
àéíôüàéíôüàéíôüàéíôüàéíôü
<!-- it renders it correctly, so it looks like it's Vue.js's fault -->
<div id='app-root'>Loading...</div>
<script src="/dist/vendor.js?v=sZcVpAmM0vrf6nyPyorxXMMIanK-hAW1RhcF2ymtHfk"></script>
<script src="/dist/main.js?v=qjoWhD1Og3eISp302v-McamzVAlCEXm_3gssJzkUnKE"></script>
</body>
</html>
Now in app.vue.html, if I use this template:
<template>
<div>
àéíôüàéíôüàéíôüàéíôüàéíôü
</div>
</template>
<style scoped src="./app.css"></style>
<script src="./app.ts"></script>
The website shows this:
àéíôüàéíôüàéíôüàéíôüàéíôü
�������������������������
<= the first line comes from _Layout.cshtml, while the second line comes from app.vue.html
The Vue version I am using is 2.5.16
This should all just work. You have the utf-8 meta tag in your document. There is certainly no problem with Vue's handling of utf-8. Check your source files (app.vue.html), and your bundle. Open them in vs-code for example and you should see utf-8 on the right of the bottom status bar. Check the character encoding in the HTTP headers of everything, particularly the bundle. A .js bundle sent with bad HTTP encoding header would seem the most likely culprit.

Categories

Resources