webpack
webpack 是什么,解决了什么问题
zhuanlan.zhihu.com/p/44438844/
webpack 是一个现代 javascript 应用程序的静态模块打包器。 当 webpack 处理应用程序时,会递归后见一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个 bundle.
解决什么什么问题
- 具备代码编译功能,将我们开发阶段编写的代码转换为大多数浏览器环境能运行的代码
- webpack 通过 loader 和 plugin 的编译,提升了开发人员的开发效率
作用
- 模块打包: 可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。保证项目结构的清晰和可读性。
- 编译兼容: 可以对代码做 polyfill ,还可以编译转换 .less, .vue, .jsx 文件。
- 能力拓展:通过 webpack 的 Plugin 机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能,帮助我们进一步提高自动化程度,工程效率以及打包输出的质量。
核心概念
- 入口(entry)
- 输出 (output)
- loader
- 插件(plugin)
- 模式(mode)
- 浏览器兼容性()
- 环境(environment)
= 在 webpack 里一个模块对应着一个文件,webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
webpack 的工作原理,构建流程简介
- 初始化:启动构建,读取和合并配置参数,
- 编译:从入口文件出发,调用模块配置的 loader,对模板进行编译,然后再找到该模块依赖的模块进行编译,递归进行编译处理
- 输出:根据入口和依赖之间的关系,将编译的模块组合成一个个的 Chunk,将 chunk 转换成文件根据配置输出到文件列表中。
webpack 的工作原理,构建流程(只需要看看)
初始化阶段:
- 1.初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
- 2.开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行 Compiler 对象的 run 方法开始执行编译;
- 3.确定入口:根据配置中的 entry 找出所有的入口文件;
编译阶段:
- 4.编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
- 5.完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
生成阶段:
- 6.输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
- 7.输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
loader
原理
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。loader 本质上是导出为函数的 JavaScript 模块
loader 的分类
后置 post,普通 normal,行内 inline,前置 pre。
优先级
四种 loader 调用先后顺序为:pre > normal > inline > post 。
在相同种类 loader 的情况下,调用的优先级为,自下而上,自右向左。(pitch 情况下,则反过来)。
简单实现一个 loader
一个 loader 是一个导出为函数的 js 模块,这个函数有三个参数:content, map, meta
- content: 表示源文件字符串或者 buffer
- map: 表示 sourcemap 对象
- meta: 表示元数据,辅助对象
// 获取webpack配置的options,写loader的固定套路第一步
cosnt { getOptions } = require('loader-utils')
module.exports = function(content, map, meta){
const options = getOptions(this) || {}
cosnt code = JSON.stringify(content)
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
const esModule = typeof options.esModule !== 'undefined' ? options.esModule : true
// 返回内容
return `${esModule ? 'export default' : 'module.exports ='} ${json};`;
}
复制代码
常用的 loader
- less-loader :将 less 规格的内容转换为标准 css
- css-loader :将 css 内容包裹为 JavaScript 模块
- style-loader :将 JavaScript 模块的导出结果以 link 、style 标签等方式挂载到 html 中,让 css 代码能够正确运行在浏览器上
plugin
plugin 通常是在 webpack 在打包的某个时间节点做一些操作,Plugin便是负责功能扩展.
webpack 基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的阶段执行自己的插件任务,从而实现自己想要的功能。
实现一个 plugin
- 一个 js 类
- 原型上需要一个 apply 方法
- 传给每个插件的 compiler 和 compilation 对象都是同一个引用,若在一个插件中修改了它们身上的属性,会影响后面的插件;
class MyPlugin {
apply(compiler) {
compiler.hooks.done.tap('My Plugin', stats => {
console.log('Bravo!');
});
}
}
module.exports = MyPlugin;
复制代码
babel
babel 就是一个 Javascript Transpiler。
用途
我们平时主要用 babel 做 3 件事情
- 转译 exNext、typescript、flow 等到目标环境支持的 js
- 一些特定用途的代码转换。比如自动埋点
- 代码的静态分析
babel 的名字来自巴别塔的典故,是一个 js 转译器,用于 es next、typescript 等代码的转换,同时还暴露出了 api 让开发者可以进行特定用途的转换。除此以外,还可以做各种静态分析。
babel 是如何把 es6 转成 es5 的
babel 的转译过程分为三个阶段:parsing(解析)、transforming(转化)、generating(生成)
- Parse: 通过 parser 把源码转成抽象语法树 (AST)
babel 将 es6+(指 es6 及以上版本)分为语法层和 api 层:语法层: let、const、class、箭头函数等,这些需要在构建时进行转译,是指在语法层面上的转译,(比如 class…将来会被转 译成 var function…) api 层:Promise、includes、map 等,这些是在全局或者 Object、Array 等的原型上新增的方法,它们可以由相应 es5 的方式重 新定义
- Transfrom: 遍历 AST, 调用各种 transform 插件对 AST 惊醒增删改
- generate: 把转换后的 AST 打印成目标代码,并生成 sourcemap
vite
是一个基于浏览器原生 ES 模块导入的开发服务器,在开发环境下,利用浏览器去解析 import,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随启随用。同时不仅对 Vue 文件提供了支持,还支持热更新,而且热更新的速度不会随着模块增多而变慢。在生产环境下使用 Rollup 打包
- 1.Webpack、Rollup 这些构建工具在本地开发调试的时候,会提前把模块打包成浏览器可读取的 js bundle。
- 2.如路由懒加载等优化手段,但懒加载并不代表懒构建,Webpack 还是需要把你的异步路由用到的模块提前构建好。
-
- Vite 利用了浏览器原生 ES Module 的支持,在本地启动一个服务器,当浏览器读取这个 html 文件的时候,会在执行到 import 的时候才向服务端发送请求模块的请求。
- 4.vite 的服务端会通过内部的一些机制,将模块解析成浏览器可以执行的 js 文件返回到浏览器端。
- 5.这就保证了只用真正使用这个模块的时候,浏览器才会请求且解析加载,最大程度的做到了按需加载
- 6.依赖预编译,其实是 Vite 2.0 在为用户启动开发服务器之前,先用 esbuild 把检测到的依赖预先构建了一遍,全部打包成为一个传统的 js bundle
<div id="app"></div>
<script type="module">
import { createApp } from 'vue';
import Main from './Main.vue';
createApp(Main).mount('#app');
</script>
复制代码
webpack 面试题
为什么开发时候的代码
HMR(Hot Module Replacement)热更新又称热替换
当你对代码进行修改并保存后,webpack 将对代码重新打包,并将新的模块发送到浏览器端,浏览器通过新的模块替换老的模块,这样在不刷新浏览器的前提下就能够对应用进行更新。
- HMR 的核心就是客户端从服务端拉取更新后的文件。准确的说是 chunk diff(chunk 需要更新的部分),
- 实际上 WDS(webpack-dev-server)与浏览器之间维护了一个 websocket 进行通信。
- 当本地资源发生变化后, WDS 会向浏览器推送更新,并带上构建时的 hash,让浏览器与上一次的资源进行对比
- 客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash),也就是请求 update.json,
- 这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该 chunk 的增量更新也就是请求 update.js。更新的 js 是存储在内存中的
常见的 loader
raw-loader,file-loader,url-loader,babel-loader,less-loader,vue-loader, ts-loader
常见的 Plugin
- DefinePlugin 定义环境变量 Webpack4 之后指定 mode 会自动配置
- html-webpack-plugin
- uglifyjs-webpack-plugin 压缩插件
- clean-webpack-plugin 目录清理
- ModuleConcatenationPlugin 开启 Scope
- speed-measure-webpack-plugin 以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时)
- webpack-bundle-analyzer 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)
loader 和 plugin 的区别
loader
module,chunk 和 bundle 的区别是什么?
我们直接写出来的是 module,webpack 处理时是 chunk,最后生成浏览器可以直接运行的 bundle。
webpck 项目优化
查看打包后的页面
"preview": "node build/index.js --preview"
复制代码
运行时优化
-
1.preload 插件去除 map 等不需要提前加载的 js 文件
-
2.删除 prefetch,Prefetch 链接将会消耗带宽。
-
3.因为开发环境的时候使用路由懒加载热更新变慢, 使用 babel-plugin-dynamic-import-node 使开发环境不需要懒加载
env: {
development: {
plugins: ['dynamic-import-node'];
}
}
复制代码
-
- 增加 postcss.config.js 自动为 css 添加不同浏览器的标识前缀:
module.exports = {
plugins: {
autoprefixer: {}
}
};
复制代码
打包时的优化
-
1.升级 vue-cli 这一步不用配置已经提升了打包速度和包大小。
-
2.splitChunks 对资源进行分块。element-ui,echarts 等
-
3.分析一些大的文件再次进行分包
1.1 大小 26.6 MB