远程加载组件,动态更新组件,主框架不更新
参考 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>