0
点赞
收藏
分享

微信扫一扫

【Webpack】基础概念及常见配置项


目录

  • ​​1. 开发环境的搭建​​
  • ​​1.1 Node.js管理​​
  • ​​1.2 NPM管理​​
  • ​​2. 基础概念及常见配置项​​
  • ​​2.1 webpack.config.js 配置文件​​
  • ​​2.2 webpack支持多类型配置​​
  • ​​2.3 webpack中的常见名词​​
  • ​​2.4 mode模式​​
  • ​​2.5 webpack的入口和输出​​
  • ​​2.6 output输出配置项​​
  • ​​2.7 resolve​​
  • ​​2.8 module​​
  • ​​2.9 条件匹配​​
  • ​​2.10 Loader配置​​
  • ​​2.11 plugin插件​​

1. 开发环境的搭建

在说webpack的基础概念和常见配置项之前,先来看一下webpack的环境搭建,列举一写常见的问题。

1.1 Node.js管理

Node.js的流行离不开NPM的贡献,使用Node.js写的代码,可以打包发到JavaScript包管理平台​​npmjs.com​​,供其他开发者使用。当我们的项目需要某个包(模块)时,就可以通过包管理工具下载安装对应的包。

NPM 中使用了一个命名为​​package.json​​​的文件作为一个 NPM 包的描述文件,​​package.json​​包含了包的基本信息(名称、版本号、描述、作者等)和依赖关系,例如:

{
"name": "demo", // 项目的名称
"version": "1.0.0",
"dependencies": {
"webpack": "^4.29.6"
}
}

其中,​​dependencies​​是该项目的依赖,对应的还有devdependencies(开发依赖)。实际项目中,webpack是构建工具,代码不会直接用 webpack 的 API,而只在开发和打包的时候采用,所以正确做法是放在devdependencies中。

我们可以看到webpack版本号前面有一个​​^​​,它就表示主版本是4的最新版本,每次执行安装命令的时候,会更新符合这个规则的最新包。

1.2 NPM管理

(1)NPM安装和删除

npm install packageName  // 安装包
npm i packageName // 简写

npm i packageName@x.x.x // 安装指定版本的包

npm i packageName --save // 将安装的包写入package.age
npm i packageName -S // 简写

npm uninstall packageName // 删除包

(2)NPM包的模式

npm的包安装,分为本地模式和全局模式,默认为本地模式。

  • 本地模式:就是在执行​​npm install​​​命令时,会在当前目录创建​​node_modules​​​,然后下载安装包及其依赖到​​node_modules​​目录。
  • 全局模式:安装到全局路径的方式。

在Node的require依赖时,会优先查找当前文件的​​node_modules​​​,如果没有,就查找上层的​​node_modules​​,如果遍历到根目录都没有找到,就会使用全局安装的模块。

全局安装的包可以使用指定全局命令,只需要在​​package.json​​中增加bin字段,并且指向包内对应的文件即可。我们如果需要全局安装一个包,使用以下命令:

npm install packageName --global
npm install packageName -g // 简写

(3)NPM其他常用的命令

  • npm set:设置环境变量,例如:​​npm set init-author-name 'Your name'​​,初始化的时候会使用默认环境变量;
  • npm info:查看某个包的信息,例如:​​npm info lodash​​;
  • npm search:查找 npm 仓库,后面可以跟字符串或者正则表达式,例如:​​npm search webpack​​;
  • npm list:树形的展现当前项目安装的所有模块,以及对应的依赖,例如:​​npm list --global​​查看全局安装的模块。

(3)NPM scripts

NPM不仅可以用于模块管理,还可以用于执行脚本。在​​package.json​​​文件中添加​​scripts​​字段,用于指定脚本命令,供npm直接调用,如下:

"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"start": "node src/scripts/dev.js"
},

根据上面代码中的配置,我们就可以执行以下命令进行对应的操作:

npm run build  //打包项目
npm run serve //启动项目
npm run lint //代码检查
npm run start //执行dev.js文件

