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
即可
<!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
文件中书写
// 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
中书写
<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>
无届的三个生命周期
//生命周期自动触发有东西插入
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
文件和配置。配置
官网是:
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
中写
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,编写一些公共代码
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
中引入
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并配置启动的参数
import { startApp } from 'node_modules/wujie/esm/index'
// 启动的参数
startApp({name, url, el})
2.如果是vue项目,可以直接安装npm i wujie-vue3
进行安装
import Wujie from 'wujie-vue3'
app.use(router).use(Wujie)
3.然后分别启动好子应用,记录端口就可以在App.vue中使用了
<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中,全部代码如下:
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中书写
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
中写
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
SWC官网
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
声明文件中写
// 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
<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传参
<template>
<WujieVue :props="{name: '张三', age: '18'}" url="http://127.0.0.1:5173" name="vue3"></WujieVue>
</template>
2.然后在vue-demo子项目中的App.vue中写JS代码
const send = () => {
// alert(window.parent.a) // 第一种方法
console.log(window.$wujie) // 第二种方式
}
3.为了防止上述$wujie
报错,可以在main.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
绑定数据
<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”
{
"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/*"]
}
}
}
参考自
一些文件爆红
1.找不到模块“./App.vue”或其相应的类型声明
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官网来看