Webpack学习(给力!篇幅较长)

前端模块化成为了主流的今天,离不开各种打包工具的贡献。webpack就是杰出代表!以下是官网对webpack介绍图。文章主要介绍概念思路,简单配置,更齐全配置一定要到官网查阅。
在这里插入图片描述
进入学习之前建议先了解一下:npm使用与package.json说明

概述

webpack是什么

本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

Webpack是一个开源的JavaScript模块打包工具,其最核心的功能是解决模块之间的依赖,把各个模块按照特定的规则和顺序组织在一起,最终合并为一个或多个bundle。这个过程就叫作模块打包一般而言,一个入口对应一个bundle,但一个入口也可产出多个bundle(比如:使用了样式分离插件)。对于webpack来说,所有的资源(.js、.css、.png)都是module。

webpack与node或npm的关系

npm是于Node社区中产生的,是nodejs的官方包管理工具 , webpack是npm生态中的一个模块 。webpack是用来解决JavaScript模块之间的依赖,npm用来解决包之间依赖和版本的控制。

模块打包的作用

为什么不直接使用普通js等资源文件呢?干嘛要打包起来?

  • 减少了网络请求连接
    普通html文档引入了多个外部资源文件,那将会建立多个http请求连接,每个连接成本都是很大的。模块打包后,可以将多个资源文件合并成一个资源文件,从而减少了http请求。
  • 解决模块间的依赖关系
    html文档中引入多个js文件,假如某两个js文件存在上下依赖关系,只能人为控制先引入哪个js文件后引入哪个js文件,这样很容易导致不清楚具体代码逻辑的人搞错。模块打包工具会分析模块间关系,帮我们维护这些依赖关系。
  • 多个模块之间的作用域是隔离的,彼此不会有命名冲突
    每个script标签中,顶层作用域即全局作用域,如果没有任何处理而直接在代码中进行变量或函数声明,就会造成全局作用域的污染。

模块打包工具的工作方式

  • 将存在依赖关系的模块按照特定规则合并为单个JS文件,一次全部加载进页面中。
  • 在页面初始时加载一个入口模块,其他模块异步地进行加载。

webpack相比于其他模块打包工具有何特长

  1. Webpack默认支持多种模块标准,包括AMD、 Commonjs,以及最新的ES6模块,而其他工具往往只能兼容一到两种。
  2. Webpack有完备的代码分割( code splitting)解决方案。可以分割打包后的资源,首屏只加載必要的部分,不太重要的功能放到后面动态地加载。提升首页渲染速度。
  3. Webpack可以处理各种类型的资源。除了 Javascript以外, Webpack还可以处理样式、模板,甚至图片等。
  4. Webpack拥有庞大的社区支持。除了 Webpack核心库以外,还有无数开发者来为它编写周边插件和工具。

核心概念

有几个概念有必要先了解,方便更好的了解webpack。先看一个例子:

关键文件目录结构

project
├── src/
|	├── index.css
|	├── index.js
|	├── common.js
|	└── utils.js
└── webpack.config.js

文件内容

//index.css
body {background-color: red;}

//utils.js
export function square(x) {
    return x * x;
}

//common.js
const {log}=console;
module.exports = {log}

//index.js
import './index.css'
import {log} from './common.js'

配置文件 webpack.config.js (看不懂没关系,后文会讲)

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports={
    entry: {
        index: "./src/index.js",
        utils: './src/utils.js',
    },
    output: {
        filename: "[name].bundle.js", // 输出 index.bundle.js 和 utils.bundle.js
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader', // css-loader 负责解析 CSS 代码, 处理 CSS 中的依赖
                ],
            },
        ]
    },
    plugins: [
        // 用 MiniCssExtractPlugin 抽离出 css 文件
        new MiniCssExtractPlugin({
            filename: '[name].bundle.css' // 输出的 css 文件名为 index.css
        }),
    ]
}

编译
在这里插入图片描述
产出文件目录结构

project
├── dist/
	├── index.bundle.css
	├── index.bundle.js
	└── utils.bundle.js

以下几个是官网说的核心概念:

入口(entry)

资源打包的入口,Webpack从这里开始进行模块依赖的查找。指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。
在这里插入图片描述

依赖关系树

输出(output)

output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认输出路径为 ./dist

预处理器(loader)

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。

主要属性:

  • test ,接受正则表达式,用于标识出需要被转换文件。
  • use ,表示进行转换时,应该使用哪个 loader。

插件(plugins)

loader 被用于转换某些文件类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件可以完成loader完成不了的任务。

更多概念:module chunk bundle

module

对于webpack来说,所有的资源(.js、.css、.png)都是module。webpack能直接处理的只有js文件,其他文件需要借助loader或者plugin。

chunk

chunk字面的意思是代码块,是webpack内部运行时的概念,在Webpack中可以理解成被抽象和包装过后的一些模块。

chunk是webpack根据功能拆分出来的,包含三种情况:

  • 项目入口(entry)
  • 通过import()动态引入的代码
  • 通过splitChunks拆分出来的代码
bundle

由chunk得到的打包产物称之为bundle。一般就是和chunk是一对一的关系,bundle就是对chunk进行编译压缩打包等处理之后的产出。

在这里插入图片描述
                    图片来源:卤蛋实验室

一般来说一个 chunk 对应一个 bundle,比如上图中的 utils.js -> chunks 1 -> utils.bundle.js;
但也有例外,比如上述例子中, MiniCssExtractPlugin 从 chunks 0 中抽离出了 index.bundle.css 文件。

module,chunk 和 bundle 其实就是同一份逻辑代码在不同转换场景下的取了三个名字:

我们直接写出来的是 module,webpack 处理时是 chunk,最后生成浏览器可以直接运行的 bundle。

创建webpack项目

安装webpack

全局安装
npm install webpack webpack-cli -g

项目合作的时候,在自己电脑测试数据运行正常,换到别人电脑,假如webpack版本不一致,可能导致输出不一样。

项目内安装
npm install webpack webpack-cli --save-dev
//npm i webpack webpack-cli -D

通过 webpack-cli 可以使用终端配置 webpack,一般一起安装使用。

