梳理了一些 webpack 的入门开发经验,分享出来,欢迎指正学习。
目录
- webpack 是什么
- webpack 做什么
- webpack 基础原理
- webpack 应用
- webpack 打包 vue
- webpack 打包 react
- webpack 优化配置
webpack 是什么
webpack 是基于 Node.js 静态资源的模块打包器。当用 webpack 打包时,它会从一个起点开始查找各个模块依赖关系,并且按照这些依赖关系把这些文件打成一个或者多个包。
webpack 做什么
webpack 可以实现以下工程化
- 默认 commonjs 规范
- 代码转换(如: ES6 转换 ES5、sass 和 less 转换为 css 等)
- 文件优化(如: 将模块内容进行压缩)
- 代码分割(如: 多页面应用公共模块的抽离、路由懒加载)
- 模块合并(如: 按照不同的功能将多个模块合并为一个模块)
- 自动刷新(如: 启动本地服务,代码更新后进行自动刷新)
- 代码校验(如: 添加 eslint 进行代码规范检查)
- 自动发布(如: 应用打包完成后,自动发布)
webpack 基础原理
配置解析 -> 内置插件&配置插件注册 -> 确认入口获取依赖资源 -> 使用 Loader 翻译资源 -> 识别资源加载语句并递归的遍历所有资源 -> 封装依赖资源输出结果
webpack 应用
环境版本基于
webpack 4.x版本
node: 12.x 版本
npm install webpack-cli -g
npm install webpack@4 -g
1.1 初始化项目
新建一个目录,初始化 npm
npm init
npm i -D webpack webpack-cli
新建一个文件夹 src ,然后新建一个文件 main.js,写一点代码测试一下
console.log("hello webpack");
打包
webpack src/main.js
如果生成了一个 dist 文件夹,并且内部含有 main.js 说明已经打包成功了
通常我们需要创建一个 webpack.config.js 自定义 webpack 配置参数。
1.2 webpack.config.js 配置文件
创建 webpack.config.js 用来配置 webpack, 一个配置文件包含:
entry 入口、 output 出口、 mode 模式、 module 模块、 resolve 解析、 loader 加载器、 plugin 插件、 devServer 服务器 、 npm 命令
我们简单的介绍下每一个参数
1.3 entry 入口
webpack 打包的起点,它从这个文件开始查找各个模块之间的依赖
## 单一入口文件
module.exports = {
entry: './src/index.js'
}
## 多个入口文件
module.exports = {
entry: {
main: __dirname + '/src/index.js',
page: __dirname + '/src/page1.js'
}
}
1.4 output 出口
webpack 打包后的文件输出目录和文件名.
- path
path 配置决定了最终打包后输出资源的文件路径,且它必须要求为系统绝对路径(这不等同于 html 中最终引入的路径)。如果使用了 html 插件,那 webpack 会智能的判断生成的 html 与 chunk 的相对路径,再以 script 方式引入。
- publicPath
有时候我们 html 与静态资源并非分发到同一个地方。如 html 为服务端渲染输出、亦或者发至专门的 html 输出服务器以便方便控制版本,而 js/css 等静态资源则专门分发到 cdn。这样就需要 html 中引入资源时以完整路径引入。这时就需要用使用 publicPath。
- library
这个属性决定了 webpack 构建出来的包是做什么用途的,能被以何种方式引用、加载执行。但对于日常工程构建来说,可以默认不配置。
## 一个输出文件
output: {
filename: 'bundle.js',
}
}
## 多个输出文件
output: {
path: __dirname + '/dist'
filename: '[name].js',
}
1.5 mode 模式
在一个工程化的项目中:生产环境下,我们希望输出的文件是经过压缩、混淆(丑化)的。而开发环境时由于调试需要,我们希望文件是未压缩与混淆的。
webpack@4+通过配置不同模式[mode],来实现该操作。
目前已有的模式有:production 、development 、none ,默认为 production。设置为 production 与 development 时会同时默认设置 process.env.NODE_ENV 为 production 或 development。 设置 none 时,webpack 不做任何附加操作。
const path = require("path");
module.exports = {
mode: "development", // 开发模式
entry: path.resolve(__dirname, "../src/main.js"), // 入口文件
output: {
filename: "output.js", // 打包后的文件名称
path: path.resolve(__dirname, "../dist"), // 打包后的目录
},
};
配置运行
"scripts" : {
"build": "webpack --config build/webpack.config.js"
}
1.6 module 模块
不同的文件类型可能需要匹配不同的 loader,做不同的文件转化。而有的模块可能需要处理,有的模块可能需要忽略,这一切相关的配置就在 module 中。
-
rules 这是一个数组配置,rules 中的每一项 rule 即配置了如何去处理一个模块
-
noParse 配置 noParse 字段,过滤某些你确定不需要递归解析模块加载的包。可以是正则、也可以是函数 如:noParse: /jquery/,
1.7 resolve 解析
这个配置决定如何去解析模块。如:是否需要缓存此模块;是否自动识别扩展文件名等。不过我们最常用的配置只有一个 alias : 创建 import 或 require 的别名,借此来简写某些模块文件的路径。
resolve: {
alias: {
'@': path.resolve(process.cwd(), './src');
}
}
在页面中使用
import utils from '#/utils'
在 webpack 中 通过 stats 配置错误信息
stats: {
warnings: false, // 取消警告信息
children: false, // 取消子级信息
modules: false, // 取消模块构建信息
entrypoints: false // 不显示入口起点
}
1.8 loader 加载器
webpack 本身只能处理 javascript 模块,需要 Loader 加载器来处理其他类型的文件。Loader 可以理解为一个模块和资源的转换器,它本身是一个函数,接受不同类型的源文件作为参数,并返回转换后的结果。
loader 的特性:
- 可以接受参数,传递配置项给 loader
- 可以通过 npm 发布和安装
- 运行在 nodejs 环境中,可以实现同步和异步操作
- 可以通过 require() 引用模块的形式添加
- 可以通过 命令行使用
- 可以支持链式传递
loader 包含的属性:
- test:一个用以匹配 loaders 所处理文件的拓展名的正则表达式(必须)
- use:loader 的名称(必须)
- include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
- query:为 loaders 提供额外的设置选项(可选)
module: {
loaders: [
{test: /\.css$/, loader: 'style-loader!css-loader'}
]
},
常用 loader:
- babel-loader 利用 babel 将最新标准的代码转成当下浏览器可执行的 js 代码。babel-loader 的配置即是 babel 的配置,其主要是以 .babelrc 配置文件的方式存在项目根目录。如配置有特殊逻辑处理,可以在 module.rules 中引用 babel-loader 处做配置覆盖。
- less-loader/sass-loader: 现代工程基本基本已离不开 less/sass 预处理。
- postcss-loader: css 样式后处理工具。css 压缩、合并、自动兼容浏览器等功能利器
- css-loader: 解释 css 文件内的 @import 和 url() ;可开启 css-module 。
- style-loader: 将 css 以 style 标签插入 dom 中。
- css-hot-loader: css 热更新 loader,线上环境时勿加,会引起 js 文件 contentHash 每次都不同。
- file-loader 对于图片这样的静态资源,我们在代码中引入时,常以当前文件为基准,引入其相对路径下的图片。而当我们访问 html 时,这个相对路径其实是基于 html 此时的路径的,故而会导致引入路径错误。 file-loader 主要解决这个问题。可以自动的识别 webpack 配置,打包资源图片,修复引入路径,进而保证资源引入正确。同时也支持修改输出后文件的路径与文件名、携带 hash 值等功能。
- url-loader 基本功能同 file-loader ,在它基础上,可以设置一个 limit 配置项,意义为文件的体积大小,单位为字节。对于小于此大小的文件,会转化成 base64 的数据,替换 url 引入。对于小图片等资源常用这样的操作,好处是减少资源的请求次数;或者在某些场景下,保证图片在 html 加载或渲染时就能展示,不需要再发起请求。
1.8.1 style-loader css-loader 处理 css 文件
引入 css 文件
npm i -D style-loader css-loader
npm i -D less less-loader
npm i -D postcss-loader autoprefixer
// webpack.config.js
module.exports = {
// ...省略其他配置
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"], // 从右向左解析原则
},
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
plugins: [require("autoprefixer")],
},
},
"less-loader",
], // 从右向左解析原则
},
],
},
};
我们可以通过 postcss-loader 自动给样式加前缀
在项目根目录下创建一个 postcss.config.js 文件,配置如下:
module.exports = {
plugins: [require("autoprefixer")], // 引用该插件即可了
};
1.8.2 file-loader、url-loader 打包 图片、字体、媒体、等文件
file-loader 就是将文件在进行一些处理后(主要是处理文件名和路径、解析文件 url),并将文件移动到输出的目录中
url-loader 一般与 file-loader 搭配使用,功能与 file-loader 类似,如果文件小于限制的大小。则会返回 base64 编码,否则使用 file-loader 将文件移动到输出的目录中。
// webpack.config.js
module.exports = {
// 省略其它配置 ...
module: {
rules: [
// ...
{
test: /\.(jpe?g|png|gif)$/i, //图片文件
use: [
{
loader: "url-loader",
options: {
limit: 10240,
fallback: {
loader: "file-loader",
options: {
name: "img/[name].[hash:8].[ext]",
},
},
},
},
],
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
use: [
{
loader: "url-loader",
options: {
limit: 10240,
fallback: {
loader: "file-loader",
options: {
name: "media/[name].[hash:8].[ext]",
},
},
},
},
],
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
use: [
{
loader: "url-loader",
options: {
limit: 10240,
fallback: {
loader: "file-loader",
options: {
name: "fonts/[name].[hash:8].[ext]",
},
},
},
},
],
},
],
},
};
1.8.3 babel-loader 、babel-core 用 babel 转义 js 文件
我们可以使用 babel 来兼容 es6
babel-loader 与 babel-core 的版本对应关系:
- babel-loader 8.x 对应 babel-core 7.x
- babel-loader 7.x 对应 babel-core 6.x
- babel-loader 只会将 ES6/7/8 语法转换为 ES5 语法
- 我们需要借助 babel-polyfill 来帮助我们转换 promise、Generator、Set、Maps、Proxy 等
npm i -D babel-loader @babel/preset-env @babel/core @babel/polyfill
// webpack.config.js
module.exports = {
entry: ["@babel/polyfill", path.resolve(__dirname, "../src/index.js")],
// 省略其它配置 ...
module: {
rules: [
{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
exclude: /node_modules/,
},
],
},
};
1.9 plugin 插件
插件是 loader 的扩展,用来完成 loader 不能完成的功能。
webpack 插件是一个 具有 apply 方法的 javascript 对象。apply 方法会被 webpack complier 调用。
plugins: [new webpack.BannerPlugin("hello webpack")];
常用 plugin:
1.9.1 clean-webpack-plugin 清除残留打包文件
每次执行 npm run build 会发现 dist 文件夹里会残留上次打包的文件,这里我们推荐一个 plugin 来帮我们在打包输出前清空文件夹 clean-webpack-plugin
npm i -D clean-webpack-plugin
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
// ...省略其他配置
plugins: [new CleanWebpackPlugin()],
};
1.9.2 html-webpack-plugin 替换 html 模板文件
主要功能就是可以根据项目中的 html 模板(没有也行),生成想要的 html 文件,并插入我们构建出来的 js 与 css 等资源。经常用于修改 html 标题、注入 meta 标签、向 html 模板中注入某些自定义变量等。对于构建多页应用来说,每需要一个 html 文件,就需增加一个 html-webpack-plugin,并引入相应的 chunk。
npm i -D html-webpack-plugin
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development", // 开发模式
entry: {
main: path.resolve(__dirname, "../src/main.js"),
header: path.resolve(__dirname, "../src/header.js"),
},
output: {
filename: "[name].[hash:8].js", // 打包后的文件名称
path: path.resolve(__dirname, "../dist"), // 打包后的目录
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
filename: "index.html",
chunks: ["main"], // 与入口文件对应的模块名
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/header.html"),
filename: "header.html",
chunks: ["header"], // 与入口文件对应的模块名
}),
],
};
1.9.3 mini-css-extract-plugin 抽离生成 css 文件
抽离 css 生成文件。如果引入了本插件,那么就需要在 css 文件的相关 loaders 中需要使用 MiniCssExtractPlugin.loader 替换 style-loader 。
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
//...
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[hash].css",
chunkFilename: "[id].css",
}),
],
modules: {
rules: [
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
},
],
},
};
1.9.4 extract-text-webpack-plugin@next 拆分 css 文件
如果你想拆分为一一对应的多个 css 文件,我们需要使用到 extract-text-webpack-plugin,而目前 mini-css-extract-plugin 还不支持此功能。我们需要安装@next 版本的 extract-text-webpack-plugin。
npm i -D extract-text-webpack-plugin@next
// webpack.config.js
const path = require("path");
const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin");
let indexLess = new ExtractTextWebpackPlugin("index.less");
let indexCss = new ExtractTextWebpackPlugin("index.css");
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: indexCss.extract({
use: ["css-loader"],
}),
},
{
test: /\.less$/,
use: indexLess.extract({
use: ["css-loader", "less-loader"],
}),
},
],
},
plugins: [indexLess, indexCss],
};
1.9.5 HotModuleReplacementPlugin 热重载页面
在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。
-
保留在完全重新加载页面时丢失的应用程序状态。
-
只更新变更内容,以节省宝贵的开发时间。
-
调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。
在开发中需要注意点:
-
webpack-dev-server 中需要设置 hot 为 true 。
-
style-loader 支持热替换,但是上小节中为了抽离 css 文件引入的 MiniCssExtractPlugin.loader 暂时 未支持热替换,故而需要开发环境时采用 style-loader ,或者引入 css-hot-loader 。
-
react 工程需要实现组件热替换的话,需引入 react-hot-loader 。vue-loader 已经实现了 HMR,无需要增加其他 loader。
1.9.6 commons-chunk-plugin 抽离公共 js (webpack@3 中)
这个插件用来抽离多个入口 chunk 内的公共代码,这在 webpack@4 以前很常用。在 webpack@4 中,已使用 optimization.splitChunks 来实现这样的功能,其内部实现基于内置的 SplitChunksPlugin。
1.9.7 uglifyjs-webpack-plugin 代码压缩 (webpack@3 中)
实现代码压缩、混淆、tree-shaking 的。过于常用,故而 webpack 也已内置。
2.0 devServer 服务器
在开发的过程中我们可以让 webpack 给我们启动一个本地服务器,它可以:
- 让浏览器监听你的代码的修改,并自动刷新显示修改后的结果;
- 提供 AJAX 接口的代理,解决开发环境的跨域问题
参数:
- host[string]
- port[number]
- disableHostCheck[boolean] 如果有绑定域名访问的需要的话,需设置为 true 。
- allowedHosts[array] 配置允许的 hosts 白名单
- contentBase[boolean|string|array] 访问服务器的 ip/域名时,对应访问的工程里的文件夹
- index[string] 配置首页
- hot[boolean] 是否热更新
- clientLogLevel[string] 日志级别
- stats 统计信息
devServer: {
contentBase: './public', // 本地服务器所加载的页面所在的目录
historyApiFallback: true, // 单页面应用路由切换时不跳转
inline: true, // 实时刷新
port: 8080,
proxy: {
'/api': { // 如果接口中带有API标志,那么就需要开始代理
target: 'http://localhost:8001',
changeOrigin: true, // target是域名的话,需要这个参数,
secure: false // 设置支持https协议的代理,不检查安全与否
}
}
}
2.1 npm 命令
在 package.json 中配置 npm run
打包 webpack -p --progress --mode production --config webpack.config.build.js
服务测试 webpack-dev-server --open
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"product": "cross-env NODE_ENV=dev webpack-dev-server --progress --mode development --config webpack.config.dev.js",
"build": "webpack -p --progress --mode production --config webpack.config.build.js",
"testBuild": "webpack-cli --entry ./app/main.js --output ./public/bundle.js",
"dev": "webpack-dev-server --open"
},
webpack 打包 vue
- vue-loader 用于解析.vue 文件
- vue-template-compiler 用于编译模板
npm i -D vue-loader vue-template-compiler vue-style-loader
npm i -S vue
const vueLoaderPlugin = require("vue-loader/lib/plugin");
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
use: ["vue-loader"],
},
],
},
resolve: {
alias: {
vue$: "vue/dist/vue.runtime.esm.js",
" @": path.resolve(__dirname, "../src"),
},
extensions: ["*", ".js", ".json", ".vue"],
},
// ...省略其他配置
devServer: {
port: 3000,
hot: true,
contentBase: "../dist",
},
plugins: [new Webpack.HotModuleReplacementPlugin(), new vueLoaderPlugin()],
};
vue 下的 webpack.config.js
// webpack.config.js
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin");
const vueLoaderPlugin = require("vue-loader/lib/plugin");
const Webpack = require("webpack");
module.exports = {
mode: "development", // 开发模式
entry: {
main: path.resolve(__dirname, "../src/main.js"),
},
output: {
filename: "[name].[hash:8].js", // 打包后的文件名称
path: path.resolve(__dirname, "../dist"), // 打包后的目录
},
module: {
rules: [
{
test: /\.vue$/,
use: ["vue-loader"],
},
{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {
presets: [["@babel/preset-env"]],
},
},
},
{
test: /\.css$/,
use: [
"vue-style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
plugins: [require("autoprefixer")],
},
},
],
},
{
test: /\.less$/,
use: [
"vue-style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
plugins: [require("autoprefixer")],
},
},
"less-loader",
],
},
],
},
resolve: {
alias: {
vue$: "vue/dist/vue.runtime.esm.js",
" @": path.resolve(__dirname, "../src"),
},
extensions: ["*", ".js", ".json", ".vue"],
},
devServer: {
port: 3000,
hot: true,
contentBase: "../dist",
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
filename: "index.html",
}),
new vueLoaderPlugin(),
new Webpack.HotModuleReplacementPlugin(),
],
};
开发环境与生产环境的不同
实际应用到项目中,我们需要区分开发环境与生产环境,我们在原来 webpack.config.js 的基础上再新增两个文件
-
webpack.dev.js 开发环境配置文件 开发环境主要实现的是热更新,不要压缩代码,完整的 sourceMap
-
webpack.prod.js 生产环境配置文件 生产环境主要实现的是压缩代码、提取 css 文件、合理的 sourceMap、分割代码
需要安装以下模块:
npm i -D webpack-merge copy-webpack-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin
webpack 打包 react
cnpm install babel-loader babel-core babel-preset-react babel-preset-es2015 --save-dev
// webpack.config.js
const path = require("path");
module.exports = {
entry: path.resolve(__dirname, "./public/main.js"),
output: {
path: path.resolve(__dirname, "./public/out"),
filename: "bundle.js",
},
module: {
loaders: [
{
test: /\.jsx?$/,
loaders: ["babel-loader?presets[]=es2015,presets[]=react"],
},
],
},
};
webpack 优化配置
构建速度指的是我们每次修改代码后热更新的速度以及发布前打包文件的速度。
1.1 缩小文件的搜索范围
- alias: 当我们代码中出现 import 'vue'时, webpack 会采用向上递归搜索的方式去 node_modules 目录下找。为了减少搜索范围我们可以直接告诉 webpack 去哪个路径下查找。也就是别名(alias)的配置。
- include exclude 同样配置 include exclude 也可以减少 webpack loader 的搜索转换时间。
- noParse 当我们代码中使用到 import jq from 'jquery'时,webpack 会去解析 jq 这个库是否有依赖其他的包。但是我们对类似 jquery 这类依赖库,一般会认为不会引用其他的包(特殊除外,自行判断)。增加 noParse 属性,告诉 webpack 不必解析,以此增加打包速度。
- extensions webpack 会根据 extensions 定义的后缀查找文件(频率较高的文件类型优先写在前面)
1.2 使用 HappyPack 开启多进程 Loader 转换
在 webpack 构建过程中,实际上耗费时间大多数用在 loader 解析转换以及代码的压缩中。日常开发中我们需要使用 Loader 对 js,css,图片,字体等文件做转换操作,并且转换的文件数据量也是非常大。由于 js 单线程的特性使得这些转换操作不能并发处理文件,而是需要一个个文件进行处理。HappyPack 的基本原理是将这部分任务分解到多个子进程中去并行处理,子进程处理完成后把结果发送到主进程中,从而减少总的构建时间
npm i -D happypack
1.3 抽离第三方模块
这里我们使用 webpack 内置的 DllPlugin DllReferencePlugin 进行抽离,在与 webpack 配置文件同级目录下新建 webpack.dll.config.js
// webpack.dll.config.js
const path = require("path");
const webpack = require("webpack");
module.exports = {
// 你想要打包的模块的数组
entry: {
vendor: ["vue", "element-ui"],
},
output: {
path: path.resolve(__dirname, "static/js"), // 打包后文件输出的位置
filename: "[name].dll.js",
library: "[name]_library",
// 这里需要和webpack.DllPlugin中的`name: '[name]_library',`保持一致。
},
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require("./vendor-manifest.json"),
}),
new CopyWebpackPlugin([
// 拷贝生成的文件到dist目录 这样每次不必手动去cv
{ from: "static", to: "static" },
]),
new webpack.DllPlugin({
path: path.resolve(__dirname, "[name]-manifest.json"),
name: "[name]_library",
context: __dirname,
}),
],
};
"dll": "webpack --config build/webpack.dll.config.js"
总结
以上总结了 webpack 的常用操作, webpack 是一个把前端代码整合化的一个工具,需要我们实际工作中多加使用,才能理解他的设计精髓。
参考
- webpack 文档 webpack.js.org/