webpack由于比较复杂,知识点实在是太多了,目前总结的还不成体系,后面慢慢再更新。
webpack的五个核心基础
entry
代表着webpack打包的入口文件(打包的起点),默认值是./src/index.js。
module.exports = {
entry:'./path/to/my/entry/file.js'
}
output
表示webpack打包资源后,输出到哪里,以及如何命名。主要输出文件的默认路径是./dist/main.js,其他生成文件默认放在./dist文件夹中。
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: { path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
}
};
loader
代码编译:由于webpack只能解析JavaScript和json文件,loader让webpack能够处理其他文件,比如vue、scss文件。
const path = require('path');
module.exports = {
output: {
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{ test: /.txt$/, use: 'raw-loader' }
]
}
};
plugin
扩展功能:可以用于执行范围更广的任务。从打包优化和压缩,一直到重新定义环境中的变量等。例如代码混淆、代码分割、代码压缩、按需加载。
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件
module.exports = {
module: {
rules: [
{ test: /.txt$/, use: 'raw-loader' }
]
},
plugins: [
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
mode
指示 Webpack使用相应模式的配置。默认为production。
module.exports = {
mode: 'production'
};
webpack和grunt/glup的比较
grunt/glup的核心是task
- 可以配置一系列的task,并且定义task要处理的事务(例如ES6、ts转化、图片压缩、scss转成css)
- 之后让grunt/gulp来依次执行这些task,而且让整个流程自动化
glup的task
- 下面的task就是将src下面的所有js文件转为ES5的语法
- 并且最终输出到dist文件夹下
const gulp = require('gulp')
const babel = require('gulp-babel')
gulp.task('js',()=>{
gulp.src('src/*.js')
.pipe(babel({ presets:['es2015'] }))
.pipe(gulp.dest('dist'))
})
什么时候用grunt/gulp
- 功能模块依赖非常简单,甚至没有用到模块化的概念,只需要进行简单的合并、压缩,就是用grunt/gulp即可
- 如果整个项目使用了模块化管理,而且相互依赖非常强,我们就使用webpack
grunt/gulp和webpack有什么不同
- grunt/gulp更强调前端流程的自动化,模块化不是它的核心
- webpack更强调模块化开发管理,而文件压缩合并、预处理等功能,是它的附带功能
有哪些常见的loader?
- raw-loader:加载文件的原始内容
- file-loader:把文件输出到一个文件夹中,在代码中通过相对应的url去引用输出的文件(处理图片和字体)
- url-loader:与file-loader类似,区别是用户可以设置一个阈值,大于阈值会交给file-loader处理,小于阈值会返回文件base64形式的编码
- image-loader:加载并且压缩图片文件
- json-loader:加载JSON文件(默认包含)
- babel-loader:把ES6语法转为ES5
- ts-loader:将typeScript语法转为JavaScript语法
- awesome-typescript-loader:将typescript转为JavaScript,性能优于ts-loader
- css-loader:加载css,支持模块化、压缩、文件导入等特征
- style-loader:把css代码注入到JavaScript中,通过DOM操作加载css
- eslint-loader:通过esLint检查JavaScript代码
- tslint-loader:通过tsLint检查typeScript代码
有哪些常见的plugin?
- define-plugin:定义环境变量 (Webpack4 之后指定 mode 会自动配置)
- ignore-plugin:忽略部分文件
- html-webpack-plugin:简化 HTML 文件创建 (依赖于 html-loader)
- web-webpack-plugin:可方便地为单页应用输出 HTML,比 html-webpack-plugin 好用
- uglifyjs-webpack-plugin:不支持 ES6 压缩 (Webpack4 以前)
- terser-webpack-plugin: 支持压缩 ES6 (Webpack4)
- webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
- mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin)
- serviceworker-webpack-plugin:为网页应用增加离线缓存功能
- clean-webpack-plugin: 目录清理
- ModuleConcatenationPlugin: 开启 Scope Hoisting
- speed-measure-webpack-plugin: 可以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时)
- webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)
loader和plugin的区别?
- Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。
- Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
- Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。
- Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。
webpack构建流程?
从启动到结束
- 初始化参数:解析webpack配置参数,合并shell,传入和webpack.config.js文件配置的参数,形成最后的配置结果
- 开始编译:上一步得到的参数初始化compiler对象,注册所有配置的插件,插件监听webpack构建生命周期的事件节点,做出相应的反应,执行对象的run方法,然后开始执行编译;
- 确定入口:从配置的entry入口,开始解析文件构建AST语法树,找出依赖,递归下去;
- 编译模块:递归中根据文件类型和loader配置,调用所有配置的loader对文件进行转换,再找出该模块依赖的模块,再递归本步骤知道所有入口依赖的文件都经过了本步骤的处理;
- 完成模块编译:经过第四步,使用loader翻译完所有模块后,得到了每个模块被翻译后的最终内容,以及他们之间的依赖关系;
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,再把每个chunk转换成一个单独的文件加入到输出列表,这一步是可以修改输出内容的最后机会;
- 输出完成:确定好输出内容后,根据配置确定输出路径和文件名,把文件内容写入到文件系统;
模块打包原理是什么?
Webpack 实际上为每个模块创造了一个可以导出和导入的环境,本质上并没有修改 代码的执行逻辑,代码执行顺序与模块加载顺序也完全一致。
文件监听的原理是什么?
在发现源码发生变化时,自动重新构建出新的输出文件。
Webpack开启监听模式,有两种方式:
- 启动 webpack 命令时,带上 --watch 参数
- 在配置 webpack.config.js 中设置 watch:true
缺点:每次需要手动刷新浏览器
原理:轮询判断文件的最后编辑时间是否变化,如果某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout 后再执行。
module.export = {
// 默认false,也就是不开启
watch: true,
// 只有开启监听模式时,watchOptions才有意义
watchOptions: {
// 默认为空,不监听的文件或者文件夹,支持正则匹配
ignored: /node_modules/,
// 监听到变化发生后会等300ms再去执行,默认300ms
aggregateTimeout:300,
// 判断文件是否发生变化是通过不停询问系统指定文件有没有变化实现的,默认每秒问1000次
poll:1000
}
}
webpack热更新(HMR)的原理
热更新(HMR)机制可以做到不刷新浏览器,而将新变更的模块替换成旧的模块。HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上 WDS(webpack dev server) 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。
后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loader 和 vue-loader 都是借助这些 API 实现 HMR。