--save-dev 把依赖保存在开发环境, iinstall 简写 -D--save-dev简写

建立第一个webpack项目

初始化项目
mkdir webpack-app
cd webpack-app
npm init -y

-y 可选参数,可以让你跳过对项目信息的填写

安装webpack
npm i webpack webpack-cli -D
创建两个有依赖关系的js模块(暂且在根目录创建)

有依赖关系是为了方便查看模块打包后的效果。

//add_context.js 
export default function(){
	document.write('Hello Webpack');
}
//index.js 
import addContent from './add_content.js'
document.write('Hello Joe<br/>');
addContent();

目前项目结构如下:

 project
	├── node_modules/
	├── add_content.js
	├── index.js
	├── package.json
	└── package-lock.json
进行模块打包
//入口文件为index.js 输出的整合文件名为bundle.js 运行模式为开发模式
npx webpack --entry=./js/index.js --output-filename=bundle.js --mode=development

npx 在node环境执行 webpack二进制文件(软件) 后面的就是对webpack的配置参数。npx webpack --help查看更多的webpack配置参数。

执行完,根目录下生成一个 dist 文件夹,里面装有 bundle.js 文件。

新建html并引入bundle.js看页面效果

根目录新建index.html

<body>
	<script src="./dist/bundle.js"></script>
</body>

打开网页,你会看到Hello Joe Hello Webpack。


不想每次启动都写这么多:

package.json

  "scripts": {
	"build": "webpack --entry=./index.js --output-filename=bundle.js --mode=development"
  },

以后就可以 npm run build这样启动

创建一个规范化的webpack项目

1.规范工程目录

//对上面入门案例文件目录进行修改 只写了关键几个
project
	├── dist/  (存放项目的输出文件)
	├── node_module/  (node资源文件)
	├── src/  (存放项目的资源文件)
	|	├── index.js  (webpack默认源代码的入口文件)
	|	└── add_content.js
	├── index.html  
	├── package.json  (项目的描述文件 main:项目入口文件 dependencies:生产依赖 devDependencies:开发依赖)
	├── package-lock.json
	└── webpack.config.js  (webpack默认配置文件)

2.添加webpack配置文件

//webpack.config.js
module.exports={
	entry:'./src/index.js',
	output:{
		filename:'bundle.js',
	},
	mode:'development',
}

3.修改package.json中对webpack的启动脚本

"build": "webpack"

如此,webpack将会自动寻找根目录下的webpack.config.js文件来启动webpack!之后npm run build即可。

webpack-dev-server 拒绝每次调试都要编译一次

之前每次改变资源文件都需要重新编译一次,甚是麻烦!

webpack-dev-serer是一个便捷的本地开发工具 ,有—项很便捷的特性就是live-reloading(自动刷新)。

1.安装

npm install webpack-dev-server --save-dev

2.配置到package.json的启动脚本中

 "scripts": {
    "build": "webpack",
	"dev":"webpack-dev-server"
  },

3.webpack配置文件添加对webpack-dev-server的配置

module.exports={
	entry:'./src/index.js',
	output:{
		filename:'bundle.js',
	},
	mode:'development',
	devServer:{
		publicPath:'/dist'
	}
}

4.启动看效果

rpn run dev

在这里插入图片描述

可见服务发布在本地8080端口,需要通过该端口访问才能看到自动刷新的效果,直接打开index.html文件是没有这个效果的。因为webpack-dev-server并没有打包输出,而是将编译结果写在内存中,方便迅速刷新调试。
在这里插入图片描述

启动报错 Cannot find module 'webpack-cli/bin/config-yargs’
貌似高版本的webpack-cli没有config-yargs模块了,我把它卸载npm uninstall webpack-cli了,然后安装了npm i webpack-cli@3.3.9 -D版本即可!

资源输入输出

资源处理流程

在一切流程的最开始,我们需要指定一个或多个入口(entry),也就是告诉Webpack具体从源码目录下的哪个文件开始打包。如果把工程中各个模块的依赖关系当作一棵树,那么入口就是这棵依赖树的根。这些存在依赖关系的模块会在打包时被封装为—个chunk,打包后生成的就是bundle。
在这里插入图片描述

配置资源入口

webpack通过contextentry这两个配置项来共同决定入口文件的路径。在配置入口时,实际上做了两件事:

  • 确定入口模块位置,告诉Webpack从哪里开始进行打包。
  • 定义chunk name。如果工程只有一个入口,那么chunk 默认名为“main”; 如果工程有多个入口,我们需要为每个入口定义chunk name,来作为该chunk的标识。一般而言,chunk name也是bundle name,但bundle name 往往重新定义输出。
context

配置context的主要目的是让entry的编写更加简洁,尤其是在多入口的情况下。context可以省略,默认值为当前工程的根目录。context+entry的路径拼接成资源的完整路径。
在这里插入图片描述

entry

主讲数组类型和对象类型入口。

📌对象形式

如果想要定义多入口,则必须使用对象的形式。对象的属性名(key)是chunk name,属性值(value)是入口路径。

module.exports={
	entry:{
		index:'./src/index.js',
		other:'./src/other.js'
	}
}

📌数组形式

传入一个数组的作用是将多个资源预先合并,在打包时webpack会将entry数组中的最后一个元素作为实际的入口路径。

module.exports={
	entry:['./src/other_entry.js','./src/index.js'],
}

等价于

//index.js
import other_entry.js
module.exports={
	entry:'./src/index.js',
}

优化资源入口 vendor

vendor是用来提取公共且很少发生变化的模块,在webpack中vendor一般指的是工程所使用的库、框架等第三方模块集中打包而产生的bundle。

若工程只产生一个JS文件并且它的体积很大,一旦产生代码更新,用户都要重新下载整个资源文件,这对于页面的性能是非常不友好的。

entry:{
	app:'./src/index.js',
	vendor:['react','react-dom','react-router'],
},

通过这样的配置,app bundle将只包含业务模块,其依赖的第三方模块将会被抽取出来生成一个新的bundle。由于vendor仅仅包含第三方模块,这部分不会经常变动,因此可以有效地利用客户端缓存,在用户后续请求页面时会加快整体的渲染速度。

配置资源出口

filename
//多入口多输出
module.exports={
	entry:{
		index:'./src/index.js',
		other:'./src/other.js'
	}
 output:{
		filename:'[name].js',
	},
}

[name]会被代替为chunk name,还有以下几种模板变量也可以部署到filename配置中。
在这里插入图片描述
上述变量一般有如下两种作用:

  • 区分chunk。
  • 控制客户端缓存。表中的[hash]和[chunkhash]都与chunk内容直接相关,在filename中使用了这些变量后,当chunk的内容改变时,可以同时引起资源文件名的更改,客户端检查发现请求文件chunkhash发生变化就下载新文件。[query]也可以起到类似的效果,只不过它与chunk内容无关,要由开发者手动指定。

如果要控制客户端缓存,最好还要加上[chunkhash],因为每个chunk所产生的[chunkhash]只与自身内容有关,单个chunk内容的改变不会影响其他资源,可以最精确地让客户端缓存得到更新。

hash是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash值。采用hash计算的话,每一次构建后生成的哈希值都不一样,即使文件内容压根没有改变。这样子是没办法实现缓存效果

 output:{
		filename:'[name].[chunkhash].js',
	},

在这里插入图片描述

path

用来指定资源输出的位置,要求必须是绝对路径.

输出位置:打包完成后资源产生的目录,一般将其指定为工程中的dist目录。

	output:{
		filename:'[name].js',
		path:path.resolve(__dirname,'dist'),
	},

在Webpack 4之后,output.path已经默认为dist目录,除非我们需要更改它,否则不必单独配置。

publicPath

用来指定资源的请求位置.(默认值为空字符串''
请求位置:由JS或CSS所请求的间接资源路径。

output中的publicPath用于给生成的静态资源路径添加前缀,也就是会在打包生成的html文件里面引用资源路径中添加前缀。

📌 页面中的资源分为两种 :直接资源 间接资源

直接资源:由HTML页面直接请求的,比如通过<script>标签加载的JS;
间接资源:由JS或CSS请求的,如异步加载的JS、从CSS请求的图片字体等。

publicPath的作用就是指定这部分间接资源的请求位置。

首屏加载的JS资源地址是通过页面中的<script>来指定的,而间接资源(通过首屏JS再进一步加载的JS)的位置则要通过output.publicPath来指定。比如import('./bar.js')使bar.js成为了一个间接资源,我们需要配置publicPath来告诉Webpack去哪里获取它。


publicPath有3种形式:

1.html相关的

与HTML相关,也就是说我们可以将publicPath指定为HTML的相对路径,在请求这些资源时会以当前页面HTML所在路径加上相对路径,构成实际请求的URL。

// 假设当前HTML地址为 https://example.com/app/index.html
// 在该页面异步加载的资源名为 0.chunk.js   
publicPath: "" // 实际路径https://example.com/app/0.chunk.js
publicPath: "./js" // 实际路径https://example.com/app/js/0.chunk.js
publicPath: "../assets/" // 实际路径https://example.com/aseets/0.chunk.js

2.host相关的

publicPath的值以/开始,则代表此时publicPath是以当前页面的host name为基础路径的。

// 假设当前HTML地址为 https://example.com/app/index.html
// 异步加载的资源名为 0.chunk.js
publicPath: "/" // 实际路径https://example.com/0.chunk.js
publicPath: "/js/" // 实际路径https://example.com/js/0.chunk.js
publicPath: "/dist/" // 实际路径https://example.com/dist/0.chunk.js

3.CDN相关的

绝对路径的形式,一般发生于静态资源放在CDN上面时,因为域名不一样

// 假设当前页面路径为 https://example.com/app/index.html
// 异步加载的资源名为 0.chunk.js
publicPath: "http://cdn.com/" // 实际路径http://cdn.com/0.chunk.js
publicPath: "https://cdn.com/" // 实际路径https://cdn.com/0.chunk.js
publicPath: "//cdn.com/assets/" 实际路径 //cdn.com/assets/0.chunk.js
拓展:devServer 中 publicPath

webpack-dev-server下也有publicPath配置,是针对webpack-dev-server的静态资源服务路径。

devServer里面的publicPath表示的是此路径下的打包文件可在浏览器中访问,若是devServer里面没有设置publicPath,则会认可是output里面设置的publicPath的值。

const path = require('path');
module.exports = {
    entry: './src/app.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
    },
    devServer: {
        publicPath: '/assets/',
        port: 3000,
    },
};

启动webpack-dev-server的服务后,访问localhost:3000/dist/bundle.js时却会得到404。这是因为devServer.publicPath配置项将资源位置指向了localhost:3000/assets/,因此只有访问localhost:3000/assets/bundle.js才能得到我们想要的结果。

将webpack-dev-server的publicPath与Webpack中的output.path保持一致,这样在任何环境下资源输出的目录都是相同的。

loader 一切皆模块

一个Web工程通常会包含HTML、JS、CSS、模板、图片、字体等多种类型的静态资源,并且这些资源之间都存在着某种联系。比如,JS文件之间有互相依赖的关系,在CSS中可能会引用图片和字体等。对于Webpack来说,所有这些静态资源都是模块,可以像加载一个JS文件一样去加载它们。

loader

装载器(loader),它赋予了Webpack可处理不同资源类型的能力,如HTML、CSS、模板、图片、字体等,极大丰富了其可扩展性。

webpack本身只认识JavaScript,对于其他类型的资源必须预先定义一个或多个loader对其进行转译,输出为webpack能够接收的形式再继续进行,因此loader做的实际上是一个预处理的工作。loader本身只是编译核心库与webpack的连接器,有时还需要为loader补充额外的库。类似于我们装babel-loader时还要安装babel-core;编译sass除了sass-loader以外还要安装node-sass,node-sass是真正用来编译SCSS的,而sass-loader只是起到黏合的作用。

loader 执行顺序

webpack中的loader按照执行顺序可分为pre、inline、normal、post四种类型,我们直接定义的loader都属于normal类型(从后往前),inline形式官方已经不推荐使用,而pre和post则需要使用enforce配置项来指定。

