【vue3】远程组件加载

  • Post author:
  • Post category:vue




远程加载组件,动态更新组件,主框架不更新

参考 https://gitee.com/fanzhengshao/remote-components-library



用vite创建一个vue项目

添加remote目录,存放远程组件

添加rollup.config.js,用来打包配置远程组件

//  rollup.config.js
import vuePlugin from 'rollup-plugin-vue'
import babel from '@rollup/plugin-babel'
import json from "@rollup/plugin-json";
import postcss from 'rollup-plugin-postcss'
const path = require('path')
const resolve = (p) => {
    return path.resolve(__dirname, p)
}
const getEntry = (component) => {
    return resolve(`./remote/${component}/index.js`)
}

const titleCase = (strs) =>{
    let arr = strs.split('-')
    let newTitle =''
    arr.forEach(o=>{
        newTitle+= (o.slice(0,1).toUpperCase() + o.slice(1).toLowerCase())
    })
    return newTitle
}

const basePlugins = [
    vuePlugin(),
    json(), // 可以将 .json 文件转换为 ES6 模块
    babel({
        babelHelpers: 'bundled',
        exclude: 'node_modules/**'
    }),

]
const devPlugins= []
const prodPlugins = []
const generalPlugins = (name) => {
    return [
        ...basePlugins,
        postcss({
            // extract: true,
            // Or with custom file name
            extract: path.resolve(`remoteDist/${name}/remote-${name}.css`)
        })
    ]
}
const externals = ["vue"]
const getPackageConfig = (name) => {
    return {
        input: getEntry(name),
        output: [
            {
                file: `remoteDist/${name}/remote-${name}.umd.js`,
                format: 'umd',
                name: `Remote_${titleCase(name)}`,
                globals: { // 设定全局变量的名称
                    'vue': 'Vue',
                },
                exports: "named",
            },

        ],
        plugins: [
            ...generalPlugins(name)
        ],
        external: [...externals]
    }
}
export default [
    getPackageConfig('button'),
    getPackageConfig('loading')
]

修改package.json

"scripts": {
  "dev:main": "vite",// 开发模式
  "build:remote": "rollup -c",// 打包远程组件
  "build:main": "vite build",// 打包项目
  "preview": "vite preview"
},
"dependencies": {
  "vue": "^3.2.25"
},
"devDependencies": {
  "@vitejs/plugin-vue": "^2.3.0",
  "vite": "^2.9.0",
  "postcss": "^8.3.11",
  "@rollup/plugin-babel": "^5.3.0",
  "@rollup/plugin-json": "^4.1.0",
  "rollup-plugin-postcss": "^4.0.1",
  "rollup-plugin-vue": "^6.0.0",
  "@babel/core": "^7.15.8",
  "@babel/preset-env": "^7.15.8"
}



开发远程组件

假设我们要开发一个button组件

在remote中新建一个Button文件夹,在文件夹中新建index.js,index.vue。

index.js
import ScadaButton from './index.vue';
const install = (app) => {
    app.component(ScadaButton.name, ScadaButton);
}
export default {
    install,
    ScadaButton
}
index.vue
<template>
  <div>
    <button class="scadaButton" @click="clickHandler">我是一个Button组件</button>
  </div>
</template>
<script>
export default {
  name: 'ScadaButton',
  methods: {
    clickHandler() {
      console.log('点击了我的按钮哦');
    }
  }
}
</script>

<style>
.scadaButton{
  color: red;
}
</style>

在vue项目中使用这个组件,App.vue引入组件并使用,import ButtonTest from ‘…/remote/Button/index.vue’,得到如下界面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EFdWD5Aj-1665380594002)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a627ecb6b3cf4c6e975cd5164ed0bcfe~tplv-k3u1fbpfcp-watermark.image?)]



打包组件

运行 npm run build:remote,得到生成目录remoteDist



运行组件web服务

进入remoteDist目录,运行python -m http.serve 9001启动web服务



编写远程加载组件的组件

在components目录新建 Remote.vue

/**
* ======================================
* 远程加载组件
* author: admin
* file: Remote.vue
* date: 2022/4/11 16:46
* ======================================
*/
<template>
  <component :is="mode" v-bind="$attrs"></component>
</template>

<script>
import {markRaw, onMounted, ref,toRefs} from "vue";