2. 基础概念及常见配置项

2.1 webpack.config.js 配置文件

webpack是可配置的模块化打包工具。我们可以通过修改webpack的配置文件​​webpack.config.js​​来对其进行配置,webpack的配置文件遵循Node.js的模块化规范CommonJS

  • 通过​​require()​​语法导入其他文件或者使用Node.js内置的模块
  • 使用JavaScript来编写语法

简单来说,​​webpack.config.js​​就是Node.js的一个模块。

2.2 webpack支持多类型配置

webpack不仅支持JavaScript还支持typescript等语言。不同语言的核心配置都一样,只是语法不同。

除了配置文件的语法不同之外,配置的类型也是多样的,最常见的就是作为一个对象来使用,除了使用对象,webpack还支持函数、Promise和多配制数组。

(1)函数类型的webpack配置

如果我们只使用一个配置文件来区分开发环境和生产环境,就可以使用函数类型的webpack配置,函数类型的配置必须返回一个配置对象,具体方式如下:

module.exports = (env, argv) => {
return {
mode: env.production ? 'production' : 'development',
devtool: env.production ? 'source-maps' : 'eval',
plugins: [
new TerserPlugin({
terserOptions: {
compress: argv['optimize-minimize'] // 只有传入 -p 或 --optimize-minimize
}
})
]
};
};

ebpack 配置函数接受两个参数​​env​​​和​​argv​​​:分别对应着环境对象和 Webpack-cli的命令行选项,例如上面代码中的​​--optimize-minimize​​。

(2)Promise 类型的 Webpack 配置

如果需要异步加载一些 Webpack 配置需要做的变量,那么可以使用 Promise 的方式来做 Webpack 的配置,具体方式如下:

module.exports = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
entry: './app.js'
/* ... */
});
}, 5000);
});
};

(3)多配置数组

在一些特定的场景,我们可能需要一次打包多次,而多次打包中有一些通用的配置,这时候可以使用配置数组的方式,将两次以上的 Webpack 配置以数组的形式导出:

module.exports = [
{
mode: 'production'
// 配置1
},
{
// 配置2
}
];

配置的使用

默认情况下,webpack会查找执行目录下面的​​webpack.config.js​​,如果需要指定某个配置文件,可以使用以下命令:

webpack --config 配置文件名

2.3 webpack中的常见名词

参数

说明

entry

项目入口

module

开发中每一个文件都可以看做 module,模块不局限于 js,也包含 css、图片等

chunk

代码块,一个 chunk 可以由多个模块组成

loader

模块转化器,模块的处理器,对模块进行转换处理

plugin

扩展插件,插件可以处理 chunk,也可以对最后的打包结果进行处理,可以完成 loader 完不成的任务

bundle

最终打包完成的文件,一般就是和 chunk 一一对应的关系,bundle 就是对 chunk 进行便意压缩打包等处理后的产出

2.4 mode模式

Webpack4.0 开始引入了mode配置,通过配置​​mode=development​​​或者​​mode=production​​​来制定是开发环境打包,还是生产环境打包,比如生产环境代码需要压缩,图片需要优化。Webpack 默认mode是生产环境,即​​mode=production​​。

module.exports = {
mode: 'development'
};

除了在配置文件中设置mode,还可以在命令行中设置mode:

npx webpack --config webpack.config.entry.js --mode development

2.5 webpack的入口和输出

webpack是一个模块化打包工具,它是从指定的入口文件(entry)开始,经过加工处理,最终按照output设定输出固定内容的 bundle;而这个加工处理的过程,就用到了loader和plugin两个工具;loader是源代码的处理器,plugin解决的是 loader处理不了的事情。

下面主要来介绍一下entry和output。

(1)context

​context​​指的是上下文,即项目打包的相对路径的上下文,如果执行了​​context​​​,那么​​entry​​​和​​output​​​的相对路径都是相对于​​context​​​值的,包括在 JavaScript 中引入模块也是从这个路径开始的。由于​​context​​的作用,决定了context值必须是一个绝对路径。 不过一般情况下,​​context​​不需要配置。

