一。常见的构建工具对比
项目工具 | 优点 | 缺点 | 对比 |
---|---|---|---|
npm script | 1.内置,无须安装其他依赖 | 提供了pre和post两个钩子,但不能方便地管理多个任务之间但依赖 | --- |
grunt | 灵活,1.它只负责执行我们定义的任务,2大量的可复用插件封装好了常见的构建任务。 | 集成度不高,要写很多配置后才可以用,无法开箱即用。 | grunt 相当于是进化版的npm script,弥补它的不足。 |
gulp | 基于流的自动化构建工具,管理和执行任务,还支持监听、读写文件。 | 集成度不高,要写很多配置后才可以用,无法开箱即用。 | gulp 是 grunt的加强版,gulp增加了监听、读写文件,流式的处理功能。 |
Fis3 | 集成了各种web开发所需的构建功能,配置简单,开箱即用。 | 官方不维护,不支持最新node.js | 它专注于web开发的完整解决方案,如果将grunt,gulp比作汽车的发动机,则可以将fis3比作一辆完整的汽车。 |
webpack | 1,专注于处理模块化的项目,能做到开箱即用,一步到位;2.可通过plugin扩展,完整好用不失灵活。3.使用场景不局限于web开发;4.社区活跃;5.良好开发体验。 | 只能用于采用模块化开发的项目。 | ------ |
Rollup | 和webpack很类似但专注于es6但模块打包工具 | -- | -- |
DevServer
1.提供 HTTP 服务而不是使用本地文件预览;
2.监听文件的变化并自动刷新网页,做到实时预览;
3.支持 Source Map,以方便调试。
webpack-dev-server --hot --devtool source-map
配置 核心概念
- Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
- Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
- Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
- Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
- Plugin:扩展插件,在 Webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情。
- Output:输出结果,在 Webpack 经过一系列处理并得出最终想要的代码后输出结果。
配置 Loader
rules 配置模块的读取和解析规则,通常用来配置 Loader。其类型是一个数组,数组里每一项都描述了如何去处理部分文件。 配置一项 rules 时大致通过以下方式:
- 条件匹配:通过 test 、 include 、 exclude 三个配置项来命中 Loader 要应用规则的文件。
- 应用规则:对选中后的文件通过 use 配置项来应用 Loader,可以只应用一个 Loader 或者按照从后往前的顺序应用一组 Loader,同时还可以分别给 Loader 传入参数。
- 重置顺序:一组 Loader 的执行顺序默认是从右到左执行,通过 enforce 选项可以让其中一个 Loader 的执行顺序放到最前或者最后。
webpack -- 优化webpack构建
1. 优化开发体验- 既提升开发效率
- 优化构建速度
1) 缩小文件搜索范围
2) 使用 DllPlugin
3) 使用 HappyPack
4) 使用 ParallelUglifyPlugin - 优化使用体验,通过自动化手段完成一些重复的工作。
1)使用自动刷新
2)开启模块热替换
2. 优化输出质量
- 减少用户能感知的加载时间,也就是首屏加载时间
- 提升流畅度,提升代码性能
优化输出质量本质是优化构建输出的要发布到线上的代码,分为以下几点:
-
减少用户能感知到的加载时间,也就是首屏加载时间。
4-7 区分环境 :减少开发环境的代码,缩小体积。
4-8 压缩代码: 也是缩小体积,以及安全。
-
- 相同的资源被重复的加载,浪费用户的流量和服务器的成本;
- 每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。
- 虽然用户第一次打开网站的速度得不到优化,但之后访问其它页面的速度将大大提升。
提取方法:Webpack 内置插件CommonsChunkPlugin
4-12 按需加载: Webpack 内置了对 import(*) 语句的支持
以 ./show.js 为入口新生成一个 Chunk;
当代码执行到 import 所在语句时才会去加载由 Chunk 对应生成的文件。
import 返回一个 Promise,当文件加载成功时可以在 Promise 的 then 方法中获取到 show.js 导出的内容。
-
提升流畅度,也就是提升代码性能。
3. 优化总结
优化开发体验- 既提升开发效率
1) 缩小文件搜索范围
1.1 开发体验 -- 缩小文件的搜索范围
- 优化Loader配置
- 优化resolve.modules配置
- 优化resolve.mainFields配置
- 优化resolve.alias配置
- 优化resolve.extensions配置
- 优化module.noParse配置
总体的优化的配置如下:
const path = require('path');
module.exports = {
// JS 执行入口文件
entry: './src/main.js',
output: {
// 把所有依赖的模块合并输出到一个 bundle.js 文件
filename: 'bundle.js',
// 输出文件都放到 dist 目录下
path: path.resolve(__dirname, './dist'),
},
resolve: {
// 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
modules: [path.resolve(__dirname, 'node_modules')],
// 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
mainFields: ['main'],
// 使用 alias 把导入 react 的语句换成直接使用单独完整的 react.min.js 文件,减少耗时的递归解析操作
alias: {
'react': path.resolve(__dirname, './node_modules/react/dist/react.min.js'),
'react-dom': path.resolve(__dirname, './node_modules/react-dom/dist/react-dom.min.js'),
},
// 尽可能的减少后缀尝试的可能性
extensions: ['js'],
},
module: {
// 独完整的 `react.min.js` 文件就没有采用模块化,忽略对 `react.min.js` 文件的递归解析处理
noParse: [/react\.min\.js$/],
rules: [
{
// 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
test: /\.js$/,
// babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
use: ['babel-loader?cacheDirectory'],
// 只对项目根目录下的 src 目录中的文件采用 babel-loader
include: path.resolve(__dirname, 'src'),
},
]
},
};
1.2 使用 DllPlugin -- 动态链接库
为什么给 Web 项目构建接入动态链接库的思想后,会大大提升构建速度呢? 原因在于包含大量复用模块的动态链接库只需要编译一次,在之后的构建过程中被动态链接库包含的模块将不会在重新编译,而是直接使用动态链接库中的代码。 由于动态链接库中大多数包含的是常用的第三方模块,例如 react、react-dom,只要不升级这些模块的版本,动态链接库就不用重新编译。
webpack已经内置了对动态链接库的支持,需要通过2个插件完成
- DllPlugin插件:用于打包出一个个单独的动态链接库文件。
- DllReferencePlugin 插件: 用于在主要的配置文件中引入DllPlugin插件打包好的动态链接库文件。
构建出动态链接库文件
构建输出的以下这四个文件
├── polyfill.dll.js
├── polyfill.manifest.json
├── react.dll.js
└── react.manifest.json
和以下这一个文件:
├── main.js
以上JS是由两份不同的构建分别输出的。
一。动态链接库文件相关的文件需要由一份独立的构建输出,用于给主构建使用。新建一个 Webpack 配置文件 webpack_dll.config.js 专门用于构建它们,通过命令:webpack --config webpack_dll.config.js 构建,文件内容如下:
const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
module.exports = {
// JS 执行入口文件
entry: {
// 把 React 相关模块的放到一个单独的动态链接库
react: ['react', 'react-dom'],
// 把项目需要所有的 polyfill 放到一个单独的动态链接库
polyfill: ['core-js/fn/object/assign', 'core-js/fn/promise', 'whatwg-fetch'],
},
output: {
// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
// 也就是 entry 中配置的 react 和 polyfill
filename: '[name].dll.js',
// 输出的文件都放到 dist 目录下
path: path.resolve(__dirname, 'dist'),
// 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
// 之所以在前面加上 _dll_ 是为了防止全局变量冲突
library: '_dll_[name]',
},
plugins: [
// 接入 DllPlugin
new DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
// 例如 react.manifest.json 中就有 "name": "_dll_react"
name: '_dll_[name]',
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(__dirname, 'dist', '[name].manifest.json'),
}),
],
};
用于输出 main.js 的主 Webpack 配置文件内容如下:
const path = require('path');
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
module.exports = {
entry: {
// 定义入口 Chunk
main: './main.js'
},
output: {
// 输出文件的名称
filename: '[name].js',
// 输出文件都放到 dist 目录下
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
// 项目源码使用了 ES6 和 JSX 语法,需要使用 babel-loader 转换
test: /\.js$/,
use: ['babel-loader'],
exclude: path.resolve(__dirname, 'node_modules'),
},
]
},
plugins: [
// 告诉 Webpack 使用了哪些动态链接库
new DllReferencePlugin({
// 描述 react 动态链接库的文件内容
manifest: require('./dist/react.manifest.json'),
}),
new DllReferencePlugin({
// 描述 polyfill 动态链接库的文件内容
manifest: require('./dist/polyfill.manifest.json'),
}),
],
devtool: 'source-map'
};
注意:在 webpack_dll.config.js 文件中,DllPlugin 中的 name 参数必须和 output.library 中保持一致。 原因在于 DllPlugin 中的 name 参数会影响输出的 manifest.json 文件中 name 字段的值, 而在 webpack.config.js 文件中 DllReferencePlugin 会去 manifest.json 文件读取 name 字段的值, 把值的内容作为在从全局变量中获取动态链接库中内容时的全局变量名。
1)使用自动刷新
自动刷新的原理
控制浏览器刷新有三种方法:
- 借助浏览器扩展去通过浏览器提供的接口刷新,WebStorm IDE 的 LiveEdit 功能就是这样实现的。
- 往要开发的网页中注入代理客户端代码,通过代理客户端去刷新整个页面。
- 把要开发的网页装进一个 iframe 中,通过刷新 iframe 去看到最新效果。
DevServer 支持第2、3种方法,第2种是 DevServer 默认采用的刷新方法。
要让 Webpack 开启监听模式,有两种方式:
在配置文件 webpack.config.js 中设置 watch: true。
在执行启动 Webpack 命令时,带上 --watch 参数,完整命令是 webpack --watch。
- watch: 监听文件变化。
- inline: 通过什么方式去刷新页面,iframe或者客户端方式websocket。默认是websocket,
- hot: 模块热替换,既修改了什么文件就替换什么文件。
总结:若想要开发构建速度提升,关闭粗暴的inline,开启文件监听,以及排除node_modoules监听,和监听时间。
devServer: { // DevServer 相关的配置
proxy: { // 代理到后端服务接口
'/api': 'http://localhost:3000'
},
contentBase: path.join(__dirname, 'public'), // 配置 DevServer HTTP 服务器的文件根目录
compress: true, // 是否开启 gzip 压缩
historyApiFallback: true, // 是否开发 HTML5 History API 网页
hot: true, // 是否开启模块热替换功能,建议开启这个。
https: false, // 是否开启 HTTPS 模式
inline: false,
profile: true, // 是否捕捉 Webpack 构建的性能信息,用于分析什么原因导致构建性能不佳
cache: false, // 是否启用缓存提升构建速度
watch: true, // 是否开始, 这个参数webpack4 写了后报错,应该是默认开启了。
watchOptions: { // 监听模式选项
// 不监听的文件或文件夹,支持正则匹配。默认为空
ignored: /node_modules/,
// 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
// 默认为300ms
// 值越大性能越好,因为这能降低重新构建的频率。
aggregateTimeout: 300,
// 判断文件是否发生变化是不停的去询问系统指定文件有没有变化,默认每隔1000毫秒询问一次
// watchOptions.poll 值越大越好,因为这能降低检查的频率。
poll: 1000
}
}
按需加载:
//第一步:内置的webpack功能: import(/* webpackChunkName: "show" */ './show')
window.document.getElementById('btn').addEventListener('click', function () {
// 当按钮被点击后才去加载 show.js 文件,文件加载成功后执行文件导出的函数
import(/* webpackChunkName: "show" */ './show').then((show) => {
show('Webpack');
})
});
//二。搭配的配置文件chunkFilename
const path = require('path');
module.exports = {
// JS 执行入口文件
entry: {
main: './main.js',
},
output: {
// 为从 entry 中配置生成的 Chunk 配置输出文件的名称
filename: '[name].js',
// 为动态加载的 Chunk 配置输出文件的名称
chunkFilename: '[name].js',
path: path.resolve(__dirname, './dist'),
}
};
webpack ---- 常用插件
-
适用用单页-多页面的插件 web-webpack-plugin.
一个很好的html-webpack-plugin替代品, 可以使 webpack 以 HTML 为入口和方便的管理多个页面。
-
ExtractTextPlugin
插件的作用是提取出 JavaScript 代码里的 CSS 到一个单独的文件。 对此你可以通过插件的 filename 属性,告诉插件输出的 CSS 文件名称是通过 [name]_[contenthash:8].css 字符串模版生成的,里面的 [name] 代表文件名称, [contenthash:8] 代表根据文件内容算出的8位 hash 值
-
CommonsChunkPlugin 内置插件
https://webpack.docschina.org/plugins/commons-chunk-plugin/
基础使用方式
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
new CommonsChunkPlugin({
// 从哪些 Chunk 中提取
chunks: ['a', 'b'],
// 提取出的公共部分形成一个新的 Chunk,这个新 Chunk 的名称
name: 'common'
})