Serving SvelteKit app through VSCode Webview API - javascript

This question is being asked after spending weeks and giving up multiple times on trying to solve this.
I'm developing a VSCode extension that needs to make use of the Webview API. For a while, this could be using Svelte, but now with SvelteKit released as stable and being treated as default when you do npm create svelte, I targetted to use that. After configuring the app to be static SPA with SSR turned off and using the #sveltejs/adapter-static, it seems that serving it is not the same as it was with vanilla Svelte.
This is the svelte.config.js:
import adapter from '#sveltejs/adapter-static';
import preprocess from 'svelte-preprocess';
/**
* Consult https://github.com/sveltejs/svelte-preprocess
* for more information about preprocessors
*
* #type {import('#sveltejs/kit').Config} */
export default {
preprocess: preprocess(),
kit: {
adapter: adapter({ fallback: 'index.html' }),
// ssr: false, // deprecated
csp: {
directives: {
'default-src': ['none'],
'img-src': ['{{cspSource}} https:'],
'script-src': ['{{cspSource}}'],
'style-src': ['{{cspSource}}'],
},
},
// paths: {
// base: '{{baseURL}}', // not accepted
// },
},
};
This is the built HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width" />
<meta http-equiv="content-security-policy" content="default-src 'none'; img-src {{cspSource}} https:; script-src {{cspSource}} 'sha256-N2DRY+AREasGSTE5X4BdHoEYZsaGOpTvUwTBIHmryVA='; style-src {{cspSource}}">
<link rel="modulepreload" href="/_app/immutable/start-e16b6a0f.js">
<link rel="modulepreload" href="/_app/immutable/chunks/index-0576dc7c.js">
<link rel="modulepreload" href="/_app/immutable/chunks/singletons-51070258.js">
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">
<script type="module" data-sveltekit-hydrate="45h">
import { start } from "/_app/immutable/start-e16b6a0f.js";
start({
env: {},
paths: {"base":"","assets":""},
target: document.querySelector('[data-sveltekit-hydrate="45h"]').parentNode,
version: "1672682689612"
});
</script></div>
</body>
</html>
Contrary to vanilla Svelte that would generate something like:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width" />
<script src="/dist/app/main.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
That could be served through Webview like:
function getWebviewContent(webview, context) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="${[
"default-src 'none'",
`img-src ${webview.cspSource} https:`,
`script-src ${webview.cspSource}`,
`style-src ${webview.cspSource}`,
].join(';')};"
/>
<title>My Extension</title>
<script type="module" crossorigin src="${webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, 'dist/app/main.js')
)}"></script>
<link rel="stylesheet" href="${webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, 'dist/app/style.css')
)}">
</head>
<body><div id="app"></div></body>
</html>
`;
}
The file is bit more complex now, so I can't (and shouldn't) try to rewrite it in this function.
The following are the conditions:
VSCode needs absolute paths since it has different environments and protocols. Keep in mind that an extension is served locally to every user. (context.extensionUri is IMPORTANT)
SvelteKit has its set of configurations (different from Vite) that may pose to be restrictive specially with SPA mode.
Content Security Policy for Webview is also restrictive to what and what can't be used.
Probably due to Vite, the file names have hashes, so it's not consistent - but I'm preferring to keep that.
The script block in the HTML (may also need a nonce) is importing the start function relatively as a module.
Instead of writing another template as a string for VSCode that would need to be maintained, it would be ideal to have the generated HTML read into VSCode and served. Using Mustache seems good (hence you see {{cspSource}} in the HTML above).
How would one suggest to architecture and integrate this?

Related

Flutter Web - Getting flutter_ is not defined after publishing github pages

I just build a flutter web project. I hosted it on github pages. After successfully from creating repository to deploying project I am getting this following error...
(index):46 Uncaught ReferenceError: _flutter is not defined
I don't understand why is this happening. I tried flutter clean.
I searched for the solution. but many people suggested that run flutter clean command but it made no effect on the project.
Here is my code code for web build.
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="code_snippets">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png" />
<title>code_snippets</title>
<link rel="manifest" href="manifest.json">
<script>
// The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function (ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
}
}).then(function (engineInitializer) {
return engineInitializer.initializeEngine();
}).then(function (appRunner) {
return appRunner.runApp();
});
});
</script>
</body>
</html>
Uncaught ReferenceError: _flutter is not defined is may caused by wrong deployed folder.
you have to delpoy web directory insted of build/web
here some reference from github issue: https://github.com/flutter/flutter/issues/107448#issuecomment-1181591460
or you may try this solution :
Is `Flutter build web` supposed to output a file called flutter.js? Because it does not by me :(

Rendering a Vue app in Shadow DOM using Custom Element

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.

Why vue replaces static page title with Vue App

I am working on very simple one-page html project. It's static.html that is suppose to load data from external local file and generate some graphs/charts/etc. I am using Vue and I am stuck.
My public/index.html hasdefault content like follows
<!DOCTYPE html>
<html lang="en">
<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 rel="icon" type="image/png" href="img/favicon/black.png">
<title>DummyTitle</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without
JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
And there is something I do not understand. After yarn build, dist/index.html has content as follows
<!DOCTYPE html>
<html>
<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>Vue App</title>
<link href=css/app.9288aca6.css rel=preload as=style>
<link href=js/app.2c3ec05b.js rel=preload as=script>
<link href=js/chunk-vendors.6fa5766d.js rel=preload as=script>
<link href=css/app.9288aca6.css rel=stylesheet>
</head>
<body>
<div id=app></div>
<script src=js/chunk-vendors.6fa5766d.js></script>
<script src=js/app.2c3ec05b.js></script>
</body>
</html>
What the hell happened? Why title has changed to Vue App and where is favicon? I was looking for similar questions and problems and found nothing what would help me understand why is that.
First of all, I think there shouldn't be any specific reason to rename html file in public directory from index to dummy.
If you only need output name as dummy in dist folder, then rename html file in public folder to index and use this in vue.config.js. This will solve the issue.
module.exports = {
publicPath: '/',
indexPath: 'dummy.html'
}
If you need html file to be named as dummy, both in project structure and dist, just use this in vue.config.js. This will solve the title issue. Refer this official doc link Vue Config Docs Link
module.exports = {
pages: {
index: {
// entry for the page
entry: 'src/main.js',
// the source template
template: 'public/dummy.html',
// output as dist/index.html
filename: 'dummy.html'
}
},
publicPath: '/',
indexPath: 'dummy.html'
}

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?

react router rendering <noscript> in client side

In the server side it renders fine, but when it gets to the client side the react part of html vanishes and I get this error:
Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) <noscript data-reacti
(server) <div data-reactid=".z
this is my client code before compression:
import {Router, RouterContext, browserHistory} from "react-router";
import React from "react";
import ReactDOM from "react-dom";
const innerHTML = document.getElementById('react-routes').innerHTML;
const routes = JSON.parse(innerHTML);
console.log(routes);
// ReactDOM.render(<RouterContext {...routes} />, document.getElementById('react-app'));
ReactDOM.render(<Router>{routes.routes}</Router>, document.getElementById('react-app'));
// Router.run(routes, Router.HistoryLocation, function (Handler) {
// React.render(<Handler/>, document.getElementById('app'));
// });
I've tryed this three methods the two firsts gives the same error and the third says that Router.run is not a function. (the third was what I had working a few version ago)
the HTML generated by the server is this:
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<div id="react-app"><div data-reactid=".1rbyhm4ruo0" data-react-checksum="-854297298"><span data-reactid=".1rbyhm4ruo0.0">Hello </span><span data-reactid=".1rbyhm4ruo0.1">BLBALBLABA LUIZ</span><div data-reactid=".1rbyhm4ruo0.2"><input type="text" value="Login" data-reactid=".1rbyhm4ruo0.2.0"><span data-reactid=".1rbyhm4ruo0.2.1">Hello </span><span data-reactid=".1rbyhm4ruo0.2.2">Login</span></div></div></div>
<script id="react-routes" type="application/json">{"routes":[{"name":"public","path":"/","childRoutes":[{"name":"login","path":"/login"}]},{"name":"login","path":"/login"}],"params":{},"location":{"pathname":"/login","search":"","hash":"","state":null,"action":"POP","key":"zc9mx1","query":{},"$searchBase":{"search":"","searchBase":""}},"components":[null,null],"history":{},"router":{"__v2_compatible__":true},"matchContext":{"history":{},"transitionManager":{},"router":{"__v2_compatible__":true}}}</script>
<script src="https://cdn.socket.io/socket.io-1.1.0.js"></script>
<script src="/public/js/bundle.js"></script>
</body></html>
and after the client runs it becames this:
<html lang="en"><head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<div id="react-app"><noscript data-reactid=".zhdkkenpq8"></noscript></div>
<script id="react-routes" type="application/json">{"routes":[{"name":"public","path":"/","childRoutes":[{"name":"login","path":"/login"}]},{"name":"login","path":"/login"}],"params":{},"location":{"pathname":"/login","search":"","hash":"","state":null,"action":"POP","key":"qbhof0","query":{},"$searchBase":{"search":"","searchBase":""}},"components":[null,null],"history":{},"router":{"__v2_compatible__":true},"matchContext":{"history":{},"transitionManager":{},"router":{"__v2_compatible__":true}}}</script>
<script src="https://cdn.socket.io/socket.io-1.1.0.js"></script>
<script src="/public/js/bundle.js"></script>
</body></html>
I'm using the same method to renderToString() in the server.
Thanks
My problem was actually in gulp while browserifying the project.
As I'm creating dynamic routes to react-router, I don't have all the react classes being imported while gulp is running and it don't add the right packages to my bundle.js.
I want to make it fully dynamic, I'll keep on working on a better solution for this problem, but for the time being I could make it run by bundling *.react.js.

Categories

Resources