1. webpack打包原理
webpack详细原理 配置
webpack是把项目当作一个整体,通过给定的一个主文件,webpack将从这个主文件开始找到你项目当中的所有依赖的文件,使用loaders来处理它们,最后打包成一个或多个浏览器可识别的js文件
module.exports={
//入口文件的配置项
entry:{},
//出口文件的配置项
output:{},
//模块:例如解读CSS,图片如何转换,压缩
module:{
loader是用来对模块的源代码进行转换,而插件目的在于解决 loader 无法实现的其他事
基本工作流是将一个文件以字符串的形式读入,对其进行语法分析及转换(或者直接在loader中引入现成的编译工具,例如sass-loader中就引入了node-sass将SCSS代码转换为CSS代码,再交由css-loader处理),然后交由下一环节进行处理,所有载入的模块最终都会经过moduleFactory处理,转成javascript可以识别和运行的代码,从而完成模块的集成。
},
//插件,用于生产模版和各项功能
plugins:[],
//配置webpack开发服务功能
devServer:{}
}
//wepback 5
官方描述:
使用持久化缓存提高构建性能;
使用更好的算法和默认值改进长期缓存(long-term caching);
清理内部结构而不引入任何破坏性的变化;
引入一些breaking changes,以便尽可能长的使用v5版本。
通俗版描述:
减小打包后的文件体积
按需加载支持文件名模式
使用long-term caching解决生产环境下moduleIds & chunkIds变化的问题
使用cache: {type: "filesystem"}配置实现持久化缓存,提高构建速度
优化minSize&maxSize的配置方式
Node.js polyfills 自动加载功能被移除
2. web worker
- window.navigator.hardwareConcurrency计算cpu
- 单例模式缓存每次生成的sheet实例
- web worker 多线程计算
//主线程 main.js
var worker = new Worker("worker.js");
worker.onmessage = function(event){
// 主线程收到子线程的消息
};
// 主线程向子线程发送消息
worker.postMessage({
type: "start",
value: 12345
});
//web worker.js
onmessage = function(event){
// 收到
};
postMessage({
type: "debug",
message: "Starting processing..."
});
import SimpleWebWorker from 'simple-web-worker';
export default class WorkerQueue {
constructor() {
try {
this.hardwareConcurrency = window.navigator.hardwareConcurrency;
} catch (error) {
console.log(
'Set 4 Concurrency,because can`t get your hardwareConcurrency.'
);
this.concurrency = 4;
}
this.concurrency = 4;
this._worker = SimpleWebWorker;
this.workerCont = 0;
this.queue = [];
}
push(fn, callback, ...args) {
this.queue.push({ fn, callback, args });
this.run();
}
run() {
while (this.queue.length && this.concurrency > this.workerCont) {
this.workerCont++;
const { fn, callback, args } = this.queue.shift();
this._worker
.run(fn, args)
.then((res) => {
callback(res);
this.workerCont--;
this.run();
})
.catch((e) => {
// this.workerCont--;
// this.run();
throw e;
});
}
}
}
3. 性能优化
// var timing = window.performance.timing
// timing.domLoading //浏览器开始解析 HTML 文档第一批收到的字节
// timing.domInteractive // 浏览器完成解析并且所有 HTML 和 DOM 构建完毕
// timing.domContentLoadedEventStart //DOM 解析完成后,网页内资源加载开始的时间
// timing.domContentLoadedEventEnd // DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕)
// timing.domComplete //网页上所有资源(图片等)下载完成,且准备就绪的时间
let timing = performance.timing,
start = timing.navigationStart,
dnsTime = 0,
tcpTime = 0,
firstPaintTime = 0,
domRenderTime = 0,
loadTime = 0;
//DNS解析时间
dnsTime = timing.domainLookupEnd - timing.domainLookupStart;
//TCP建立时间
tcpTime = timing.connectEnd - timing.connectStart;
//首屏时间
firstPaintTime = timing.responseStart - start;
//dom渲染完成时间
domRenderTime = timing.domContentLoadedEventEnd - start;
//页面onload时间
loadTime = timing.loadEventEnd - start;
- 代码
1. 路由懒加载
2. 组件按需加载
3. 防抖节流
4. 重排重绘
5. 缓存
6. 预加载、预渲染
7. 图片优化
8. 长列表滚动到可视区域动态加载
9. SPA 页面采用keep-alive缓存组件
10. 骨架屏
11. 避免重定向
- 浏览器+后端
12. http2
13. 减少http请求数量
14. gzip
15. cdn
16. 缓存
17. ssr后端渲染, 减少前端渲染时间,就是一次性输出html内容,不用频繁调用ajax
- wepack
18. speed-measure-webpack-plugin //速度分析
19. webpack-bundle-analyzer //体积分析
20. thread-loader(webpack4官方所推荐)和HappyPack //多进程/多实例构建
21. uglifyjs-webpack-plugin //多进程并行压缩代码
22. DllPlugin 和 DllReferencePlugin //预编译资源模块
23. babel-loader开启缓存 使用cache-loader 使用hard-source-webpack-plugin//利用缓存提升二次构建速度
24. babel-polyfill //动态 Polyfill 服务
25. optimization.splitChunks代码分割
26. sourceMap优化
27. 使用cdn加载第三方模块
4. pdf 预览并加上水印
- pdf js 预览
PDFJS.getDocument({
url: filePath,
cMapUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@2.2.228/cmaps/",
cMapPacked: true
}, null, null, this.progressCallback.bind(this)).then((pdf) => {
this.setState({ pdf, containerWidth });
});
- 水印
- 背景图实现全屏水印
- canvas 生成水印 url 赋值给 css background 属性
pointer-events 如果值为none,元素永远不会成为鼠标事件的 target。
通过这个属性呢我们就可以实现我们之前的穿透水印遮罩的需求了。该属性也可用来提高滚动时的帧频。因为,当滚动时,鼠标悬停在某些元素上,则触发其上的hover效果,然而这些影响通常不被用户注意,并多半导致滚动出现问题。对 body 元素应用pointer-events:none,禁用了包括 hover 在内的鼠标事件,从而提高滚动性能。
/**
* 先设置crossOrigin 后设置src 否则报错 Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
* @param config
* @constructor
* @author mjt
*/
export default function ImgWatermark(config = {}) {
const img = new Image();
img.crossOrigin = 'Anonymous';
img.src = config.watermark_img_url;
img.onload = function () {
let canvas = document.createElement('canvas');
let dropWidth = Math.floor(img.width / 3)
let dropHeight = Math.floor(img.height / 4)
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = `${config.watermark_fontsize} Microsoft Yahei`;
ctx.fillStyle = config.watermark_color;
ctx.rotate(-(config.watermark_angle) * Math.PI / 180);
for (let i = 0; i < dropHeight; i++) {
for (let j = 0; j < dropWidth; j++) {
ctx.fillText(config.watermark_txt, (dropWidth + config.watermark_x_space) * i, (dropHeight + config.watermark_y_space) * j);
}
}
const base64Url = canvas.toDataURL();
setTimeout(() => {
const imgDom = document.querySelector(config.watermark_parent_node)
imgDom && (imgDom.src = base64Url)
}, 0)
}
}
- SVG 方式 (动态设置text 然后设置backgroundImage)
支持 gif 图水印,不存在跨域问题,使用 repeat 属性,无插入 dom 过程,无性能问题;
5. 页面卡顿 fiber拆分计算任务,匀到多帧
React是怎么做的?
React 在页面更新时,会自顶向下计算 virtual dom 上的不同处。如果计算任务耗时过长,渲染线程在 16 ms 中无法执行任务,页面会出现掉帧/卡死现象。
React Fiber 解决这个问题的思路是把渲染/更新过程(递归diff)拆分成一系列小任务。每次检查树上的一小部分,做完看是否还有时间继续下一个任务,有的话继续,没有的话把自己挂起,主线程不忙的时候再继续
按照 fiber 的设计思想,拆分计算任务,来解决页面卡死的问题
const repeat = (str, count) => {
let maxLoopCount = 999999;
async function runTask(count, cb) {
let taskStr = '';
let taskResult = new Promise((rs) => {
// window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
window.requestAnimationFrame(() => {
for (let i = 0; i < count && i < maxLoopCount; i++) {
taskStr += str;
}
// 拆分任务 每次执行的任务不大于maxLoopCount
if (count > maxLoopCount) {
runTask(count - maxLoopCount, (restValue) => {
cb && cb(taskStr + restValue);
rs(taskStr + restValue);
});
} else {
cb && cb(taskStr);
rs(taskStr);
}
});
});
return taskResult;
}
return runTask(count);
};
repeat('1', 999999999).then((result) => console.log(result));
6. PWA+直出+预加载(H5秒开)
计算首屏加载时间
<script type="text/javascript">
// 不兼容performance.timing 的浏览器,如IE8
window.pageStartTime = Date.now();
</script>
window.onload = function(){
window.firstPaint = Date.now();
}
可使用 Performance API 时
白屏时间 = firstPaint - performance.timing.navigationStart;
不可使用 Performance API 时
白屏时间 = firstPaint - pageStartTime;
不管是离线包技术,还是 webview 代理请求,都是对前端侵入非常大的,pwa 作为 web 标准,能够通过纯 web 的方案去加速和优化加载性能。
首先,pwa 的能够通过 cacheStorage 缓存普通的图片、js、css 资源。另一方面,在传统的 http cache 中,我们一般不会缓存 html,这是因为页面一旦设置了过长的 max-age,在浏览器缓存过期时间内,用户看到的永远将是旧的。
如果使用了 pwa 的 html 页面,能否直接缓存呢?由于 pwa 可精细化控制缓存,答案是可以的。
对于直出 html,我们可以配合 pwa,将从后台直出的文件,缓存到 cacheStorage,在下一次请求时,优先从本地缓存中获取,同时发起网络请求更新本地 html 文件。
但是在 hybrid 的 h5 应用,第一次启动的加载资源仍然费时,我们可以通过 app 端上支持预加载一个 javascript 脚本,拉取需要 PWA 缓存的页面,可以提前完成缓存。
对于非直出的页面,我们仍然无法避免浏览器渲染 html 时间的问题,应该如何减少这里的时间呢?
这里明确两个点,第一次永远只能靠提前加载,所以上面的借助端上预加载脚本仍然生效;第二点非直出页面,每个页面需要有独一无二的标记,比如 hash。浏览器获取到数据,并且渲染好的 html,能够通过 outerHTML 方法,将 html 页面缓存到 cacheStorage 中,第二次访问优先从本地获取,同时发起 html 请求,通过对比其中唯一标识的差异,决定是否需要更新。
pwa 一系列方案替代离线包策略,带来的好处是,属于 web 标准,适用于普通能够支持 service-worker 的 H5 页面。在允许兼容问题允许的情况下,建议主加。
总结:
- 在整个链路中减少中间环节。比如将串行改并行,包括小程序内部执行机制。
- 尽可能的预加载、预执行。比如从数据预取,到页面预取渲染等。
7. react-reactivity
- 通过@vue/reactivity定义响应式state
- 在父组件通过Provider将store传递下去
- 子组件通过useStore传入一个schedule(store)方法
- react-reactivity先通过useReducer在当前组件中注册一个强制更新的函数
- 通过useContext读取用户从Provider中传入的store
- 再通过Vue的effect去帮我们执行selector(store),并且指定scheduler为forceUpdate,这样就完成了依赖收集
- 响应式数据更新后,触发effect调用forceUpdate,完成页面渲染
8. 微前端
“取巧”实现一个微前端沙箱
qiankun源码解析
微前端原理+实践篇
微前端通信篇
沙箱
genSandbox 内部的沙箱主要是通过是否支持 window.Proxy 分为 LegacySandbox 和 SnapshotSandbox 两种
- LegacySandbox (LegacySandbox 的沙箱隔离机制利用快照模式实现)
- LegacySandbox 的沙箱隔离是通过激活沙箱时还原子应用状态,卸载时还原主应用状态(子应用挂载前的全局状态)实现的
- 在激活沙箱时,沙箱会通过 currentUpdatedPropsValueMap 查询到子应用的独立状态池(沙箱可能会激活多次,这里是沙箱曾经激活期间被修改的全局变量),然后还原子应用状态。
- 在关闭沙箱时,通过 addedPropsMapInSandbox 删除在沙箱运行期间新增的全局变量,通过 modifiedPropsOriginalValueMapInSandbox 还原沙箱运行期间被修改的全局变量,从而还原到子应用挂载前的状态。
- ProxySandbox(ProxySandbox 是一种新的沙箱模式,目前用于多实例模式的状态隔离。在稳定后以后可能会成为 单实例沙箱)
- 当调用 set 向子应用 proxy/window 对象设置属性时,所有的属性设置和更新都会命中 updateValueMap,存储在 updateValueMap 集合中(第 38 行),从而避免对 window 对象产生影响(旧版本则是通过 diff 算法还原 window 对象状态快照,子应用之间的状态是隔离的,而父子应用之间 window 对象会有污染)。
- 当调用 get 从子应用 proxy/window 对象取值时,会优先从子应用的沙箱状态池 updateValueMap 中取值,如果没有命中才从主应用的 window 对象中取值(第 49 行)。对于非构造函数的取值将会对 this 指针绑定到 window 对象后,再返回函数。
- 如此一来,ProxySandbox 沙箱应用之间的隔离就完成了,所有子应用对 proxy/window 对象值的存取都受到了控制。设置值只会作用在沙箱内部的 updateValueMap 集合上,取值也是优先取子应用独立状态池(updateValueMap)中的值,没有找到的话,再从 proxy/window 对象中取值。
相比较而言,ProxySandbox 是最完备的沙箱模式,完全隔离了对 window 对象的操作,也解决了快照模式中子应用运行期间仍然会对 window 造成污染的问题。
- SnapshotSandbox(在不支持 window.Proxy 属性时,将会使用 SnapshotSandbox 沙箱)
- SnapshotSandbox 的沙箱环境主要是通过激活时记录 window 状态快照,在关闭时通过快照还原 window 对象来实现的。
- 在沙箱激活时,会先给当前 window 对象打一个快照,记录沙箱激活前的状态(第 38~40 行)。打完快照后,函数内部将 window 状态通过 modifyPropsMap 记录还原到上次的沙箱运行环境,也就是还原沙箱激活期间(历史记录)修改过的 window 属性。
- 在沙箱关闭时,调用 inactive 函数,在沙箱关闭前通过遍历比较每一个属性,将被改变的 window 对象属性值(第 54 行)记录在 modifyPropsMap 集合中。在记录了 modifyPropsMap 后,将 window 对象通过快照 windowSnapshot 还原到被沙箱激活前的状态(第 55 行),相当于是将子应用运行期间对 window 造成的污染全部清除。
- SnapshotSandbox 沙箱就是利用快照实现了对 window 对象状态隔离的管理。相比较 ProxySandbox 而言,在子应用激活期间,SnapshotSandbox 将会对 window 对象造成污染,属于一个对不支持 Proxy 属性的浏览器的向下兼容方案。
9. 架构
- 前端性能优化(依托性能监控平台,异常报错平台)
- 前端工具平台
- 图标库平台
- css样式平台
- 组件库(原生、vue/react)
- 组件调试平台
- 组件文档平台
- 前端业务开发工具包(根据公司实际业务)
- 前端脚手架自动化页面配置平台优化(依托性能监控平台,异常报错平台)
- 代码管理平台(gitlab、代码审核…)
- 测试、生产发布平台
- 前端团队建设(一部分主管职责)
- 招聘常
- 规业务技术选型,新业务技术选型和探索
10、小程序自适应
百度智能小程序,是 H5 和 Native 技术结合的产物,
包括 API、组件、框架、动态库组件、服务端API 等多种类别的1000多项能力,具有多端、多宿主运行的特点。
11、一语直达vsl
// 监听body
const targetNode = document.body;
// 观察body下的任意节点发生变化
const config = {
attributes: true,
childList: true,
subtree: true
};
// 当观察到变动时执行的回调函数
const callback = debounce(mutationsList => {
if (!this.lifeCycle.disposed && document.visibilityState === 'visible') {
//注册检测到的语音指令
this.registerVSLComponent();
}
}, 1000);
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
observer.observe(targetNode, config);
12、全埋点
1.可视化埋点
通过可视化交互的手段,代替代码埋点。将业务代码和埋点代码分离,提供一个可视化交互的页面,输入为业务代码,通过这个可视化系统,可以在业务代码中自定义的增加埋点事件等等,最后输出的代码耦合了业务代码和埋点代码。例如:GrowingIO、
可视化埋点实际上是用一个系统来实现手动插入代码埋点的过程。
优点
- 埋点只需业务同学接入,无需开发支持
- 解决了代码埋点的工作量大和更新代价大问题
缺点
- 无法做到自定义获取数据,可视化埋点覆盖的功能有限
- 开发周期长,难度大
- 无埋点
前端的任意一个事件都被绑定一个标识,所有的事件都记录下来。上传记录文件,配合文件解析,解析出来我们想要的数据传递给server端。
比如从页面的js代码中,找出dom上被绑定的事件,然后进行全埋点。
优点
- 由于采集的是全量数据,所以产品迭代过程中是不需要关注埋点逻辑的,也不会出现漏埋、误埋等现象
- 减少了沟通成本
缺点
- 无埋点采集全量数据,给数据传输和服务器增加压力
- 无法灵活的定制各个事件所需要上传的数据
- 配置化埋点
结合无埋点+代码埋点,通过配置文件或者特定标示指定需要埋点的元素或者方法,当定位到用户点击的元素或者执行的方法在配置文件中则上传埋点。
优点
- 可灵活的定制各个事件所需要上传的数据
- 减轻服务器压力
- 维护成本低
缺点
- 需求开发支持配置