(2)entry

Webpack 的entry支持多种类型,包括字符串、对象、数组。从作用上来说,包括了单文件入口多文件入口两种方式。

  • 单文件入口

单入口文件可以创建一个只有单一文件入口的情况,但是单文件入口的方式比较简单,在扩展配置的时候灵活性较低。

module.exports = {
entry: 'path/to/my/entry/file.js'
};
// 或者使用对象方式
module.exports = {
entry: {
main: 'path/to/my/entry/file.js'
}
}

entry还可以传入包含文件路径的数组,当entry为数组的时候也会合并输出,例如下面的配置:

module.exports = {
mode: 'development',
entry: ['./src/app.js', './src/home.js'],
output: {
filename: 'array.js'
}
}

上面的代码虽然都是只有一个入口,但是在打包产出上会有些差异:

1)如果是string类型,webpack就会直接把该string指定的模块作为入口模块

2)如果是数组类型,webpack会生成另外一个入口模块,并将数组中每个元素指定的模块的内容加载进来,并将最后一个模块的 ​​module.exports​​​ 作为入口模块的 ​​module.exports​​ 导出。

  • 多文件入口

多文件入口是使用对象语法来通过支持多个​​entry​​,多文件入口的对象语法相对于单文件入口,具有较高的灵活性,例如多页应用、页面模块分离优化。多文件入口的语法如下:

module.exports = {
entry: {
home: 'path/to/my/entry/home.js',
search: 'path/to/my/entry/search.js',
list: 'path/to/my/entry/list.js'
}
};

上面的语法将entry分成了 3 个独立的入口文件,这样会打包出来三个对应的 bundle。

(3)output

webpack 的​​output​​​是指定了​​entry​​​对应文件编译打包后的输出 ​​bundle​​​。​​output​​的常用属性是:

  • path:此选项制定了输出的 bundle 存放的路径,比如dist、output等
  • filename:这个是 bundle 的名称
  • publicPath:指定了一个在浏览器中被引用的 URL 地址

需要注意的是,当不指定 ​​output​​​ 的时候,默认输出到 ​​dist/main.js​​​ ,即 ​​output.path​​​ 是​​dist​​​,​​output.filename​​​ 是 ​​main​​。

​output​​​只能有一个,对于不同的entry可以通过​​output.filename​​占位符语法来区分,比如:

module.exports = {
entry: {
home: 'path/to/my/entry/home.js',
search: 'path/to/my/entry/search.js',
list: 'path/to/my/entry/list.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
};

其中​​[name]​​就是占位符,它对应的是entry的key(home、search、list),所以最终输出结果是:

path/to/my/entry/home.js  dist/home.js
path/to/my/entry/search.js dist/search.js
path/to/my/entry/list.js dist/list.js

下面是webpack中支持的占位符:

占位符

含义

[hash]

模块标识符的 hash

[chunkhash]

chunk 内容的 hash

[name]

模块名称

[id]

模块标识符

[query]

模块的 query,例如,文件名 ? 后面的字符串

[function]

一个 return 出一个 string 作为 filename 的函数

其中​​[hash]​​​ 和 ​​[chunkhash]​​​ 的长度可以使用 ​​[hash:16]​​(默认为 20)来指定。或者,通过指定 output.hashDigestLength 在全局配置长度,他们的区别:

  • ​[hash]​​:是整个项目的 hash 值,其根据每次编译内容计算得到,每次编译之后都会生成新的 hash,即修改任何文件都会导致所有文件的 hash 发生改变;在一个项目中虽然入口不同,但是 hash 是相同的;hash 无法实现前端静态资源在浏览器上长缓存,这时候应该使用 chunkhash;
  • ​[chunkhash]​​:根据不同的入口文件(entry)进行依赖文件解析,构建对应的 chunk,生成相应的 hash;只要组成 entry 的模块文件没有变化,则对应的 hash 也是不变的,所以一般项目优化时,会将公共库代码拆分到一起,因为公共库代码变动较少的,使用 chunkhash 可以发挥最长缓存的作用;

注意:占位符可以组合使用,例如​​[name]-[hash:16]​​。

(4)output.publicPath

对于使用​​<script>​​​ 和 ​​<link>​​​标签时,当文件路径不同于他们的本地磁盘路径(由​​output.path​​​指定)时,​​output.publicPath​​被用来作为src或者link指向该文件。这种做法在需要将静态文件放在不同的域名或者 CDN 上面的时候是很有用。

module.exports = {
output: {
path: '/home/git/public/assets',
publicPath: '/assets/'
}
};

// 输出为:
<head>
<link href="/assets/logo.png" />
</head>

上面的​​/assets/logo.png​​​就是根据publicPath输出的,​​output.path​​​制定了输出到本地磁盘的路径,而​​output.publicPath​​则作为实际上线到服务器之后的 url 地址。所以我们在上 CDN 的时候可以这样配置:

module.exports = {
output: {
path: '/home/git/public/assets',
publicPath: 'http://cdn.example.com/assets/'
}
};

// 输出为:
<head>
<link href="http://cdn.example.com/assets/logo.png" />
</head>

2.6 output输出配置项

下面再来详细看一下与output输出相关的三个配置项:​​externals​​​,​​target​​​ 和 ​​devtool​

(1)externals

externals配置项用于去除输出的打包文件中依赖的第三方JS模块(例如Vue,React),来减小打包的体积。

使用场景: 该功能通常在开发自定义的JS库的时候用到,用于去除自定义js库依赖的其他第三方js模块。这些依赖模块应该由使用者提供,而不应该包含在js库文件中。

(2)target

在开发过程中,我们不仅是开发web应用,还可能开发node应用等,这时因为对应的宿主环境不同,所以在构建的时候需要特殊处理。webpack中可以通过target来指定构建的目标。

module.exports = {
target: 'web' // 默认是 web,可以省略
};

​target​​的值有两种类型string和function:

string类型支持以下七种值:

  • web:默认,编译为类浏览器环境里可用;
  • node:编译为类 Node.js 环境可用(使用 Node.js require 加载 chunk);
  • async-node:编译为类 Node.js 环境可用(使用 fs 和 vm 异步加载分块);
  • electron-main:编译为 Electron 主进程;
  • electron-renderer:编译为 Electron 渲染进程;
  • node-webkit:编译为 Webkit 可用,并且使用 jsonp 去加载分块。支持 Node.js 内置模块和 nw.gui 导入(实验性质);
  • webworker:编译成一个 WebWorker。

除了string类型,target 还支持 function 类型,这个函数接收一个compiler作为参数,如下面代码可以用来增加插件:

const webpack = require('webpack');

const options = {
target: compiler => {
compiler.apply(new webpack.JsonpTemplatePlugin(options.output), new webpack.LoaderTargetPlugin('web'));
}
};

(3)devtool

devtool是来控制怎么显示sourcemap,通过 sourcemap 我们可以快速还原代码的错误位置。

但是由于 sourcemap 包含的数据量较大,而且生成算法需要计算量支持,所以 sourcemap 的生成会消耗打包的时间。

一般在实际项目中,建议生产环境不使用或者使用 source-map(如果有 Sentry 这类错误跟踪系统),开发环境使用​​cheap-module-eval-source-map​​。

2.7 resolve

Webpack进行构建的时候会从入口文件开始遍历各个模块的依赖,resolve配置就是为了帮助webpack查找依赖模块的,通过resolve的配置,可以帮助webpack快速查找依赖,也可以替代对应的依赖。配置形式如下:

module.exports = {
resolve: {
// resolve的配置
}
};

(1)resolve.extensions

​extensions​​是用来帮助webpack解析扩展名的配置,默认为​​.wasm​​​ 、​​.mjs​​​、​​.js​​​、​​.json​​。所以,在引入这几种类型文件的时候可以不写扩展名,我们可以在里面添加需要解析的文件的扩展名,这样引入文件时就可以不写扩展名(前提是没有重名的已在extensions定义扩展名的文件):

module.exports = {
resolve: {
extensions: ['.js', '.json', '.css']
}
};

(2)resolve.alias

​alias​​是用来帮助webpack更快的查找模块依赖,而且可以让我们编写代码更加方便。

如有有一个很深层的层级的引入,我们写的很长的话就不是很好看,比如下面的例子:

src
├── lib
└── utils.js
└── pages
└── demo
└── index.js

如果index.js需要引入utils.js文件,就要写成:​​import utils from '../../lib/utils'​

我们可以通过alias来缩短这种写法:

module.exports = {
resolve: {
alias: {
src: path.resolve(__dirname, 'src'),
'@lib': path.resolve(__dirname, 'src/lib')
}
}
};

这样,就可以直接使用​​require('@lib/utils')​​​或​​require('src/lib/utils')​​来引入文件。这样看起来进很简洁,如果需要多次引入,这样写会大大的提高开发效率。

2.8 module

在webpack解析模块的同时,不同的模块需要使用不同类型的模块处理器来处理,这部分的设置就是在module中实现的。module有两个配置:​​noParse​​​和​​rules​​。

(1)module.noParse

noParse配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析和处理,这样做的好处是能提高构建性能,接收的类型为正则表达式、正则表达式数组或者接收模块路径参数的一个函数:

module.exports = {
module: {
// 使用正则表达式
noParse: /jquery|lodash/

// 使用函数
noParse: (content) => {
// content 代表一个模块的文件路径
// 返回 true or false
return /jquery|lodash/.test(content);
}
}
}

注意,这里一定要确定被排除出去的模块代码中不能包含import、require、define等内容,以保证 webpack 的打包包含了所有的模块,不然会导致打包出来的 js 因为缺少模块而报错。

(2)module.rules

​rules​​是在处理模块时,将符合规则条件的模块,提交给对应的处理器来处理,通常用来配置 loader,其类型是一个数组,数组里每一项都描述了如何去处理部分文件。每一项 rule 大致可以由以下三部分组成:

  • 条件匹配:通过test、include、exclude等配置来命中可以应用规则的模块文件;
  • 应用规则:对匹配条件通过后的模块,使用use配置项来应用loader,可以应用一个 loader 或者按照从后往前的顺序应用一组 loader,当然我们还可以分别给对应 loader 传入不同参数;
  • 重置顺序:一组 loader 的执行顺序默认是从后到前(或者从右到左)执行,通过enforce选项可以让其中一个 loader 的执行顺序放到最前(pre)或者是最后(post)。

rules中的​​parser​​ 用来控制模块化语法,和上面noParse作用类似,因为webpack是以模块化的JavaScript文件为入口,所以内置了对模块化JavaScript的解析功能。支持AMD、Commonjs、SystemJs、ES6。parse属性可以更细粒度的配置哪些模块语法要解析,哪些不解析。

支持的选项如下:

module: {
rules: [{
test: /\.js$/,
use: ['babel-loader'],
parser: {
amd: false, // 禁用 AMD
commonjs: false, // 禁用 CommonJS
system: false, // 禁用 SystemJS
harmony: false, // 禁用 ES6 import/export
requireInclude: false, // 禁用 require.include
requireEnsure: false, // 禁用 require.ensure
requireContext: false, // 禁用 require.context
browserify: false, // 禁用 browserify
requireJs: false, // 禁用 requirejs
}
}]
}

注意,parser是语法层面的限制,noParse只能控制哪些文件不进行解析。

2.9 条件匹配

条件匹配相关的配置有​​test​​​、​​include​​​、​​exclude​​​、​​resource​​​、​​resourceQuery​​​和​​issuer​​​。条件匹配的对象包括三类:​​resource​​​,​​resourceQuery​​​和​​issuer​​。

  • resource:请求文件的绝对路径。它已经根据 resolve 规则解析;
  • issuer: 被请求资源的模块文件的绝对路径,即导入时的位置。

例如,从 app.js 导入 ​​'./style.css?inline'​​:

  • resource 是​​/path/to/style.css​​;
  • resourceQuery 是?之后的​​inline​​;
  • issuer 是​​/path/to/app.js​​。

注意,上面的​​test​​​、​​include​​​、​​exclude​​​、​​resource​​匹配的对象都是resource。

看下面的例子,匹配的条件为:来自src和test文件夹,不包含​​node_modules​​​和​​bower_modules​​​子目录,模块的文件路径为​​.tsx​​​和​​.jsx​​结尾的文件。

{
test: [/\.jsx?$/, /\.tsx?$/],
include: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'test')
],
exclude: [
path.resolve(__dirname, 'node_modules'),
path.resolve(__dirname, 'bower_modules')
]
}

rules 最重要的内容有以下四个:

  • test:正则匹配需要处理的模块文件;
  • use:loader 数组配置,内部有loader和options;
  • include:包含;
  • exclude:排除;

2.10 Loader配置

Loader是解析处理器,通过loader可以将 ES6 语法的 js 转化成 ES5 的语法,可以将图片转成 base64 的dataURL,在 JavaScript 文件中直接import css 和 html 也是通过对应的loader来实现的。

(1)Loader基本配置

在使用之前,我们要先安装它,例如引入less:

npm i -D less-loader

然后指定所有的​​.less​​文件都是用less-loader解析:

module.exports = {
module:{
rules:[
test: /\.less$/,
use:'less-loader'
]
}
}

上面的配置简单来说就是test来匹配.less 的文件,然后交给less-loader来处理。这样所有的less文件都会处理为css文件。

除了直接在webpack.config.js使用 loader 的方式之外,还可以在对应的 JavaScript 文件中使用 loader,来在js文件中引入html或者css:

// 先配置
module.exports = {
module: {
rules: [{test: /\.html$/,
use: ['html-loader']}]
}
};
// 再使用
const html = require('html-loader!./loader.html');
console.log(html);

上面的代码实际上是将loader.html转化为string变量直接输出了,等同于:

const html = require('./loader.html');
console.log(html);

