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:
npm install express # or yarn add express
node -e "require('express')().use(require('express').static(__dirname, {index:'index.html'})).listen(8181)"
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@next"></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);
},
},
additionalModuleHandlers: {
'.json': (source, path, options) => 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@next"></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() {},
}
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@next"></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@next"></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,
stylus: source => Object.assign(stylus(source), { deps: () => [] }), // note: deps() does not work in this bundle of stylus (see https://stylus-lang.com/docs/js.html#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@next"></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@next/dist/vue.runtime.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue3-sfc-loader.js"></script>
<script>
/* <!-- */
const vueContent = `
<template> Hello World !</template>
`;
/* --> */
const options = {
moduleCache: { vue: Vue },
getFile: () => vueContent,
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@next"></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() {},
}
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>
<script src="https://unpkg.com/vue@next"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue3-sfc-loader.js"></script>
<script>
const options = {
moduleCache: {
vue: Vue,
},
getFile(url) {
switch ( url ) {
case 'a.vue':
return `
<template>
<i> a </i>
</template>
`;
case 'b.vue':
return `
<template>
<b> b </b>
</template>
`;
}
return fetch(url).then(res => res.ok ? res.text() : Promise.reject( new Error(res.statusText) ));
},
addStyle() {},
}
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>