分析
micro-app 源码解读,分析框架内js隔离和css隔离具体做法:
-
整体流程是采用
WebComponent自定义好micro-app组件 -
页面渲染时触发组件钩子
attributeChangedCallback、connectedCallback -
根据当前子应用的url首页地址,fetch到
Html Entry入口html源代码 -
将上述html代码中head和body标签改为
micro-app-head和micro-app-body -
继续将上述html代码中
meta、icon、prefetch、preload等连接去掉,因为后面主要需要css和js文件 -
获取上述html代码中js和css文件,进一步根据地址获取到文件内容
-
如果是css外联文件,则请求css文件内容,向
micro-app-head执行appendChild方法,添加style标签,标签内容就是css外联文件内容,此时appendChild会触发判断是style标签,则给所有样式增加前缀,区分每个子应用样式,做到样式隔离,唯一注意的一个小点是,基座样式会影响子应用的样式,所以需要注意基座中不要写太多样式 -
如果是js外联文件,则请求js文件文件内容,同步做
windowProxy = new Proxy代理,请求到js文件内容后,根据inline参数不同,却是用script标签执行js或者用new Function执行js,外面套代理;(function(proxyWindow){with(proxyWindow.__MICRO_APP_WINDOW__){})(windowProxy),做到js隔离。如果其中有style的appendChild,又会执行上一条,给css增加前缀 -
micro-app默认是不开启shadowDOM,并且不建议开启,原因说是shadowDOM在React框架及一些UI库中的兼容不是很好
启动源码调试
-
当前micro-app版本是v0.8.10 micro-app
-
使用yarn
-
执行
yarn bootstrap装包 -
执行
yarn start:main-vue2启动,可以源代码写debugger调试 -
打开浏览器
http://localhost:4000/vue2/#/,因为vue2项目的启动是inline可以直接调试,默认的react16没有inline不能调试
一、初始化全局环境和配置项
备份全局环境
首先执行在 MicroApp.start() 方法,其中 initGlobalEnv() 方法 备份全局环境, 因为后面会代理window,并且会重写 document.appendChild 等方法,所以先备份纯净的全局环境
初始化配置项参数
重点几个需要关注的:
name,url,data,inline,shadowDOM,disableScopecss,disableSandbox,lifeCycles,preFetchApps
二、注册组件micro-app
这里有个细节,是data参数如何生效的?
看过我前面 微前端系列 - Web组件自定义元素介绍 会指定,html中attribute 和 类中property 是各自独立,所以像主应用中data属性的getter和setter会响应
<micro-app name="vue2" url="http://localhost:4001/micro-app/vue2" :data="data" inline></micro-app>
组件挂着到document后,connectedCallback触发,并将 生命周期Created压入微任务队列
三、组件生效
此时,我们在主应用中写<micro-app name="vue2" url="http://localhost:4001/micro-app/vue2"></micro-app>应用子应用,发生了什么呢?
-
组件
appendChild到document上,此时页面还是空白组件内容,仅仅只有下面这一句<micro-app name="vue2" url="http://localhost:4001/micro-app/vue2"></micro-app> -
组件渲染时,先赋值属性就会先多次执行钩子函数
attributeChangedCallback,比如name和url属性 -
组件
appendChild到document上后,会执行钩子函数connectedCallback,开始执行组件实现内容
四、组件渲染过程
1、是否生成 shadowDom 确定根节点
2、生成运行时实例并报错
实例中,开启沙箱,请求Html Entry内容,js文件请求,js执行,css文件请求,css隔离,元素隔离 等等
到这里,loadSourceCode() 方法是异步请求,所以当前单线程同步就逻辑就走完了,后续是怎么处理获得的子应用html,js,css了
五、fetch子应用入口html文件
-
直接fetch子应用html文件源代码
-
将head标签改为micro-app-head,将body标签改为micro-app-body
-
将注册的插件应用到html中
六、js文件内容和css文件内容请求并存起来
-
上述获得html文件内容后,回调
extractSourceDom方法 -
将子应用html改后的源代码添加到div
-
在html源文件里面去掉meta、icon、preload等标签,并筛选出js和css外联文件地址,如果是css内联代码,就
scopeCss方法加上前缀 -
请求css外联后,将请求到的内容通过style标签appcentChild到micro-app-head头中,以为appendChild已经重写,会触发
scopeCss给css内容增加子应用前缀 -
请求js外联后,将请求到的内容存起来
七、子应用执行
1、window事件拦截和快照
init里面回调recordUmdSnapshot方法,里面做window快照,记录子应用生效前全局window事件
2、html应用到指定容器
-
上述html文件、js和css文件都请求到并存起来后
-
根据
app.onLoad()逐步进入到mount()方法 -
请求js外联后,将请求到的内容根据配置项inline判断,如果有inline则用script标签appendChild到micro-app-body底部,当然需要包裹windowProxy;如果没有inlin则用new Function带有windowProxy内存执行
至此,子应用的 html,css,js 都应用到主应用容器上了