webpack
webpack 是一个现代 JavaScript 应用程序的静态模块打包器,已经成为前端开发不可获取的工具。特别是在开发大型项目时,项目太大,文件过多导致难以维护,或者是优化网络请求时,webpack 都是不可获取的利器。
从最基础的开始,使用的 webpack 版本是^4.39.2,搭建时会用到一下内容:
- 从单页面到多页面
- 代码切片
- 热更新
- 热替换
- CSS 分离
- HTML 模板
- babel 的使用
- 支持 img、sass、jsx、css、typescript 等工具
- webpack 一些插件的使用
- postcss 的简单配置
- 不同开发环境的配置
- 配置 react 开发环境
- webpack
- 安装 webpack,设置程序打包命令
- entry 入口配置(必须的)
- output 配置
- html-webpack-plugin 插件
- mode 环境变量
- module 配置
- resolve 配置项
- devServer 配置项
- 使用 watch 简化操作
- webpack优化
- 生产环境配置
- webpack 小插件
首先,配置 webpack,大致的骨架是这样的,这是最基本的配置内容:
{
entry: "", // 入口配置
mode: 'development', // 环境配置
output: {}, // 打包输出配置
module: {}, // loader 配置项
plugins: [], // 插件配置项
devServer: {}, // 服务器配置
}
那么就开始一一进行配置。
安装 webpack,设置程序打包命令
首先是安装: yarn add webpack --dev 或者 npm install webpack --save-dev 或者 yarn add webpack -D npm i webpack -D
在开发环境安装。
安装完时候,来到 package.json 文件,在 scripts 项中写入一下命令:
"build": "webpack"
当运行命令 npm run build 后,webpack 默认会在项目根目录下查找一个叫 webpack.config.js 的文件,然后进行打包。当文件名不想叫这个或者不想在根目录下创建这个文件时,可以在后面的 --config 字段之后写上文件的所在路径。例如:
"build" "webpack --config config/webpack.config.dev.js"
这样,当运行时,webpack 就从 项目根路径/config/webpack.config.dev.js 这个路径查找配置文件。
在运行命令时,可能会提醒安装
webpack-cli输入yes即可。
entry 入口配置(必须的)
entry 大致有四种写法,分别是字符串的形式、数组形式、函数形式和对象形式。代表的含义分别是:
| 形式 | 含义 | 举例 |
|---|---|---|
| 字符串的形式 | 这种表示单个入口 | 例如:entry: "path(__dirname,"../src/index.js")" |
| 数组形式 | 这种也是表示单个入口 | 例如:entry: "["./01.js","./index.js"]" |
| 函数形式 | 可以是单入口也可以是多入口 | 该函数应该返回一个字符路径、数组或对象作为打包入口 |
| 对象形式 | 这种表示多个入口 | 例如:entry: {app: './src/app.js',vendors: './src/vendors.js'} |
注意!:第一种和第二中都表示单入口,但含义不同。使用数组的作用是将多个资源预先 合并,在打包的时候, webpack 会将数组的最后一项作为实际的入口路径。
modules.exports = {
entry: ["babel-polyfill","./src/index.js"]
}
就相当于:
// webpack.config.js
modules.exports = {
entry: "./src/index.js"
}
// 在打包后的文件中,会包含数组中所有的路径对应的文件
import "babel-polyfill"; // 以及 ./src/index.js 文件
output 配置
output 配置项很多,有两个是必须的:
path指定文件输出时的文件夹(不存在时会自动创建);filename指定文件输出时文件的名字;
单页面
{
entry: path.join(__dirname,"../src/index.js"), // 入口配置
output: {
path: path.join(__dirname,"../build"),
filename: "index.js"
}, // 打包输出配置
}
运行后,就会在 build 文件夹下创建一个 index.js 的打包文件。
多页面
{
entry: {
index: path.join(__dirname,"../src/index.js"),
demo: path.join(__dirname,'../src/demo.js')
}, // 入口配置
output: {
path: path.join(__dirname,"../build"),
filename: "[name].js"
}, // 打包输出配置
}
运行后,在 build 文件夹下就会多出两个文件。注意 output 中 filename 的文件名 —— [name] 这个 name 对应的就是 entry 对象的键。当然,也可以为 filename 指定别的字段,但也是要用 [] 包裹。可以指定的字段有:
[name]当前 chunk 的名字[hash]此次打包所有资源生成的 hash[id]指代当前 chunk 的 id[chunkhash]指代当前 chunk 内容的 hash[query]模块的 query,例如,文件名 ? 后面的字符串
表中
hash和chunkhash的作用:当下一次请求时,请求到的资源会被立刻下载新的版本,而不会用本地缓存。query也有类似的效果,只是需要人为指定。
publicPath
publicPath 是 output 中的一个配置项,不是必须的。但是它是一个非常重要的配置项。
与 path 属性不同,publicPath 用来指定资源的请求位置,而 path 是用来指定资源的输出位置(打包后文件的所在路径)。在 HTML 页面中,我们可能会通过 <script> 标签来加载 JS 代码,标签中的 src 路径就是一个请求路径(不光是 HTML 中的JS文件,也可能是CSS中的图片、字体等资源、HTML中的图片、CSS文件等)。publicPath 的作用就是指定这部分间接资源的请求位置。
publicPath 有三种形式,分别是:
- 与 HTML 相关,在请求这些资源时会以当前页面 HTML 所在路径加上相对路径,构成实际请求的 URL。
- Host 相关,若 publicPath 的值以 ‘/’开始,则代表此时
publicPath是以当前页面的 hostname 为基础路径的。 - CDN 相关,上面两种都是相对路径,而这个是 绝对路径。如:publicPath 为:"https://www.example.com/",代表当前路径是 CDN 相关。
举个例子,当使用第一种形式时,当我们使用html-webpack-plugin插件动态生成一个 HTML,并打包到 build 文件夹后,JS 文件(指定的 entry)会自动插入到 HTML 中。当我们指定publicPath: '/',后就会变成:
当没有指定 publicPath 时,默认是 "",即:
而如果是 "/static" 是,HTML 引入的资源路径前都将有一个 "/static"。这个路径是相对于项目根路径的。
html-webpack-plugin 插件
这是一个很实用的插件,在上面的例子中,都没有提到 html,而这个插件可以动态生成 html。下载: npm install html-webpack-plugin -D 或者 yarn add npm install html-webpack-plugin -D。
配置:
const HtmlWebpackPlugin = require('html-webpack-plugin');
{
plugins: [
new HtmlWebpackPlugin({
// 以下都是可选的配置项:
title: "hello world!", // html 的 title 标签内容
// html 模板路径
template: path.join(__dirname,'../public/index.html'),
// title favicon 路径
favicon: path.join(__dirname,'../public/favicon.ico'),
// 指定引入的 js 代码 插入到哪里(默认是body最底部,即:true)
// 也可以指定字符串:"body" 或 "head"
inject: false,
// 指定 打包输出后,文件的名字(不指定的话还是原来的名字)
filename: "hello.html",
// 还有许多配置,这是常用的几个
// 压缩 HTML 代码
minify: {
// 删除标签属性值的双引号或单引号
removeAttributeQuotes: true,
// 将代码压缩成一行
collapseWhitespace: true,
},
// 将引入的 js 文件添加上 hash 值(防止缓存)
hash: true
})
]
}
多个 HTML 页面的配置
有时候,想要配置多页面应用,这时就要多实例化几个这个插件。这时就可能会用到别的一个配置属性 —— chunks
{
entry: {
index: path.join(__dirname,"../src/index.js"),
demo: path.join(__dirname,'../src/demo.js')
}, // 入口配置
output: {
path: path.join(__dirname,"../build"),
filename: "[name].js"
}, // 打包输出配置
plugins: [
new HtmlWebpackPlugin({
chunks: ['index'], // 指定 chunk 的名字(一般就是 entry 对象的键)
// html 模板路径
template: path.join(__dirname,'../public/index.html'),
favicon: path.join(__dirname,'../public/favicon.ico'),
inject: "body",
filename: "index.html",
}),
new HtmlWebpackPlugin({
chunks: ['demo'],
// html 模板路径
template: path.join(__dirname, '../public/demo.html'),
favicon: path.join(__dirname, '../public/favicon.ico'),
inject: "body",
filename: "demo.html",
}),
], // 插件配置项
}
mode 环境变量
mode 的选项一般是这两者其一:development 或 production,即:开发模式或生产模式。生产模式的代码一般是压缩过的。
单纯的指定 mode 值,可能不能满足我们的需要,这时可以使用另一种办法来设置 mode 值。就是在 package.json 文件的 'scripts' 命令中传入参数。
// ....
"scripts": {
"build": "cross-env NODE_ENV='development' webpack --config config/webpack.config.dev.js"
}
// ....
可以注意到,在之前的命令中,我们在前面又添加了一部分内容:cross-env NODE_ENV='development 这是给 Node.js 的全局变量 process 的 env 属性传入了一个值,前面 cross-env 是一个 npm 包,主要为了解决在 Windows 系统下不支持传值命令。
这样,在 webpack 配置文件中,就可以接收到这个值:
var mode = process.env.NODE_ENV; // development
这样就可以根据传入的值,来对配置文件作进一步的改进:
const mode = process.env.NODE_ENV;
const isDev = mode === 'development';
const config = {
// 公共配置项,比如 loader、mode、entry 和 output 中相同的配置项
mode: mode,
entry: "xxx",
// 等等
}
if(isDve){
// 是开发模式时的配置,比如:
config.devServer = {
// 对 webpack-dev-server 的配置
}
}else{
// 是生产模式时的配置
}
// 最后导出:
module.exports = config;
module 配置
这一部分比较多,主要是配置各种loader,比如 css-loader,babel-loader,sass-loader等等。而这些配置存在于 module.rules 这个配置项中。
所有的 loader 都是需要安装的。 通过 npm install xxx-loader 或 yarn add xxx-loader 的形式进行安装。
style-loader 和 css-loader
两者有很大不同,css-loader 的作用仅仅是处理 CSS 的各种加载语法,例如 @import 和 url() 等。而 style-loader 才是真正让样式起作用的 loader(会将 CSS 引入到 head 标签里的 style 标签中)。因此这两个一般配合使用:
{
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader','css-loader']
}
]
}
}
还需要注意的是:webpack打包时是按数组从后往前的顺序将资源交给loader处理的,因此要把最后生效的放在前面。
loader options
有时候使用一个 loader 时,可能要对它进行一些配置,例如 babel-loader babel 的一些配置就可以写在 options 里,当然也可以建一个 .babelrc 文件进行配置。当一个 loader 需要配置时,它就不能在 use 属性里是个单纯的字符串了,而是一个对象。
{
module: {
rules: [
{
test: /\.css$/,
use: [{
loader: 'style-loader',
options: {
// 将样式放到顶部
// 当 HTML 模板中也有 CSS 样式(通过 style 标签写的),你又不想被覆盖掉,可以将 引入的 CSS 放到最顶部防止原来的样式被覆盖。
insertAt: 'top',
}
},{
// 对 css-loader 配置时,是个对象
loader: 'css-loader',
options: {
// css-loader 的配置项
}
}]
}
]
}
}
需要注意的是,css 中还不能书写背景图片路径(例如:
background: url())。不然会报错。因为加载的不是样式,而是图片,在 webpack 中,想要加载图片,还需要使用file-loader,之后会介绍 。
sass-loader 和 less-loader
sass 和 less 是 CSS 的预处理器,需要安装。而且最终会编译成 CSS,因此我们还需要 style-loader 和 css-loader。而且还要安装编译 Sass 的包:node-sass(不然会报错)。
module: {
rules: [
{
test: /\.(sass|scss)$/,
use: ["style-loader", "css-loader","sass-loader"]
},
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"]
}
]
}
html-loader
有了这个 loader,我们可以将一个 html 文件通过 JS 加载进来。比如这样:
rules: [
{
test: /\.html$/,
use: 'html-loader'
}
]
// loader.html
<h1>Hello World!</h1>
// index.js
import html from './loader.html';
document.write(html);
file-loader 和 url-loader
file-loader 用于打包文件类型的资源。比如 CSS 的背景图片和字体、HTML 的 img 标签中的 src 路径等。
rules: [
{
test: /\.(png|jpg|gif)$/,
use: "file-loader",
}
]
这样就可以对 png、jpg、gif类型的图片文件进行打包,而且可以在 JS 中加载图片。
file-loader 中的 options
主要有两个配置项:
name,指定打包后文件的名字,默认是 hash 值加上文件后缀。也可以制定成:[name].[ext]表示原来的名字和文件后缀。publicPath这里的 publicPath 与 output 中的 publicPath 一样,在这里指定后,会覆盖原有的 output.publicPath。 比如:
rules: [
{
test: /\.(png|jpg|gif)/,
use: {
loader: "file-loader",
options: {
name: '[name].[ext]',
publicPath: "",
}
}
}
]
url-loader
与 file-loader 作用类似,唯一的不同是:url-loader 可以设置一个文件大小的阈(yù)值。当大于该阈值时与 file-loader 一样返回 publicPath,而小于阈值时则返回文件的 base64 形式编码。比如:
{
test: /\.(png|jpg|gif)$/,
use: {
loader: "url-loader",
options: {
// 当文件小于这个值时,使用 base64 编码形式
// 大于该值时,使用 publicPath
// 这个属性在 file-loader 中是没有的。
limit: 10240,
name: '[name].[ext]',
// 将所有的图片打包到 img 目录下
outputPath: 'img/',
}
}
}
html-withimg-loader
当我们在 HTML 模板中有 img 标签是,img 标签的 src 的路径并不会被 webpack 转化,因此需要使用 html-withimg-loader,使用之前同样需要先下载。然后配置:
{
rules: [
test: /\.html/,
use: 'html-withimg-loader'
]
}
ts-loader
使用 ts-loader 可以让我们使用 typescript 来编写 js 代码。安装该 loader 后,还要安装 typescript。
yarn add ts-loader typescript
rules: [
{
test: /\.ts$/,
use: "ts-loader",
}
]
babel-loader
babel-loader 很重要,使用 babel-loader可以让我们写的 JS 代码更加兼容浏览器环境。配置 babel-loader 时需要下载好几个其他的包。yarn add babel-loader @babel/core @babel/preset-env -D 。这三个是最核心的模块。主要作用如下:
babel-loader它是 babel 与webpack协同工作的模块;@babel/corebabel 编译器的核心模块;@babel/preset-env它是官方推荐的预置器,可根据用户设置的目标环境自动添加所需的插件和补丁来编译 ES6+ 代码。
具体配置如下:
rules: [
{
test: /\.js$/,
// 不要编译 node_modules 下面的代码
exclude: path.join(__dirname,'../node_modules'),
use: {
loader: "babel-loader",
options: {
// 当为 true 时,会启动缓存机制,
// 在重复打包未改变过的模块时防止二次编译
// 这样做可以加快打包速度
"cacheDirectory": true,
}
}
}
]
对于 options 其它部分,可以在项目根目录下新建一个 .babelrc 文件。.babelrc 文件相当于一个 json 文件。它的配置项大概是这样的:
{
"presets": [],
"plugins": [],
}
比如要配置的一个内容:
{
"presets": [
["@babel/env", // 每一个 preset 就是数组的每一项
// 当有的 preset 需要配置时,这一项将也是一个数组
// 数组的第一项是 preset 名称,第二项是该 preset 的配置内容,是一个对象
{ // @babel/preset-env 会将 ES6 module 转成 CommonJS 的形式
// 将 mudules 设置成 false,可以禁止模块语句的转化
// 而将 ES6 module 的语法交给 webpack 本身处理
"mudules": false,
// targets 可以指定兼容的各个环境的最低版本
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
}
}
]
],
"plugins": [
// 语法转换(ES6转ES5)将常用到这个包
// 在开发环境下载
// 下载这个插件后还需要下载另一个包:@babel/runtime
// @babel/runtime 需要下载到生产环境中(--save)。不需要配置
"@babel/plugin-transform-runtime",
]
}
env 的 targets 属性,可以配置的环境名称有:
chrome,opera,edge,firefox,safari,ie,ios,android,node,electron。当然 targets 的值也可以是一个字符串,例如:"targets": "> 0.25%, not dead"表示仅包含浏览器具有> 0.25%市场份额的用户所需的polyfill和代码转换。
上面配置了 @babel/plugin-transform-runtime 插件,解决了语法问题(比如 Promise、async/await、迭代器),而 ES6 以及往上的 API 浏览器也不一定支持,比如字符串的 includes 方法,这时就需要另一个 babel 包:@babel/polyfill,下载:yarn add @babel/polyfill。这个包不需要配置到 babel 中,要使用这个包,就在文件中引入:require('@babel/polyfill');。
eslint
eslint 是 JS 语法的校验器,它提供了一个 loader:eslint-loader。使用之前需要先下载:yarn add eslint eslint-loader,配置如下:
{
rules: [
{
test: /\.js$/,
use: {
laoder: 'eslint-loader',
}
}
]
}
设置好 loader 后,还要在项目根目录下建一个 .eslintrc.json 文件再进行其他配置。
当然,也可以来到这个网址 https://eslint.org/demo/,下载默认的配置文件。下载好后把文件修改成 .eslintrc.json 名称(名称前有一个点),然后把该文件剪切到项目根目录下。
需要注意的是,loader 的执行顺序是从右到左(对于一个规则,多个loader的情况,配置 .css laoder时,use 项中有多个 loader),从下到上(对于一个多个规则,比如同是处理 .js 文件的配置,写了好几个规则(test)),因此,eslint-loader 应该放在所有 .js 规则中的最后一个(先检验,再做别的事情)。
{
rules: [
{
test: /\.js$/,
use: [
loader: "babel-loader",
]
},{
test: /\.js$/,
use: [
loader: "eslint-loader",
]
}
]
}
也可以使用 options 中的 enforce 配置项:
{
rules: [
{
test: /\.js$/,
use: [
loader: "eslint-loader",
options: {
// 强制让这个 loader 最先执行
enforce: "pre"
}
]
},{
test: /\.js$/,
use: [
loader: "babel-loader",
]
}
]
}
enforce 默认值是 normal,除了 pre 和 normal 之外,还有 post,表示强制最后执行在 normal 之后执行这个loader。