module:{
	rules:[{
		test:/\.css$/,
		use:['style-loader','css-loader'],
	}]
}

use 数组是逆序加载的,因此要把最后生效的loader放在use数组最前面。

loader 的使用案例

比如:在js文件引入css文件

import './style.css';
安装css-loader

为了能在js文件引入css模块,第一步要把css-loader加到工程中。

npm i css-loader -D
配置loader,将css-loader引入工程中
module:{
	rules:[{
		test:/\.css$/,
		use:['css-loader'],
	}]
}

loader相关的配置都在module对象中,其中module.rules代表了模块的处理规则。test可接收一个正则表达式,use可接收一个数组,数组包含该规则所使用的loader。/\.css$/匹配所有以.css结尾的文件。

此时,CSS的样式仍然没有在页面上生效。这是因为css-loader的作用仅仅是处理CSS的各种加载语法(@importurl()函数等),如果要使样式起作用还需要style-loader来把样式插入页面。

把style-loader加到工程
npm i style-loader -D
将style-loader引入工程中
module:{
	rules:[{
		test:/\.css$/,
		use:['style-loader','css-loader'],
	}]
}

把style-loader加到了css-loader前面,这是因为在webpack打包时是将资源按照use数组逆序传给loader处理的,因此要把最后生效的放在前面。否则报错。

npm run build模块打包,发现生成的 dist 文件夹里并没有css文件,因为样式一并打包到入口的bundle.js中了。原本你在普通html文档中,只要引入这个bundle.js 就可同时兼具样式的效果了。

样式预处理

样式预编译语言,如Sass(scss)、Less等。
sass,less编译后可生成css。

sass

1.安装

npm install sass-loader node-sass

发现node-sass下载太慢,后面直接就是连不上,将node-sass库设为阿里源的node-sass库!

npm config set sass_binary_site=https://npm.taobao.org/mirrors/node-sass/

2.配置

 module: {
    rules: [
        {
            test: /\.scss$/,
            use: ['style-loader', 'css-loader', 'sass-loader'],
        }
    ],
},

3.测试运行

//style.scss
	$color='red'
	body{
		color: $color;
	}

//index.js
import './style.scss'

打包后生成的css文件

body{
		color: ‘red’;
	}
less
npm install less-loader less

其他步骤雷同

sourceMap

背景:我们在打包中,将开发环境中源代码经过压缩,去空格,babel编译转化,最终可以得到适用于生产环境的项目代码,这样处理后的项目代码和源代码之间差异性很大,会造成无法debug的问题。
生产环境的代码都是经过压缩处理等的,调试的时候只能定位到压缩处理后的代码的位置,无法定位到开发环境中对应的源代码所在位置。

sourcemap就是为了解决上述代码定位的问题,简单理解,就是构建了处理前的代码和处理后的代码之间的桥梁。主要是方便开发人员的错误定位。这里的处理操作包括:
I)压缩,减小体积
II)将多个文件合并成同一个文件
III)其他语言编译成javascript,比如TypeScript和CoffeeScript等

假如我们想要在浏览器的调试工具里查看源码,需要分别为sass-loader和css-loader单独添加source map的配置项。

    rules: [
        {
            test: /\.scss$/,
            use: [
                'style-loader',
                {
                    loader: 'css-loader',
                    options: {
                        sourceMap: true,
                    },
                }, {
                    loader: 'sass-loader',
                    options: {
                        sourceMap: true,
                    },
                }
            ],
        }
    ],

loader的其他配置项

exclude include

excludeinclude是用来排除或包含指定目录下的模块,可接收正则表达式或者字符串(文件绝对路径),以及由它们组成的数组。

module:{
	rules:[{
		test:/\.css$/,
		use:['style-loader','css-loader'],
		exclude:/node_modules/
	}]
}

node_modules模块不会执行这条规则,避免遍历该模块,加快打包速度.

同时配置exclude include ,exclude 优先级高

resource issuer

在Webpack中,被加载模块是resource,而加载者是issuer。

// index.js  
import './style.css';

resource为/path/of/app/style.css,issuer是/path/of/app/index.js。

配置项test、exclude、include本质上属于对resource也就是被加载者的配置。

只有在/src/pages/目录下面的JS文件引用了CSS文件,这条rule才会生效

rules: [
    {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
        exclude: /node_modules/,
        issuer: {
            test: /\.js$/,
            include: /src/pages/,
        },
    }
],

上述可读性较差,以下是等价的形式

{
        use: ['style-loader', 'css-loader'],
        resource: {
            test: /\.css$/,
            exclude: /node_modules/,
        },
        issuer: {
            test: /\.js$/,
            exclude: /node_modules/,
        },
    }
],
enforce

webpack中的loader按照执行顺序可分为pre、inline、normal、post四种类型,上面我们直接定义的loader都属于normal类型(从后往前),inline形式官方已经不推荐使用,而pre和post则需要使用enforce来指定。

enforce用来指定一个loader的种类,只接收“pre”或“post”两种字符串类型的值。

“pre”,代表它将在所有正常loader之前执行
“post”,代表在所有loader之后执行

rules: [
    {
        test: /\.js$/,
        enforce: 'pre',
        use: 'eslint-loader',
    }
],

在配置中添加了一个eslint-loader来对源码进行质量检测,其enforce的值为“pre”,代表它将在所有正常loader之前执行。

常用loader介绍

babel-loader
npm install babel-loader @babel/core @babel/preset-env -D
rules: [
  {
    test: /\.js$/,
    exclude: /node_modules/,
    use: {
      loader: 'babel-loader',
      options: {
        cacheDirectory: true,
        presets: [[
          'env', {
            modules: false,
          }
                  ]],
      },
    },
  }
],

由于@babel/preset-env会将ES6 Module转化为CommonJS的形式,这会导致Webpack中的tree-shaking特性失效,将@babel/preset-env的modules配置项设置为false会禁用模块语句的转化,而将ES6 Module的语法交给Webpack本身处理。

babel-loader支持从.babelrc文件读取Babel配置,因此可以将presets和plugins从Webpack配置文件中提取出来,也能达到相同的效果。

html-loader

html-loader用于将HTML文件转化为字符串并进行格式化,这使得我们可以把一个HTML片段通过JS加载进来。

npm install html-loader -D
rules: [
    {
        test: /\.html$/,
        use: 'html-loader',
    }
],
// header.html
<header>
h1>This is a Header.</h1>
</header>

// index.js
import headerHtml from './header.html';
document.write(headerHtml);
file-loader

file-loader用于打包文件类型的资源(图片等),并返回其publicPath。

npm install file-loader -D
rules: [
          {
              test: /\.(png|jpg|gif)$/,
              use: 'file-loader',
          }
      ],

上面我们对png、jpg、gif这类图片资源使用file-loader,然后就可以在JS中加载图片了。

import avatarImage from './avatar.jpg';
console.log(avatarImage); // c6f482ac9a1905e1d7d22caa909371fc.jpg
url-loader

url-loader与file-loader作用类似,唯一的不同在于用户可以设置一个文件大小的阈值,当大于该阈值时与file-loader一样返回publicPath,而小于该阈值时则返回文件base64形式编码。

npm install url-loader -D
rules: [
    {
        test: /\.(png|jpg|gif)$/,
        use: {
            loader: 'url-loader',
            options: {
                limit: 10240,
                name: '[name].[ext]',
                publicPath: './assets-path/',
            },
        },
    }
import avatarImage from './avatar.jpg';
console.log(avatarImage); // data:image/jpeg;base64,/9j/2wCEAAgGBg……

插件

webpack具有大量的插件支持,详细看webpack plugins 以下只对几个常用插件做介绍。

mini-css-extract-plugin (样式分离)

该插件将CSS提取到单独的文件中。

1.安装

npm i mini-css-extract-plugin -D

2.创建css文件

project
	├── src/  
	|	├── common.css
	|	├── index.css
	|	└── index.js
	├── package.json
	└── webpack.config.js  
//common.css
body{text-align: center;}

//index.css
body{background-color: #00FFFF;}

//index.js
import './common.css'
import './index.css'

3.配置

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
	entry: './app.js',
	output: {
		filename: '[name].js',
	},
	mode: 'development',
	module: {
		rules: [{
			test: /\.css$/,
			use: [MiniCssExtractPlugin.loader,'css-loader'],
		}],
	},
	plugins: [
		new MiniCssExtractPlugin({
			filename: '[name].css',
		})
	],
};

4.运行

npx webpack  

产出 main.css main.js

project
├── dist/
	├── main.css
	└── main.js
//main.css
body{text-align: center;}
body{
	background-color: #00FFFF;
}

被index.js引入的两个css文件合并为一了。

terser-webpack-plugin (压缩JS) ~tree shaking

Webpack 4中默认使用的压缩JavaScript的插件为terser-webpack-plugin。从Webpack 4之后,这项配置被移到了config.optimization.minimize。(如果开启了mode:production,则不需要人为设置)。

1.默认启动

mode:'production'

2.手动启动

optimization: {
        minimize: true,
    },

3.自定义配置

npm i -D uglifyjs-webpack-plugin

webpack.config.js

const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
		minimize: true,
        minimizer: [ // 覆盖默认的 minimizer
            new TerserPlugin({
                /* your config */
                test: /\.js(\?.*)?$/i,
                exclude: /\/excludes/,
                extractComments: true,//提取注释到一个独立文件
            })
        ],
      }
  }

optimize-css-assets-webpack-plugin (压缩css)

样式分离后才可压缩css。

npm i optimize-css-assets-webpack-plugin -D

webpack.config.js

const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
optimization: {
            minimizer: [new OptimizeCSSAssetsPlugin({
            // 生效范围,只压缩匹配到的资源
            assetNameRegExp: /\.optimize\.css$/g,
            // 压缩处理器,默认为 cssnano
            cssProcessor: require('cssnano'),
            // 压缩处理器的配置
            cssProcessorOptions: { discardComments: { removeAll: true } },
            // 是否展示 log
            canPrint: true,
        })],
    },
};

compression-webpack-plugin (资源压缩)

这个和上面的压缩不同,这个是使用算法压缩,改变原有的文件格式,前端接收数据后需要解压。上面的2种压缩只是去空白,死代码等,打包后还是原来资源的格式。

npm i compression-webpack-plugin -D

webpack.config.js

const CompressionWebpackPlugin = require('compression-webpack-plugin')
module.exports = {
    plugins: [
		new CompressionWebpackPlugin({
		        filename: '[path][name].gz[query]',
		        algorithm: 'gzip',
		        test: /\.(js|css|json|ttf)(\?.*)?$/i,//压缩了代码和字体
		        threshold: 0,
		        minRatio: 0.8,
		      }),
    ]
};

html-webpack-plugin (生成注入依赖的html文件)

html-webpack-plugin插件用于简化创建HTML文件,它会在body中用script标签来包含我们生成的所有bundles文件。不需要我们手动引入。

该插件的两个主要作用:

  • 为html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题,也不用每次打包后手动引入发生变化的资源文件(加了hash命名的文件)

  • 可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置N个html-webpack-plugin可以生成N个页面入口

将 webpack中entry配置的相关入口chunk 和 mini-css-extract-plugin抽取的css样式 插入到该插件提供的template或者templateContent配置项指定的内容基础上生成一个html文件,具体插入方式是将样式link插入到head元素中,script插入到head或者body中。

npm i html-webpack-plugin -D
var HtmlWebpackPlugin = require('html-webpack-plugin')
    
webpackconfig = {
    ...
    plugins: [
        new HtmlWebpackPlugin()
    ]
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
  <link href="index-af150e90583a89775c77.css" rel="stylesheet"></head>
  <body>
  <script type="text/javascript" src="common-26a14e7d42a7c7bbc4c2.js"></script>
  <script type="text/javascript" src="index-af150e90583a89775c77.js"></script></body>
</html>

输出的资源名绑定了chunkhash之后,资源改变,打包时资源名也会改变。
html-webpack-plugin会自动地将我们打包出来的资源名放入生成的[chunkname].html中,这样我们就不必手动地更新资源URL了。

配置多个html页面

  ...
 plugins: [
        new HtmlWebpackPlugin({
             template: 'src/html/index.html',
              excludeChunks: ['list', 'detail']
        }),
        new HtmlWebpackPlugin({
            filename: 'list.html',
            template: 'src/html/list.html',
            chunks: ['common', 'list']
        }), 
        new HtmlWebpackPlugin({
          filename: 'detail.html',
          template: 'src/html/detail.html',
           chunks: ['common', 'detail']
        })
    ]
    ...

我们也可以传入一个已有的HTML模板

<!DOCTYPE html>
<!-- template.html -->
<html lang="zh-CN">
    <head>
    <meta charset="UTF-8">
        <title>Custom Title</title>
    </head>
    <body>
        <div id="app">app</div>
        <p>text content</p>
    </body>
</html>

// webpack.config.js
new HtmlWebpackPlugin({
    template: './template.html',
})

html-webpack-plugin详解

概念补充

mode

提供mode配置选项将告诉webpack采用相应环境下的内置优化。
有三个值:development ||production || none(退出任何默认优化选项)

development 模式下默认优化项

// webpack.development.config.js
module.exports = {
 mode: 'development'
 devtool: 'eval',
 cache: true,
 performance: {
   hints: false
 },
 output: {
   pathinfo: true
 },
 optimization: {
   moduleIds: 'named',
   chunkIds: 'named',
   mangleExports: false,
   nodeEnv: 'development',
   flagIncludedChunks: false,
   occurrenceOrder: false,
   concatenateModules: false,
   splitChunks: {
     hidePathInfo: false,
     minSize: 10000,
     maxAsyncRequests: Infinity,
     maxInitialRequests: Infinity,
   },
   emitOnErrors: true,
   checkWasmTypes: false,
   minimize: false,
   removeAvailableModules: false
 },
 plugins: [
   new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
 ]
}

production 模式下默认优化项

// webpack.production.config.js
module.exports = {
  mode: 'production',
 performance: {
   hints: 'warning'
 },
 output: {
   pathinfo: false
 },
 optimization: {
   moduleIds: 'deterministic',
   chunkIds: 'deterministic',
   mangleExports: 'deterministic',
   nodeEnv: 'production',
   flagIncludedChunks: true,
   occurrenceOrder: true,
   concatenateModules: true,
   splitChunks: {
     hidePathInfo: true,
     minSize: 30000,
     maxAsyncRequests: 5,
     maxInitialRequests: 3,
   },
   emitOnErrors: false,
   checkWasmTypes: true,
   minimize: true,
 },
 plugins: [
   new TerserPlugin(/* ... */),
   new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
   new webpack.optimize.ModuleConcatenationPlugin(),
   new webpack.NoEmitOnErrorsPlugin()
 ]
}

tree shaking

tree shaking 树抖动,就会掉死叶子。在代码中,就是去无用代码的意思。

tree shaking本身只是为死代码添加上标记,真正去除死代码是通过压缩工具来进行的。使用我们前面介绍过的terser-webpack-plugin即可。在Webpack 4之后的版本中,直接mode:production也可以达到相同的效果。

ES6 Module依赖关系的构建是在代码编译时而非运行时。tree shaking功能,它可以在打包过程中帮助我们检测工程中没有被引用过的模块,这部分代码将永远无法被执行到,因此也被称为“死代码”。Webpack会对这部分代码进行标记,并在资源压缩时将它们从最终的bundle中去掉。

// index.js
import { foo } from './util';
foo();

// util.js
export function foo() {
    console.log('foo');
}
export function bar() { // 没有被任何其他模块引用,属于“死代码”
    console.log('bar');
}

在Webpack打包时会对bar()添加一个标记,在正常开发模式下它仍然存在,只是在生产环境的压缩那一步会被移除掉。

模块热替换(Hot Module Replacement,HMR)

热更新 在我们每次改变代码,或者资源文件的时候,整个页面其实都会刷新。
热替换,直接替换更改后的依赖模块,而不用刷新整个页面,可以简单理解成局部更新。

许多Web开发框架和工具都提供了live reload来做热更新。模块热替换是 webpack 提供的最有用的功能之一。HMR对于大型应用尤其适用。试想一个复杂的系统每改动一个地方都要经历资源重构建、网络请求、浏览器渲染等过程,怎么也要几秒甚至几十秒的时间才能完成;

项目是基于webpack-dev-server开发时才可以启动模块热替换

const webpack = require('webpack');
module.exports = {
  // ...
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ],
  devServer: {
    hot: true,
  },
};

Webpack会为每个模块绑定一个module.hot对象,这个对象包含了HMR的API。

调用HMR API有两种方式,一种是手动地添加这部分代码;另一种是借助一些现成的工具,比如react-hot-loader、vue-loader等。

// index.js
import { add } from 'util.js';
add(2, 3);

if (module.hot) {
  module.hot.accept();
}

index.js是应用的入口,那么我们就可以把调用HMR API的代码放在该入口中,这样HMR对于index.js和其依赖的所有模块都会生效。当发现有模块发生变动时,HMR会使应用在当前浏览器环境下重新执行一遍index.js(包括其依赖)的内容,但是页面本身不会刷新。

webpack打包原理

webpack打包原理是根据文件间的依赖关系对其进行静态分析,然后将这些模块按指定规则生成静态资源,当 webpack 处理程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

webpack有两种组织模块的依赖方式,同步、异步。异步依赖将作为分割点,形成一个新的块;在优化了依赖树之后,每一个异步区块都将作为一个文件被打包。

生产环境调优 打包优化

样式分离 MiniCssExtractPlugin

通过loader可以将样式文件引入到js文件中,模块打包后输出的还是js文件(样式也直接写入到该js文件了)。此时,哪怕只是修改了js部分的代码,那么css模块也会被重新打包;或者只修改了css文件,js文件是没有变化的,但是他们都是在一个bundle中,所以都会被认为都有修改。

常用的分离样式的插件有两个:extract-text-webpack-plugin(webpack4以下) 和 mini-css-extract-plugin(webpack4以上)

我使用的"webpack": “^5.10.1”,想试用extract-text-webpack-plugin,结果折腾半天也是报错,罢了罢了,反正过期货(估计已经被版本pass掉了),官方推崇的也是mini-css-extract-plugin。

上文有对mini-css-extract-plugin的介绍了。

代码分片 SplitChunks

代码分片(code splitting)可以把代码按照特定的形式进行拆分,按需加载。用户每次只加载必要的资源,优先级不太高的资源则采用延迟加载等技术渐进式地获取,这样可以保证页面的首屏速度。

手动提取公共模块

在Webpack中每个入口(entry)都将生成一个对应的资源文件,一些库和工具是不常变动,可以把它们放在一个单独的入口中,因此可以有效地利用客户端缓存,让用户不必在每次请求页面时都重新加载。(上文的vendor)

// webpack.config.js
entry: {
        app: './app.js',//业务逻辑代码 常变
    	lib: ['lib-a', 'lib-b', 'lib-c']//工具类库  少变
},
output: {
	filename: '[name].[chunkcode].js',
},


// index.html
<script src="dist/lib.1ac59013ea124.js"></script>
<script src="dist/app.43b659013ea12.js"></script>

lib.1ac59013ea124.js chunkcode根据文件内容生成的,内容不变,这个也不变,这样就不会重新打包生成!

以后不必担心引入这个生成的名麻烦的问题,有html-webpack-plugin帮解决,下文会讲到。

自动提取公共模块 optimization.SplitChunks

SplitChunks是Webpack 4内部自带的插件,用于将多个Chunk中公共的部分提取出来。

提取公共模块的好处

  • 开发过程中减少了重复模块打包,可以提升开发速度;
  • 减小整体资源体积;
  • 合理分片后的代码可以更有效地利用客户端缓存。

🌰案例对证

1.模块不作提取时

//index.js
import React from 'react'
document.write('index'+React.version)

//other.js
import React from 'react'
document.write('other'+React.version)
//webpack.config.js
const path=require('path')
module.exports={
	context:path.resolve(__dirname,'./src'),
	entry:{
		index:'./index.js',
		other:'./other.js',
	},
	output:{
		filename:'[name].js',
	},
}

在这里插入图片描述
可见index.js和other.js都分别加载了react模块。

2.使用SplitChunks提取公共模块
SplitChunks是在webpack的优化项中的,查看更多优化项webpack.optimization

//webpack.config.js
	optimization:{
		splitChunks:{
			chunks:'all',
		}
	}

chunks的值为all,这个配置项的含义是,SplitChunks将会对所有的chunks生效。默认情况下,SplitChunks只对异步chunks,比如import('xx.js')生效。

ok!看下打包结果:
在这里插入图片描述

project
├── dist/
	├── vendors-node_modules_react_index_js.js
	├── index.js
	└── other.js

多生成一个文件vendors-node_modules_react_index_js.js,这个文件存储了原index.js和other.js的公共模块react。

webpack5自动分块需要满足以下条件:

  • 可以共享新块,或者模块来自node_modules文件夹
  • 新的块将大于20kb(在min + gz之前)
  • 按需加载块时并行请求的最大数量将小于或等于30
  • 初始页面加载时并行请求的最大数量将小于或等于30
    当试图满足最后两个条件时,最好使用较大的块。

如果提取后的资源体积太小,那么带来的优化效果也比较一般。

同时加载过多的资源,每一个请求都要花费建立链接和释放链接的成本,更多需要花费更多时间和性能,为此做出了平衡。

看下splitChunks默认配置便可知:

splitChunks: {
      chunks: 'async',
      minSize: 20000,
      minRemainingSize: 0,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      automaticNameDelimiter: '~',
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }

(1)SplitChunks工作模式
chunks配置SplitChunks的工作模式。它有3个可选值,分别为async(默认)、initial和all。async即只提取异步chunk,initial则只对入口chunk生效(如果配置了initial则上面异步的例子将失效),all则是两种模式同时开启。
(2)匹配条件
minSize、minChunks、maxAsyncRequests、maxInitialRequests都属于匹配条件
(3)命名
配置项name默认为true,它意味着SplitChunks可以根据cacheGroups和作用范围自动为新生成的chunk命名,并以automaticNameDelimiter分隔。如vendorsab~c.js意思是cacheGroups为vendors,并且该chunk是由a、b、c三个入口chunk所产生的。据在webpack5.10.3测试,automaticNameDelimiter并不生效
(4)cacheGroups
可以理解成分离chunks时的规则。默认情况下有两种规则——vendors和default。vendors用于提取所有node_modules中符合条件的模块,default则作用于被多次引用的模块。我们可以对这些规则进行增加或者修改,如果想要禁用某种规则,也可以直接将其置为false。当一个模块同时符合多个cacheGroups时,则根据其中的priority配置项确定优先级。

异步加载 import()

将一些暂时使用不到的模块延迟加载,初次渲染的时候用户下载的资源尽可能小,后续的模块等到恰当的时机再去触发加载。因此一般也把这种方法叫作按需加载

//bar.js
export function add(a,b){
	return a+b;
}
//foo.js
import('./bar.js').then(({add})=>{console.log(add(1,2))})

webpack.config.js

const path=require('path')
module.exports={
	context:path.resolve(__dirname,'./src/js/'),
	entry:{
		foo:'./foo.js',
		bar:'./bar.js',
	},
	output:{
		filename:'[name].[chunkhash].js',
		publicPath:'/dist'
	},
	mode:'development',
}
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<script  src="src/js/foo.js"></script>
	</body>
</html>

首屏加载的JS资源地址是通过页面中的script标签来指定的,而间接资源(通过首屏JS再进一步加载的JS)的位置则要通过output.publicPath来指定。上面我们的import()相当于使bar.js成为了一个间接资源,我们需要配置publicPath来告诉Webpack去哪里获取它。

观察Network面板:
在这里插入图片描述
Initiator指向的是当前资源的的启动文件。

可以发现bar.js是由foo.js发起请求得到的结果,并且是在foo.js加载完后才加载的,实际是异步加载的。

代码压缩

上文插件部分分别介绍了js css gzip压缩!不累赘
在这里插入图片描述

缩小打包范围

exclude和include

当exclude和include规则有重叠的部分时,exclude的优先级更高。

