webpack进阶之路二、(配置)

  • Entry:配置模块的入口。
  • Output:配置如何输出最终想要的代码。
  • Module:配置处理模块的规则。
  • Resolve:配置寻找模块的规则。
  • Plugins:配置扩展插件
  • DevServer:配置DevServer。
  • 其他配置项:其他零散的配置项。
  • 整体配置解构:整体地描述各配置项的结构。
  • 多种配置类型:配置文件不止可以返回一个Object,还可以返回其他形式。
  • 配置总结:寻找配置Webpack的规律,减少思维负担。

一、Entry

entry是配置模块的入口,可抽象成输入,Webpack执行构建的第一步将从 入口开始,搜寻及递归解析出所有入口依赖的模块。(必填)

1、context

Webpack在寻找相对路径的文件时会以context为根目录,context默认为执行启动Webpack时所在的当前工作目录。如果想改变context的默认配置,则可以在配置文件时设置:

module.exports = {
	context: path.resolve(__dirname, 'app')
}

注意,context必须是一个绝对路径的字符串,除此之外,还可以通过在启动Webpack时带上参数webpack --context来设置context。
Entry的路径及其依赖的模块的路径可能采用相对于context的路径来描述,context会影响到这些相对路径所指向的真实文件。

2、Entry类型
  • string ‘./app/entry’ 入口模块的文件路径,可以是相对路径
  • array [’./app/entry1’, ‘./app/entry2’] 入口模块的文件路径,可以是相对路径
  • object {a: ‘./app/entry-a’, b: [’./app/entry-b1’, ‘./app/entry-b2’]} 配置多个入口,每个入口生成一个Chunk
    注意:如果是array类型,则搭配output.library配置项使用时,只有数组里的最后一个入口文件的模块会被导出。
3、Chunk的名称
  • 如果entry是一个string或array,就只会生成一个Chunk,这时Chunk的名称是main。
  • 如果entry是一个object,就可能会出现多个Chunk,这时Chunk的名称是object键值对中键的名称。
4、配置动态Entry

假如项目里有多个页面需要为每个页面的入口配置一个Entry,但这些页面的数量可能不断增长,则这时Entry的配置会受到其他因素的影响,导致不能写成静态的值。其解决方法是将Entry设置成一个函数动态地返回上面所说的配置,如:

// 同步函数
entry: () => {
	return {
		a: './pages/a',
		b: './pages/b',
	}
};
// 异步函数
entry: () => {
	return new Promise((resolve) => {
		resolve({
			a: './pages/a',
			b: './pages/b',
		})
	})
}

二、Output

output配置如何输出最终想要的代码。

1、filename

output.filename配置输出文件的名称,为string类型。如果只有一个输出文件,则可以将它写成静态不变的:filename: 'bundle.js',但是在有很多Chunk要输出时,就需要借助模板和变量,之前所说,Webpack会为每个Chunk取一个名称,所以我们可以根据Chunk的名称来区分输出的文件名:filename: '[name].js'
代码里的[name]代表内置的name变量去替换[name],这时我们可以将它看作一个字符串模块函数,每个要输出的Chunk都会通过这个函数去凭借输出的文件名称。内置变量除了包括name,还包括如下:

  • id Chunk的唯一标识,从0开始
  • name Chunk的名称
  • hash Chunk的唯一标识的Hash值
  • chunkhash Chunk内容的Hash值
    其中,hash和chunkhash的长度是可指定的,[hash:8]代表取8位Hash值,默认是20位。
2、chunkFilename

output.chunkFilename配置无入口的Chunk在输出时的文件名称。chunkFilename和上面的filename非常类似,但chunkFilename只用于指定在运行过程中生成的Chunk在输出时的文件名称。会在运行时生成Chunk的常见场景包括:使用CommonChunkPlugin、使用import(‘path/to/module’)动态加载等。chunkFilename支持和filename一致的内置变量。

3、path

output.path配置输出文件存在本地目录,必须是string类型的绝对路径。通常通过Node.js的path模块去获取绝对路径:

path: path.resolve(__dirname, 'dist_[hash]')
4、publicPath

在复杂的项目里可能会有一些构建出的资源需要异步加载,加载这些异步资源需要对应的URL地址。
output.publicPath配置发布到线上资源的URL前缀,为string类型,默认值是字符串’’,即使用相对路径。
例如:需要将构建出的资源文件上传到CDN服务上,以利于加快页面的打开速度,配置代码如下:

filename: '[name]_[chunkhash:8].js'
publicPath: 'https://cdn.example.com/assets/'

这时发布到线上的HTML在引入JavaScript文件时就需要以下配置项:


