Vue.JS has a super-cool “Single File Components” (SFC) capability. These are files that contain a component’s HTML, JS and CSS. Usually a visual component, as invisible things would normally be left to library functions if sharing was needed. Normally this is less than relevant to the ultimate deployable application as WebPack (etc) would munge the larger application into the smallest size/count amount of JavaScript that would be injectable into a page using ‘script’ tags. That the components were once .vue files (single file components; SFCs) is lost in mechanism of delivery.

I’m going to talk about loading SFC components into a page without first running a build of any sort.

I made a Calculator demo on a micro-site

https://github.com/paul-hammant/VueSfcDemo .

That’s four components: Calculator.vue, Button.vue, Display.vue and Blinker.vue.

I can also embed that same calculator into this Jekyll blog entry

That component is still in the separate location - github.com/paul-hammant/VueSfcDemo - and loaded at run time like so:

<div>
  <div id="app"></div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.5.2/velocity.min.js" integrity="sha512-cWbIzX5dm6Uyc6jg+DmJTWherzF5usoVXN/3MYggEQhsgfOJdfIPAvz5Ktprf4Gx2HoxVUqMIVDuyBp5PQ1Hwg==" crossorigin="anonymous"></script>
  <script src="https://unpkg.com/vue@next"></script>
  <script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.7.3/dist/vue3-sfc-loader.js"></script>
  <script>

    const options = {
      pathResolve({ refPath, relPath }) {
          
        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));
      },		
      moduleCache: {
        vue: Vue
      },
      async getFile(url) {
        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);
      },
    }
    const { loadModule } = window['vue3-sfc-loader'];
    const app = Vue.createApp({
      components: {
        'my-calculator': Vue.defineAsyncComponent( () => loadModule('//raw.githubusercontent.com/paul-hammant/VueSfcDemo/master/comps/calc/Calculator.vue', options) )
      },
      template: '<my-calculator></my-calculator>'
    });
    app.mount('#app');
  </script>
</div>

That’s just inlined into the Jekyll markdown page. The outermost <div> element is the clue to Jekyll that raw HTML is there (and JS and CSS).

Using the work of others

Two people made this a quick effort to complete:

Frank has been working on this .vue loader technology for four years, so it is quite mature now, even if he did make a new repo for the jump to Vue 3.x.

Thoughts

Would I use this in production settings rather than a proof of concept?

  • Yes, for intranet solutions.
  • Yes, for apps on the web for enrolled clients
  • Probably not for webscale needs. It would be nice for some post-processing webpack-alike to take a tested vue3-sfc-loader and do the usual minification++ tricks.

What the Vue community needs

  • A ‘component marketplace’ that is all about SFC components.

The .NET community gets this right:

This comment would be true of all other UI widget/component technologies too, fat and thin: QML, Swing, Angular, Flutter. NPM and similar isn’t enough: the component marketplace needs to have inline examples of use, or pics where that’s not possible.

Updates: May 2021 - Frank fixed bug #19.



Published

February 16th, 2021
Reads: