- 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.loadModule
hook - Dynamic component (using
:is
Special 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
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(),
}
},
...
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/[email protected]/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>
<!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/[email protected]/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>
<!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/[email protected]/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>
<!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/[email protected]/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>
<!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/[email protected]/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>
<!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/[email protected]/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>
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/[email protected]/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>
<!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/[email protected]/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>
<!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/[email protected]/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>
<!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/[email protected]/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>
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/[email protected]/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>
<!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/[email protected]/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>
<!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/[email protected]/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>
<!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/[email protected]/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>
<!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/[email protected]/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>
<!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/[email protected]/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>
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/[email protected]/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>
<!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/[email protected]/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>
<!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/[email protected]/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>