export default {
  name: "Remote",
  // 如果你不希望组件的根元素继承特性,你可以在组件的选项中设置inheritAttrs: falseinheritAttrs: false,
  props: {
    componentInfo: {
      type: Object,
      default() {
        return {
          js: '',
          css: '',
          libraryName: '',
          componentName: ''
        }
      }
    }
  },
  setup(props, { emit }) {
    let mode = ref(null);
    const {js,css,libraryName,componentName} = toRefs(props.componentInfo)
    console.log("props",js,css,libraryName,componentName)
    /**
     * 使用动态 script方式加载远程js
     */
    function asyncScript(url) {
      // 动态script
      return new Promise((resolve, reject) => {
        const script = document.createElement("script");
        const target = document.getElementsByTagName("script")[0] || document.head;
        script.type = "text/javascript";
        script.src = url;
        script.onload = resolve;
        script.onerror = reject;
        target.parentNode.insertBefore(script, target);
      });
    }

    /**
     * 加载js
     * @param url js文件地址
     * @param libraryName 库名
     * @param componentName 组件名
     * @return {Promise<void>}
     */
    async function loadScript(url, libraryName,componentName) {
      // 动态script
      await asyncScript(url);
      console.log(window[libraryName]);
      mode.value = markRaw(window[libraryName].default[componentName]);
    }

    /**
     * 加载样式
     * @param url css样式文件地址
     */
    function loadStyles(url) {
      let link = document.createElement("link");
      link.rel = "stylesheet";
      link.type = "text/css";
      link.href = url;
      let head = document.getElementsByTagName("head")[0];
      head.appendChild(link);
    }

    onMounted(() => {
      // 加载css
      loadStyles(css.value);
      // 加载js
      loadScript(js.value,libraryName.value,componentName.value);
    });
    return {
      mode,
    };
  }
}
</script>



使用远程组件

在main.js中添加,将vue挂载全局

import('vue').then(res => {
    console.log("vue",res)
    window.Vue = res
})

然后在需要使用组件的地方添加

import Remote from "./components/Remote.vue"
<remote :component-info="componentInfo"/>

componentInfo:{
  js:'http://localhost:9001/button/remote-button.umd.js',
  css:'http://localhost:9001/button/remote-button.css',
  libraryName:'Remote_Button',
  componentName:'ScadaButton'
}

得到如下界面,此时修改这个按钮组件文件,远程加载的不会在改变。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AdtUmxwj-1665380594003)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec4c3c06e96140ad8f0f676e709e014b~tplv-k3u1fbpfcp-watermark.image?)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wTWVhFcN-1665380594004)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cb92690ea8d64ce3b00634fbfbe2a291~tplv-k3u1fbpfcp-watermark.image?)]

浏览器网络请求中有这个组件的文件加载

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fDV8tDB4-1665380594004)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/382c115c0b0a4739812b3a7b6b2a823c~tplv-k3u1fbpfcp-watermark.image?)]



组件示例

/**
 * ======================================
 * 这是个示例
 * author: admin
 * file: RemoteBaseComponent.vue
 * date: 2022/4/11 16:46
 * ======================================
 */
<template>
  <div>
    当你看到这个时,远程组件应该可以工作了!
    <component :is="mode" v-bind="$attrs"></component>
  </div>
</template>

<script>
import { markRaw,  onMounted, ref, defineComponent } from "vue";

export default defineComponent({
  name: "RemoteBaseComponent",
  // inheritAttrs: false,
  props: {
    componentInfo: {
      type:Object,
      default(){
        return{
          js:'',
          css:'',
          libraryName:'',
          componentName:''
        }
      }
    },
  },
  setup(props, { emit }) {
    let mode = ref(null);

    const type = ref(props.type);
    /**
     * 使用动态 script方式加载远程js
     */
    function asyncScript(url) {
      // 动态script
      return new Promise((resolve, reject) => {
        const script = document.createElement("script");
        const target =
          document.getElementsByTagName("script")[0] || document.head;
        script.type = "text/javascript";
        script.src = url;
        script.onload = resolve;
        script.onerror = reject;
        target.parentNode.insertBefore(script, target);
      });
    }

    /**
     * 加载js
     * @param url js文件地址
     * @param componentName 组件名
     * @return {Promise<void>}
     */
    async function loadScript(url, componentName) {
      // 动态script
      await asyncScript(url);
      console.log(window.Remote_Button);
      mode.value = markRaw(window.Remote_Button.default.ScadaButton);
    }

    /**
     * 加载样式
     * @param url css样式文件地址
     */
    function loadStyles(url) {
      let link = document.createElement("link");
      link.rel = "stylesheet";
      link.type = "text/css";
      link.href = url;
      let head = document.getElementsByTagName("head")[0];
      head.appendChild(link);
    }

    onMounted(() => {
      // 加载css
      loadStyles(`/button/remote-button.css`);
      // 加载js
      loadScript(
        `/button/remote-button.umd.js`,
        "remoteButton"
      );
    });
    return {
      mode,
    };
  },
});
</script>



版权声明:本文为sky529063865原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。