作者: 菜鸡前端工程师
原文链接:webpack4从入门到精通保姆级教程
本文主要通过3部分来介绍webpack4相关技术点,分为基础、进阶、原理3各部分全面讲述。以下是基础部分。
基础篇:webpack 与构建发展简史
为什么需要构建工具?
- 转换 ES6 语法
- 转换 JSX
- CSS 前缀补全/预处理器
- 压缩混淆
- ES6 module 主流浏览器支持情况图片压缩
为什么选择 webpack?
- 社区生态丰富
- 配置灵活和插件化扩展
- 官方更新迭代速度快
初识 webpack:配置⽂件名称
- webpack 默认配置⽂文件:webpack.config.js
- 可以通过 webpack --config 指定配置⽂件
初识 webpack:webpack 配置组成
module.exports = {
// 打包的⼊入⼝口⽂文件
entry: "./src/index.js",
// 打包的输出
output: "./dist/main.js",
// 环境
mode: "production",
// Loader 配置
module: {
rules: [{ test: /.txt$/, use: "raw-loader" }]
},
// 插件配置
plugins: [
new HtmlwebpackPlugin({
template: "./src/index.html"
})
]
};
复制代码
环境搭建:安装 Node.js 和 NPM
-
安装 nvm(github.com/nvm-sh/nvm)
- 通过 curl 安装:curl -o- raw.githubusercontent.com/nvm-sh/nvm/… | bash
- 通过 wget 安装:wget -qO- raw.githubusercontent.com/nvm-sh/nvm/… | bash
-
安装 Node.js 和 NPM
- nvm install v10.15.3
- 检查是否安装成功:node -v, npm -v
环境搭建:安装 webpack 和 webpack-cli
创建空目录和 package.json
- mkdir my-project
- cd my-project
- npm init -y
安装 webpack 和 webpack-cli
- npm install webpack webpack-cli --save-dev
- 检查是否安装成功:./node_modules/.bin/webpack -v
基础篇:webpack 基础⽤法
核⼼概念之 Entry
Entry ⽤来指定 webpack 的打包入口
理解依赖图的含义
依赖图的入口是 entry 对于非代码,比如图片、字体依赖也会不断加入到依赖图中
Entry 的⽤法
单入口:entry 是⼀个字符串
module.exports = {
entry: "./path/to/my/entry/file.js"
};
复制代码
多入口:entry 是⼀个对象
module.exports = {
entry: {
app: "./src/app.js",
adminApp: "./src/adminApp.js"
}
};
复制代码
核心概念之 Output
Output ⽤来告诉 webpack 如何将编译后的⽂件输出到磁盘
Output 的用法:单入口配置
module.exports = {
entry: './path/to/my/entry/file.js'
output: {
filename: 'bundle.js’,
path: __dirname + '/dist'
}
};
复制代码
Output 的用法:多入口配置
module.exports = {
entry: {
app: "./src/app.js",
search: "./src/search.js"
},
output: {
// 通过占位符确保⽂件名称的唯⼀
filename: "[name].js",
path: __dirname + "/dist"
}
};
复制代码
核心概念之 Loaders
webpack 开箱即用只支持 JS 和 JSON 两种文件类型,通过 Loaders 去支持其它文件类型并且把它们转化成有效的模块,并且可以添加到依赖图中。本身是一个函数,接受源文件作为参数,返回转换的结果。
常见的 Loaders 有哪些?
Loaders 的用法
- test 指定匹配规则
- use 指定使⽤的 loader 名称
const path = require("path");
module.exports = {
output: {
filename: "bundle.js"
},
module: {
rules: [{ test: /.txt$/, use: "raw-loader" }]
}
};
复制代码
核心概念之 Plugins
插件用于 bundle 文件的优化,资源管理和环境变量注入.作用于整个构建过程
常见的 Plugins 有哪些?
Plugins 的用法
const path = require("path");
module.exports = {
output: {
filename: "bundle.js"
},
// 放到 plugins 数组⾥
plugins: [new HtmlWebpackPlugin({ template: "./src/index.html" })]
};
复制代码
核心概念之 Mode
Mode 用来指定当前的构建环境是:production、development 还是 none。设置 mode 可以使⽤ webpack 内置的函数,默认值为 production
Mode 的内置函数功能
资源解析:解析 ES6
使用 babel-loader。babel 的配置文件是:.babelrc
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist")
},
module: {
rules: [
{
test: /.js$/,
use: "babel-loader"
}
]
}
};
复制代码
资源解析:增加 ES6 的 babel preset 配置
{
"presets": [
// 增加 ES6 的 babel preset 配置
"@babel/preset-env"
],
"plugins": [
"@babel/proposal-class-properties"
]
}
复制代码
资源解析:解析 React JSX
{
"presets": [
"@babel/preset-env",
// 增加 React 的 babel preset 配置
"@babel/preset-react"
],
"plugins": [
"@babel/proposal-class-properties"
]
}
复制代码
资源解析:解析 CSS
- css-loader ⽤于加载 .css ⽂件,并且转换成 commonjs 对象
- style-loader 将样式通过 style 标签插⼊到 head 中
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist")
},
module: {
rules: [
{
test: /.css$/,
use: ["style-loader", "css-loader"]
}
]
}
};
复制代码
资源解析:解析 Less 和 SaSS
less-loader ⽤于将 less 转换成 css
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(___dirname, "dist")
},
module: {
rules: [
{
test: /.less$/,
use: ["style-loader", "css-loader", " less-loader"]
}
]
}
};
复制代码
资源解析:解析图片
file-loader 用于处理⽂件
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist")
},
module: {
rules: [
{
test: /.(png|svg|jpg|gif)$/,
use: ["file-loader"]
}
]
}
};
复制代码
资源解析:解析字体
file-loader 用于处理文件
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist")
},
module: {
rules: [
{
test: /.(woff|woff2|eot|ttf|otf)$/,
use: ["file-loader"]
}
]
}
};
复制代码
webpack 中的文件监听
⽂件监听是在发现源码发生变化时,自动重新构建出新的输出文件。
webpack 开启监听模式,有两种方式:
- 启动 webpack 命令时,带上 --watch 参数
- 在配置 webpack.config.js 中设置 watch: true
webpack 中的文件监听使用
唯⼀缺陷:每次需要手动刷新浏览器
{
"name": "hello-webpack",
"version": "1.0.0",
"description": "Hello webpack",
"main": "index.js",
"scripts": {
"build": "webpack ",
"watch": "webpack --watch"
},
"keywords": [],
"author": "",
"license": "ISC"
}
复制代码
文件监听的原理分析
轮询判断文件的最后编辑时间是否变化
某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout、
module.export = {
//默认 false,也就是不开启
watch: true,
//只有开启监听模式时,watchOptions才有意义
wathcOptions: {
//默认为空,不监听的文件或者文件夹,支持正则匹配
ignored: /node_modules/,
//监听到变化发生后会等300ms再去执行,默认300ms
aggregateTimeout: 300,
//判断文件是否发生变化是通过不停询问系统指定文件有没有变化实现的,默认每秒问1000次
poll: 1000
}
};
复制代码
热更新:webpack-dev-server
WDS 不刷新浏览器
WDS 不输出⽂件,而是放在内存中
使⽤ HotModuleReplacementPlugin 插件
{
"name": "hello-webpack",
"version": "1.0.0",
"description": "Hello webpack",
"main": "index.js",
"scripts": {
"build": "webpack ",
"dev": "webpack-dev-server --open"
},
"keywords": [],
"author": "",
"license": "ISC"
}
复制代码
热更新:使用 webpack-dev-middleware
WDM 将 webpack 输出的文件传输给服务器。使用于灵活的定制场景
const express = require("express");
const webpack = require("webpack");
const webpackDevMiddleware = require("webpack-dev-middleware");
const app = express();
const config = require("./webpack.config.js");
const compiler = webpack(config);
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
})
);
app.listen(3000, function() {
console.log("Example app listening on port 3000!\n");
});
复制代码
热更新的原理分析
Webpack Compile: 将 JS 编译成 Bundle
HMR Server: 将热更新的⽂件输出给 HMR Rumtime
Bundle server: 提供⽂件在浏览器的访问
HMR Rumtime: 会被注⼊到浏览器,更新⽂件的变化
bundle.js: 构建输出的⽂件
什么是文件指纹?
打包后输出的⽂件名的后缀
文件指纹如何生成
-
Hash
- 和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改
-
Chunkhash
- 和 webpack 打包的 chunk 有关,不同的 entry 会生成不同的 chunkhash 值
-
Contenthash
- 根据⽂件内容来定义 hash ,⽂件内容不变,则 contenthash 不变
JS 的文件指纹设置
设置 output 的 filename,使用 [chunkhash]
module.exports = {
entry: {
app: "./src/app.js",
search: "./src/search.js"
},
output: {
filename: "[name][chunkhash:8].js",
path: __dirname + "/dist"
}
};
复制代码
CSS 的文件指纹设置
设置 MiniCssExtractPlugin 的 filename,使用 [contenthash]
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name][chunkhash:8].js',
path: __dirname + '/dist'
},
plugins: [
new MiniCssExtractPlugin({
filename: [name][contenthash:8].css
})
]
}
复制代码
图片的文件指纹设置
设置 file-loader 的 name,使用 [hash]
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist")
},
module: {
rules: [
{
test: /.(png|svg|jpg|gif)$/,
use: [
{
loader: "file-loader",
options: {
name: "img/[name][hash:8].[ext] "
}
}
]
}
]
}
};
复制代码
代码压缩
- HTML 压缩
- CSS 压缩
- JS 压缩
JS 文件的压缩
内置了 uglifyjs-webpack-plugin
CSS 文件的压缩
使⽤ optimize-css-assets-webpack-plugin,同时使用cssnano
module.exports = {
entry: {
app: "./src/app.js",
search: "./src/search.js"
},
output: {
filename: "[name][chunkhash:8].js",
path: __dirname + "/dist"
},
plugins: [
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /.css$/g,
cssProcessor: require("cssnano")
})
]
};
复制代码
html 文件的压缩
修改 html-webpack-plugin,设置压缩参数
module.exports = {
entry: {
app: "./src/app.js",
search: "./src/search.js"
},
output: {
filename: "[name][chunkhash:8].js",
path: __dirname + "/dist"
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, "src/search.html"),
filename: "search.html",
chunks: ["search"],
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
]
};