一、为什么要学 Webpack?
当前 Web 开发面临的困境 文件依赖关系错综复杂、静态资源请求效率低、模块化支持不友好、浏览器对高级 JS 特性兼容性较低
Webpack 是一个流行的前端项目构建工具(打包工具),可以解决当前 web 开发中所面临的困境,是⼯程化、⾃动化思想在前端开发中的体现。 webpack 提供了友好的模块化支持,以及代码压缩混淆、处理 js 兼容问题(需要结合babel)、性能优化等强大的功能,从而让程序员把 工作的重心放到具体的功能实现上,提高了开发效率和项目的可维护性。。
二、 环境搭建
- 新建项目空白目录,并运行 npm init –y 命令,初始化包管理配置文件 package.json
- 运行 npm install webpack webpack-cli –D 命令,安装 webpack 相关的包
- -D 开发依赖, --save-dev 简写,只是为了帮助你更好的开发,项目运行不会依赖的文件,不会被部署到线上
- -S 运行依赖, --save 简写
- webpack4+,都需要安装webpack-cli,用来以 webpack 协议连接相应服务
- npx webpack -v 检查是否安装成功 npx(npx 的原理很简单,就是运行的时候,会到node_modules/.bin路径和环境变量$PATH里面,检查命令是否存在。由于 npx 会检查环境变量$PATH,所以系统命令也可以调用。)
三、启动 webpack 执⾏构建
1. module chunk bundle 的区别
- module - 各个源码文件, webpack 中一切皆模块
- chunk - 代码块 可以是多模块合并成的
- bundle - 最终的输出文件
2.默认配置
- webpack默认⽀持JS模块和JSON模块
- ⽀持CommonJS Es moudule AMD等模块类型
- webpack4⽀持零配置使⽤,但是很弱,稍微复杂些的场景都需要额外扩展
3. 执行构建
- 新建 src 源代码目录
- 新建 src -> index.js
- 执行构建
- 第一种方式: npx webpack
- 第二种方式:修改 package.json
"scripts": {
"test": "webpack"
}
npm run test
- 构建成功后我们会发现⽬录下多出⼀个dist⽬录,⾥⾯有个main.js,这个 ⽂件是⼀个可执⾏的JavaScript⽂件,⾥⾯包含webpackBootstrap 启动函数。
四、Webpack 的核心概念
webpack有默认的配置⽂件,webpack.config.js,我们可以 对这个⽂件进⾏修改,进⾏个性化配置
webpack.config.js配置基础结构
module.exports = {
entry: "./src/index.js", //打包⼊⼝⽂件
output: "./dist", //输出结构
mode: "production", //打包环境
module: {
rules: [
//loader模块处理
{
test: /\.css$/,
use: "style-loader"
}
]
},
plugins: [new HtmlWebpackPlugin()] //插件配置
};
1. entry
入口(entry)指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。
entry 的值可以是 字符串,数组,对象
1.1 字符串
//单⼊⼝ SPA,本质是个字符串
entry:{
main: './src/index.js'
}
等同于
entry: "./src/index.js",
1.2 数组
entry: ["./src/index.js", "./src/other.js"]
- webpack 会自动生成另外一个入口模块,并将数组中的每个指定的模块加载进来,并将最后一个 module.exports 作为入口模块的 module.exports 导出。
1.3 对象
//多⼊⼝ entry是个对象
entry: {
// 默认值
// main: "./src/index.js",
index: "./src/index.js",
other: "./src/other.js"
}
注意:只有对象这种方式才是多入口的
2. output
输出(output)指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名。
output: {
filename: "bundle.js",//输出⽂件的名称
path: path.resolve(__dirname, "dist")//输出⽂件到磁盘的⽬录,必须是绝对路径
},
//多⼊⼝的处理
output: {
filename: "[name][chunkhash:8].js",//利⽤占位符,⽂件名称不要重复
path: path.resolve(__dirname, "dist")//输出⽂件到磁盘的⽬录,必须是绝对路径
}
2.1 占位符
- hash
- 每次构建会生成一个hash。和整个项目有关,只要有项目文件更改,就会改变hash
- chunkhash
- 根据不同的 entry 进行依赖解析,构建对应的 chunk, 生成相应的hash 只要组成 entry 的模块没有内容改动,则对应的 hash 不变
- contenthash
- 和单个文件的内容相关。指定文件的内容发生改变,就会改变hash
- name
- id 理解webpack的hash,contenthash,chunkhash
3. mode
模式(mode)指示 webpack 使用相应模式的配置:
| 描述 | 特点 | |
|---|---|---|
| development | 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置 为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。 | 能让代码本地调试 运行的环境 |
| production | 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置 为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 TerserPlugin。 | 能让代码优化上线 运行的环境 |
| none | 退出任何默认优化选项 |
development: 的开启会有利于热更新的处理,识别哪个模块变化。 production: 自动开启代码压缩, Tree-Shaking, Vue React等会删除掉调式代码(如开发环境的 warning), 处理副作⽤等⼀些功能
4. loader
loader 让 webpack 能 够 去 处 理 那 些 不识别的文件 (webpack 自 身 只 理 解 .js 和 .json)
常见的loader
style-loader
css-loader
less-loader
sass-loader
postcss-loader 添加浏览器前缀
ts-loader //将Ts转换成js
babel-loader//转换ES6、7等js新特性语法
file-loader //处理静态资源模块
url-loader // file-loader加强版本, 还可以把图片转成 base64 的格式
eslint-loader
...
5. module
模块,在 Webpack ⾥⼀切皆模块,⼀个模块对应着⼀个⽂件。 Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
当webpack处理到不认识的模块时,需要在webpack中的module 处进⾏配置,当检测到是什么格式的模块,使⽤什么loader来处 理。
5.1 处理样式
注意: loader 执行顺序从后往前
module.epxort = {
module: {
rules: [
{
test: /\.less$/, // 指定匹配规则, 参数正则表达式
// loader 模块转换 模块处理
// loader 的执行顺序是从后往前
// css-loader 是把 css 模块的内容 加入到 js 模块中去
// css in js 方式
// style-loader 从 js 中提取 css 的 loader 在 html 中创建 style 标签 把 css 的内容放在这个 style 标签中
use: [
'style-loader',
'css-loader',
'less-loader'
]
}
]
}
}
样式⾃动添加前缀:
Postcss-loader
npx install postcss-loader autoprefixer -D
新建 postcss.config.js, postcss-loader 的配置文件
// postcss.config.js
module.exports = {
plugins: [
require("autoprefixer")({
overrideBrowserslist: ["last 2 versions",">1%"]
})
]
};
// webpack.config.js
{
test: /\.css$/,
use: ["style-loader", "css-loader", "postcss-loader"]
}
5.2 处理图片、字体等静态资源文件
- file-loader:处理静态资源模块
原理是把打包⼊⼝中识别出的资源模块,移动到输出⽬录,并且 返回⼀个地址名称
场景:就是当我们需要模块,仅仅是从源代码挪移到打包⽬录, 就可以使⽤file-loader来处理,txt,svg,csv,excel,图⽚资 源啦等等
yarn add file-loader -D
{
test: /\.(png|jpe?g|gif)$/,
//use使⽤⼀个loader可以⽤对象,字符串,两个loader需要⽤数组
use: {
loader: "file-loader",
// options额外的配置,⽐如资源名称
options: {
// placeholder 占位符 [name]⽼资源模块的名称
// [ext]⽼资源模块的后缀
// name: "[name]_[hash].[ext]",
//打包后的存放位置
outputPath: "images/"
}
}
}
{
test: /\.(eot|ttf|woff|woff2|svg)$/,
use: "file-loader"
}
- url-loader: file-loader加强版本, file-loader 能干它都能干,另外它还可以将图片转换成 base64 格式,适用于小图片base64的利弊
yarn add url-loader -D
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: "url-loader",
options: {
name: "[name]_[hash].[ext]",
outputPath: "images/",
// ⼩于2048,才转换成base64
limit: 2048 // 单位字节 1024 = 1kb
}
}
}
6. plugins
插件(plugins)作⽤于webpack打包整个过程, webpack的打包过程是有(⽣命周期概念)钩⼦,plugin 可以在webpack运⾏到某个阶段的时候,帮你做⼀些事情,类似于⽣命周期的概念, 可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩, 一直到重新定义环境中的变量等。
6.1 HtmlWebpackPlugin
htmlwebpackplugin会在打包结束后,⾃动⽣成⼀个html⽂件,并 把打包⽣成的js模块引⼊到该html中。
npm install -D html-webpack-plugin
配置:
title: ⽤来⽣成⻚⾯的 title 元素
filename: 输出的 HTML ⽂件名,默认是 index.html, 也可以直
接配置带有⼦⽬录。
template: 模板⽂件路径,⽀持加载器,⽐如 html!./index.html
inject: true | 'head' | 'body' | false ,注⼊所有的资
源到特定的 template 或者 templateContent 中,如果设置为
true 或者 body,所有的 javascript 资源将被放置到 body 元
素的底部,'head' 将放置到 head 元素中。
favicon: 添加特定的 favicon 路径到输出的 HTML ⽂件中。
minify: {} | false , 传递 html-minifier 选项给 minify
输出
hash: true | false, 如果为 true, 将添加⼀个唯⼀的
webpack 编译 hash 到所有包含的脚本和 CSS ⽂件,对于解除
cache 很有⽤。
cache: true | false,如果为 true, 这是默认值,仅仅在⽂件
修改之后才会发布⽂件。
showErrors: true | false, 如果为 true, 这是默认值,错误
信息会写⼊到 HTML ⻚⾯中
chunks: 允许只添加某些块 (⽐如,仅仅 unit test 块)
chunksSortMode: 允许控制块在添加到⻚⾯之前的排序⽅式,⽀持
的值:'none' | 'default' | {function}-default:'auto'
excludeChunks: 允许跳过某些块,(⽐如,跳过单元测试的块)
示例:
const htmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
plugins: [
new htmlWebpackPlugin({
title: "My App",
filename: "app.html",
template: "./src/index.html"
})
]
}
<!-- index.html 模板 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=devicewidth, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible"
content="ie=edge" />
<!-- 使用 htmlWebpackPlugin 里的标题 -->
<title><%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
6.2 clean-webpack-plugin
帮我们清除 打包之后 dist 目录下的其他多余或者无用的代码
npm install clean-webpack-plugin -D
const { CleanWebpackPlugin } = require("cleanwebpack-plugin");
module.epxorts = {
plugins: [
new CleanWebpackPlugin()
]
}
6.3 mini-css-extract-plugin
将CSS提取到单独的文件中。它为每个包含CSS的JS文件创建一个CSS文件。它支持CSS和SourceMap的按需加载。
yarn add mini-css-extract-plugin
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name][contenthash:8].css"
})
]
}
五、Babel
1. Babel 处理 ES6
Babel是JavaScript编译器,能将ES6代码转换成ES5代码,让我们开发过程中放⼼使⽤JS新特性⽽不⽤担⼼兼容性问题。并且还可以通过插件机制根据需求灵活的扩展。
Babel在执⾏编译的过程中,会从项⽬根⽬录下的 .babelrc JSON⽂件中读取配置。没有该⽂件会从 loader的options地⽅读取配置。
npm i babel-loader @babel/core @babel/preset-env -D
1.babel-loader是webpack 与 babel的通信桥梁,不会做把es6转成es5的⼯作,这部分⼯作需要⽤到 @babel/preset-env来做 2.@babel/preset-env⾥包含了es,6,7,8转es5的转换规则 Babel在执⾏编译的过程中,会从项⽬根⽬录下的 .babelrc JSON⽂件中读取配置。没有该⽂件会从 loader的options地⽅读取配置。
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"]
}
}
}
// .babelrc
{
"presets": [
[
"babel/preset-env"
]
],
"plugins": [
]
}
通过上⾯的⼏步 还不够,默认的Babel只⽀持let等⼀些基础的特性转换,Promise等⼀些符合 ES5 语法规范得 ES6 新出的 API 还没有转换过来,这时候需要借助@babel/polyfill,把es的新特性都装进来,来弥补低版本浏览器中缺失的特性
2. babel-polyfill
- Polyfill 是很早提出的概念,就是根据浏览器兼容性进行兼容
- regenerator(支持 generator(生成器函数)语法) 和 core-js(其他 ES6 语法)
- babel-polyfill 即两者的集合 (Babel 7.4 弃用),推荐直接使用core-js 和 regenerator
安装
npm install --save @babel/polyfill
注意: 以全局变量的⽅式注⼊进来的。windows.Promise,它会造成全局对象的污染
// 打包入口文件 index.js 顶部
import "@babel/polyfill";
按需加载, 减少冗余 打包入口文件头部不需要引入 "@babel/polyfill"
// .babelrc
{
"presets": [
[
"babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": []
}
useBuiltIns 选项是 babel 7 的新功能,这个选项告诉 babel 如何配置 @babel/polyfill
| 作用 | |
|---|---|
| entry | 需要在 webpack 的⼊⼝⽂件⾥ import "@babel/polyfill" ⼀次。 babel 会根据你的使⽤情况导⼊垫⽚,没有使⽤的功能不会被导⼊相应的垫⽚。 |
| usage | 不需要 import ,全⾃动检测,但是要安装 @babel/polyfill 。 |
| false | 如果你 import "@babel/polyfill" ,它不会排除掉没有使⽤的垫⽚,程序体积会庞⼤。(不推荐) |
3. babel-runtime
将有兼容性 API 更改成另一个名称,从而不污染全局变量
例如:Promise 变成 _Promise
npm i @babel/runtime -S
npm i @babel/plugin-transform-runtime -D
// .babelrc
{
"presets": [
[
"babel/preset-env"
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 3,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}