(2)Loader的参数

给 loader 传参的方式有两种:

  • 通过options传入
  • 通过query的方式传入

// inline内联写法,通过 query 传入
const html = require("html-loader?attrs[]=img:src&attrs[]=img:data-src!./file.html");
// config内写法,通过 options 传入
module: {
rules: [{
test: /\.html$/,
use: [{
loader: 'html-loader',
options: {
minimize: true,
removeComments: false,
collapseWhitespace: false
}
}]
}]
}
// config内写法,通过 query 传入
module: {
rules: [{
test: /\.html$/,
use: [ {
loader: 'html-loader?minimize=true&removeComments=false&collapseWhitespace=false',
}]
}]
}

(3)Loader的解析顺序

需要注意的是,Loader的解析顺序是逆序的,也就是从后往前,有些Loader的的解析是需要顺序,所以不要搞错顺序,例如:

const styles = require('css-loader!less-loader!./src/index.less');

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

如果需要调整 Loader 的执行顺序,可以使用enforce,enforce取值是​​pre|post​​,pre表示把放到最前,post是放到最后:

use: [
{
loader: 'babel-loader',
enforce: 'post' // 将babel-loader放在最后执行
}
];

2.11 plugin插件

plugin是 Webpack 的重要组成部分。Webpack 本身就是由很多插件组成的,所以内置了很多插件,可以直接通过webpack对象的属性来直接使用,例如,压缩js文件到的插件:

module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin();
]
}

除了一些内置的插件,还可以通过 NPM 包的方式来使用插件:

const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
//....
plugins: [
// 导出css文件到单独的内容
new ExtractTextPlugin({
filename: 'style.css'
})
]
}

注意,loader面向的是解决某个或者某类模块的问题,而plugin面向的是项目整体,解决的是loader解决不了的问题。


举报

相关推荐

0 条评论