Skip to content

使用 vue-loader 在 html 中引入单页面组件

背景

在很多业务需求下,我们没办法使用工程化环境,例如架构已定的 electron项目、SSR 框架的视图中、一些没必要起脚手架的小项目、一些没办法起脚手架的老框架等。这时如果还想要编写 .vue 后缀名的单页面组件,就需要绕一些弯路来实现。

vue3-sfc-loader

Github

官方示例

html
<html>
<body>
  <div id="app"></div>
  <script src="https://unpkg.com/vue@latest"></script>
  <script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader/dist/vue3-sfc-loader.js"></script>
  <script>

    const options = {
      moduleCache: {
        vue: Vue
      },
      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(),
        }
      },
      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-component': Vue.defineAsyncComponent( () => loadModule('./myComponent.vue', options) )
      },
      template: '<my-component></my-component>'
    });

    app.mount('#app');

  </script>
</body>
</html>

使用 Vue2 或 Vue3

github:vue3-sfc-loader/dist

封装 initApp 方法

js
/**
 * 初始化 Vue 3 实例并挂载
 * @param {*} el 挂载 Vue 实例的元素,id 选择器,以 # 开头
 * @param {*} rootComponentPath 根组件路径,相对于 src 目录,以 ./ 开头
 * @param {*} componentName 组件名称,默认 'app-page-vue'
 */
initApp: (el, rootComponentPath, componentName = 'app-page-vue') => {
    const options = {
        moduleCache: { 
            vue: Vue 
        },
        async getFile(url) {
            try {
                const response = await fetch(url);
                if (!response.ok)
                    throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
                
                return {
                    getContentData: (asBinary) => asBinary ? response.arrayBuffer() : response.text()
                }
            } catch (e) {
                console.error(e);
                throw e;
            }
        },
        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);
            } 
        },
        log(type, ...args) {
            console[type](...args);
        }
    }

    // 创建 Vue 应用
    const app = Vue.createApp({
        template: `<${componentName} />`,
        components: {
            [componentName]: Vue.defineAsyncComponent(() => {
                return loadModule(
                    rootComponentPath,
                    options
                ).catch(err => {
                    console.error('Failed to load component:', err);
                    throw err;
                });
            })
        }
    });

    // 挂载应用
    app.mount(el);

    return app
}

调用

js
initApp('#myPage', './myPage.vue', 'my-page-vue')

浏览器版本问题

一些低版本浏览器(老 Electron、IE)中会遇到如下报错:

sh
TypeError: Cannot destructure property `resolveComponent` of 'undefined' or

此时需要下载源码,自己按需求来编译。

vue3-sfc-loader/build-your-own-version

最后更新于: