webpack
webpack是一个打包工具,通过各种loader和plugin,从入口文件开始,将整个依赖链上的所有资源(js,ts,jpg|png|jpeg|svg,html,fonts,css,json,txt等等)都当作模块来处理,不同的资源类型由不同的loader来处理,配合插件,最终打包合成一个或多个bundles。每个依赖链打包过程可以看作一个chunk。
bundle:结果,chunk: 过程
常用概念
-
entry: 入口,有多种写法。单入口:
module.exports = { entry: './path/to/my/entry/file.js', };单入口带依赖:
module.exports = { entry: ['./src/file_1.js', './src/file_2.js'], // 实际就是file_2中引入了file_1,入口文件是file_2 output: { filename: 'bundle.js', }, };多入口:
module.exports = { entry: { main: './src/app.js', vendor: './src/vendor.js', }, }; -
output: 出口,bundle的位置module.exports = { output: { path: path.resolve(__dirname, 'dist'), // 使用Node的path组件,相当于当前文件的位置下的dist文件夹 filename: '[name].[contenthash:8].js', // 基于entry的output,输出的文件名使用占位符来动态生成文件名 chunkFilename: '[name].[chunkhash:8].js' // 某些情况下webpack会额外启用一些chunk用来打包对应的文件,比如lazy load,此时输出的文件就会用这个命名规则 publicPath: '/', // 公共路径,打包后所有的资源url都会有这个前缀 clean: true, }, }对于lazy load,比如:
import('./App.tsx'),此时[name]的表现类似hash,可以使用import(/* webpackChunkName:"app" */ './App.tsx')的方式来命名chunk从而可以使用这个名称。 -
module:这就是用来处理不同类型的资源(模块)的,默认webpack只会处理js和json,其他类型的都需要为他们指定loader。module.exports = { module: { rules: [ { test: /\.tsx?$/, use: 'babel-loader', exclude: /node_modules/ }, { test: /\.(png|jpg|jpeg|gif)$/, type: 'asset', generator: { filename: 'images/[hash][ext][query]', }, parser: { dataUrlCondition: { maxSize: 4 * 1024 }, }, }, { test: /\.less$/, use: [devMode ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'], }, { test: /\.html$/, loader: 'html-loader', }, ], }, }通过
test匹配对应的资源,通过use来指定loader,有很多用法,数组语法,可以一次指定多个loader,从右至左的顺序生效;对象语法,可以为loader添加选项。test后面使用正则,常见用法:/\.后缀$/,如果不想区分后缀大小写,可以使用/\.后缀$/i。webpack5中对于
file-loader,url-loader的使用有了改变,通过type来使用:-
asset/resource发送一个单独的文件并导出 URL。之前通过使用file-loader实现。 -
asset/inline导出一个资源的 data URI(比如对于小图像可以使用这种格式来代替引用,默认使用base64算法)。之前通过使用url-loader实现。常见的比如
svg如果很小的话,完全可以都用data URI,还可以自行指定 data URI的生成算法,官网例子 -
asset/source导出资源的源代码(比如.txt文件的内容)。之前通过使用raw-loader实现。 -
asset在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用url-loader,并且配置资源体积限制实现。
-
-
resolve:模块解析相关,设置后引用时可以省略扩展名。注意:这只是用于解析自己的源码,对于第三方库,按照本身设置的逻辑解析,比如typescript中,就是使用tsconfig中的配置
moduleResolution。module.exports = { resolve: { extensions: ['.tsx', '.ts', '.js', '...'], // '...'表示还包含webpack默认的 }, } -
devServer: 开发服务器,用于测试。module.exports = { devServer: { static: { directory: path.join(__dirname, 'dist'), // 注意这里是join,相当于绝对路径 publicPath: '/app/', // 公共访问路径,dev server run的时候,打包的内容是放在内存中的,这个publicPath就是访问内存中的资源的公共路径。而output中的publicPath,是打包后的资源都会加上这个公共路径。可以理解为dev server的publicPath就是nginx设置的虚拟路径,而output的publicPath会修改src等资源的引用路径。2者配合才有效果。 }, open: true, port: 8080, hot: true, }, } -
mode: 值可以是none,development,production,不同的模式打包后不一样,比如production自动会进行压缩、丑化和tree shaking。 -
target: 值可以是web或node,不同的环境打包编译也有不同。 -
optimization: 优化配置,一般用来剥离chunk,比如被多个chunk共同引用的内容,可以单独设置一个chunk,第三方包也可以单独设置一个chunk。还有个
runtimeChunk配置,runtimeChunk,表示运行时chunk,为什么有这个东西?这里面有2个东西:
-
webpack在编译后的代码中运行时的依赖,主要就是模块加载和解析
-
模块的映射关系。比如A模块引用B模块,如果B模块变了,contenthash变化,此时A模块中的引用也要变,导致A模块的content变化,从而A模块的contenthash也会变,此时A模块的缓存就失效了,又要重新请求,但是实际上此时只有B模块变了,只需要重新加载B模块就行了,为了解决这个问题,就需要使用
runtimeChunk,让A模块不直接维护和B模块的引用关系。效果: B模块变了,B模块的contenthash重新计算,
runtimeChunk对应的bundle内容跟着改变,也会重新计算contenthash,此时就只需要重新加载B模块和runtimeChunk对应的bundle,虽然也是2个,但是runtimeChunk体积非常小。
module.exports = { optimization: { splitChunks: { cacheGroups: { // 打包业务中公共代码 common: { name: 'common', chunks: 'all', minSize: 0, // 大于某个大小时才会单独chunk priority: 0, minChunks: 2, // 最少引用次数 }, // 打包第三方库的文件 vendor: { name: 'vendor', test: /[\\/]node_modules[\\/]/, chunks: 'all', minSize: 0, priority: 10, minChunks: 2, }, }, }, runtimeChunk: { name: 'manifest' }, // 运行时代码 }, } -
-
plugins: 插件,用于扩展webpack的功能。最常用的有
HtmlWebpackPlugin,可以生成html文件,并自动关联js,css文件,特别对于单页面应用,基本不用配置,开箱即用,还可以指定模板。MiniCssExtractPlugin,可以用于单独打包css,而不是放在style中(style-loader的作用)。 -
devtool: source map相关配置,注意生产环境下的配置,不要轻易暴露源码。
常用占位符
一般用于bundle的filename。
[fullhash]:根据整个应用计算。[fullhash: 8]可以指定长度[contenthash]:根据文件内容计算[chunkhash]:基于chunk计算[name]: 文件名,一般用在entry是对象形式时[id]: chunk id,[ext]: 扩展名[query]: 模块的 query,例如,文件名 ? 后面的字符串
配置文件语言
建议使用typescript来写。
需要额外安装ts-node。也就是配置文件实际是通过ts-node来编译的。
注意: webpack.config.ts是运行在node环境下,而node环境下只支持commonjs模块。所以tsconfig的compilerOptions必须要是commonjs,如果你想使用es模块,那必须要给ts-node单独配置compilerOptions:
// tsconfig.json
{
"compilerOptions": {
"module": "ESNext",
},
"ts-node": {
"compilerOptions": {
"module": "CommonJS"
}
}
}常见模板:
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
如何在webpack中直接使用typescript?
可以直接使用ts-loader来编译(就是使用tsc),但是更好的方式是使用babel-loader。
因为直接编译后生成的js,在没有经过babel的转换时,可能无法在目标环境中运行,有些目标环境不支持es6以后的语法和API。
babel-loader可以通过配置,将typescript直接编译成目标环境适配的js。
但是:
babel只能编译,无法进行类型检查,也就是babel只是代替了tsc的编译功能。
这里也能看出babel只是进行编译,并且它是按照自己的配置文件来编译,并不会读取tsconfig.json中的配置。
所以:
最好的选择是2者配合使用:
babel编译,读取.babelrc.jstsc类型检查,读取tsconfig.json。此时需要配置关闭生成js文件,只是进行类型检查。
所以有些配置项需要写2次。
build时还要先输入tsc来检查,很麻烦,可以利用插件fork-ts-checker-webpack-plugin完成。
babel
babel作用:
-
语法转换:语法降级
比如箭头函数,转换为常规的声明式或表达式
-
ployfill: API补充比如有些环境是不支持
Promise,Async/Await,Array.flat等等功能的,需要”填平”,也就是引入ployfill,来支持这些功能。
babel针对不同的内容,都提供了对应的转换插件,非常多,一个个配置是很难的,幸运的是官方提供了preset,可以看作是插件的集合。
常用的有:
- @babel/preset-env:基本包含了所有的转换降级规则
- @babel/preset-typescript: 用于编译typescript
react,vue也有对应的预设。
上面的预设只是进行了语法的转换,ployfill呢?
其实@babel/preset-env,可以自动引入ployfill,通过
useBuiltIns来配置,当然需要先安装@babel/polyfill或者core-js和regenerator-runtime。但是这种方式会污染全局API,所以建议使用插件的方式,插件不会污染全局API,插件会另外再生成一个API,然后让你的源码改为调用这个API。
通过插件@babel/plugin-transform-runtime来完成。
只需要配置插件的corejs就行了,可以取boolean,2,3,取值false表示不进行ployfill,取值2表示使用@babel/runtime-corejs2来ployfill,取值3表示使用@babel/runtime-corejs3来ployfill,建议使用3,当然前提是安装了这个包(生产环境下需要)。
@babel/plugin-transform-runtime默认开启了对Generator/async的转换,不需要配置。
// .babelrc.js 可被babel-loader读取
module.exports = {
presets: [['@babel/env', { corejs: false}], '@babel/typescript'], // 多个preset,从右至左的顺序生效
plugins: [['@babel/plugin-transform-runtime', { helpers: true, corejs: 3 }]],
};
注意:使用插件来plofyfill,那@babel/env中的就要关闭。
@babel/plugin-transform-runtime还有个作用,就是抽离公共辅助函数,类似tsconfig中的importHelpers,这个功能依赖@babel/runtime-corejs2|@babel/runtime-corejs3|@babel/runtime。
browserslist
babel可以根据目标环境自行选择怎么语法降级和ployfill,这个目标环境就是browserslist。
可以在package.json中配置:
{
"browserslist": [
"defaults",
"not IE 11",
"maintained node versions"
]
}可以通过npx browserslist命令(需要安装这个包),查看这个配置对应的浏览器及版本。
browserslist可以被用于很多地方,比如postcss,eslint等等。