🌰include使babel-loader只生效于源码目录/src/scripts。

module: {
  rules: [
    {
      test: /\.js$/,
      include: /src\/scripts/,
      loader: 'babel-loader,
    }
  ],
},
noParse

不去解析但仍会打包到bundle中.

有些库我们是希望Webpack完全不要去进行解析的,即不希望应用任何loader规则,库的内部也不会有对其他模块的依赖,那么这时可以使用noParse对其进行忽略。

module.exports = {
  //...
  module: {
    noParse: /lodash/,
  }
};

上面的配置将会忽略所有文件名中包含lodash的模块,这些模块仍然会被打包进资源文件,只不过Webpack不会对其进行任何解析。

IgnorePlugin

exclude和include是确定loader的规则范围,noParse是不去解析但仍会打包到bundle中。IgnorePlugin,它可以完全排除一些模块,被排除的模块即便被引用了也不会被打包进资源文件中。

一些由库产生的额外资源我们用不到但又无法去掉,因为引用的语句处于库文件的内部。比如,Moment.js是一个日期时间处理相关的库,为了做本地化它会加载很多语言包,对于我们来说一般用不到其他地区的语言包,但它们会占很多体积,这时就可以用IgnorePlugin来去掉。

plugins: [
  new webpack.IgnorePlugin({
    resourceRegExp: /^\.\/locale$/, // 匹配资源文件
    contextRegExp: /moment$/, // 匹配检索目录
  })
],
Cache

有些loader会有一个cache配置项,用来在编译代码后同时保存一份缓存,在执行下一次编译前会先检查源码文件是否有变化,如果没有就直接采用缓存,也就是上次编译的结果。这样相当于实际编译的只有变化了的文件,整体速度上会有一定提升。

在Webpack 5中添加了一个新的配置项“cache:{type:"filesystem"}”,它会在全局启用一个文件缓存。要注意的是,该特性目前仅仅是实验阶段,并且无法自动检测到缓存已经过期。目前的解决办法就是,当我们更新了任何node_modules中的模块或者Webpack的配置后,手动修改cache.version来让缓存过期。

开发环境调优

优化构建 happyPack

在打包过程中有一项非常耗时的工作,就是使用loader将各种资源进行转译处理。

工作流程概括如下:
1)从配置中获取打包入口;
2)匹配loader规则,并对入口模块进行转译;
3)对转译后的模块进行依赖查找(如a.js中加载了b.js和c.js);
4)对新找到的模块重复进行步骤2)和步骤3),直到没有新的依赖模块。

从步骤2)到步骤4)是一个递归的过程,此处的Webpack是单线程的。这里的问题在于Webpack是单线程的,假设一个模块依赖于几个其他模块,Webpack必须对这些模块逐个进行转译。虽然这些转译任务彼此之间没有任何依赖关系,却必须串行地执行。

HappyPack是一个通过多线程来提升Webpack打包速度的工具。适用于那些转译任务比较重的工程,类似babel-loader和ts-loader效果更佳。

npm i -D happypack
单个loader优化
const HappyPack = require('happypack');
module.exports = {
  //...
  module: {
    rules: [
    {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'happypack/loader',
      }
    ],
  },
  plugins: [
    new HappyPack({
      loaders: [
        {
          loader: 'babel-loader',
          options: {
            presets: ['react'],
          },
        }
        ],
    })
  ],
};

使用happypack/loader替换了原有的babel-loader,并在plugins中添加了HappyPack的插件,将原有的babel-loader连同它的配置插入进去即可。

多个loader优化

在使用HappyPack优化多个loader时,需要为每一个loader配置一个id,否则HappyPack无法知道rules与plugins如何一一对应。

const HappyPack = require('happypack');
module.exports = {
  //...
  module: {
      rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'happypack/loader?id=js',
      },
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        loader: 'happypack/loader?id=ts',
      }
    ],
  },
  plugins: [
    new HappyPack({
      id: 'js',
      loaders: [{
        loader: 'babel-loader',
        options: {}, // babel options
      }],
    }),
    new HappyPack({
      id: 'ts',
      loaders: [{
        loader: 'ts-loader',
        options: {}, // ts options
      }],
    })
  ]
};

webpack-bundle-analyzer (图形化分析bundle的构成和内存)

npm i webpack-bundle-analyzer  -D
const Analyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
    // ...
    plugins: [
        new Analyzer()
    ],
};

npx webpack 占据终端并自动打开浏览器
在这里插入图片描述
在这里插入图片描述

webpack-dashboard (更直观的看到控制台的打包信息)

npm i webpack-dashboard -D
const DashboardPlugin = require('webpack-dashboard/plugin');

plugins:[new DashboardPlugin()],

修改package.json

为了使webpack-dashboard生效还要更改一下webpack的启动方式,就是用webpack-dashboard模块命令替代原本的webpack或者webpack-dev-server的命令,并将原有的启动命令作为参数传给它。如:

"dev": "webpack-dashboard -- webpack-dev-server"

在这里插入图片描述

多个webpack配置文件 (webpack-merge)

多个环境下的webpack配置文件如何切换?

在这里插入图片描述
这些名字是可以随意取得
webpack默认只认识webpack.config.js;

npx webpack --config webpack.dev.config 

公共的配置提取出来,webpack-merge是配置合并的工具。

npm i webpack-merge -D

webpack.common.js

// webpack.common.js
module.exports = {
  entry: './app.js',
  output: {
    filename: '[name].js',
  },
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        use: 'file-loader',
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ],
      }
        ],
  },
};

webpack.prod.js 重写对 test: /\.css$/规则的处理

// webpack.prod.js
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = merge.smart(commonConfig, {
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader', // css-loader 负责解析 CSS 代码, 处理 CSS 中的依赖
                ],
      }
    ]
  },
      plugins: [
        // 用 MiniCssExtractPlugin 抽离出 css 文件
        new MiniCssExtractPlugin({
            filename: '[name].bundle.css' // 输出的 css 文件名为 index.css
        }),
    ]
});

参考文档:
《webpack实战 入门进阶与调优》
webpack官网
webpack 中,module,chunk 和 bundle 的区别是什么?

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值