使用该配置项时要小心,稍有不慎将导致资源加载404错误。
output.path和output.publicPath都支持字符串模板,内置变量只有一个,即hash,代表一次编译操作的Hash值。

5、crossOriginLoading

Webpack输出的部分代码块可能需要异步加载,而异步加载时通过JSONP方式实现的。JSONP的原理是动态地向HTML中插入一个标签去加载异步资源。output.crossOriginLoading则是用于配置这个异步插入的标签的crossorigin值。
script标签的crossorigin属性可以取以下值:

  • anonymous(默认),在加载此脚本资源时不会带上用户的Cookies。
  • use-credentials,在加载此脚本资源时会带上用户的Cookies。
    通常用设置crossorigin来获取异步加载的脚本执行时的详细错误信息。
6、libraryTarget和library
当Webpack去构建一个可以被其他模块导入使用的库时,需要用到libraryTarget和library。
  • output.libraryTarget配置以何种方式导出库。
  • output.library配置导出库的名称。
    它们通常搭配在一起使用
    output.libraryTarget是字符串的枚举类型,支持以下配置。
    1. var(默认)
      编写的库将通过var被赋值给通过library指定名称的变量。
      假如配置了output.library=‘LibraryName’,则输出和使用的代码如下:
      		// Webpack输出的代码
      		var LibraryName = lb_code;
      		// 使用库的方法
      		LibraryName.doSomething();
      
      假如output.library为空,则直接输出:
      lib_code
      其中,lib_code是指导出库的代码内容,是有返回值的一个自执行函数。
    2. commonjs
      编写库将通过CommonJS规范导出。
      假如配置了output.library=“LibraryName”,则输出和使用的代码如下:
      // Webpack输出的代码
      exports['LibraryName'] = lib_code;
      // 使用库的方法
      require('library-name-in-npm')['LibraryName'].doSomeThing();
      
      其中,library-name-in-npm是指模块被发布到npm代码仓库时的名称。
  1. commonjs2
    编写的库将通过CommonJS2规范导出,输出和使用的代码如下:
    // Webpack输出的代码
    module.exports = lib_code;
    // 使用库的方法
    require('library-name-in-npm').doSomething();
    
    CommonJS2和CommonJS规范相似,差别在于CommonJS只能用exports导出,CommonJS2在CommonJS的基础上增加了module.exports的导出方式。
    1. this
      编写的库将通过this被赋值给通过library指定的名称,输出和使用的代码如下:
      	// Webpack输出的代码
      	this['LibraryName'] = lib_code;
      	// 使用库的方法
      	this.LibraryName.doSomething();
      
    2. window
      编写的库将通过window赋值给通过library指定名称,输出和使用的代码如下:
      	// Webpack输出的代码
      	window['LibraryName'] = lib_code;
      	// 使用库的方法
      	window.LibraryName.doSomething();
      
7、libraryExport

output.libraryExport配置要导出的模块中哪些子模块需要被导出。它只有在output.libraryTarget被设置成commonjs或者commonjs2时使用才有意义。
假如要导出的模块源代码是:
export const a = 1;
export default b = 2;
而现在想让构建输出的代码只导出其中的a,则可以将output.libraryExport设置成a,那么构建输出的代码和使用方法将变成如下内容:
// Webpack输出的代码 module.exports = lib_code['a']; // 使用库的方法 require('library-name-in-npm') === 1;
以上只是output中的常用配置项。

三、Module

module配置处理模块的规则

1.配置Loader

rules配置模块的读取和解析规则,通常用来配置Loader。其类型是一个数组,数组里的每一个都描述了如何处理部分文件。配置一项rules时大致可通过以下方式来完成。

  • 条件匹配:通过test、include、exclude三个配置项来选中Loader要应用规则的文件。
  • 应用规则:对选中的文件通过use配置项来应用Loader,可以只应用一个Loader或者按照从后往前的顺序应用一组Loader,同时可以分别向Loader传入参数。
  • 重置顺序:一组Loader的执行顺序默认是从右到左执行了,通过enforce选项可以将其中一个Loader的执行顺序放到最前或者最后。
    例如:
module: {
	rules: [
		{
			// 命中JavaScript文件
			test: /\.js$/,
			// 用babel-loader转换JavaScript文件
			// ?cancheDirectory表示传给babel-loader的参数,用于缓存babel的编译结果加快重新编译的速度
			use: ['babel-loader?cacheDirectory'],
			// 只命中src目录里的JavaScript文件,加快Webpack的搜索速度
			include: path.resolve(__dirname, 'src')
		},
		{
			// 命中SCSS文件
			test: /\.scss$/,
			// 使用一组Loader去处理SCSS文件
			// 处理顺序为从后到前,即先交给sass-loader处理,再将结果交给css-loader,最后交给style-loader
			user: ['style-loader', 'scc-loader', 'sass-loader'],
			// 排除node_modules目录下的文件
			exclude: path.resolve(__dirname, 'node_module'),
		},
		{
			// 对非文本文件采用file-loader加载
			test: /\.(gif|png|jpe?g|eot|woff|ttf|svg|pdf)$/,
			use: ['file-loader'],
		},
	]
}

在Loader需要传入很多参数时,我们还可以通过一个Object来描述,例如在上面的babel-loader配置中有如下代码:
use: [
{
loader: ‘babel-loader’,
options: {
cacheDirectory: true,
},
// enforce: 'post’的含义是将该Loader的执行顺序放到最后
// enforce的值还可以是pre,代表将Loader的执行顺序放到最前面
enforce: ‘post’
},
// 省略其他Loader
]
在上面的例子中,test、include、exclude这三个命中文件的配置项中只传入一个字符串或正则,其实它们也支持数组类型,使用如下:

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

数组里的每项之间是“或”的关系,即文件的路径只要满足数组中的任何一个条件,就会被命中。

2、noParse

noParse配置项可以让Webpack忽略对部分没蔡总模块化的文件的递归解析和处理,这样做的好处是能提高构建性能。原因是一些库如jQuery、ChartJS庞大又没有采用模块化标准,让Webpack去解析这些文件既耗时又没有意义。
noParse是可选的配置项,类型需要RegExp、[RegExp]、function中的一种。
例如,若想要huluejQuery、ChartJS,则可以使用如下代码:
// 使用正则表达式
noParse: /jquery | chartjs/
// 使用函数,从Webpack 3.0.0 开始支持
noParse: (content) => {
// content代表一个模块的文件路径
// 返回true或false
return /jquery | chartjs/.test(content);
}
注意,被忽略的文件里不应该包含import、require、define等模块化语句,不然会导致在构建出的代码中包含无法再浏览器环境下执行的模块化语句。

3、parser

因为Webpack是以模块化的JavaScript文件为入口的,所以内置了对模块化JavaScript的解析功能,支持AMD、CommonJS、SystemJS、ES6。parser属性可以更细粒度地配置哪些模块语法被解析、哪些不被解析。同noParse配置项的区别在于,parser可以精确到语法层面,而noParse只能控制哪些文件不被解析。parser的使用方法如下:

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
			}
		}
	]
}
4、Resolve

Webpack在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve配置Webpack如何寻找模块所对应的文件。Webpack内置JavaScript模块化语法解析功能,默认会采用模块化标准里约定的规则去寻找,但我们也可以根据自己的需要修改默认的规则。

  1. alias
    resolve.alias配置项通过别名来将原导入的路径映射成一个新导入路径。例如使用以下配置:
// Webpack alias配置
resolve: {
	alias: {
		components: './src/components'
	}
}

当通过import Button from 'components/button’导入时,实际上呗alias等价替换成了import Button from ‘./src/components/button’。
以上alias配置的含义是,将导入语句里的components关键字替换成./src/components/。
这样做可能会命中太多导入语句,alias还支持通过$符号来缩小范围到只命中以关键字结尾的导入语句:

resolve: {
	alias: {
		'react$': '/path/to/react.min.js'
	}
}

react$只会命中以react结尾的导入语句,即只会将import 'react’关键字替换成import ‘/path/to/react.min.js’。

  1. mainFields
    有一些第三方模块会针对不同的环境提供几份代码。例如分别提供采用了ES5和ES6的两份代码的位置写在package.json文件里,代码如下:
{
	"jsnext: main": "es/index.js",		// 采用ES6语法的代码入口文件
	“main”: "lib/index.js"		// 采用ES5语法的代码入口文件
}

Webpack会根据mainFields的配置去决定优先采用哪份代码,mainFields默认如下:
mainFields: ['browser', 'main']
Webpack会按照数组里的顺序在package.json文件里寻找,只会使用找到的第一个文件。
假如我们想优先采用ES6的那份代码,则可以这样配置:
mainFields: ['jsnext: main', 'browser', 'main']
Webpack会按照数组里的顺序在package.json文件里寻找,只会使用找到的第一个文件。
假如我们想优先采用ES6的那份代码,则可以这样配置:
mainFields: ['jsnext: main', 'browser', 'main']

  1. extensions
    在导入语句没带文件后缀时,Webpack会自动带上后缀后去尝试访问文件是否存在。resolve.extensions用于配置在尝试过程中用到的后缀列表,默认是:
    extensions: [’.js’, ‘.json’]
    也就是说,当遇到require(’./data’)这样的导入语句时,Webpack会先寻找./data.js文件,如果该文件不存在,就去寻找./data.json文件,如果还是找不到,就报错。
    假如我们想让Webpack优先使用目录下的TypeScript文件,则可以这样配置:
    extensions: [’.ts’, ‘.js’, ‘.json’]

  2. modules
    resolve.module配置Webpack去哪些目录下寻找第三方模块,默认只会去node_modules目录下寻找。有时我们的项目里会有一些模块被其他模块大量依赖和导入,由于其他模块的位置不定,针对不同的文件都要计算被导入的模块文件的相对路径,这个路径有时会很长,就像import ‘…/…/…/components/button’,这时可以利用modules配置项优化。假如哪些被大量导入的模块都在./src/components/目录下,则将modules配置成modules: [’./src/components’, ‘node_modules’]后,可以简单的通过import 'button’导入。

  3. descriptionFiles
    resolve.descriptionFiles配置描述第三方模块的文件名称,也就是package.json文件。默认如下:
    descriptionFiles: ['package.json']

  4. enforceExtension
    如果resolve.enforceExtension被配置为true,则所有导入语句都必须带文件后缀,例如开启前import './foo’能正常工作,开启后就必须写成import ‘./foo.js’。

  5. enforceModuleExtension
    enforceModuleExtension和enforceExtension的作用类似,但enforceModuleExtension只对node_modules下的模块生效。enforceModuleExtension通常搭配enforceExtension使用,在enforceExtension: true时,因为安装第三方模块中大多数导入语句都没带文件的后缀,所以这时通过配置enforceModuleExtension: false来兼容第三方模块。

5、Plugin

Plugin用于扩展Webpack的功能,各种各样的Plugin几乎可以让Webpack做任何与构建相关的事情。
Plugin的配置很简单,plugins配置项接收一个数组,数组里的每一项都是一个要使用的Plugin的实例,Plugin需要的参数通过构造函数传入。

const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
module.exports = {
	plugins: [
		// 所有页面都会用到的公共代码被提到common代码块中
		new CommonsChunkPlugin({
			name: 'common',
			chunks: ['a', 'b']
		})
	]
}

使用Plugin的难点在于掌握Plugin本身提供的配置项,而不是如何在Webpack中接入Plugin。

6、DevServer

