B站课程视频
课程视频
课程课件笔记:
1.微前端
2.无界
现有的微前端框架:iframe、qiankun、Micro-app(京东)、EMP(百度)、无届
前置
初始化
新建一个文件夹
1.通过npm i typescript -g
安装ts
2.然后可以使用tsc --init
初始化项目,这样项目目录下会有tsconfig.json
配置文件
3.再新建index.ts
4.使用tsc -w
命令可以实时的编译index.ts
出一个index.js
文件
5.通过index.html
引入index.js
即可
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./index.js"></script>
</head>
<body>
</body>
</html>
webComponents初使用
主要是为了样式隔离
wu-jie初体验
在index.ts
文件中书写
js
// webComponents 的写法
window.onload = () => {
// 初始化
class Wujie extends HTMLElement {
// 类的基本用法
constructor() {
super()
// this.attachShadow创建shadowdom并打开就具有样式隔离的性质,不会影响外层的样式
let dom = this.attachShadow({mode: 'open'})
// 获取template
let template = document.querySelector('#wujie') as HTMLTemplateElement
// 并绑定,这里是把template的内容深度克隆一份给dom,而不是直接给,true表示深度克隆
dom.appendChild(template.content.cloneNode(true))
// 这样就把template的内容渲染到了<wu-jie></wu-jie>的组件里面
console.log(this.getAttr('url'), this.getAttr('age'))
}
// 获取传参this.getAttribute 传入属性名即可
private getAttr(attr: string) {
return this.getAttribute(attr)
}
}
// webComponents挂载,注意名字不能启成驼峰的但是-横线连接可以,第二个参数是上面类的名字
// 类似vue组件 原生js写的一个组件 可以在html中使用了<wu-jie></wu-jie>
window.customElements.define('wu-jie', Wujie)
}
在上述建立的index.html
中书写
html
<body>
<!-- 相当于组件,也可以传参 -->
<wu-jie url='xxxxx' age='18'></wu-jie>
<div>我是外层的div</div>
<template id="wujie">
<div>我是 template 里面的div</div>
<style>
div{
background: red;
}
</style>
</template>
</body>
无届的三个生命周期
js
//生命周期自动触发有东西插入
connectedCallback () {
console.log('类似于vue 的mounted');
}
//生命周期卸载
disconnectedCallback () {
console.log('类似于vue 的destory');
}
//跟watch类似
attributeChangedCallback (name:any, oldVal:any, newVal:any) {
console.log('跟vue 的watch类似,有属性发生变化自动触发');
}
pnpm介绍
1.全局安装npm i pnpm -g
2.使用 pnpm -v
可以查看版本号
3.pnpm
比 npm
的优势,在pnpm
中文网的动机这样写pnpm官网
简单来说就是,但有100个vue项目,每个项目都会去安装相应的依赖包,这样导致磁盘的缩小和安装速度慢(每次都会重新下载依赖包),很麻烦。
但是pnpm使用软链接
、硬链接
和仓库
解决
硬链接
1.使用cmd查看提示,表示 /H就是硬链接
2.创建命令含义 mklink /H 硬链接的名字 通过谁创建
例如:在pnpm
文件目录下建立index.js
文件,并通过mklink /H ying.js index.js
创建了硬链接
3.并且目录下多出了一个ying.js
4.然后打开文件发现ying.js
中内容和index.js
中一样
5.而且当修改index.js
中的内容时,ying.js
中的内容会跟着修改,因为他们共享的同一个磁盘地址
软链接(符号链接)
1.创建软链接需要管理员权限,所以管理员打开cmd后进入目录可以使用cd ../../
等回退到根目录下,然后直接 D:
进入相应盘,再cd
命令
2.默认就是软链接 mklink ruan.js index.js
即可,不用加任何修饰符
3.成功创建后VScode中会有一个标志
4.且文件管理器中查看也是一个0字节的,因为他只记录一个路径(快捷方式,并不会占用资源),点击会跳转,指向的还是index.js
pnpm如何利用硬链接、软链接解决上述问题
1.使用pnpm init
命令创建package.json
2.以安装vue包为例,使用pnpm i vue
安装vue依赖,可以看到node_modules下面有vue的软链接(快捷方式,地址指向)
3.实际上上述软链接指向在.pnpm
包下找到真正的vue@3.3.13的包下面的vue
文件夹,这个vue硬链接指向.pnpm store
4.实际上就对应了pnpm官网的这张图,非扁平化的方式(可能嵌入,因为这个库可能还依赖其他库,这样依次来)
绿色黄色实线为软链接、红色虚线为硬链接
pnpm的CLI命令管理
假设你之前使用的npm
安装包,会生成一个package-lock.json
,或者使用yarn
安装过,会生成一个yarn.lock
但是你再通过pnpm import
命令 他会把你的上述的软件包管理器的 lockfile 生成 pnpm-lock.yaml
文件
monorepo项目
创建monorepo项目目录
1.使用命令npm init vue
创建vue项目,并创建项目名为main
2.创建web
文件目录存放子应用
3.创建vue子应用,使用npm init vite
选择vue+typescript
4.创建react子应用,使用npm init vite
选择react+typescript
5.在web文件夹下有多个子应用,分别安装有点繁琐,所以在monorepo
文件夹下使用命令pnpm init
生成文件,并手动创建pnpm-workspace.yaml
文件和配置。配置
yaml
官网是:
packages:
# all packages in direct subdirs of packages/
- 'packages/*'
# all packages in subdirs of components/
- 'components/**'
# exclude packages that are inside test directories
- '!**/test/**'
替换为自己的目录
packages:
# all packages in direct subdirs of packages/
- 'main/*'
# all packages in subdirs of components/
- 'web/**'
此时目录如下:
6.替换好pnpm-workspace.yaml
之后可以在根目录直接pnpm i
即可自动为所有的项目安装包
最外层根目录下的node_modules
是所有项目公共的包,而vue项目或者react项目里面的node_modules
是他们单独所需要的,这样结构更加清晰。
子项目启动:
假设这时候我们要跨级执行命令,想要执行react-demo
项目中package.json
的dev
命令,可以使用pnpm -F react-demo dev
,其中F是过滤filter
子模块复用:
1.新建目录common
下,并进入,想要common目录下的东西其余子项目(vue、react等都可以使用)
2.使用pnpm init
初始化生成package.json文件。在pnpm-workspace.yaml
中写
yaml
packages:
# all packages in direct subdirs of packages/
- 'main/*'
# all packages in subdirs of components/
- 'web/**'
- 'common'
3.安装axios包了pnpm i axios
3.新建index.ts,编写一些公共代码
ts
import axios from 'axios'
// 公共的提取出来
export const a = axios.get('xxx')
4.进入main目录使用命令pnpm -F main add common
则把common添加进main项目的依赖中,查看main/package.json
文件里面有
5.然后就可以在main/src/main.ts
中引入
ts
import { a } from 'common'
6.同理,也可以去给react-demo项目添加或者vue-demo添加:pnpm -F vue-demo add common
,一样查看那个package.json
有common包,然后可以导入进行使用
无届
安装
1.使用pnpm i wujie
,在main.ts中引入wujie并配置启动的参数
ts
import { startApp } from 'node_modules/wujie/esm/index'
// 启动的参数
startApp({name, url, el})
2.如果是vue项目,可以直接安装npm i wujie-vue3
进行安装
ts
import Wujie from 'wujie-vue3'
app.use(router).use(Wujie)
3.然后分别启动好子应用,记录端口就可以在App.vue中使用了
html
<template>
<div>
<h1>这是主应用</h1>
<!-- 分别是子应用,子应用启动之后各自的端口 -->
<WujieVue url="http://127.0.0.1:5174" name="vue3"></WujieVue>
<WujieVue url="http://127.0.0.1:5175" name="react"></WujieVue>
</div>
</template>
在main.ts中,全部代码如下:
ts
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import Wujie from 'wujie-vue3' // 引入一下对应的框架
import { preloadApp } from 'wujie'
const app = createApp(App)
app.use(router).use(Wujie) // 注册无界
app.mount('#app')
// exac预加载为true即可,那么components/react.vue和vue3.vue就会有url
preloadApp({ name: "vue3", url: "http://127.0.0.1:5173", exec: true})
preloadApp({ name: "react", url: "http://127.0.0.1:5174", exec: true})
封装无届
1.创建的目录
封装无届
1.创建的目录文件如下:
其中使用pnpm init
创建package.json
使用tsc --init
创建 tsconfig.json
2.使用pnpm i wujie
安装无届
3.使用pnpm i vue -D
把vue装在开发环境中
4.安装webpack,使用pnpm i webpack webpack-cli -D
5.安装typescript,使用 pnpm i typescript -D
6.安装typescript解析器,使用pnpm i ts-loader -D
书写相关配置代码
配置代码部分可以看源码参照下面的代码
写完后使用 npm run lib
打包
1.在env.d.ts中书写
ts
import { defineComponent, h, getCurrentInstance, onMounted, watch, onBeforeUnmount } from 'vue'
import type { App, PropType } from 'vue'
import { Props } from './type'
import { startApp, bus } from 'wujie'
// 函数式定义组件
const wujie = defineComponent({
props: {
width: { type: String, default: "" },
height: { type: String, default: "" },
name: { type: String, default: "", required: true },
loading: { type: HTMLElement, default: undefined },
url: { type: String, default: "", required: true },
sync: { type: Boolean, default: undefined },
prefix: { type: Object, default: undefined },
alive: { type: Boolean, default: undefined },
props: { type: Object, default: undefined },
attrs: { type: Object, default: undefined },
replace: { type: Function as PropType<Props['replace']>, default: undefined },
fetch: { type: Function as PropType<Props['fetch']>, default: undefined },
fiber: { type: Boolean, default: undefined },
degrade: { type: Boolean, default: undefined },
plugins: { type: Array as PropType<Props['plugins']>, default: null },
beforeLoad: { type: Function as PropType<Props['beforeLoad']>, default: null },
beforeMount: { type: Function as PropType<Props['beforeMount']>, default: null },
afterMount: { type: Function as PropType<Props['afterMount']>, default: null },
beforeUnmount: { type: Function as PropType<Props['beforeUnmount']>, default: null },
afterUnmount: { type: Function as PropType<Props['afterUnmount']>, default: null },
activated: { type: Function as PropType<Props['activated']>, default: null },
deactivated: { type: Function as PropType<Props['deactivated']>, default: null },
},
setup(props, { emit }) {
// 读取当前组件实例
// this.$refs.wujie // 在vue2中可以通过this获取当前组件实例
const instance = getCurrentInstance() // vue3中通过getCurrentInstance获取组件实例
// 封装启动函数
const init = () => {
// startApp({name, url, el})
//初始化无界
startApp({
name: props.name,
url: props.url,
el: instance?.refs.wujie as HTMLElement,
loading: props.loading,
alive: props.alive,
fetch: props.fetch,
props: props.props,
attrs: props.attrs,
replace: props.replace,
sync: props.sync,
prefix: props.prefix,
fiber: props.fiber,
degrade: props.degrade,
plugins: props.plugins,
beforeLoad: props.beforeLoad,
beforeMount: props.beforeMount,
afterMount: props.afterMount,
beforeUnmount: props.beforeUnmount,
afterUnmount: props.afterUnmount,
activated: props.activated,
deactivated: props.deactivated,
})
}
watch([props.name, props.url], () => {
init() // 如果发生变化就重新执行startApp
})
const handlerEmit = (event:string, ...args:any[]) => {
emit(event, ...args)
}
onMounted(() => {
// 发布订阅模式
bus.$onAll(handlerEmit)
init()
})
onBeforeUnmount(() => {
bus.$offAll(handlerEmit)
})
// 定义渲染函数
return ()=> h('div', {
style: {
width: props.width,
height: props.height
},
ref: "wujie" // 方便之后读取
})
}
})
// install 方法给vue使用的,app.use(router).use(wujie)会调用install
wujie.install = function(app: App) {
app.component('WujieVue', wujie)
}
export default wujie
2.type.ts
中写
ts
import type { plugin } from 'wujie'
type lifecycle = (appWindow: Window) => any;
interface Props {
/** 唯一性用户必须保证 */
name: string;
/** 需要渲染的url */
url: string;
/** 需要渲染的html, 如果用户已有则无需从url请求 */
html?: string;
/** 渲染的容器 */
loading?: HTMLElement;
/** 路由同步开关, false刷新无效,但是前进后退依然有效 */
sync?: boolean;
/** 子应用短路径替换,路由同步时生效 */
prefix?: { [key: string]: string };
/** 子应用保活模式,state不会丢失 */
alive?: boolean;
/** 注入给子应用的数据 */
props?: { [key: string]: any };
/** js采用fiber模式执行 */
fiber?: boolean;
/** 子应用采用降级iframe方案 */
degrade?: boolean;
/** 自定义运行iframe的属性 */
attrs?: { [key: string]: any };
/** 自定义降级渲染iframe的属性 */
degradeAttrs?: { [key: string]: any };
/** 代码替换钩子 */
replace?: (codeText: string) => string;
/** 自定义fetch,资源和接口 */
fetch?: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
/** 子应插件 */
plugins: Array<plugin>;
/** 子应用生命周期 */
beforeLoad?: lifecycle;
/** 没有做生命周期改造的子应用不会调用 */
beforeMount?: lifecycle;
afterMount?: lifecycle;
beforeUnmount?: lifecycle;
afterUnmount?: lifecycle;
/** 非保活应用不会调用 */
activated?: lifecycle;
deactivated?: lifecycle;
};
export { Props }
3.webpack.config.js
中写
const { Configuration } = require('webpack')
const path = require('path')
/**
* @type {Configuration} //配置智能提示
*/
const config = {
entry: "./src/index.ts", // 入口文件
mode: "none",
output: {
filename: "index.js",
path: path.resolve(__dirname, 'lib')
},
externals: { // 防止打包后的代码包含wujie和vue代码显得过多
vue: "vue",
wujie: "wujie"
},
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader"
}
]
}
}
module.exports = config
SWC和babel
1.swc比babel快20倍,如果是4核情况下,快70倍
使用pnpm add -D @swc/score swc-loader
安装命令
2.然后替换webpack.config.js
中的use
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader" // babel的ts-loader,换成swc-loader
}
]
}
3.因为swc底层是rust写的,rust性能是js的好几倍所很快
4.然后再打包npm run lib
打包后还不能直接使用,
因为前端有很多代码规范,AMD, CMD,CommonJS,ESModule,所以统一配置一个UMD,
5.安装pnpm i -D @swc/cli @swc/core
以便使用swc-cli
6.新建配置文件.swcrc
{
"$schema": "https://json.schemastore.org/swcrc",
"jsc": {
"parser": {
"syntax": "typescript", // js是ecmascript
// "jsx": false,
// "dynamicImport": false,
// "privateMethod": false,
// "functionBind": false,
// "exportDefaultFrom": false,
// "exportNamespaceFrom": false,
// "decorators": false,
// "decoratorsBeforeExport": false,
// "topLevelAwait": false,
// "importMeta": false
},
// "transform": null,
"target": "es5",
"loose": false,
"externalHelpers": false,
// Requires v1.2.50 or upper and requires target to be es2016 or upper.
"keepClassNames": false
},
"minify": false
}
7.在根目录的package.json
的 scripts中编写打包命令写"esm": "swc src/index.ts -d esm"
,
8.控制台执行 npm run swc
即可打包在esm
文件中
9.修改增添package.json中
设定一些版本号和启动的入口
"name": "vuejie-setup",
"version": "0.0.1",
"main": "lib/index.js",
"module": "esm/index.js",
添加files
"files": [
"lib",
"esm",
"index.d.ts"
],
10.在根目录下的index.d.ts
声明文件中写
ts
// import { bus, preloadApp, destroyApp, setupApp } from "wujie";
import type { App } from 'vue';
declare const WujieVue: {
// bus: typeof bus;
// setupApp: typeof setupApp;
// preloadApp: typeof preloadApp;
// destroyApp: typeof destroyApp;
install: (app: App) => void
};
export default WujieVue;
发布包
1.切换镜像 mmp use
,输入 npm
2.使用 npm adduser
命令,然后输入npm的用户名
3.使用npm login
4.使用 npm publish
发布
使用
1.在MONOREPO
项目中切换进主应用cd main
2.命令安装pnpm i vuejie-setup
3.在main.ts中引入import Wujie from 'vuejie-setup'
4.使用npm run dev
启动各种主应用或者子应用查看效果
无届的预加载和FPS
1.requestldleCallback函数
MDN的解释
2.FPS扩展
无届的传参
总共三种方法
第一种方法
无届的架构是把JS单独存放在了iframe中,那么iframe可以通过window和父级进行通讯,所以第一种传参就是通过
window.parent.变量名
进行读取。
1.在vue-demo/App.vue中添加按钮,绑定函数获取a
html
<script setup lang="ts">
const send = () => {
alert(window.parent.a) //
}
</script>
<button @click="send">点击</button>
2.在控制台输入变量 var a = '控制台的输入'
3.点击按钮,发现可以弹出
第二种方法
通过props 传参,子应用不需要绑定无界,主应用接进来之后会自动捕获vue实例,所以子应用可以访问到无界实例对象获取参数
1.例如在主应用的components/App.vue
组件中挂载数据,通过props传参
html
<template>
<WujieVue :props="{name: '张三', age: '18'}" url="http://127.0.0.1:5173" name="vue3"></WujieVue>
</template>
2.然后在vue-demo子项目中的App.vue中写JS代码
js
const send = () => {
// alert(window.parent.a) // 第一种方法
console.log(window.$wujie) // 第二种方式
}
3.为了防止上述$wujie
报错,可以在main.ts
中添加
ts
declare global {
interface Window {
$wujie: {
props: Record<string, any>
bus: {
$emit: any
}
}
}
}
4.打印的结果如下(可以看到还有bus ,第三种方法获取)
4.上述可以访问props console.log(window.$wujie)
进行打印
d第三种方案eventBus发布订阅
可以双向数据传递,直接使用wujie的bus.$on
1.在 主应用的App.vue中通过bus.$on
绑定数据
ts
<script lang="ts">
import { bus } from 'wujie'
bus.$on('vue3', (data: any) => {
console.log(data, '我是主应用的bus.$on')
})
</script>
2.在子应用vue-demo的App.vue中通过实例直接使用
const send = () => {
// alert(window.parent.a)
// console.log(window.$wujie.props) // 获取到App.vue下的props
window.$wujie.bus.$emit('vue3', '我是子应用vuedemo')
}
模块联邦
1.创建文件如下,目录
2.安装下面四个依赖包
npm i webpack webpack-cli webpack-server html-webpack-plugin -D
3.书写代码,略,结果如下,总之host里面没有的List数据却通过引入到了remote的数据进行展示
4.pnpm run build
进行打包host项目下
点开dist/bundle.js,可以看到使用CDN的方式进行引入的
之前:10个项目引用同一个模块,通过把这个项目发布到npm上面,然后这是个项目可以install这个模块,但是当这个模块发生改变的时候,例如从1.0.0 -> 1.0.1,那么这十个项目得重新下载install一遍
现在的CDN,当你的模块修改了,直接就是最新的版本,通过这个链接,远程调用联邦的技术正是用的这种
报错
1.在tsconfig.app.json
中报错:没有 "node" 模块解析策略的情况下,无法指定选项 "-resolveJsonModule"。
修改tsconfig.app.json
文件中 compilerOptions 选项配置 "moduleResolution": "node"
json
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
/*
* 模块解析策略,ts默认用node的解析策略,即相对的方式导入, 可选值:node、classic
* 如果未指定,则 --module commonjs 默认为 node,否则默认为 classic(包括 --module 设置为 amd、system、umd、es2015、esnext 等)
* Node 模块解析是 TypeScript 社区中最常用的,推荐用于大多数项目。
* 如果您在 TypeScript 中遇到导入和导出的解析问题,请尝试设置 moduleResolution: "node" 以查看它是否解决了问题。
*/
"moduleResolution": "node",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
一些文件爆红
2.ts.config.app.json找不到文件vue tsconfig tsconfig dom json
如果报错vite找不到之类的,以及奇怪的错误请删除两个子应用重新建立和安装 和初始化最外层的包pnpm init
ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL vue-demo@0.0.0 dev: `vite` Exit status 1
一些常用的命令
pnpm i 和 pnpm init
pnpm i
是 pnpm install的简写,表示安装包,会根据package.json的包版本进行安装,
pnpm init
是初始化一个项目,一般只会在空项目文件下创建package.json
一些必要的包
pnpm run dev 和 pnpm run start
前者是开发环境,后者是打包之后的生产环境运行项目
使用脚手架建立项目
因为涉及诸多配置,建议按照vite、webpack官网来看