Dynamically load a template in a Svelte component - javascript

I'm not entirely sure if this is possible, but I want to dynamically load a component's template in runtime in Svelte. Basically download some user-defined template and use that to build out the component in run-time. Is this actually possible to do?

You would have to include the Svelte compiler and use that to compile the template. You could check out how the REPL's repository sets that up.
The REPL performs two major steps:
Convert the code to a raw component which still references 'svelte/internal'
Bundle the component so all referenced code is included via Rollup
The first step is easy, you just do something like this:
import { compile } from 'svelte/compiler';
const { js, css } = compile(template, {
filename: 'Component.svelte',
format: 'esm',
});
console.log(js.code, css.code);
The bundling is a bit more involved (see its worker's code). You could however, make the internals available for the import and just run the code directly.
E.g. using unpkg.com for testing and throwing the code into an iframe.srcdoc:
const code = `
${js.code.replace(
'svelte/internal',
'https://unpkg.com/svelte#3.49.0/internal/index.mjs',
)}
new Component({ target: document.body });
`;
srcdoc = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Svelte App</title>
<${''}style>${css.code}</${''}style>
</head>
<body>
<${''}script type="module">${code}</${''}script>
</body>
</html>
`;
(The tags are split using ${} because the Svelte parser will break otherwise.)
You could also insert the code directly into your document via a new module script, you just have to identify the target element somehow, e.g. by generating a unique random ID.
Here is something neat you can do to directly use the generated code: Dynamically import the script from a Blob.
const componentSrc = URL.createObjectURL(
new Blob([code], { type: 'text/javascript' })
);
const { default: Component } = await import(componentSrc);
URL.revokeObjectURL(componentSrc);
new Component({ target: ... });
If you use the component outside a sandboxed iframe, you should be aware of the XSS risk that you expose yourself to.

Related

How can I read and update the value of a bundled variable in Svelte?

I created a simple app with Svelte, and I used the bundle files to embed the app into a new web page like the following:
<link rel = "stylesheet" type = "text/css" href = "https://.../bundle.css">
<script defer src = "https://.../bundle.js"></script>
Is it possible in Svelte to make the bundle.js reusable so everyone can change the value of a variable for example to update something in the app (without creating a npm package)?
You can build the bundle in way that it exports the main component and pass values as properties or context. It then can just be imported in an inline <script> and parameterized that way:
<script>
import App from 'https://.../bundle.js';
new App({
target: document.body,
props: { ... },
context: new Map(...),
});
</script>
Contexts are useful if the variable is deeply nested in the component hierarchy.

How to add custom scripts bundle in NextJS

I have some legacy custom javascripts that I need to bundle and put them in _document.js as a link. The filename should include a hash.
What would be the best way to accomplish this?
I tried webpack configs regarding entry/output but they break NextJs build.
The problem is that we use things like window, document, etc that do crash in server side.
Ideally what is needed is to inject this into a tag, as compiled / babelified javascript code.
What I tried is
Webpack HTML Plugin plus other plugins like InlineChunk or
InlineSource plugins. They didn't work because they generate code in
an index.html that is not used by NextJS.
Using Raw Loader to get the file content. Doesn't work because it is
not babelified.
Adding a custom entry to the Webpack config, like scripts:
'path/to/my-entry.js'. Didn't work because it adds a hash name to the
file and I have no way of knowing it.
Adding a custom entry into the NextJs polyfills. I thought it made
sense, but the polyfill tag has a nomodule which prevents its code to
run on new browsers.
Another options is to add the javascript code as a string, and then using __dangerouslySetInnerHtml but the problem is that I lose linter and babel abilities there.
I tried adding it as a page, but crashes for local development and even on build
webpack.config.js
module.exports = (nextConfig = {}) =>
Object.assign({}, nextConfig, {
webpack(config, options) {
const nextJsEntries = config.entry;
config.entry = async () => {
const entries = await nextJsEntries();
entries['pages/rscripts'] = 'test/test.js';
return entries;
};
...
Then in _document.js
<script src={`${publicRuntimeConfig.ASSET_PREFIX}/_next/${this.props.buildManifest.pages['/rscripts'][2]}`} />
You can just import js file like import 'path/to/js_file' in your _app.js/app.tsx file
import "../styles/globals.css"
import "../js/test"
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
This one works fine for me
I wanted to add another answer here as I came across this and I believe some things have changed in Next JS. Next now has this script component that you can use to load external scripts or dangerously set a script.
The Next.js Script component, next/script, is an extension of the HTML
element. It enables developers to set the loading priority of
third-party scripts anywhere in their application, outside next/head,
saving developer time while improving loading performance.
The cool thing is you can put them into whatever pages you want, maybe you have a script you want on a homepage, but not other pages, and Next will extract them and place them on the page based on the strategy you select. There are a few gotchas, can't load in the head, beforeInteractive is a little finicky, so I would read the link above and the actual API reference before making any choices.
import { useEffect } from 'react';
import Script from 'next/script';
function thirdPartyScript() {
useEffect(() => {
// just for fun. This actually fires
// before the onLoad callback
}, []);
return (
<Script
id="test-script"
strategy="afterInteractive"
src="/public/pages/scripts/test.js"
onLoad={() => {
console.log('Onload fires as you would expect');
}}
/>
);
}

VueJs 3 - Use bundled sfc combined with Client Side Rendering

Greetings
Hello fellow Vuers!
So I've got the following situation:
I use ASP.Net Core 3.1 as my server and I would like to use the Vue SFC setup including Typescript support and bind the resulting components into my .cshtml.
Example Usage
Example.vue
<template>
<label :for="name">{{ content }}</label>
<input :id="name" :placeholder="content"/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
someCode
}),
</script>
<style scoped>
/*some styling*/
</style>
Index.cshtml.cs
public class IndexModel: PageModel{
public string Name { get; set; }
public string Content { get; set; }
}
Index.cshtml
#page
#model Namespace.To.IndexModel
<example :name="#Model.Name" :content="#Model.Content" />
The Question
Is there a way to bundle the Single File Components and then use the Components as needed inside of a .(cs)html?
Preferably I'd like to have each component inside of it's own .js file to load them on demand, but it's not a must have.
Thanks in advance
It is possible. But to clarify, it seems like you want to use the components individually. That means that what you're calling a SFC/component, will need to be treated as its own Vue app.
Assuming that's the case, instead of making the components bundled separately you'll need to create a vue app for each one, and individually export them.
You will need to decide how/when to mount them. I've relied on a more manual way of mounting, which requires a line of js to link the DOM element with the widget and pass the attributes. Alternatively you can rely on the DOM tag only. Admittedly, the js way is a bit more verbose, but less prone to edge cases.
Here is the example for the js way
components/example/index.js:
import Example from './Example.vue';
export const mountExample = (el, props) =>
Vue.createApp(el, props);
Then will wrap the component in a function that allows you to pass the DOM element to use and the props.
You would need
<script src="vue.js"/><!-- if it's not bundled in there -->
<script src="example.js"/>
<div id="example" />
<script>
mountExample('#example', {name:"#Model.Name", content="#Model.Content});
</script>
The other way (without the js instantiation) would be to wrap it in a function (IIFE) that looks for <example> tags, parses the content and then mounts the app with the provided parameters. It's quite a bit more work than the other example, but shouldn't be terribly complex.
So for the Example example, I'd organize it something like this: example
/components/example/
|-- Example.vue
|-- index.js
and then use webpack chaining via vue.config.js to do multiple of these
module.exports = {
// tweak internal webpack configuration.
// see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
chainWebpack: config => {
// remove the standard entry point
config.entryPoints.delete('app')
// then add your own
config.entry('example')
.add('./src/components/example/index.js')
.end()
.entry('menu')
.add('./src/components/menu/index.js')
.end()
}
}
A caveat; I've used rollup to generate these and I haven't tested this, but webpack should work too
resources for the webpack config:
https://cli.vuejs.org/guide/webpack.html#simple-configuration
https://github.com/neutrinojs/webpack-chain

How to use external JavaScript objects in Vue.js methods

I'm trying to get Stripe working with my Vue.js 2 application. For PCI-DSS reasons, Stripe requires that their Javascript is always loaded from js.stripe.com. I've followed the instructions in:
How to add external JS scripts to VueJS Components
How to include a CDN to VueJS CLI without NPM or Webpack?
but I get a 'Stripe' is not defined error when I try to use the library. These solutions seemed to be aimed at merely getting a <script> tag into the output HTML (e.g. for analytics), not actually consuming the functions and objects in that script.
Here's what my component Javascript looks like:
<script>
export default {
name: "PaymentPage",
mounted() {
let stripeScript = document.createElement('script');
stripeScript.setAttribute('src', 'https://js.stripe.com/v3/');
document.head.appendChild(stripeScript);
let s = Stripe('pk_test_Fooo');
console.log(s);
}
}
</script>
I also tried adding the script tag to my public/index.html file instead, but I get the same outcome. This would probably be my preferred route, since Stripe encourages developers to import their script on all pages on the site.
<!DOCTYPE html>
<html lang="en">
<head>
// ...
<script src="https://js.stripe.com/v3/"></script>
</head>
How can I pull a script from an external CDN and use it within my component's Javascript?
I'm aware of some libraries to integrate Vue.js with Stripe (e.g. matfish2/vue-stripe and jofftiquez/vue-stripe-checkout), but the former doesn't import properly for me (I'm hitting issue #24) and the latter is built against the older Stripe API and the new version is still in beta.
You aren't giving the script time to load before checking if Stripe is there. What you need is something like this:
<script>
export default {
name: "PaymentPage",
mounted() {
let stripeScript = document.createElement('script');
stripeScript.setAttribute('src', 'https://js.stripe.com/v3/');
stripeScript.onload = () => {
let s = Stripe('pk_test_Fooo');
console.log(s);
};
document.head.appendChild(stripeScript);
}
}
</script>
Thanks to yuriy636's comment, I realised that errors were only from the linter, which presumably can't statically figure out what I'm up to.
I opted to put the script into index.html, then ensured I squashed linter errors with:
// eslint-disable-next-line no-undef
let s = Stripe('pk_test_Fooo');
In my case, I still had errors calling functions of the specific script. So it was required to specify the ¨window¨ scope. Also, if you need to access any Vue element inside the ¨onload¨function, you need a new variable for the ¨this¨ instance.
<script>
export default {
name: "PaymentPage",
mounted() {
let stripeScript = document.createElement('script');
// new variable for Vue elements.
let self = this;
stripeScript.onload = () => {
// call a script function using 'window' scope.
window.Stripe('pk_test_Fooo');
// call other Vue elements
self.otherVueMethod();
};
stripeScript.setAttribute('src', 'https://js.stripe.com/v3/');
document.head.appendChild(stripeScript);
}
}
I worked with this on Vue 2.6.
Just install the npm package npm install #stripe/stripe-js and use it like a regular import
import { loadStripe } from "#stripe/stripe-js";
export default {
async mounted() {
// init stripe
const stripe = await loadStripe('your_stripe_key_here');
this.stripe = stripe; // store the stripe instance
// access the stripe instance in your methods or where you want to use them
},
}
It's working as of 6th Jan 2022.

Importing javascript file for use within vue component

I am working on a project that requires using a js plugin. Now that we're using vue and we have a component to handle the plugin based logic, I need to import the js plugin file within the vue component in order to initialize the plugin.
Previously, this was handled within the markup as follows:
<script src="//api.myplugincom/widget/mykey.js
"></script>
This is what I tried, but I am getting a compile time error:
MyComponent.vue
import Vue from 'vue';
import * from '//api.myplugincom/widget/mykey.js';
export default {
data: {
My question is, what is the proper way to import this javascript file so I can use it within my vue component?
...
Include an external JavaScript file
Try including your (external) JavaScript into the mounted hook of your Vue component.
<script>
export default {
mounted() {
const plugin = document.createElement("script");
plugin.setAttribute(
"src",
"//api.myplugincom/widget/mykey.js"
);
plugin.async = true;
document.head.appendChild(plugin);
}
};
</script>
Reference: How to include a tag on a Vue component
Import a local JavaScript file
In the case that you would like to import a local JavaScript in your Vue component, you can import it this way:
MyComponent.vue
<script>
import * as mykey from '../assets/js/mykey.js'
export default {
data() {
return {
message: `Hello ${mykey.MY_CONST}!` // Hello Vue.js!
}
}
}
</script>
Suppose your project structure looks like:
src
- assets
- js
- mykey.js
- components
MyComponent.vue
And you can export variables or functions in mykey.js:
export let myVariable = {};
export const MY_CONST = 'Vue.js';
export function myFoo(a, b) {
return a + b;
}
Note: checked with Vue.js version 2.6.10
try to download this script
import * from '{path}/mykey.js'.
or import script
<script src="//api.myplugincom/widget/mykey.js"></script>
in <head>, use global variable in your component.
For scripts you bring in the browser way (i.e., with tags), they generally make some variable available globally.
For these, you don't have to import anything. They'll just be available.
If you are using something like Webstorm (or any of the related JetBrains IDEs), you can add /* global globalValueHere */ to let it know that "hey, this isn't defined in my file, but it exists." It isn't required, but it'll make the "undefined" squiggly lines go away.
For example:
/* global Vue */
is what I use when I am pulling Vue down from a CDN (instead of using it directly).
Beyond that, you just use it as you normally would.
I wanted to embed a script on my component and tried everything mentioned above, but the script contains document.write. Then I found a short article on Medium about using postscribe which was an easy fix and resolved the matter.
npm i postscribe --save
Then I was able to go from there. I disabled the useless escape from eslint and used #gist as the template's single root element id:
import postscribe from 'postscribe';
export default {
name: "MyTemplate",
mounted: function() {
postscribe(
"#gist",
/* eslint-disable-next-line */
`<script src='...'><\/script>`
);
},
The article is here for reference:
https://medium.com/#gaute.meek/how-to-add-a-script-tag-in-a-vue-component-34f57b2fe9bd
For anyone including an external JS file and having trouble accessing the jQuery prototype method(s) inside of the loaded script.
Sample projects I saw in vanilla JS, React and Angular were simply using:
$("#someId").somePlugin(options)
or
window.$("#someId").somePlugin(options)
But when I try either of those in my VueJS component I receive:
Error: _webpack_provided_window_dot$(...).somePluginis not a function
I examined the window object after the resources had loaded I was able to find the jQuery prototype method in the window.self read-only property that returns the window itself:
window.self.$("#someId").somePlugin(options)
Many examples show how to load the external JS file in VueJS but not actually using the jQuery prototype methods within the component.

Categories

Resources