Examples
September 13, 2024 · View on GitHub
- Examples
- Vue2 basic example
- using esm version
- A more complete API usage example
- Load a Vue component from a string
- Using another template language (pug)
- Using another style language (stylus)
- SFC style CSS variable injection (new edition)
- import style
- Minimalist Hello World example
- Use
options.loadModulehook - Dynamic component (using
:isSpecial Attribute) - Nested components
- Use SFC Custom Blocks for i18n
- Use Options.getResource() and process the files (nearly) like webpack does
- Load SVG dynamically (using
watch()) - Load SVG dynamically (using
async setup()and<Suspense>) - Use remote components
- image loading
- IE11 example
find more examples here
Examples
:warning: beware, the following examples are sticky to version 0.9.5. For your use, you would prefer the latest version
Try the examples locally
Since most browsers do not allow you to access local filesystem, you can start a small express server to run these examples.
Run the following commands to start a basic web server on port 8181:
npm install express # or yarn add express
node -e "require('express')().use(require('express').static(__dirname, {index:'index.html'})).listen(8181)"
note:
In the following examples, for convenience, we just returns static content as file. In real world, you would probably use something like this :
...
async getFile(url) {
const res = await fetch(url);
if ( !res.ok ) {
throw Object.assign(new Error(res.statusText + ' ' + url), { res });
}
return {
getContentData: (asBinary) => asBinary ? res.arrayBuffer() : res.text(),
}
},
...
Vue2 basic example
note: Vue2 do not have the Vue.defineAsyncComponent() function. Here we mount the app when the main component is ready.
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@2/dist/vue.runtime.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue2-sfc-loader.js"></script>
<script>
/* <!-- */
const mainComponent = `
<template>
<span>Hello from Vue {{ require('myData').vueVersion }} !</span>
</template>
`;
/* --> */
const { loadModule, vueVersion } = window['vue2-sfc-loader'];
const options = {
moduleCache: {
vue: Vue,
myData: {
vueVersion,
}
},
getFile(url) {
if ( url === '/main.vue' )
return Promise.resolve(mainComponent);
},
addStyle() { /* unused here */ },
}
loadModule('/main.vue', options)
.then(component => new Vue(component).$mount('#app'));
</script>
</body>
</html>
using esm version
<!DOCTYPE html>
<html>
<body>
<script type="module">
import * as Vue from 'https://unpkg.com/vue@3/dist/vue.runtime.esm-browser.prod.js'
import { loadModule } from 'https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.esm.js'
const options = {
moduleCache: { vue: Vue },
getFile: () => `<template>vue3-sfc-loader esm version</template>`,
addStyle: () => {},
}
Vue.createApp(Vue.defineAsyncComponent(() => loadModule('file.vue', options))).mount(document.body);
</script>
</body>
</html>
A more complete API usage example
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<!-- here we need to load Vue3 full version because we use template:'...' -->
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
const componentSource = /* <!-- */`
<template>
<span class="example">{{ msg }}</span>
</template>
<script>
export default {
data () {
return {
msg: 'world!'
}
}
}
</script>
<style scoped>
.example {
color: red;
}
</style>
`/* --> */;
const options = {
moduleCache: {
vue: Vue,
},
async getFile(url) {
if ( url === '/myComponent.vue' )
return Promise.resolve(componentSource);
const res = await fetch(url);
if ( !res.ok )
throw Object.assign(new Error(url+' '+res.statusText), { res });
return await res.text();
},
addStyle(textContent) {
const style = Object.assign(document.createElement('style'), { textContent });
const ref = document.head.getElementsByTagName('style')[0] || null;
document.head.insertBefore(style, ref);
},
log(type, ...args) {
console[type](...args);
},
compiledCache: {
set(key, str) {
// naive storage space management
for (;;) {
try {
// doc: https://developer.mozilla.org/en-US/docs/Web/API/Storage
window.localStorage.setItem(key, str);
break;
} catch(ex) {
// handle: Uncaught DOMException: Failed to execute 'setItem' on 'Storage': Setting the value of 'XXX' exceeded the quota
window.localStorage.removeItem(window.localStorage.key(0));
}
}
},
get(key) {
return window.localStorage.getItem(key) ?? undefined;
},
},
handleModule(type, source, path, options) {
if ( type === '.json' )
return JSON.parse(source);
}
}
const { loadModule } = window['vue3-sfc-loader'];
const myComponent = loadModule('/myComponent.vue', options);
const app = Vue.createApp({
components: {
'my-component': Vue.defineAsyncComponent( () => myComponent ),
},
template: 'Hello <my-component></my-component>'
});
app.mount('#app');
</script>
</body>
</html>
Load a Vue component from a string
<!DOCTYPE html>
<html>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
/* <!-- */
const sfcContent = `
<template>
Hello World !
</template>
`;
/* --> */
const options = {
moduleCache: {
vue: Vue,
},
getFile(url) {
if ( url === '/myComponent.vue' )
return Promise.resolve(sfcContent);
},
addStyle() { /* unused here */ },
}
const { loadModule } = window['vue3-sfc-loader'];
Vue.createApp(Vue.defineAsyncComponent(() => loadModule('/myComponent.vue', options))).mount(document.body);
</script>
</body>
</html>
Using another template language (pug)
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"></script>
<script src="https://pugjs.org/js/pug.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
/* <!-- */
const sfcContent = `
<template lang="pug">
ul
each val in ['p', 'u', 'g']
li= val
</template>
`;
/* --> */
const options = {
moduleCache: {
vue: Vue,
pug: require('pug'),
},
getFile(url) {
if ( url === '/myPugComponent.vue' )
return Promise.resolve(sfcContent);
},
addStyle: () => {},
}
const { loadModule } = window["vue3-sfc-loader"];
Vue.createApp(Vue.defineAsyncComponent(() => loadModule('/myPugComponent.vue', options))).mount('#app');
</script>
</body>
</html>
Using another style language (stylus)
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script src="//stylus-lang.com/try/stylus.min.js"></script>
<script>
/* <!-- */
const vueContent = `
<template>
Hello <b>World</b> !
</template>
<style lang="stylus">
b
color red
</style>
`;
/* --> */
const options = {
moduleCache: {
vue: Vue,
// note: deps() does not work in this bundle of stylus (see https://stylus-lang.com/docs/js.html#deps)
stylus: source => Object.assign(stylus(source), { deps: () => [] }),
},
getFile: () => vueContent,
addStyle(styleStr) {
const style = document.createElement('style');
style.textContent = styleStr;
const ref = document.head.getElementsByTagName('style')[0] || null;
document.head.insertBefore(style, ref);
},
}
Vue.createApp(Vue.defineAsyncComponent(() => window['vue3-sfc-loader'].loadModule('file.vue', options))).mount(document.body);
</script>
</body>
</html>
SFC style CSS variable injection (new edition)
see at vuejs/rfcs
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
/* <!-- */
const sfcContent = `
<template>
Hello <span class="example">{{ msg }}</span>
</template>
<script>
export default {
data () {
return {
msg: 'world!',
color: 'blue',
}
}
}
</script>
<style scoped>
.example {
color: v-bind('color')
}
</style>
`;
/* --> */
const options = {
moduleCache: {
vue: Vue,
},
getFile(url) {
if ( url === '/myComponent.vue' )
return Promise.resolve(sfcContent);
},
addStyle(textContent) {
const style = Object.assign(document.createElement('style'), { textContent });
const ref = document.head.getElementsByTagName('style')[0] || null;
document.head.insertBefore(style, ref);
},
}
const { loadModule } = window["vue3-sfc-loader"];
Vue.createApp(Vue.defineAsyncComponent(() => loadModule('/myComponent.vue', options))).mount('#app');
</script>
</body>
</html>
import style
<!DOCTYPE html>
<html>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
/* <!-- */
const config = {
// note: Here, for convenience, we simply retrieve content from a string.
files: {
'/style.css': `
.styled { color: red }
`,
'/main.vue': `
<template>
<span class="styled">hello</span> world
</template>
<script>
import './style.css'
export default {
}
</script>
`,
}
};
/* --> */
const options = {
moduleCache: { vue: Vue },
getFile: url => config.files[url],
addStyle(textContent) {
const style = Object.assign(document.createElement('style'), { textContent });
const ref = document.head.getElementsByTagName('style')[0] || null;
document.head.insertBefore(style, ref);
},
handleModule: async function (type, getContentData, path, options) {
switch (type) {
case '.css':
options.addStyle(await getContentData(false));
return null;
}
},
}
Vue.createApp(Vue.defineAsyncComponent(() => window['vue3-sfc-loader'].loadModule('/main.vue', options))).mount(document.body);
</script>
</body>
</html>
Minimalist Hello World example
<!DOCTYPE html>
<html>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
const options = {
moduleCache: { vue: Vue },
getFile: () => `<template>Hello World !</template>`,
addStyle: () => {},
}
Vue.createApp(Vue.defineAsyncComponent(() => window['vue3-sfc-loader'].loadModule('file.vue', options))).mount(document.body);
</script>
</body>
</html>
Use options.loadModule hook
<!DOCTYPE html>
<html>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
/* <!-- */
const sfcContent = `
<template>
Hello World !
</template>
`;
/* --> */
const options = {
moduleCache: { vue: Vue },
async loadModule(path) {
// (TBD)
},
getFile(url) {
if ( url === '/myComponent.vue' )
return Promise.resolve(sfcContent);
},
addStyle() { /* unused here */ },
}
const { loadModule } = window['vue3-sfc-loader'];
Vue.createApp(Vue.defineAsyncComponent(() => loadModule('/myComponent.vue', options))).mount(document.body);
</script>
</body>
</html>
Dynamic component (using :is Special Attribute)
In the following example we use a trick to preserve reactivity through the Vue.defineAsyncComponent() call (see the following discussion)
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<!-- here we need to load Vue3 full version because we use template:'...' -->
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
const options = {
moduleCache: {
vue: Vue,
},
getFile(url) {
// note: Here, for convenience, we simply retrieve content from a string.
return ({
'/a.vue': `
<template>
<i> a </i>
</template>
`,
'/b.vue': `
<template>
<b> b </b>
</template>
`,
})[url] || Promise.reject( new Error(res.statusText) );
},
addStyle() { /* unused here */ },
}
const { loadModule } = window["vue3-sfc-loader"];
const app = Vue.createApp({
template: `
<button
@click="currentComponent = currentComponent === 'a' ? 'b' : 'a'"
>toggle</button>
dynamic component: <component :is="comp"></component>
`,
computed: {
comp() {
const currentComponent = this.currentComponent; // the trick is here
return Vue.defineAsyncComponent( () => loadModule(`/${ currentComponent }.vue`, options) );
// or, equivalently, use Function.prototype.bind function like this:
// return Vue.defineAsyncComponent( (url => loadModule(url, options)).bind(null, `/${ this.currentComponent }.vue`) );
}
},
data() {
return {
currentComponent: 'a',
}
}
});
app.mount('#app');
</script>
</body>
</html>
Nested components
<!DOCTYPE html>
<html>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
/* <!-- */
const config = {
// note: Here, for convenience, we simply retrieve content from a string.
files: {
'/main.vue': `
<template>
<foo/>
</template>
<script>
import foo from './foo.vue'
export default {
components: {
foo,
},
created() {
console.log('main created')
},
mounted() {
console.log('main mounted')
}
}
</script>
`,
'/foo.vue': `
<template>
<bar/>
</template>
<script>
import bar from './bar.vue'
export default {
components: {
bar,
},
created() {
console.log('foo created')
},
mounted() {
console.log('foo mounted')
}
}
</script>
`,
'/bar.vue': `
<template>
end
</template>
<script>
export default {
components: {
},
created() {
console.log('bar created')
},
mounted() {
console.log('bar mounted')
}
}
</script>
`
}
};
/* --> */
const options = {
moduleCache: { vue: Vue },
getFile: url => config.files[url],
addStyle: () => {},
}
Vue.createApp(Vue.defineAsyncComponent(() => window['vue3-sfc-loader'].loadModule('/main.vue', options))).mount(document.body);
</script>
</body>
</html>
Use SFC Custom Blocks for i18n
<!DOCTYPE html>
<html>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"></script>
<script src="https://unpkg.com/vue-i18n@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
/* <!-- */
const config = {
// note: Here, for convenience, we simply retrieve content from a string.
files: {
'/component.vue': `
<template>
{{ $t('hello') }}
</template>
<i18n>
{
"en": {
"hello": "hello world!"
},
"ja": {
"hello": "こんにちは、世界!"
}
}
</i18n>
`
}
};
/* --> */
const i18n = VueI18n.createI18n();
const options = {
moduleCache: { vue: Vue },
getFile: url => config.files[url],
addStyle: () => {},
customBlockHandler(block, filename, options) {
if ( block.type !== 'i18n' )
return
const messages = JSON.parse(block.content);
for ( let locale in messages )
i18n.global.mergeLocaleMessage(locale, messages[locale]);
}
}
const app = Vue.createApp(Vue.defineAsyncComponent(() => window['vue3-sfc-loader'].loadModule('/component.vue', options)));
app.use(i18n);
app.mount(document.body);
</script>
</body>
</html>
Use Options.getResource() and process the files (nearly) like webpack does
<!DOCTYPE html>
<html>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
const config = {
files: {
// note: Here, for convenience, we simply retrieve content from a string.
'/main.vue': {
getContentData: () => /* <!-- */`
<template>
<pre><b>'url!./circle.svg' -> </b>{{ require('url!./circle.svg') }}</pre>
<img width="50" height="50" src="~url!./circle.svg" />
<pre><b>'file!./circle.svg' -> </b>{{ require('file!./circle.svg') }}</pre>
<img width="50" height="50" src="~file!./circle.svg" /> <br><i>(image failed to load, this is expected since there is nothing behind this url)</i>
</template>
`/* --> */,
type: '.vue',
},
'/circle.svg': {
getContentData: () => /* <!-- */`
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="50" />
</svg>
`/* --> */,
type: '.svg',
}
}
};
const options = {
moduleCache: {
'vue': Vue,
'file!'(content, path, type, options) {
return String(new URL(path, window.location));
},
'url!'(content, path, type, options) {
if ( type === '.svg' )
return `data:image/svg+xml;base64,${ btoa(content) }`;
throw new Error(`${ type } not handled by url!`);
},
},
handleModule(type, getContentData, path, options) {
switch (type) {
case '.svg': return getContentData(false);
default: return undefined; // let vue3-sfc-loader handle this
}
},
getFile(url, options) {
return config.files[url] || (() => { throw new Error('404 ' + url) })();
},
getResource({ refPath, relPath }, options) {
const { moduleCache, pathResolve, getFile } = options;
// split relPath into loaders[] and file path (eg. 'foo!bar!file.ext' => ['file.ext', 'bar!', 'foo!'])
const [ resourceRelPath, ...loaders ] = relPath.match(/([^!]+!)|[^!]+$/g).reverse();
// helper function: process a content through the loaders
const processContentThroughLoaders = (content, path, type, options) => {
return loaders.reduce((content, loader) => {
return moduleCache[loader](content, path, type, options);
}, content);
}
// get the actual path of the file
const path = pathResolve({ refPath, relPath: resourceRelPath }, options);
// the resource id must be unique in its path context
const id = loaders.join('') + path;
return {
id,
path,
async getContent() {
const { getContentData, type } = await getFile(path);
return {
getContentData: async (asBinary) => processContentThroughLoaders(await getContentData(asBinary), path, type, options),
type,
};
}
};
},
addStyle() { /* unused here */ },
}
const { loadModule } = window['vue3-sfc-loader'];
Vue.createApp(Vue.defineAsyncComponent(() => loadModule('/main.vue', options))).mount(document.body);
</script>
</body>
</html>
Load SVG dynamically (using watch())
<!DOCTYPE html>
<html>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
/* <!-- */
const config = {
// note: Here, for convenience, we simply retrieve content from a string.
files: {
'/circle0.svg': `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50" /></svg>`,
'/circle1.svg': `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="40" /></svg>`,
'/main.vue': `
<template>
<mycomponent
:name="'circle' + index % 2"
/>
</template>
<script>
import mycomponent from './myComponent.vue'
import { ref } from 'vue'
export default {
components: {
mycomponent
},
setup() {
const index = ref(0);
setInterval(() => index.value++, 1000);
return {
index,
}
},
}
</script>
`,
'/myComponent.vue': `
<template>
<span v-html="svg" />
</template>
<script>
import { ref, watch } from 'vue'
function asyncToRef(callback) {
const val = ref();
watch(() => callback(), promise => promise.then(value => val.value = value), { immediate: true }); // TBD handle catch()...
return val;
}
export default {
props: {
name: String
},
setup(props) {
return {
svg: asyncToRef(() => import('./' + props.name + '.svg')),
}
}
}
</script>
`
}
};
/* --> */
const options = {
moduleCache: { vue: Vue },
getFile: url => config.files[url],
addStyle(textContent) {
const style = Object.assign(document.createElement('style'), { textContent });
const ref = document.head.getElementsByTagName('style')[0] || null;
document.head.insertBefore(style, ref);
},
handleModule: async function (type, getContentData, path, options) {
switch (type) {
case '.svg':
return getContentData(false);
}
},
}
Vue.createApp(Vue.defineAsyncComponent(() => window['vue3-sfc-loader'].loadModule('/main.vue', options))).mount(document.body);
</script>
</body>
</html>
Load SVG dynamically (using async setup() and <Suspense>)
<!DOCTYPE html>
<html>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
/* <!-- */
const config = {
// note: Here, for convenience, we simply retrieve content from a string.
files: {
'/circle.svg': `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50" /></svg>`,
'/main.vue': `
<template>
<Suspense>
<mycomponent
:name="'circle'"
/>
</Suspense>
</template>
<script>
import mycomponent from './myComponent.vue'
export default {
components: {
mycomponent
},
}
</script>
`,
'/myComponent.vue': `
<template>
<span v-html="svg"/>
</template>
<script>
export default {
props: {
name: String
},
async setup(props) {
return {
svg: await import('./' + props.name + '.svg'),
}
}
}
</script>
`
}
};
/* --> */
const options = {
moduleCache: { vue: Vue },
getFile: url => config.files[url],
addStyle(textContent) {
const style = Object.assign(document.createElement('style'), { textContent });
const ref = document.head.getElementsByTagName('style')[0] || null;
document.head.insertBefore(style, ref);
},
handleModule: async function (type, getContentData, path, options) {
switch (type) {
case '.svg':
return getContentData(false);
}
},
}
Vue.createApp(Vue.defineAsyncComponent(() => window['vue3-sfc-loader'].loadModule('/main.vue', options))).mount(document.body);
</script>
</body>
</html>
Use remote components
Here we import vue-calendar-picker and also manage the date-fns dependent module.
This example use Vue2 because vue-calendar-picker is written for Vue2.
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@2/dist/vue.runtime.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue2-sfc-loader.js"></script>
<script>
const options = {
moduleCache: {
vue: Vue,
'date-fns/locale/en/index.js': {}, // handle require('date-fns/locale/' + this.locale.toLowerCase() + '/index.js');
},
pathResolve({ refPath, relPath }, options) {
if ( relPath === 'date-fns' )
return 'https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.30.1/date_fns.min.js';
if ( relPath === '.' ) // self
return refPath;
// relPath is a module name ?
if ( relPath[0] !== '.' && relPath[0] !== '/' )
return relPath;
return String(new URL(relPath, refPath === undefined ? window.location : refPath));
},
getFile: async (url) => {
// note: here, for convinience, we just returns a content from a string
if ( new URL(url).pathname === '/main.vue' ) {
return {
getContentData: () => /*<!--*/`
<template>
<div>
<calendar-range locale="EN" :selection="selection" :events="calendarEvents"/>
<button @click="add">add</button>
</div>
</template>
<script>
import calendarRange from 'https://raw.githubusercontent.com/FranckFreiburger/vue-calendar-picker/v1.2.1/src/calendarRange.vue'
export default {
components: {
calendarRange,
},
data: {
selection: { start: Date.now(), end: Date.now() },
calendarEvents: []
},
methods: {
add: function() {
this.calendarEvents.push({
color: '#'+Math.floor(Math.random()*16777215).toString(16),
start: this.selection.start,
end: this.selection.end
});
}
}
}
</script>
`/* --> */,
type: '.vue',
}
}
return fetch(url).then(res => res.text());
},
addStyle(textContent) {
const style = Object.assign(document.createElement('style'), { textContent });
const ref = document.head.getElementsByTagName('style')[0] || null;
document.head.insertBefore(style, ref);
},
}
const { loadModule } = window['vue2-sfc-loader'];
loadModule('/main.vue', options)
.then(component => new Vue(component).$mount('#app'))
</script>
</body>
</html>
image loading
<!DOCTYPE html>
<html>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue3-sfc-loader.js"></script>
<script>
/* <!-- */
const config = {
files: {
// note: Here, for convenience, we simply retrieve content from a string.
'/theComponent.vue': `
<script setup>
const pngData = await import('https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png')
// crate an ObjectURL of our image
const pngBlobUrl = URL.createObjectURL(new Blob([pngData]));
// cleanup the ObjectURL, see https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static#memory_management
const onImageLoaded = (ev) => URL.revokeObjectURL(ev.target.src);
</script>
<template>
image loaded by the browser, the usual way:
<img src="https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"/>
<hr/>
image loaded by vue3-sfc-loader through <pre>fetch()</pre>:
<img @load="onImageLoaded" :src="pngBlobUrl" />
</template>
<style>
img { width: 128px; vertical-align: middle; }
pre { display: inline; }
</style>
`,
'/main.vue': `
<script setup>
import theComponent from '/theComponent.vue'
</script>
<template>
<Suspense>
<theComponent/>
</Suspense>
</template>
`,
}
};
/* --> */
const options = {
devMode: true,
moduleCache: {
vue: Vue,
},
async getFile(url) {
if ( config.files[url] )
return config.files[url];
const res = await fetch(url);
if ( !res.ok )
throw Object.assign(new Error(res.statusText + ' ' + url), { res });
return {
getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
}
},
addStyle(textContent) {
const style = Object.assign(document.createElement('style'), { textContent });
const ref = document.head.getElementsByTagName('style')[0] || null;
document.head.insertBefore(style, ref);
},
handleModule: async function (type, getContentData, path, options) {
switch (type) {
case '.png':
return getContentData(true); // load as binary
}
},
log(type, ...args) {
console[type](...args);
}
}
const app = Vue.createApp(Vue.defineAsyncComponent(() => window['vue3-sfc-loader'].loadModule('/main.vue', options)))
app.mount(document.body);
</script>
</body>
</html>
IE11 example
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@2/dist/vue.runtime.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.9.5/dist/vue2-sfc-loader.js"></script>
<script>
const config = {
// note: Here, for convenience, we simply retrieve content from a string.
files: {
/* <!-- */
'/app.vue': ''
+ ' <template> '
+ ' <div>{{ index }}</div> '
+ ' </template> '
+ ' <script> '
+ ' '
+ ' export default { '
+ ' data() { '
+ ' return { '
+ ' index: 0, '
+ ' } '
+ ' }, '
+ ' async mounted() { '
+ ' '
+ ' for ( ; this.index < 100; ++this.index ) '
+ ' await new Promise(resolve => setTimeout(resolve, 1000)); '
+ ' } '
+ ' } '
+ ' </script> '
/* --> */
}
};
const options = {
moduleCache: { vue: Vue },
getFile: function(url) { return config.files[url] },
addStyle: function () {},
}
window['vue2-sfc-loader'].loadModule('/app.vue', options)
.then(function(app) {
new Vue(app).$mount('#app')
});
</script>
</body>
</html>