一.webpack的一些理解
1.什么是webpack
webpack是一款模块加载器兼打包工具,它把各种资源,例如js(含jsx)、coffee、样式(含less/sass)、图片等作为模块来使用和处理,它的目的就是把有依赖关系的各种文件打包成一系列的静态资源
2.webpack的优势
- webpack是以commonJs的形式来书写脚本的,但对AMD/CMD的支持也很全面
- 支持很多模块加载器的调用,可以使模块加载器灵活定制,比如babel-loader加载器,该加载器能使我们使用es6的语法来编写代码;less-loader加载器,可以将less编译成css文件
- 开发便捷,能替代部分grunt/gulp的工作,比如打包、压缩混淆、图片转base64等
- 可以通过配置打包成多个文件,有效的利用浏览器的缓存功能提升性能
3.webpack与其他类似工具有哪些不同
- 有同步和异步两种不同的加载方式
- loader,加载器可以将其他资源整合到js文件中,通过这种方式,将所有源文件形成一个模块
- 优秀的语法分析能力,支持CommonJs AMD规范
- 有丰富的开源插件库,可以根据自己的需求定制webpack的配置
4.webpack的打包流程
4-1.读取webpack的配置参数
4-2.启动webpack,创建Compiler对象并开始解析项目
4-3.从入口文件entry开始解析,并且找到其导入的依赖模块,递归遍历分析,形成依赖关系树
4-4.对不同文件类型的依赖模块文件使用对应的Loader进行编译,最终转为javascript文件
4-5.整个过程中webpack会通过发布订阅模式,向外抛出一些hooks,而webpack的插件即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的
5.webpack文件的解析与构建
文件的解析与构建是一个比较复杂的过程,在webpack源码中主要依赖compiler和compilation两个核心对象实现
compiler是一个全局单例,他负责把控整个webpack打包的构建过程,compilation对象是每一次构建的上下文对象,它包含了当次构建所需要的所有信息,每次热更新和重新构建,compiler都会重新生成一个新的compilation对象,负责此次更新的构建过程
而每个模块间的依赖关系,则依赖于AST语法树。每个模块文件在通过Loader解析完成之后,会通过acorn库生成模块代码的AST语法树,通过语法树就可以分析这个模块是否还有依赖的模块,进而继续循环执行下一个模块的编译解析。
最终Webpack打包出来的bundle文件是一个IIFE的执行函数。
6.webpack里的sourcemap
sourmap是一项将编译、打包、压缩后的代码映射回源代码的技术,由于打包压缩后的代码没有阅读性可言,一旦报错或者遇到问题,我们只能定位到压缩处理后的代码位置,无法定位到开发环境的代码,不好调试,而sourcemap可以快速帮我们定位到源代码的位置,提高开发效率
在项目打包完后。在打包的文件夹里除了js,css等资源文件外,还有xxx.js.map的文件,这种带map后缀的文件就是sourcemap文件,它保存了源代码和转换之后代码(通常经过压缩混淆和其他转换)的关系
6-1.常见的转换过程包括但不限于:
压缩混淆(UglifyJS)
编译(TypeScript, CoffeeScript)
转译(Babel)
合并多个文件,减少带宽请求。
6-2.sourcemap
SourceMap 的主要作用是为了方便调试
映射转换过后的代码和源代码之间的关系
源代码引入 //# sourceMappingURL=build.js.map
source Map 解决了源代码和运行代码不一致所产生的问题
注:sourceMap并不是webpack特有的功能
二.webpack配置
1.使用不同的配置文件:
如果需要使用不同的配置文件,需要在package.json文件中使用--config标志修改,例:
2.webpack使用不同编程语言和数据描述格式来编写配置文件
2-1.typeScript
npm install --save-dev typescript ts-node @types/node @types/webpack
# 如果使用版本低于 v4.7.0 的 webpack-dev-server,还需要安装以下依赖
npm install --save-dev @types/webpack-dev-server
#webpack.config.ts
import * as path from 'path';
import * as webpack from 'webpack';
// in case you run into any typescript error when configuring `devServer`
import 'webpack-dev-server';
const config: webpack.Configuration = {
mode: 'production',
entry: './foo.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'foo.bundle.js',
},
};
export default config;
该示例需要 typescript 版本在 2.7 及以上,并在 tsconfig.json 文件的 compilerOptions 中添加 esModuleInterop 和 allowSyntheticDefaultImports 两个配置项。
注意:你需要确保tsconfig.json的compilertions文件的compilerOptions中module选项的值为common.js,否则webpack的运行会失败报错,因为ts-node不支持commonjs以外的其他的模块规范。
你可以通过三个途径来完成module的设置:
- 直接修改tscinfig.json文件
- 修改tsconfig.json并且添加ts-node的设置
- 使用tsconfig-paths
第一种方法就是打开你的 tsconfig.json 文件,找到 compilerOptions 的配置,然后设置 target 和 module 的选项分别为 "ES5" 和 "CommonJs" (在 target 设置为 es5 时你也可以不显示编写 module 配置)。
第二种方法 就是添加 ts-node 设置:
你可以为 tsc 保持 "module": "ESNext"配置,如果你是用 webpack 或者其他构建工具的话,为 ts-node 设置一个重载(override)
{
"compilerOptions": {
"module": "ESNext",
},
"ts-node": {
"compilerOptions": {
"module": "CommonJS"
}
}
}
第三种方法需要先安装 tsconfig-paths 这个 npm 包,如下所示:
npm install --save-dev tsconfig-paths
安装后你可以为 webpack 配置创建一个单独的 TypeScript 配置文件,示例如下:
# tsconfig-for-webpack-config.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"esModuleInterop": true
}
}
注:ts-node 可以根据 tsconfig-paths 提供的环境变量 process.env.TS_NODE_PROJECT 来找到 tsconfig.json 文件路径。
# package.json
{
"scripts": {
"build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack-config.json\" webpack"
}
}
注:之所以要添加 cross-env,是因为我们在直接使用 TS_NODE_PROJECT 时遇到过 "TS_NODE_PROJECT" unrecognized command 报错的反馈,添加 cross-env 之后该问题也似乎得到了解决。
2-2.coffeeScript
npm install --save-dev coffeescript
示例如下:
#webpack.config.coffee
HtmlWebpackPlugin = require('html-webpack-plugin')
webpack = require('webpack')
path = require('path')
config =
mode: 'production'
entry: './path/to/my/entry/file.js'
output:
path: path.resolve(__dirname, 'dist')
filename: 'my-first-webpack.bundle.js'
module: rules: [ {
test: /\.(js|jsx)$/
use: 'babel-loader'
} ]
plugins: [
new HtmlWebpackPlugin(template: './src/index.html')
]
module.exports = config
3.导出
3-1.导出函数:
module.exports=function(env,argv){
return {
mode:env.production ? 'production' : 'development',
devtool:env.production ? 'source-map' : 'eval',
plugins:[
new TerserPlugin({
terserOptions:{
compress:argv.mode === 'production' //only if `--mode production` was passed
}
})
]
}
}
3-2.导出promise
module.exports=()=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve({
entry:'./app.js'
})
},5000)
})
}
注:支持使用Promise.all导出多个promise
3-3.导出多种配置
module.exports = [
{
output: {
filename: './dist-amd.js',
libraryTarget: 'amd',
},
name: 'amd',
entry: './app.js',
mode: 'production',
},
{
output: {
filename: './dist-commonjs.js',
libraryTarget: 'commonjs',
},
name: 'commonjs',
entry: './app.js',
mode: 'production',
},
];
注:如果你只传了一个
--config-name
名字标识,webpack 将只会构建指定的配置项。
3-3-1.dependencies
以防你的某个配置依赖于另一个配置的输出,你可以使用一个dependencies列表指定一个依赖列表
#webpack.config.js
module.exports = [
{
name: 'client',
target: 'web',
// …
},
{
name: 'server',
target: 'node',
dependencies: ['client'],
},
];
3-3-2.parallelism
如果你导出了多个配置,你可以在配置中使用parallelism选项来指定编译的最大并发数
#webpack.config.js
module.exports = [
{
//config-1
},
{
//config-2
},
];
module.exports.parallelism = 1;
4.入口和上下文
入口对象是用于 webpack 查找开始构建 bundle 的地方。上下文是入口文件所处的目录的绝对路径的字符串。
4-1.content(基础目录,绝对路径,用于从配置中解析入口点和加载器)
const path = require('path');
module.exports = {
//...
context: path.resolve(__dirname, 'app'),
};
默认使用 Node.js 进程的当前工作目录,但是推荐在配置中传入一个值。这使得你的配置独立于 CWD(current working directory, 当前工作目录)。
4-2.entry开始应用程序打包过程的一个或多个起点(配置文件中entry接受三种形式的值:字符串,数组,函数和对象,如果entry是字符串或者字符串数组,文件导出后会被命名为main,如果传入的是个对象,则每个属性的键是导出的文件的名字,键值则是该文件的入口点)
例1:
module.exports={
entry:'./app.js'
}
例2:
entry: {
'path/of/entry': './deep-app.js',
'app': './app.js'
}
例3:
entry: {
vendor: ['jquery', 'lodash']
}
例4:
entry: () => new Promise((resolve) => resolve(['./demo', './demo2'])),
如果传入的是个函数,它将在每次make事件中被调用
默认情况下,入口的输出文件名是从output.filename中提取出来的,但可以指定一个自定义的输出文件名
例5:
module.exports = {
//...
entry: {
app: './app.js',
home: { import: './contact.js', filename: 'pages/[name][ext]' },
about: { import: './about.js', filename: 'pages/[name][ext]' },
},
};
5.mode
5-1.用法
在配置对象中使用mode选项
例:
module.exports={
mode:'development'
}
从CLI参数中传递
例:
webpack --mode-development
mode支持以下字符串值
development:会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。
production:会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin
none:不使用任何默认优化选项
如果没有设置,webpack的mode的默认值是production
6.output
6-1.asyncChunks:创建按需加载的异步chunk
例:
module.exports={
output:{
asyncChunks:true
}
}
6-2.filename控制输出资源的文件名,也可以是个相对路径,文件夹不存在会在输出时创建
例:
module.exports={
entry:'/src/main.js',
output:{
filename:'bundle.js
}
}
6-3.path(指定资源输出的位置,在webpack4之后,output.path默认为dist目录,除非我们想更改它,否则不必单独配置,不需要写)
const path=require('path')
module.exports={
entry:'./src/main.js',
output:{
filename:'bundle.js',
//将资源输出位置设置为该项目的dist目录
path: path.resolve(__dirname, 'dist')
},
}
多入口的情况下,我们需要对产生的每个bundle指定不同的名字,Webpack支持使用一种类似模板语言的形式动态地生成文件名
module.exports = {
entry:{
main:'./src/main.js',
vender:'./src/vender.js'
},
output: {
filename: '[name].js',
},
};
filename中的[name]会被替换为chunk name即main和vender。因此最后会生成vendor.js与main.js
模板变量
可在编译层面进行替换的内容
[fullhash]:指代Webpack此次打包所有资源生成的hash
[hash]:指代Webpack此次打包所有资源生成的hash,已弃用
可在chunk层面进行替换的内容
[id]:指代当前chunk的id
[name]:设置则设置的值为此chunk的名称,否则使用chunk的id
[chunkhash]:指代当前chunk内容的hash,包含该chunk的所有元素
[contenthash]:此 chunk 的 hash 值,只包括该内容类型的元素(受 optimization.realContentHash 影响)
可在模块层面替换的内容
[id]:模块的 ID
[moduleid]:模块的 ID,已弃用
[hash]:模块的 Hash 值
[modulehash]:同上,但已弃用
[contenthash]:模块内容的 Hash 值
可在文件层面替换的内容
[file] : filename 和路径,不含 query 或 fragment
[query] :带前缀 ?
的 query
[fragment] :带前缀 #
的 fragment
[base]:只有 filename(包含扩展名),不含 path
[filebase]:同上,但已弃用
[path]:只有 path,不含 filename
[name]:只有 filename,不含扩展名或 path
[ext]: 带前缀 .
的扩展名(对 output.filename不可用)
可在 URL 层面替换的内容
[url]:URL
6-4.publicPath
publicPath是一个非常重要的配置项,用来指定资源的请求位置
原本图片请求的地址是./img.jpg,而在配置上加上publicPath后,实际路径就变成了了./dist/static/img/img.jpg,这样就能从打包后的资源中获取图片了
6-5.HtmlWebpackPlugin
npm install --save-dev html-webpack-plugin
//添加插件 plugins:[ new HtmlWebpackPlugin({ title:'output management' }) ],
打包完成后你会发现dist中出现了一个新的index.html,上面自动帮我们添加所生成的资源,打开后会发现浏览器会展示出内容
6-6. assetModuleFilename配置图片打包
图片打包,webpack通过配置assetModuleFilename可以打包css里面的图片。js import的图片默认是不能失败的。webpack5通过内置配置可以实现,也可以安装url-loader或者file-loader模块。
6-7.auxiliaryComment模块化导出技术的注释属性(需要output.library和output.libraryTarget一起使用,如果想要注释更细粒度的控制,可以传入一个对象)
例:
module.exports = {
//...
output: {
library: 'someLibName',
libraryTarget: 'umd',
filename: 'someLibName.js',
auxiliaryComment: 'Test Comment',
},
};
将会生成:
(function webpackUniversalModuleDefinition(root, factory) {
// Test Comment
if (typeof exports === 'object' && typeof module === 'object')
module.exports = factory(require('lodash'));
// Test Comment
else if (typeof define === 'function' && define.amd)
define(['lodash'], factory);
// Test Comment
else if (typeof exports === 'object')
exports['someLibName'] = factory(require('lodash'));
// Test Comment
else root['someLibName'] = factory(root['_']);
})(this, function (WEBPACK_EXTERNAL_MODULE_1) {
// ...
});
例2:
module.exports = {
//...
output: {
//...
auxiliaryComment: {
root: 'Root Comment',
commonjs: 'CommonJS Comment',
commonjs2: 'CommonJS2 Comment',
amd: 'AMD Comment',
},
},
};
6-8.charset(告诉webpack为html的script的标签添加)
6-9.chunkFilename
当你的代码中使用了动态import时,webpack会将动态import的包,单独打包, 这样子实现按需载入,但是打包后的文件名可能是一个随机串。
所以为了识别这些bundle块,使用的时候需要传入name,如下所示
document.getElementById('btn').onclick = function() {
import( /* webpackChunkName: "test123123" */ './test').then((res) => {
console.log(res)
console.log(res.default())
})
}
import调用的时候在括号里传入注释,如上所示,注释是/* webpackChunkName: "test123123" */,后面跟个空格,接上路径地址,意思是打包出来的bundle文件名叫test123123.js,webpack的output需要配置成 chunkFilename: 'static/js/[name].js',,这个[name]就能取到test123123
6-10.chunkLoadTimeout
chunk 请求到期之前的毫秒数,默认为 120000
6-11.chunkLoadingGlobal
webpack用于加载春看到·全局变量
例:
module.exports = {
//...
output: {
//...
chunkLoadingGlobal: 'myCustomFunc',
},
};
6-12.chunkLoading加载chunk方法
'jsonp' (web)、'import' (ESM)、'importScripts' (WebWorker)、'require' (sync node.js)、'async-node' (async node.js)
6-13.clean
例:
module.exports={
output:{
clean:true//在生成文件之前清空output目录
}
}
例2:
module.exports={
output:{
clean:{
dry:true//打印而不是删除应该移除的静态资源
}
}
}
例3:
module.exports={
output:{
clean:{
keep:/ignored/dir//, //保留ignored/dir下的静态资源
}
}
}
6-14.compareBeforeEmit(告诉webpack在写入到输出文件系统时检查输出的文件是否已经存在并且拥有相同内容)
6-15.crossOriginLoading(告诉webpack启用cross-origin属性加载chunk,仅在target设置为web时生效,通过使用JSONP来添加脚本标签,实现按需加载模块)
'anonymous'-不带凭据(credential)启用跨域加载
'use-credentials'-携带凭据(credential)启用跨域加载
6-16.library(输出一个库,为你的入口做导出)
module.exports = {
// …
entry: './src/index.js',
output: {
library: 'MyLibrary',
},
};
library.export(指定哪一个导出应该被暴露为一个库)
7.module
7-1.generator(在一个地方配置所有生成器的选项)
module.exports = {
module: {
generator: {
asset: {
// asseet 模块的 generator 选项
// 自定义 asset 模块的 publicPath,自 webpack 5.28.0 起可用
publicPath: 'assets/',
// 将静态资源输出到相对于 'output.path' 的指定文件夹中,webpack 5.67.0 后可用
outputPath: 'cdn-assets/',
},
'asset/inline': {
// asset/内联模块的 generator 选项
},
'asset/resource': {
// asset/资源模块的 generator 选项
// 自定义 asset/resource 模块的 publicPath,自 webpack 5.28.0 起可用
publicPath: 'assets/',
// 将静态资源输出到相对于 'output.path' 的指定文件夹中,webpack 5.67.0 后可用
outputPath: 'cdn-assets/',
},
javascript: {
// 该模块类型尚不支持 generator 选项
},
'javascript/auto': {
// 同上
},
'javascript/dynamic': {
// 同上
},
'javascript/esm': {
// 同上
},
// 其他...
},
},
};
7-2.parser(类似于module.generator,你可以用 module.parser
在一个地方配置所有解析器的选项)
7-3.noParse(防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中 不应该含有 import, require, define 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能)
例:
module.exports = {
//...
module: {
noParse: /jquery|lodash/,
},
};
7-4.unsafeCache(缓存模块请求的解析)
包含如下几个默认值
如果cache未被启用,则默认值为false
如果cache被启用,并且此模块的来自于node_modules则值为true,否则为false
module.exports = {
//...
module: {
unsafeCache: false,
},
};
7-5.rules
创建模块时,匹配请求的规则数组,这些规则能够修改模块的创建方式。 这些规则能够对模块(module)应用 loader,或者修改解析器(parser)。
7-5-1.rule:每个规则可以分为三部分 - 条件(condition),结果(result)和嵌套规则(nested rule)
7-5-1-1.rule条件
条件有两种输入值
- 1.resource:资源文件的绝对路径,它根据resolve规则解析
- 2.issuer:请求者的文件绝对路径,是导入时的位置