Skip to content

Latest commit

 

History

History
1501 lines (1193 loc) · 77.9 KB

examples.md

File metadata and controls

1501 lines (1193 loc) · 77.9 KB

find more examples here

Examples

⚠️ 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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

open in JSBin ▶

🔝

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/[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>

🔝