/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const webpack = require('webpack');
//以下都是插件的引入
const HappyPack = require('happypack');
//html-webpack-plugin插件, 让webpack打包后生成html文件并自动引入打包后的js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
/*;
webpack本身是, node的一个第三方模块包, 用于打包代码;
把很多文件打包整合到一起, 缩小项目体积, 提高加载速度
功能:
less/sass -> css
ES6/7/8 -> ES5 处理js兼容
支持js模块化
处理css兼容性
html/css/js -> 压缩合并
所有要被打包的资源都要跟入口产生直接/间接的引用关系
*/
/*
module:每个import引入的文件就是一个模块(也就是直接手写的代码)
chunk:当module源文件传到webpack进行打包时,webpack会根据文件引用关系生成chunk(也就是module在webpack处理时是chunk)
bundle:是对chunk进行压缩等处理后的产出(也就是打包后可以直接运行的文件)*/
//webpack只能解析js和json这样的文件,loader可以让webpack解析其它类型的文件
//并将这些文件转化为有效的模块
//babel-loader 可以让webpack 对高版本js语法做降级处理后打包
const babelLoader = [
{
loader: 'babel-loader',
options: {
/*
sourceType可以是 “script” | “module” | “unambiguous”,默认其实是"module"
"script": 使用正常的script标签里的js语法解析文件,没有import/export,并且不是严格模式
"module": 使用ES6 module解析文件,自动为严格模式,支持import/export语法
"unambiguous": babel根据文件中是否出现import/exports来确定是否处于"module"模式还是"script"模式
*/
sourceType: 'unambiguous',
babelrc: false,
// 预设:转码规则
//Presets就是一组转换JavaScript插件的集合。执行顺序从右到左,和loader一样
presets: ['babel-preset-expo'],
//Babel可以使用各种插件针对不同特性JavaScript特性进行转换。
plugins: [
['import', { libraryName: 'antd-mobile', style: true }],
[
'module-resolver',
{
alias: {
'react-native-reanimated': path.resolve(
__dirname,
'node_modules/react-native-reanimated/src/Animated.js'
),
},
},
],
],
},
},
];
// 获取文件绝对路径
function resolve(...pathNames) {
const paths = [__dirname, ...pathNames];
return path.resolve(...paths);
}
//为了获取环境变量,需要将其写成函数形式
//如果你想根据webpack.config.js中的mode变量改变行为,你必须导出一个函数而不是对象
module.exports = (env, argv) => {
const IS_PROD = argv.mode === 'production';
const CDN_PATH = '/';
const PUBLIC_PATH = IS_PROD ? CDN_PATH : '/';
const webpackConfig = {
modules: false, // 默认true,是否添加构建模块信息
//主要包含了webpack在编译源代码时的一些模块统计信息。这些统计数据可以用来分析应用程序的依赖关系图,也可以用来优化编译速度。
// 默认true,是否添加children信息(设置为false,解决webpack4打包时出现:Entrypoint undefined = index.html)
stats: { children: false },
/*
mode 表示webpack当前的环境以及对不同的环境的配置。
一共有production、development和none三种。
development 开发阶段,简易打包,打包速度快
production 发布阶段,打包精细,打包速度慢
*/
mode: 'production',
//入口
// 默认为 ./src
// 这里应用程序开始执行
// webpack 开始打包
//对象中的每一对属性对,都代表着一个入口文件,因此多页面配置时,肯定是要用这种形式的entry配置。
//value如果是字符串,而且必须是合理的noderequire函数参数字符串。比如文件路径:'./app.js'(require('./app.js'));比如安装的npm模块:'lodash'(require('lodash'))
entry: {
//指定了把那些模块打包为vendor
//value如果是数组,则数组中的元素需为上面描述的合理字符串值。数组中的文件一般是没有相互依赖关系,但又出于某些原因需要打包在一起。
vendor: [
'react',
'core-js',
'dva-loading',
'fastclick',
'react-native-web',
'react-dom',
'rc-form',
'react-navigation',
'react-singleton',
'react-imageview',
],
app: './index.js',
},
//webpack通过resolve实现模块的依赖和引用
resolve: {
//通过别名来把原来导入路径映射成一个新的导入路径
alias: {
'react-native': 'react-native-web',
'react-native-reanimated': path.join(__dirname, 'node_modules/react-native-reanimated/src/Animated.js'),
),
'react-native-gesture-handler': path.join(__dirname, 'node_modules/react-native-gesture-handler'),
},
//esolve.modules配置webpack去哪些目录下寻找第三方模块。默认是去node_modules目录下寻找。有时你的项目中会有一些模块大量被其他模块依赖和导入,由于其他模块的位置分布不定,针对不同的文件都要去计算被导入模块文件的相对路径,这个路径有时候会很长,例如:import './../../components/button',这时你可以利用modules配置项优化,假如那些大量导入的模块都在./src/components目录下:
modules: [resolve('src/components'), resolve('src'), 'node_modules', './node_modules'],
//使用的扩展名,相同文件名顺序优先识别
extensions: ['.web.ts', '.ts', '.web.tsx', '.tsx', '.web.js', '.js', '.less', '.web.jsx', '.jsx', '.json'],
},
// webpack 如何输出结果的相关选项
output: {
// 所有输出文件的目标路径
// 必须是绝对路径(使用 Node.js 的 path 模块)
path: path.resolve(__dirname, 'public'),
// 用于输出文件的文件名
//[]是webpack自带的默认生成文件名的方法
filename: IS_PROD ? 'assets/scripts/[name].[chunkhash].js' : 'assets/scripts/[name].js',
// 用于动态加载的chunk
chunkFilename: IS_PROD ? 'assets/scripts/[chunkhash].chunk.js' : 'assets/scripts/[id].chunk.js',
//配置公共路径
// 输出解析文件的目录,url 相对于 HTML 页面
publicPath: PUBLIC_PATH,
},
/*
1.为静态文件提供web服务
2.自动刷新和热替换(HMR)
自动刷新指当修改代码时webpack会进行自动编译,更新网页内容
HMR:Hot Module Replacement 热模块替换。当修改代码时, webpack 默认会将所有模块全部重新打包编译,整个页面重新加载,速度很慢。HMR 热模块替换支持在程序运行中(webpack-dev-server 已启动),修改哪个模块,就自动修改(替换、添加、删除)该模块。
简而言之:起一个开发服务器, 在电脑内存中打包, 缓存一些已经打包过的内容, 只重新打包修改的文件, 最终运行加载在内存中给浏览器使用
*/
devServer: {
// 运行代码的目录
contentBase: path.resolve(__dirname, 'public'),
//代表的是启动的时候,就在的页面
index: 'index.html',
//指定监听请求的端口号。
port: 8082,
//historyApiFallback,它主要的作用是解决SPA页面在路由(前端路由)跳转之后,进行页面刷新时,返回404的错误。
//可以配置from来匹配路径,决定要跳转到哪一个页面;
historyApiFallback: {
rewrites: [{ from: /^.*$/, to: 'index.html' }],
},
},
node: {
global: true,
},
module: {
//通过配置多个对象加载不同文件
// 模块规则(配置 loader、解析器等选项)
rules: [
{
//test属性使用正则表达式定义加载文件类型
test: /\.(svg)$/,
//use属性 在定义转化的时候应该使用哪个loader来进行转化;如果有多个,会逆向转化。
// use数组里从右向左运行
// 先用 css-loader 让webpack能够识别 css 文件的内容并打包
// 再用 style-loader 将样式, 把css插入到dom中
/万物皆模块, 引到入口, 才会被webpack打包, css打包进js中, 然后被嵌入在style标签插入dom上
use: ['svg-sprite-loader'],
//exclude和include更多的像是在test匹配的基础上做详细的配置,或者说成是test的子集。
//include 指定要包含的文件
include: [require.resolve('antd-mobile').replace(/warn\.js$/, '')],
},
{
test: /\.jsx?$/,
include: [
path.resolve(__dirname, 'index.web.js'),
path.resolve(__dirname, 'envs'),
path.resolve(__dirname, 'src'),
path.resolve(__dirname, './themes'),
path.resolve(__dirname, 'node_modules/react-native-screens'),
path.resolve(__dirname, 'node_modules/react-native-reanimated'),
path.resolve(__dirname, 'node_modules/@react-navigation'),
path.resolve(__dirname, 'node_modules/react-navigation'),
path.resolve(__dirname, 'node_modules/react-native-screens'),
path.resolve(__dirname, 'node_modules/react-native-gesture-handler'),
path.resolve(__dirname, 'node_modules/react-native-tab-view'),
path.resolve(__dirname, 'node_modules/react-native-storage'),
path.resolve(__dirname, 'node_modules/react-native-safe-area-view'),
path.resolve(__dirname, 'node_modules/react-navigation-deprecated-tab-navigator'),
path.resolve(__dirname, 'node_modules/react-navigation-tabs'),
path.resolve(__dirname, 'node_modules/react-navigation-drawer'),
path.resolve(__dirname, 'node_modules/react-navigation-stack'),
path.resolve(__dirname, 'node_modules/react-navigation-redux-helpers'),
],
loader: 'happypack/loader?id=babel',
},
{
test: /\.tsx?$/,
include: [
path.resolve(__dirname, 'index.web.js'),
path.resolve(__dirname, 'envs'),
path.resolve(__dirname, 'src'),
path.resolve(__dirname, './themes'),
path.resolve(__dirname, 'node_modules/react-native-screens'),
path.resolve(__dirname, 'node_modules/react-native-reanimated'),
path.resolve(__dirname, 'node_modules/@react-navigation'),
path.resolve(__dirname, 'node_modules/react-navigation'),
path.resolve(__dirname, 'node_modules/react-native-screens'),
path.resolve(__dirname, 'node_modules/react-native-gesture-handler'),
path.resolve(__dirname, 'node_modules/react-native-tab-view'),
path.resolve(__dirname, 'node_modules/react-native-storage'),
path.resolve(__dirname, 'node_modules/react-native-safe-area-view'),
path.resolve(__dirname, 'node_modules/react-navigation-deprecated-tab-navigator'),
path.resolve(__dirname, 'node_modules/react-navigation-tabs'),
path.resolve(__dirname, 'node_modules/react-navigation-drawer'),
path.resolve(__dirname, 'node_modules/react-navigation-stack'),
path.resolve(__dirname, 'node_modules/react-navigation-redux-helpers'),
],
use: [
'happypack/loader?id=babel',
{
loader: 'ts-loader',
options: {
transpileOnly: true,
allowTsInNodeModules: true,
},
},
],
},
//图片处理
{
test: /\.(gif|jpe?g|png)$/,
// 匹配文件, 尝试转base64字符串打包到js中
loader: 'url-loader',
options: {
// 配置limit, 超过512k, 不转
limit: 512,
// 配置输出的文件名,[hash:16]: hash值取16位,[ext]: 使用之前的文件扩展名
name: 'assets/files/[name].[hash:16].[ext]',
},
},
],
},
plugins: [
//HappyPack是一个通过多线程来提升webpack打包速度的工具。
//需要为每个loader配置一个id,否则 HappyPack 无法知道rules与plugins如何一一对应。
new HappyPack({
id: 'babel',
loaders: babelLoader,
//指示对应 loader 编译源文件时同时使用的进程数,默认是 3
threads: 4,
}),
// DefinePlugin 最为常用的用途就是用来处理我们开发环境和生产环境的不同。
//比如一些 debug 的功能在生产环境中需要关闭、开发环境中和生产环境中 api 地址的不同。
new webpack.DefinePlugin({
//通过配置了DefinePlugin,那么这里面的标识就相当于全局变量,你的业务代码可以直接使用配置的标识。
__DEV__: false,
}),
//在使用时将不再需要import和require进行引入,直接使用即可。
new webpack.ProvidePlugin({
ENV: `envs/${argv.mode || 'development'}`,
}),
//忽略第三方包指定目录,让这些指定目录不要被打包进去
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
//自定义打包的html模版,和输出文件名字
new HtmlWebpackPlugin({
filename: 'index.hbs',
template: './src/views/index.ejs',
}),
/*它的作用是将 CSS 代码从 JavaScript 中分离出来,生成单独的 CSS 文件。
使用 mini-css-extract-plugin 可以避免将 CSS 代码打包到 JavaScript 文件中,减少 JavaScript 的体积,同时也可以使得 CSS 文件可以被浏览器缓存,提高页面加载速度。*/
new MiniCssExtractPlugin({
//filename 是生成的 CSS 文件名,[name] 会被替换为入口文件名,[contenthash] 会根据文件内容生成//一个 hash 值,用于版本管理。
filename: `assets/styles/[name]${IS_PROD ? '[contenthash]' : ''}.css`,
allChunks: true,
}),
],
//公共部分提取: optimization
//多入口文件的项目,难免在不同的入口存在相同的部分(使用了相同组建、公共样式等),将多个css chunk合并成一个css文件
optimization: {
//根据这个来拆分chunk
splitChunks: {
// 要提取的chunk最少被引用1次
minChunks: 1,
// 分割chunk的组
//根据cacheGroups的配置进来分包并包的
cacheGroups: {
// node_modules文件会被打包到vendors组的chunk中。--> vendors~xxx.js
// 满足上面的公共规则,如:至少被引用一次
vendors: {
/*
async:当设置chunks的值为async时,只有在异步加载模块的时候,才会进行分包处理该模块
initial:同步加载模块的时候,也会进行分包处理。
all:同步异步都会进行分包处理
*/
chunks: 'initial'
//提取出来的文件名
name: 'vendor',
test: 'vendor',
// 如果发现页面中未引用公共文件,加上enforce: true
enforce: true,
},
},
},
},
};
//判断不同模式添加不同插件。
if (argv.mode === 'production') {
// // gizp 压缩
/*gzip 可以压缩所有的文件,但是我们不需要对所有文件进行压缩,一般情况对我们写的代码(css,js,html)之类的文件有很好的压缩效果,图片之类文件不会被 gzip 压缩太多,因为它们已经内置了一些压缩,再去压缩可能会让生成的文件体积更大一些。*/
webpackConfig.plugins.push(
new CompressionPlugin({
test: /\.js|\.css/,
algorithm: 'gzip',
threshold: 10240, // 10K以内不进行压缩
minRatio: 0.8,
})
);
}
if (argv.mode === 'development') {
//devtool的值有20+种。它有固定的模式。
//通过关键词的组合,就可以生成用于各种场景的Source Map
//指定devtool时,要与mode配合使用。
//Source Map,有了它浏览器就可以从转换后的代码直接定位到转换前的代码,精准定位错误代码。
//选择一种 source map 格式来增强调试过程。不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。
//所以我们要根据开发环境,或者生产环境的不同需求配置不同的格式选项
//在webpack中,可以通过devtool选项来配置Source Map。
// map文件生成
webpackConfig.devtool = 'source-map';
// eslint-disable-next-line function-paren-newline
webpackConfig.plugins.push(
new HtmlWebpackPlugin({
filename: 'index.html',
template: './src/views/index.ejs',
})
);
}
return webpackConfig;
};
/*
webpack-cli 命令的选项比较多,详细可以通过 webpack-cli 的文档进行查阅,这里总结我们日常用的最多的几个选项(options):
–config:指定一个 Webpack 配置文件的路径;
–mode:指定打包环境的mode,取值为development和production,分别对应着开发环境和生产环境;
–json:输mode出 Webpack 打包的结果,可以使用webpack --json > stats.json方式将打包结果输出到指定的文件;
–progress:显示 Webpack 打包进度;
–watch, -w:watch 模式打包,监控文件变化之后重新开始打包;
–color, --colors/–no-color, --no-colors:控制台输出的内容是否开启颜色;
–hot:开启 Hot Module Replacement模式,后面会详细介绍;
–profile:会详细的输出每个环节的用时(时间),方便排查打包速度瓶颈。
$ webpack -p//压缩混淆脚本,这个非常非常重要!
$ webpack -d//生成map映射文件,告知哪些模块被最终打包到哪里了其中的
如果出于某些原因,需要根据特定情况使用不同的配置文件,则可以通过在命令行中使用 --config 标志修改
"scripts": {
"build": "webpack --config prod.config.js"
}
*/
vendorchunk是由webpack打包出来的文件,chunk是指webpack在进行模块的依赖分析的时候,代码分割出来的代码块。module是开发中的单个模块。