语雀笔记地址:www.yuque.com/zuiyu-qwofk…
背景
因XX需求,需要深入了解学习前端微服务,经过网文的安利,最终选择了 single-spa 这个框架。
将前端微服务 single-spa 使用的相关技术,组织成了一个系列文章,方便自己今后快速回忆,也方便和众多有需要人士共同交流。
研习路径
- 复习了所有相关技术之后需要实践一下,于是找到官方基于vue的demo,阅读、运行、调试源码;
- 动手从零开始按照官方vue-demo的架构搭建一套,目的:
- 体会框架的筋骨脉络;
- 遇到的各种问题,框架是如何去解决的;
- 先按照demo的技术路线固化实现,再提炼、总结、升华;
工程结构图
建立工程
-
npm 全局安装工具 lerna,我安装的是版本4;
- npm install -g lerna
-
npm 全局安装工具 create-single-spa(初版就用官方给的脚手架,以后再定制webpack配置文件);
- npm install --global create-single-spa
- create-single-spa 可以建立的类型是三种:
- create-single-spa --moduleType root-config
- create-single-spa --moduleType app-parcel
- create-single-spa --moduleType util-module
-
npm 全局安装 @vue/cli工具,当前我的版本是 @vue/cli@4.5.15
-
使用 lerna init 工程之后,在 packages 目录下建立 root-config 工程;
- 运行命令: create-single-spa root-config --moduleType root-config
- 过程里面交互输入:orgName,npmClient,typescript及其他
-
多工程dev端口规划:
- root-config项目使用:9000
- navbar的vue的导航框架使用:9001,然后是 app1:9002,app2:9003
-
多工程nginx端口规划:
- root-config 9100
- navbar:9101 app1:9102 app2:9103
-
在 packages 目录下建立 navbar 的 vue 工程
- vue create navbar
-
然后再spa-demo根目录执行命令 lerna bootstrap
- 发现 root-config 中的 prepare 脚本 husky install 报错,就先屏蔽了
-
在 lerna 根目录的 package.json 中添加自定义命令:
- --scope 后面带的应该是 packages 目录下工程的名字,而不是目录的名字
- 通过lerna启动的 packages 的子工程竟然是独立 vscode 实例的,关闭 vscode 都不可以停止服务
关键库的版本
@vue/cli: 4.5.15 // 这个框架又决定了以下的版本号
"@vue/cli-service": "~4.5.0",
"core-js": "^3.6.5",
"vue": "^2.6.11",
"webpack": "^4.46.0",
// 无 webpack-cli
"webpack-dev-server": "^3.11.3",
create-single-spa@4.1.2 // 这个框架又决定了以下的版本号
"webpack": "^5.51.1",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.7.0",
这就导致了 root-config 工程和子工程实际依赖的webpack、webpack-dev-server版本不一致,查看对应文档的时候需谨慎。
root-config的改造
- 在html这个唯一入口添加所有的js文件,都放到 public目录下,带有版本号和sourcemap文件方便调试;
- 添加 .editorconfig 和 .prettierrc文件,使得代码格式化自动补全风格一致;
navbar-后台管理系统框架工程基于@vue/cli的改造
后管系统包含,用户登录、注册、忘记密码已经进入系统之后的,菜单管理和用户信息、消息系统主体框架。然后,根据登录用户的权限获取菜单和消息,用户点击菜单,中间的业务区域就渲染成不同的业务页面,也就是各个子系统中的功能。
安装@vue/cli插件依赖: vue-cli-plugin-single-spa
npm i -D vue-cli-plugin-single-spa
目前版本是:3.1.2
注意:
- 以 vue-cli-plugin开发的是 @vue/cli规范中的社区插件;
- 在执行vue-cli-service命令的时候,他会自动解析package.json中的依赖,自动加载@vue/cli的plugins,所以不需要在vue.config.js中显示的加载;
- 具体规范查看:cli.vuejs.org/zh/guide/pl…
vue-cli-plugin-single-spa@3.1.2的问题
// 3.1.2 的源码如下,注释是我自己加的
const SystemJSPublicPathWebpackPlugin = require("systemjs-webpack-interop/SystemJSPublicPathWebpackPlugin");
const StandaloneSingleSpaPlugin = require("standalone-single-spa-webpack-plugin");
module.exports = (api, options) => {
options.css.extract = false;
const packageJsonPath = api.resolve("package.json");
const { name } = require(packageJsonPath);
if (!name) {
throw Error(
`vue-cli-plugin-single-spa: could not determine package name -- change your package json name field`
);
}
api.chainWebpack((webpackConfig) => {
webpackConfig.devServer
.headers({
"Access-Control-Allow-Origin": "*",
})
.set("disableHostCheck", true);
webpackConfig.optimization.delete("splitChunks");
webpackConfig.output.libraryTarget("umd");
webpackConfig.output.devtoolNamespace(name);
webpackConfig.set("devtool", "sourcemap");
webpackConfig
.plugin("SystemJSPublicPathWebpackPlugin")
.use(SystemJSPublicPathWebpackPlugin, [
{
rootDirectoryLevel: 2,
systemjsModuleName: name,
},
]);
webpackConfig
.plugin("StandaloneSingleSpaPlugin")
.use(StandaloneSingleSpaPlugin, [
{
appOrParcelName: name,
disabled: process.env.STANDALONE_SINGLE_SPA !== "true",
},
]);
webpackConfig.output.set("jsonpFunction", `webpackJsonp__${name}`);
webpackConfig.externals(["single-spa"]);
});
};
// 链式操作修改 webpack 配置
chainWebpack: (config) => {
if (process.env.VUE_APP_ANALYZER === "true") {
config.plugin("webpack-bundle-analyzer").use(BundleAnalyzerPlugin);
}
config.devServer
.headers({
"Access-Control-Allow-Origin": "*",
})
.set("disableHostCheck", true);
config.optimization.delete("splitChunks");
config.output.libraryTarget("umd");
config.output.set("jsonpFunction", `webpackJsonp__${projectName}`);
// 配合 devtool:sourcemap 选项, 让多个工程有独立的调试命名空间,不至于 sourcemap 中地址冲突,无法调试
config.output.devtoolNamespace(projectName);
config.set("devtool", "sourcemap");
// config.output["filename"] = "[name].js";
// config.output["chunkFilename"] = "[name].js";
// config.module
// .rule("images")
// .use("url-loader")
// .tap((options) => {
// // 修改它的选项...
// options.fallback.options.name = "js/" + options.fallback.options.name;
// return options;
// });
},
安装 single-spa-vue库
npm install -S single-spa-vue
目前版本是:2.5.1
安装 systemjs-webpack-interop
npm install -S systemjs-webpack-interop
目前版本是:2.3.7
抽取公共库放到externals
@vue/cli中自定义webpack的必要项
configureWebpack选项
- 如果这个值是一个对象,则会通过 webpack-merge 合并到最终的配置中;
- 如果这个值是一个函数,则会接收被解析的配置作为参数。该函数既可以修改配置并不返回任何东西,也可以返回一个被克隆或合并过的配置版本;
chainWebpack
是一个函数,会接收一个基于 webpack-chain 的 ChainableConfig 实例。允许对内部的 webpack 配置进行更细粒度的修改。
外置vue的基础依赖
此处应该查看webpack v4 的外部扩展: v4.webpack.docschina.org/configurati…
我们把基础的 vue、vue-router、vuex抽取出来,放到SPAs的公共html这个唯一入口中去
configureWebpack: {
externals: ["vue", "vue-router", "vuex", /^@sdemo/.+/],
},
vue app入口的改造
-
在root-config的唯一入口html中,没有vue工程以往的
标签;- 运行成功之后,会看到类似这样的标签
- 运行成功之后,会看到类似这样的标签
-
在src目录下添加文件set-public-path.js,并在main.js的第一行引入这个文件
import { setPublicPath } from "systemjs-webpack-interop";
setPublicPath("@sdemo/navbar", 2);
- 修改vue根app的通常启动方式
// vue 2 app 通常创建方式
// new Vue({
// render: (h) => h(App),
// }).$mount("#app");
const vueLifecycles = singleSpaVue({
Vue,
appOptions: {
render(h) {
return h(App, { props: { githubLink: this.githubLink } });
},
router,
},
});
export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;
- 在控制台中,可能会看到请求的库是dev版本的并且是从cdn.jsdelivr.net网站下载的
构建品分析插件
npm i -D webpack-bundle-analyzer@4.4.2
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
排除了vue、vue-router、vuex之后,使用分析命令分析navbar的构成,发现最大的包是 vue.runtime.esm.js
vue.js 和 vue.runtime.js 的区别和使用方法
- vue.js(完整版)
- 拥有全部的组件(如 compiler)
- vue.runtime.js(非完整版)
- 没有 compiler
总结
经过一些列的折腾之后,总结如下:
- 前端微服务需要的系统工程的知识,涉及到源码工程管理(lerna)、公共组件提取(element-ui、Antd),工程切分;
- 使用SystemJS加载AMD格式的包;
- 将通过@vue/cli 构建出来的工程修改为:
- 所有公共组件 externals 出去,放到 root-config 唯一口的 html 中加载
- 引入 vue-cli-plugin-singel-vue,和 single-spa-vue;
- 改造入口并导出 single-app的生命周期。
- 改造单独项目就是重中之重了
- 可以考虑修改为,本地开发、独立部署运行模式,有自己的html入口,独立app运行;
- 可以考虑,开发时期引入微服务框架的开发模式,这个开发模式根据自己的业务,带入基础的页面框架,以方便本地调试;
- root-config目前只是一个入口项目,包含html、SystemJS的基础配置,没有实质性的内容;navbar是导航窗口。可以考虑改造:
- 将微服务的统领配置移入navbar,改名为root-nav(portal),意义就是后管系统的面门系统,应该包含用户注册、用户登录、消息系统、后管框架一级系统菜单及业务绘制区域;
- event pub/sub对象在用户登录、消息系统登录之后注入到每一个 app 入口;