随着公司项目的模块越来越多,每次打包后的项目都非常大,而且每修改一个小的模块,都会将整个项目打包,会非常的麻烦,随着前端的发展,微服务的出现,很好的解决了项目庞大的问题,而且每修改一个模块,可以单独打包发布。
项目的公共组件,因为每个单独项目都会有公共的组件,不可能每个项目都复制一份,这样冗余代码太多,也不好维护
1、可以通过构建一个项目组件,发布到npm上面
npm麻烦的地方,在于每次组件的修改,都需要升级npm的版本号,每个项目需要重新更新新的组件,也挺麻烦的
2、还有一个个就是用 Module Federation来解决跨应用代码共享的问题
下面我们就来看下Module Federation是怎么实现代码共享的
Module Federation的意思是模块联邦,使得javascript应用可以从另一个javascript应用中动态的加载代码,共享组件
如何使用ModuleFederationPlugin
配置示例
new ModuleFederationPlugin({
filename: "remoteEntry.js",
name: "hcc",
library: { type: "umd", name: "hcc" },
exposes: {
'./myCom': "./src/index.js",
},
remotes: {
myCom: "project@http://localhost:3000/remoteEntry.js",
},
shared: ['vue']
})
配置属性
- name,必须,唯一 ID,作为输出的模块名,使用的时通过 ${name}/${expose} 的方式使用;
- library,必须,其中这里的 name 为作为 umd 的 name;
- remotes,可选,表示作为 Host 时,去消费哪些 Remote;
- exposes,可选,表示作为 Remote 时,export 哪些属性被消费;
- shared,可选,优先用 Host 的依赖,如果 Host 没有,再用自己的;
知道了这个这些概念,下面我们来看看怎么在项目中使用
创建项目
我们创建两个项目,一个用来作为组件库hcc,一个是项目project,项目都是基于webpack5来构建的
创建组件库
我们先创建一个组件项目,hcc,通过npm init初始化项目,再下载一些依赖
npm install webpack weback-cli webpack-dev-server
还需要文件,vue文件的一些插件
npm isntall vue-loader babel-loader @babel/preset-env @babel/core vue-template-compiler
在根目录下新建一个src目录,里面建一个index.js文件,作为道出的入口文件
{
"name": "hcc",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"serve": "webpack-dev-server --config webpack.config.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@babel/core": "^7.20.12",
"@babel/preset-env": "^7.20.2",
"babel-loader": "^9.1.2",
"vue-loader": "^17.0.1",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
}
}
在根目录下再新建一个webpack.config.js用来打包的文件
我们来创建两个简单的组件
在src文件下创建组件1【jq-header.vue】
<template>
<div class="jq-header">
<h1>我是jq-header组件</h1>
</div>
</template>
<script>
export default {
name: 'JqHeadder',
componentName: 'JqHeadder',
}
</script>
组件2【jq-footer】
<template>
<div class="jq-footer">
<h1>我是jq-footer组件</h1>
</div>
</template>
<script>
export default {
name: 'JqFooter',
componentName: 'JqFooter',
}
</script>
在index.js中引入
import JqHeader from "./JqHeader.vue"
import JqFooter from "./JqFooter.vue"
const components = [JqHeader, JqFooter]
const install = (Vue) => {
components.forEach(component=>{
Vue.component(component.name,component)
})
}
export default {
install
}
下面我们就需要在webpack.config.js中,将我们的组件打包出去
入口文件就是我们的src/index.js,
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const {ModuleFederationPlugin} = require('webpack').container
module.exports = {
mode:"development",
entry: path.join(__dirname, './src/index.js'),
devServer:{
port:3000
},
module:{
rules:[
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
]
}
}
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins:[
// 处理rules的配置,加载文件
new VueLoaderPlugin(),
new ModuleFederationPlugin({
filename:"remoteEntry.js",
name:"hcc",
library: { type: 'umd', name: 'hcc' },
exposes:{
"./hcc":"./src/index.js"
}
})
]
}
因为我们项目中会加载vue,js文件,所以需要配置babel-loader和vue-loader,并且通过VueLoaderPlugin来处理rules
这样我们的组件库就基本配置好了,我们来启动项目
npm run serve
没有报错就代表项目启动成功了,但是我们打开服务地址其实是这样的
其实我们在共享组件代码的时候其实用的是remoteEntry.js,我们试着访问下
出现这样的代码,就代表我们的组件已经暴露出去,在项目的就可以引用了
创建项目
我们通过vue create来创建项目,因为我们用的是webpack5,所以我们需要升级我们的vue-cli到4版本,这样我们创建的项目才是webpack5
npm install -g @vue/cli
创建完项目以后,我们看下package.json,是不是webpack5,然后我们在vue.config.js配置moduleFederationPlugin
const { defineConfig } = require('@vue/cli-service')
const {ModuleFederationPlugin} = require('webpack').container
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack:config=>{
config.plugins.push(new ModuleFederationPlugin({
name: "jqCom",
remotes: {
jqCom: "hcc@http://localhost:3000/remoteEntry.js",
},
}))
}
})
我们在这里定义的jqCom引入的组件库的remoteEntry.js
我们来启动我们的项目,不报错的话,代表我们引入组件库的文件成功了
下面我们就在项目中引用下我们的组件,先去注册下组件,
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
(async ()=>{
let jqCom = await import('jqCom/hcc')
Vue.use(jqCom.default)
new Vue({
render: h => h(App),
}).$mount('#app')
})()
这里需要注意的一点就是我们的组件库导出的其实是一个异步函数,是一个new Promise,所以我们在引入的时候,需要通过await来接收,创建一个自执行函数,来注册组件
然后我们修改下HelloWorld.vue组件内容,引用我们组件库的组件
<template>
<div class="hello">
<jq-header></jq-header>
<jq-footer></jq-footer>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String,
},
}
</script>
我们打开页面,就可以看到组件库中的组件了
这样就实现了多个项目共享一套自定义组件库功能,提高我们的开发效率
到这里其实还没有结束,因为我们只打包了js,因为我们组件库都是有样式的,我们试着在组件库添加样式,看能不能被引用到项目中,有没有效果
<template>
<div class="jq-header">
<h1>我是jq-header组件</h1>
</div>
</template>
<script>
export default {
name: 'JqHeadder',
componentName: 'JqHeadder',
}
</script>
<style>
.jq-header {
color: red;
}
</style>
其实只是这样的话,项目是会报错的,因为我们没有对css进行处理,需要下载相应的loader去处理
比如我们在组件库中使用scss
npm install style-loader css-loader node-sass sass-loader
在webpack.config.js中加上配置,如果没有style-loader,是不会把css代码打包到js当中,外部项目引用不到
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader'],
},
{
test: /\.scss$/,
use: ['style-loader','css-loader', 'sass-loader'],
},
把css修改成scss
<template>
<div class="jq-header">
<h1>我是jq-header组件</h1>
</div>
</template>
<script>
export default {
name: 'JqHeadder',
componentName: 'JqHeadder',
}
</script>
<style lang='scss'>
.jq-header {
color: red;
}
</style>
重新运行组件项目