使用DevServer可以提高开发效率,它提供了一些配置项可以用于改变DevServer的默认行为,要配置DevServer,除了可以在配置文件里通过devServer传入参数,还可以通过命令行传入参数。注意,只有在通过DevServer启动Webpack时,配置文件里的devServer才会生效,因为这些参数所对应的功能都是DevServer提供的,Webpack本身并不认识devServer配置项。

  1. hot
    devServer.hot配置是否启用模块热替换功能,DevServer的默认行为是在发现源代码被更新后通过自动刷新整个页面来做到实时预览,开启模块热替换功能后,将在不刷新整个页面的情况下通过用新模块替换老模块来做到实时预览。

  2. inline
    DevServer的实时预览功能依赖一个注入页面里的代理客户端,去接收来自DevServer的命令并负责刷新网页的工作。devServer.inline用于配置是否将这个代理客户端自动注入将运行在页面中的Chunk里,默认自动注入。DevServer会根据我们是否开启inline来调整它的自动刷新策略。

    • 如果开启inline,则DevServer会在构建变化后的代码时通过代理客户端控制网页刷新。
    • 如果关闭inline,则DevServer将无法直接控制要开发的网页。这时它会通过iframe的方式去运行要开发的网页。在构建完变化后的代码时,会通过刷新iframe来实现实时预览,但这时我们需要去http://localhost:8080/webpack-dev-server/实时预览自己的网页。
      如果想使用DevServer的模块热替换机制去实现实时预览,则最方便的方法是直接开启inline。
  3. historyApiFallback
    devServer.historyApiFallback用于方便地开发使用了HTML5 History API(https://developer.mozilla.org/en-US/docs/Web/API/History)的单页面应用,这类单页应用要求服务器在针对任何命中的路由时,都返回一个对应的HTML文件。例如在访问http://localhost/user和http://localhost/home时都返回index.html文件,浏览器端的JavaScript代码会从URL里解析出当前页面的状态,显示对应的页面。
    配置historyApiFallback的简单做法是:

    	historyApiFallback: true
    

    这会导致任何请求都会返回index.html文件,这只能用于只有一个HTML文件的应用。
    如果我们的应用由多个单页应用组成,则需要DevServer根据不同的请求返回不同的HTML文件,配置如下:

    	historyApiFallback: {
    		// 使用正则匹配命中路由
    		rewrite: [
    			//  /user开头的都返回user.html
    			{ from: /^\user/, to: '/user.html' },
    			{ from: /^\/game/, to: '/game.html' },
    			// 其他的都返回index.html
    			{ from: /./, to: 'index.html' },
    		]
    	}
    
  4. contentBase
    devServer.contentBase配置DevServerHTTP服务器的文件根目录。在默认情况下为当前的执行目录,通常是项目根目录,所以在一般情况下不必设置它,除非有额外的文件需要被DevServer服务。例如,若想将项目根目录下的public目录设置成DevServer服务器的文件根目录,则可以这样配置:

    	devServer: {
    		contentBase: path.join(__dirname, 'public')
    	}
    

    这里解释一下可能会让我们感到疑惑的地方,DevServer服务器通过HTTP服务暴露文件的方式可分为两类:

    • 暴露本地文件;
    • 暴露webpack构建出的结果,由于构建出的结果交给了DevServer,所以我们在使用DevServer时,会在本地找不到构建出的文件。
      contentBase只能用来配置暴露本地文件的规则,可以通过contentBase: false来关闭暴露本地文件。
  5. headers
    devServer.headers配置项可以在HTTP响应中注入一些HTTP响应头,使用如下:

    devServer: {
    	headers: {
    		'X-foo': 'bar'
    	}
    }
    
  6. host
    devServer.host配置项用于配置DevServer服务监听的地址,只能通过命令行参数传入。例如,若想让局域网中的其它设备访问自己的本地服务,则可以在启动DevServer时带上 --host 0.0.0.0。 host的默认值是127.0.0.1,即只有本地可以访问DevServer的HTTP服务。

  7. port
    devServer.host配置项用于配置DevServer服务监听端口,默认使用8080端口,如果8080端口已经被其他程序占用,就是用8081。

  8. allowedHosts
    devServer.allowedHosts配置一个白名单列表,只有HTTP请求的HOST在列表里才正常返回,使用如下:

    	allowedHosts: [
    		// 匹配单个域名
    		'host.com',
    		'sub.host.com',
    		// host2.com和所有的子域名 *.host2.com都将匹配
    		'.host2.com'
    	]
    
  9. disableHostCheck
    devServer.disableHostCheck配置项用于配置是否关闭用于DNS重新绑定的HTTP请求的HOST检查。DevServer默认只接收来自本地的请求,关闭后可以接收来自任意HOST的请求。它通常用于搭配--host 0.0.0.0使用,因为想让其他设备访问自己的本地服务,但访问时是直接通过IP地址访问而不是通过HOST访问,所以需要关闭HOST检查。

  10. https
    DevServer默认使用HTTP服务,它也能使用HTTPS服务。在某些情况下我们必须使用HTTPS,例如HTTP2和Service Worker就必须运行在HTTPS上。要切换成HTTPS服务,最简单的方式是:
    devServer: { https: true }
    DevServer会自动为我们生成一份HTTPS证书。
    如果我们想用自己的证书,则可以这样设置:
    devServer: { https: { key: fs.readFileSync('path/to/server.key'), cert: fs.readFileSync('path/to/server.crt'), ca: fs.readFileSync('path/ro/ca.pem') } }

  11. clientLogLevel
    devServer.clientLogLevel配置客户端的日志等级,这会影响到我们在浏览器开发者工具控制台里看到的日志内容。clientLogLevel是枚举类型,可取如下值之一:none、error、warning、info。默认为info级别,即输出所有类型的日志,设置成none时可以不输出任何日志。

  12. compress
    devServer.compress配置是否启用Gzip压缩,为boolean类型,默认为false。

  13. open
    devServer.open用于在DevServer启动且第一次构建完是,自动用我们系统默认浏览器去打开要开发的网页,还提供了devServer.openPage配置项来打开指定URL的网页。

7、其他配置项

除了前面介绍到的配置项,Webpack还提供了一些零散的配置项。下面介绍这些配置项中常用部分。

  1. Target
    由于JavaScript的应用场景越来越多,从浏览器到Node.js,这些运行在不同环境中的JavaScript代码存在一些差异。target配置项可以让webpack构建出针对不同运行环境的代码。target可以是如下所示:

    target值 描述
    web 针对浏览器(默认),所有代码都集中在一个文件里
    node 针对Node.js,使用require语句加载Chunk代码
    async-node 针对Node.js,使用异步加载Chunk代码
    webworker 针对WebWorker
    electron-main 针对Electron(http://electron.atom.io/)主线程
    electron-renderer 针对Electron渲染线程

    例如,在设置target: 'node’时,在源代码中导入Node.js原生模块的语句require(‘fs’)将会被保留,fs模块的内容不会被打包到Chunk里。

  2. Devtool
    devtool配置Webpack如何生成Source Map,默认值是false,即不生成Source Map,若想为构建出的代码生成Source Map以方便调试,则可以这样配置:

    module.export = {
    	devtool: 'source-map'
    }
    
  3. Watch和WatchOptions
    前面介绍过Webpack的监听模式,它支持监听文件更新,在文件发生变化重新编译。在使用Webpack时,监听模式默认是关闭的,若想打开,则需要如下配置:
    module.export = { devtool: 'source-map' }
    在使用DevServer时,监听默认默认是开启的。
    除此之外,Webpack还提供了watchOptions配置项去更灵活的控制监听模式,使用如下:
    module.export = { // 只有在开启监听模式时,watchOptions才有意义 // 默认为false,也就是不开启 watch: true, // 监听模式运行时的参数 // 在开启监听模式时,才有意义 watchOptions: { // 不监听的文件或文件夹,支持正则匹配 // 默认为空 ignored: /node_modules/, // 监听到变化后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高 // 默认为300ms aggregateTimeout: 300, // 判断文件是否发生变化是通过不停的询问系统指定文件有没有变化的实现的 // 默认每秒询问1000次 poll: 1000 } }

  4. Externals
    Externals用来告诉在Webpack要构建的代码中使用了哪些不被打包的模块,也就是说这些模块是外部环境提供的,Webpack在打包时可以忽略它们。
    有些JavaScript运行环境可能内置了一些全局变量或者模块,例如在我们的HTML HEAD标签里通过以下代码引入jQuery:

    这时,全局变量jQuery就会被注入网页的JavaScript运行环境里。
    如果想在使用模块化的源代码里导入和使用jQuery,则可能需要这样:

    import $ from 'jquery';
    $('.my-element');
    

    构建后我们会发现输出的Chunk里包含的jQuery库的内容,这导致jQuery库出现了两次,浪费,浪费加载流量,最好是Chunk里不包含jQuery库的内容。
    Externals配置项就是用于解决这个问题的。
    通过externals可以告诉Webpack在JavaScript运行环境中已经内置了哪些全局变量,不用将这些全局变量打包到代码中而是直接使用它们。要解决以上问题,可以这样配置externals:

    module.export = {
    	externals: {
    		// 将导入语句里的jquery替换成运行环境里的全局变量jQuery
    		jquery: 'jQuery'
    	}
    }
    
  5. ResolveLoader
    ResolveLoader用来告诉Webpack如何去寻找Loader,因为在使用Loader时是通过其包名称去处理源文件。
    ResolveLoader的默认配置如下:

    module.exports = {
    	resolveLoader: {
    		// 去哪个目录下寻找Loader
    		modules: ['node_modules'],
    		// 入口文件的后缀
    		extensions: ['.js', '.json'],
    		// 指明入口文件位置的字段
    		mainFields: ['loader', 'main']
    	}
    }
    

    该配置项常用于加载本地的Loader。

8. 整体配置结构

基础整体结构:

const path = require('path');
module.exports = {
	// entry表示入口,Webpack执行构建的第一步将从Entry开始,可抽象成输入
	// 类型可以是string、object、array
	entry: './app/entry',		// 只有1个入口,入口只有1个文件
	entry: ['./app/entry1', './app/entry2'],		// 只有1个入口,入口有两个文件
	entry: {		// 有两个入口
		a: './app/entry-a',
		b: ['./app/entry-b1', './app/entry-b2']
	},
	// 如何输出结果;在Webpack经过一系列处理后,如何输出最终想要的代码
	output: {
		// 输出文件存放的目录,必须是string类型的绝对路径
		path: path.resolve(__dirname, 'dist'),
		// 输出文件的名称
		filename: 'bundle.js',		// 完整的名称
		filename: '[name].js',		// 在配置了多个entry时,通过名称模板为不同entry生成不同的文件名称
		filename: '[chunkhash].js',		// 根据文件内容的Hash值生成文件的名称,用于浏览器长时间缓存文件
		// 发布到线上的所有资源的URL前缀,为string类型
		publicPath: '/assets/',		// 放到指定目录下
		publicPath: '',		// 放到根目录下
		publicPath: 'https://cdn.example.com/',		// 放到CDN上
		// 导出库的名称,为string类型
		// 不填它时,默认的输出格式是匿名的立即执行函数
		library: 'MyLibrary',
		// 导出库的类型,为枚举类型,默认是var
		// 可以是umd、umd2、commonjs2、commonjs、amd、this、var、assign、window、global、jsonp
		libraryTarget: 'umd',
		// 是否包含有用的文件路径信息到生成的代码里,为boolean类型
		pathinfo: true,
		// 附件Chunk的文件名称
		chunkFilename: '[id].js',
		chunkFilename: '[chunkhash].js',
		// JSONP异步加载资源时的回调函数名称,需要和服务端搭配使用
		jsonpFunction: 'myWebpackJsonp',
		// 生成的Source Map文件的名称
		sourceMapFilename: '[file].map',
		// 浏览器开发者工具里显示的源码模块名称
		devtoolModuleFilenameTemplate: 'webpack:///[resource-path]',
		// 异步加载跨域的资源时使用的方式
		crossOriginLoading: 'use-credentials',
		crossOriginLoading: 'anonymous',
		crossOriginLoading: false,		
	},
	// 配置模块相关
	module: {
		rules: [		// 配置Loader
			{
				test: /\.jsx?$/,			// 正则匹配命中要使用Loader的文件
				include: [			// 忽略这里面的文件
					path.resolve(__dirname, 'app')
				],
				exclude: [			// 忽略这里面的文件
					path.resolve(__dirname, 'app/demo-files')
				],
				use: [		// 使用哪些Loader,有先后次序,从后向前执行
					'style-loader',		// 直接使用Loader的名称
					{
						loader: 'css-loader',
						options: {			// 向html-loader传一些参数
						}
					}
				]
			}
		],
		noParse: [		// 不用解析和处理的模块
			/special-library\.js$/			// 用正则匹配
		]
	},
	// 配置插件
	plugins: [
	],
	// 配置寻找模块的规则
	resolve: {
		modules: [		// 寻找模块的根目录,为array类型,默认以node_module为根目录
				'node_modules',
				path.resolve(__dirname, 'app')
		],
		extensions: ['.js', '.json', '.jsx', '.css'],		// 模块的后缀名
		alias: {		// 模块别名配置,用于映射模块
			// 将'module'映射成'new-module',同样,'module/path/file'也会被映射成'new-module/path/file'
			'module': 'new-module',
			// 使用结尾符号$后,将'only-module'映射成'new-module',
			// 但是不想上面的,'module/path/file'不会被映射成'new-module/path/file'
			'only-module$': 'new-module',
		},
		alias: [		// alias还支持使用数组来更详细地进行配置
			{
				name: 'module',		// 老模块
				alias: 'new-module',		// 新模块
				// 是否只映射模块,如果是true,则只有'module'会被映射;如果是false,则'module/inner/path'也会被映射
				onlyModule: true,
			}
		],
		symlinks: true,		// 是否跟随文件的软连接去搜寻模块的路径
		descriptionFiles: ['package.json'],		// 模块的描述文件
		mainFields: ['main'],		// 模块的描述文件里描述入口的文件的字段名
		enforceExtension: false,		// 是否强制导入语句写明文件后缀
	},
	// 输出文件的性能检查配置
	performance: {
		hints: 'warning',	// 有性能问题时输出警告
		hints: 'error',		// 有性能问题时输出错误
		hints: false,		// 关闭性能检查
		maxAssetSize: 200000,		// 最大文件的大小(单位为bytes)
		maxEntrypointSize: 400000,		// 最大入口文件的大小(单位为bytes)
		assetFilter: function(assetFilename){		// 过滤要检查的文件
			return assetFilename.endsWith('.css')	||  assetFilename.endsWith('.js');
		}
	},
	devtool: 'source-map',		// 配置source-map类型
	context: __dirname,		// Webpack使用的根目录,string类型必须是绝对路径
	// 配置输出代码的运行环境
	target: 'web',		// 浏览器,默认
	target: 'webworker',		// WebWorker
	target: 'node',		// Node.js,使用`require`语句加载Chunk代码
	target: 'async-node',		// Node.js,异步加载Chunk代码
	target: 'node-webkit',		// nw.js
	target: 'electron-main',		// electron, 主线程
	target: 'electron-renderer',		// electron,渲染线程
	externals: {		// 是用来自JavaScript运行环境提供的全局变量
		jquery: 'jQuery'
	},
	stats: {		// 控制台输出日志控制
			assets: true,
			colors: true,
			errors: true,
			errorDetails: true,
			hash: true,
	},
	devServer: {		// DevServer相关配置
		proxy: {		// 代理到后端服务接口
			'/api': 'http://localhost:3000'
		},
		contentBase: path.join(__dirname, 'public'),		// 配置DevServer HTTP服务器的文件根目录
		compress: true,		// 是否开启Gzip压缩
		historyApiFallback: true,			// 是否开发HTML5 History API网页
		hot: true,		// 是否开启模块热替换功能
		https: false,		// 是否开启HTTPS模式
	},
	profile: true,		// 是否捕捉Webpack构建的性能信息,用于分析是什么原因导致构建性能不佳
	cache: false,		// 是否启用缓存来提升构建速度
	watch: true,		// 是否开始
	watchOptions: {		// 监听模式选项
	//不监听的文件或文件夹,支持正则匹配,默认为空
	ignored: /node_modules/,
	// 监听到变化发生后,等300ms在执行动作,截流,防止文件更新太快导致重新编译频率太快,默认为300ms
	aggregateTimeout: 300,
	// 不停地询问系统指定的文件有没有发生变化,默认每秒询问1000次
	poll: 1000
	}
}

9、多种配置类型

除了通过导出一个Object来描述Webpack所需的配置,还有其他更灵活的方式,以简化不同场景的配置。下面来一一介绍它们。

  1. 导出一个Function
    在大多数时候,我们需要从同一份源代码中构建出多份代码,例如一份用于开发,一份用于发布到线上。
    如果采用导出一个Object来描述Webpack所需配置的方法,则需要写两个文件,一个用于开发环境,一个用于线上环境。再启动时通过webpack --config webpack.config.js指定使用哪个配置文件。
    采用导出一个Function的方式,能通过JavaScript灵活地控制配置,做到只用写一个配置文件就能完成以上要求。
    导出一个Function的使用方式如下:

    	const path = require('path');
    	const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
    	module.exports = function(env = { }, argv){
    		const plugins = [];
    		const isProduction = env['production'];
    		// 在生成环境中才压缩
    		if(ifProduction){
    			plugins.push(
    				// 压缩输出的JavaScript代码
    				new UglifyJsPlugin()
    			)
    		} 
    		return {
    			plufins: plufins,
    			// 在生成环境中不输出Source Map
    			devtool: isProduction ? undefined : 'source-map',
    		}
    	}
    

    在运行Webpack时,会向这个函数传入两个参数,如下所述。

    • env: 当前运行时的Webpack专属环境变量,env时一个Object。读取时直接访问Object的属性,将它设置为需要在启动Webpack时带上参数。例如启动命令是webpack --env.production --env,bao=foo,则env的值是{"production": "true", "bao": "foo"}
    • argv: 代表在启动Webpack时通过命令行传入的所有的参数,例如--config、--env、--devtool,可以通过webpack -h列出所有Webpack支持的命令行参数。
      就以上配置文件而言,在开发时执行命令webpack构建出方便调试的代码,在需要构建出发布到线上的代码时执行webpack --env.production构建出压缩的代码。
  2. 导出一个返回Promise函数
    在某些情况下不能以同步的方式返回一个描述配置的Object,Webpack还支持导出一个返回Promise的函数,使用如下:

module.exports = function(env = { }, argv){
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			resolve({
				// ...
			})
		}, 5000)
	})
}
  1. 导出多份配置
    除了导出一份配置,Webpack还支持导出一个数组,数组中可以包含每份配置且每份配置都会执行一遍构建。
    注意,Webpack从3.1.0版本才开始支持该特性。
    使用如下:
    module.export = [
    	// 采用Object描述一份配置
    	{
    		// ...
    	},
    	// 采用函数描述的一份配置
    	function(){
    		return {
    			// ...
    		}
    	},
    	// 采用异步函数描述的一份配置
    	function(){
    		return Promise();
    	}
    ]
    
    以上配置会导致Webpack针对这三份配置执行三次不同的构建。
    这特别适合用Webpack构建一个要上传到Npm仓库的库,因为库中可能需要包含多种模块化格式的代码,例如CommonJS、UMD。
10、总结

从前面的配置看有很多选项,Webpack内置了很多功能,我们不必都记住它们,只需要大概明白Webpack原理和核心概念,并判断选项大致属于哪个大模块下,再去查详细的使用文档即可。
通常我们可用如下经验去判断如何配置Webpack:

  • 若想让源文件加入构建流程中被Webpack控制,则配置entry;
  • 若想自定义输出文件的位置和名称,则配置output;
  • 若想自定义寻找依赖模块时的策略,则配置resolve;
  • 若想自定义解析和转换文件的策略,则配置module,通常是配置module.rules里的Loader;
  • 若其他大部分需求可能通过Plugin去实现,则配置plugin。

此篇为本人Webpack学习笔记,来源《深入浅出Webpack》

你可能感兴趣的:(webpack)