重学webpack系列(十) -- webpack与框架的结合实践
前面几章重学webpack系列(零) -- 全局概况我们依次讲解了webpack
的核心机制
、运行原理
、高级特性
、性能优化
,但是种种来说还是需要去学习一下比较权威的webpack
配置,那么这一章我们就来探索一下在Vue2.x
和React
里面的webpack
配置到底是怎么样配的吧,这里讨论的只是官方帮我们配好的一些必须的配置,与我们的实际业务相比还是有一点差距的,Vue3.x
已经拥抱了Vite
,这个暂时不是我们讨论的范围,如果你对Vite
感兴趣,不妨看看这个 >>> 前端构建工具vite进阶系列
webpack与Vue2.x的结合实践
- 初始化一个
vue-cli
// 全局安装vueCli
npm i @vue-cli -g
// 使用webpack
vue init webpack myProject
// vue create myProject
// Y
? Project name xx
? Project description A Vue.js project
? Author 一溪之石 <xxx.github.com>
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Set up unit tests Yes
? Pick a test runner jest
? Setup e2e tests with Nightwatch? Yes
? Should we run `npm install` for you after the project has been created? (recommended) npm
vue-cli · Generated "myProject".
# Installing project dependencies ...
# ========================
...
// 进入项目目录,启动项目
cd myProject
npm run dev // "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
所以vue2
里面帮我们用webpack-dev-server
启动了一个本地服务器,来访问页面。
- 页面效果
基本配置
在build
目录下我们可以看到三个webpack.base.conf.js
,webpack.dev.conf.js
,webpack.prod.conf.js
文件,下面我们来依次解读它们。
webpack.base.conf.js:
公共配置文件webpack.dev.conf.js:dev
环境下的配置文件webpack.prod.conf.js:prod
环境下的配置文件
webpack.base.conf.js
'use strict'
// 引入依赖,工具方法,config配置,vueLoaderConfig
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
// 定义resolve方法
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
// 处理js/vue文件
const createLintingRule = () => ({
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [resolve('src'), resolve('test')],
options: {
formatter: require('eslint-friendly-formatter'),
emitWarning: !config.dev.showEslintErrorsInOverlay
}
})
module.exports = {
context: path.resolve(__dirname, '../'), //
// 入口
entry: {
app: './src/main.js'
},
// 输出
output: {
path: config.build.assetsRoot, // assetsRoot: path.resolve(__dirname, '../dist'),
filename: '[name].js',
// 如果是production publicPath:'/',如果是dev dev publicPath:'/'
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
// resolve属性,如何解析模块,详见https://webpack.js.org/configuration/resolve/
resolve: {
extensions: ['.js', '.vue', '.json'],
// 别名
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
},
module: {
rules: [
// useEslint默认为true,
...(config.dev.useEslint ? [createLintingRule()] : []),
// 处理vue文件
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
// 处理js文件
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
// 处理图片
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
// 当小于10000kb的时候,其他的要转为buffer
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
// 音视频处理
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
// 字体处理
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
},
node: {
// 处理以前webpack与vue的不兼容吧???webpack3.0已经撤销了此选项,
// https://webpack.js.org/configuration/node/#node
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}
webpack.dec.conf.js
'use strict'
// 引入模块,配置,插件,工具方法
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')
// 定义主机域名
const HOST = process.env.HOST
// 定义端口
const PORT = process.env.PORT && Number(process.env.PORT)
// 合并公共配置与dev配置
const devWebpackConfig = merge(baseWebpackConfig, {
// loader模块 cssSourceMap: true,usePostCSS:true
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
// dev环境下推荐使用devtool: 'cheap-module-eval-source-map'
// 详见[重学webpack系列(六) -- webpack的sourceMap实践与原理](https://juejin.cn/post/7147958108403269645/ "https://juejin.cn/post/7147958108403269645/")
devtool: config.dev.devtool,
// 配置devServer
devServer: {
clientLogLevel: 'warning',
historyApiFallback: {
rewrites: [
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
],
},
// 开启热更新
hot: true,
// prod推荐使用CopyWebpackPlugin,dev下不处理也行
contentBase: false,
// 开启gzip压缩
compress: true,
// 主机名,端口
host: HOST || config.dev.host,
port: PORT || config.dev.port,
// 是否自动打开浏览器窗口, autoOpenBrowser: false,
open: config.dev.autoOpenBrowser,
// 当出现错误的时候,在浏览器全屏覆盖,黑窗口,errorOverlay: true,
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
// 公共路径
publicPath: config.dev.assetsPublicPath,
// 代理配置,提供给用户的配置入口,proxyTable:{}
proxy: config.dev.proxyTable,
quiet: true, // necessary for FriendlyErrorsPlugin
// 监听选项,pool:false
watchOptions: {
poll: config.dev.poll,
}
},
// 插件
plugins: [
// 定义全局变量插件,module.exports = merge(prodEnv, {NODE_ENV: '"development"'})
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
// 热更新插件,webpack5中已经不需要此配置了
new webpack.HotModuleReplacementPlugin(),
// 模块命名插件,已经被废弃掉了
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
// 报错插件。。。
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
// 注入在哪里,比如注入head,body等
inject: true
}),
// copy custom static assets
// 不参与构建的静态资源,直接输出到目录
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
//to为输入到哪里,assetsSubDirectory: 'static',
to: config.dev.assetsSubDirectory,
// 忽视
ignore: ['.*']
}
])
]
})
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// publish the new Port, necessary for e2e tests
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port
// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})
webpack.prod.js
'use strict'
// 引入模块,配置,方法,插件
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
// 区分环境,使用不同的环境变量
const env = process.env.NODE_ENV === 'testing'
? require('../config/test.env')
: require('../config/prod.env')
// 合并prod与公共配置
const webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true,
usePostCSS: true
})
},
// sourceMap,生产环境推荐使用devtool: '#source-map',??没搞明白他这里为什么在生产环境使用这个
// productionSourceMap: true,
devtool: config.build.productionSourceMap ? config.build.devtool : false,
// 出口
output: {
path: config.build.assetsRoot, // assetsRoot: path.resolve(__dirname, '../dist'),
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
// 全局变量插件
new webpack.DefinePlugin({
'process.env': env
}),
// 压缩js代码
new UglifyJsPlugin({
uglifyOptions: {
// gzip压缩
compress: {
warnings: false
}
},
sourceMap: config.build.productionSourceMap, // true
parallel: true
}),
// extract css into its own file
// 提取css到单独文件,注意这里使用的是contenthash
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
// Setting the following option to `false` will not extract CSS from codesplit chunks.
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
allChunks: true, // chunks:"all"
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
// 压缩css插件,已经被废弃掉了,使用optimizeCssAssetsWebpackPlugin代替了
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: process.env.NODE_ENV === 'testing'
? 'index.html'
: config.build.index,
template: 'index.html',
inject: true,
// 压缩效果
minify: {
// 去除注释
removeComments: true,
// 去除空格
collapseWhitespace: true,
// 去除属性之间的空格
removeAttributeQuotes: true
// 更多配置查看
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// 保证文件的hansh稳定插件
new webpack.HashedModuleIdsPlugin(),
// 作用域提升,比如webpack5中的optimization:{concatenateModules: true,}
new webpack.optimize.ModuleConcatenationPlugin(),
// 提取bundle.js里面的css文件,单独出去,现在使用mini-css-extract-plugin
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks (module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3
}),
// ...
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {//config.build.productionGzip : false
// 如果没有开启gzip,使用这个插件
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
// bundleAnalyzerReport: process.env.npm_config_report
// 打包结果分析
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
到这里vue2.x
就把我们项目中该用到的webpack
配置全部配置到了,其实并不难,难的是得花费时间去查看项目中需要什么样的配置来处理。
webpack与React18.x的结合实践
- 初始化一个
create-react-app
项目
// 安装create-react-app
sudo npm i create-react-app -g
// 创建项目
create-react-app myProject
// 进入目录,启动项目
cd myProject
npm start
Compiled successfully!
You can now view mykk in the browser.
Local: http://localhost:3000
On Your Network: http://192.168.xx.xx:3000
Note that the development build is not optimized.
To create a production build, use npm run build.
webpack compiled successfully
...
react
里面通过react-scripts start
帮我们启动一个服务来访问项目。
- 页面效果
基本配置
在react
中,webpack
配置是隐藏的,我们需要运行npm run eject
来显示webpack
等配置。
// 执行eject
npm run eject
// 生成配置
Are you sure you want to eject? This action is permanent. … yes
Ejecting...
Copying files into myProject
Adding /config/env.js to the project
Adding /config/getHttpsConfig.js to the project
Adding /config/modules.js to the project
Adding /config/paths.js to the project
Adding /config/webpack.config.js to the project
Adding /config/webpackDevServer.config.js to the project
Adding /config/jest/babelTransform.js to the project
Adding /config/jest/cssTransform.js to the project
Adding /config/jest/fileTransform.js to the project
Adding /scripts/build.js to the project
Adding /scripts/start.js to the project
Adding /scripts/test.js to the project
Adding /config/webpack/persistentCache/createEnvironmentHash.js to the project
Updating the dependencies
Removing react-scripts from dependencies
Adding @babel/core to dependencies
Adding @pmmmwh/react-refresh-webpack-plugin to dependencies
Adding @svgr/webpack to dependencies
Adding babel-jest to dependencies
Adding babel-loader to dependencies
Adding babel-plugin-named-asset-import to dependencies
Adding babel-preset-react-app to dependencies
Adding bfj to dependencies
Adding browserslist to dependencies
Adding camelcase to dependencies
Adding case-sensitive-paths-webpack-plugin to dependencies
Adding css-loader to dependencies
Adding css-minimizer-webpack-plugin to dependencies
Adding dotenv to dependencies
Adding dotenv-expand to dependencies
Adding eslint to dependencies
Adding eslint-config-react-app to dependencies
Adding eslint-webpack-plugin to dependencies
Adding file-loader to dependencies
Adding fs-extra to dependencies
Adding html-webpack-plugin to dependencies
Adding identity-obj-proxy to dependencies
Adding jest to dependencies
Adding jest-resolve to dependencies
Adding jest-watch-typeahead to dependencies
Adding mini-css-extract-plugin to dependencies
Adding postcss to dependencies
Adding postcss-flexbugs-fixes to dependencies
Adding postcss-loader to dependencies
Adding postcss-normalize to dependencies
Adding postcss-preset-env to dependencies
Adding prompts to dependencies
Adding react-app-polyfill to dependencies
Adding react-dev-utils to dependencies
Adding react-refresh to dependencies
Adding resolve to dependencies
Adding resolve-url-loader to dependencies
Adding sass-loader to dependencies
Adding semver to dependencies
Adding source-map-loader to dependencies
Adding style-loader to dependencies
Adding tailwindcss to dependencies
Adding terser-webpack-plugin to dependencies
Adding webpack to dependencies
Adding webpack-dev-server to dependencies
Adding webpack-manifest-plugin to dependencies
Adding workbox-webpack-plugin to dependencies
Updating the scripts
Replacing "react-scripts start" with "node scripts/start.js"
Replacing "react-scripts build" with "node scripts/build.js"
Replacing "react-scripts test" with "node scripts/test.js"
Configuring package.json
Adding Jest configuration
Adding Babel preset
Running npm install...
...
Ejected successfully!
Staged ejected files for commit.
解包之后我们在项目目录中可以看到config
和script
文件夹,那么我们依次来看看吧。
start.js:
npm run start执行文件。build.js:
npm run build执行文件。env.js:
环境区分文件。getHttpsConfig.js:
协议处理文件。
webpack.config.js
'use strict';
// 引入模块,插件,方法
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const resolve = require('resolve');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const ESLintPlugin = require('eslint-webpack-plugin');
const paths = require('./paths');
const modules = require('./modules');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin =
process.env.TSC_COMPILE_ON_ERROR === 'true'
? require('react-dev-utils/ForkTsCheckerWarningWebpackPlugin')
: require('react-dev-utils/ForkTsCheckerWebpackPlugin');
// react的hrm处理插件
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
// 创建环境hash
const createEnvironmentHash = require('./webpack/persistentCache/createEnvironmentHash');
// 是否生成sourceMap
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
...
// 处理文件loader正则
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
// 判断jsx运行环境,createElemet || jsxRuntime
const hasJsxRuntime = (() => {
if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') {
return false;
}
try {
require.resolve('react/jsx-runtime');
return true;
} catch (e) {
return false;
}
})();
// 具体webpack配置
module.exports = function (webpackEnv) {
// 定义环境标识
const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production';
const isEnvProductionProfile =
isEnvProduction && process.argv.includes('--profile');
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
const shouldUseReactRefresh = env.raw.FAST_REFRESH;
//
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
// 使用MiniCssExtractPlugin插件,提供单独打包css,通过link引入到页面中
loader: MiniCssExtractPlugin.loader,
// 指定路径
options: paths.publicUrlOrPath.startsWith('.')
? { publicPath: '../../' }
: {},
},
// 追加css-loader
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
// 使用postcss-loader处理页面适配
{
loader: require.resolve('postcss-loader'),
options: {
postcssOptions: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
config: false,
plugins: !useTailwind
? [
'postcss-flexbugs-fixes',
[
'postcss-preset-env',
{
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
},
],
'postcss-normalize',
]
: [
'tailwindcss',
'postcss-flexbugs-fixes',
[
'postcss-preset-env',
{
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
},
],
],
},
// 是否启用sourceMap
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
},
},
].filter(Boolean);
if (preProcessor) {
// 添加url-loader
loaders.push(
{
loader: require.resolve('resolve-url-loader'),
options: {
// 是否启用sourceMap
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
root: paths.appSrc,
},
},
{
loader: require.resolve(preProcessor),
options: {
sourceMap: true,
},
}
);
}
return loaders;
};
return {
target: ['browserslist'],
stats: 'errors-warnings',
// mode
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
// Stop compilation early in production
bail: isEnvProduction,
// sourceMap
devtool: isEnvProduction
? shouldUseSourceMap
? 'source-map'
: false
: isEnvDevelopment && 'cheap-module-source-map',
// 入口
entry: paths.appIndexJs,
// 出口
output: {
path: paths.appBuild,
pathinfo: isEnvDevelopment,
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/bundle.js',
// chunk模块名称
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
// 与filename相同,用于assetModule
assetModuleFilename: 'static/media/[name].[hash][ext]',
// 指定服务器访问路径,
publicPath: paths.publicUrlOrPath,
// 使用模板
devtoolModuleFilenameTemplate: isEnvProduction
? info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
},
// 缓存
cache: {
// 存储方式
type: 'filesystem',
// 缓存数据版本
version: createEnvironmentHash(env.raw),
// 缓存的目录,默认为 `node_modules/.cache/webpack`。
cacheDirectory: paths.appWebpackCache,
// 告诉 webpack 什么时候将数据存放在文件系统中。
store: 'pack',
// webpack 将使用这些项和所有依赖项的哈希值来使文件系统缓存失效。
buildDependencies: {
defaultWebpack: ['webpack/lib/'],
config: [__filename],
tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f =>
fs.existsSync(f)
),
},
},
// 其他配置
infrastructureLogging: {
level: 'none',
},
// 优化配置
optimization: {
// 是否开启压缩,生产环境下开启
minimize: isEnvProduction,
// 压缩配置
minimizer: [
// js压缩,TerserPlugin配置查看https://webpack.docschina.org/plugins/
new TerserPlugin({
terserOptions: {
parse: {
ecma: 8,
},
// gzip压缩开启
compress: {
ecma: 5,
warnings: false,
comparisons: false,
inline: 2,
},
mangle: {
safari10: true,
},
keep_classnames: isEnvProductionProfile,
keep_fnames: isEnvProductionProfile,
output: {
ecma: 5,
comments: false,
ascii_only: true,
},
},
}),
// 压缩css
new CssMinimizerPlugin(),
],
},
...
module: {
strictExportPresence: true,
rules: [
// Handle node_modules packages that contain sourcemaps
shouldUseSourceMap && {
enforce: 'pre',
exclude: /@babel(?:\/|\\{1,2})runtime/,
test: /\.(js|mjs|jsx|ts|tsx|css)$/,
loader: require.resolve('source-map-loader'),
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
//数组,当规则匹配时,只使用第一个匹配规则。
{
test: [/\.avif$/],
type: 'asset',
mimetype: 'image/avif',
parser: {
dataUrlCondition: {
maxSize: imageInlineSizeLimit,
},
},
},
// 处理文件,图片
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: imageInlineSizeLimit,
},
},
},
{
test: /\.svg$/,
use: [
{
loader: require.resolve('@svgr/webpack'),
options: {
prettier: false,
svgo: false,
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
titleProp: true,
ref: true,
},
},
{
loader: require.resolve('file-loader'),
options: {
name: 'static/media/[name].[hash].[ext]',
},
},
],
issuer: {
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
},
},
// 解析js等文件
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
presets: [
[
require.resolve('babel-preset-react-app'),
{
runtime: hasJsxRuntime ? 'automatic' : 'classic',
},
],
],
plugins: [
isEnvDevelopment &&
shouldUseReactRefresh &&
require.resolve('react-refresh/babel'),
].filter(Boolean),
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
compact: isEnvProduction,
},
},
// Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features.
{
test: /\.(js|mjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve('babel-loader'),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
require.resolve('babel-preset-react-app/dependencies'),
{ helpers: true },
],
],
cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
// Babel sourcemaps are needed for debugging into node_modules
// code. Without the options below, debuggers like VSCode
// show incorrect code and set breakpoints on the wrong lines.
sourceMaps: shouldUseSourceMap,
inputSourceMap: shouldUseSourceMap,
},
},
// 处理一般的loader
{
test: cssRegex,
exclude: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
}),
// 副作用
sideEffects: true,
},
// 处理css-module
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
}),
},
// 处理sass
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
},
'sass-loader'
),
sideEffects: true,
},
// sass-module
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
},
'sass-loader'
),
},
{
exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
type: 'asset/resource',
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
].filter(Boolean),
},
plugins: [
// 插件处理
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: paths.appHtml,
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
// InlineChunkHtmlPlugin
// InterpolateHtmlPlugin
isEnvProduction &&
shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
// 模块notFound
new ModuleNotFoundPlugin(paths.appPath),
// 全局变量插件
new webpack.DefinePlugin(env.stringified),
// hmr刷新插件
isEnvDevelopment &&
shouldUseReactRefresh &&
new ReactRefreshWebpackPlugin({
overlay: false,
}),
...
isEnvDevelopment && new CaseSensitivePathsPlugin(),
isEnvProduction &&
// 提取css插件
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
// 通过 [`WebpackManifestPlugin`](https://github.com/shellscape/webpack-manifest-plugin) 插件,可以将 manifest 数据提取为一个 json 文件以供使用。
new WebpackManifestPlugin({
fileName: 'asset-manifest.json',
publicPath: paths.publicUrlOrPath,
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path;
return manifest;
}, seed);
const entrypointFiles = entrypoints.main.filter(
fileName => !fileName.endsWith('.map')
);
return {
files: manifestFiles,
entrypoints: entrypointFiles,
};
},
}),
// 插件太多了,不想看了。。。
new webpack.IgnorePlugin(....),
new WorkboxWebpackPlugin.InjectManifest(....),
new ForkTsCheckerWebpackPlugin(....),
new ESLintPlugin(....),
].filter(Boolean),
performance: false,
};
};
webpackDevServer.config.js
'use strict';
// 引入模块,配置,方法
const fs = require('fs');
const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
const ignoredFiles = require('react-dev-utils/ignoredFiles');
const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware');
const paths = require('./paths');
const getHttpsConfig = require('./getHttpsConfig');
// 处理主机域名,端口等
const host = process.env.HOST || '0.0.0.0';
const sockHost = process.env.WDS_SOCKET_HOST;
const sockPath = process.env.WDS_SOCKET_PATH; // default: '/ws'
const sockPort = process.env.WDS_SOCKET_PORT;
// 这里的proxy室用户传进来的,用户在开发的时候,只需要关注proxy
module.exports = function (proxy, allowedHost) {
const disableFirewall =
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true';
return {
// 允许的主机名
allowedHosts: disableFirewall ? 'all' : [allowedHost],
// 配置headers
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Headers': '*',
},
// gzip压缩
compress: true,
// 打包之后的目录文件
static: {
directory: paths.appPublic,
publicPath: [paths.publicUrlOrPath],
watch: {
ignored: ignoredFiles(paths.appSrc),
},
},
// 浏览器宿主
client: {
webSocketURL: {
hostname: sockHost,
pathname: sockPath,
port: sockPort,
},
// 报错覆盖全屏
overlay: {
errors: true,
warnings: false,
},
},
// 中间件
devMiddleware: {
publicPath: paths.publicUrlOrPath.slice(0, -1),
},
// 协议
https: getHttpsConfig(),
// 主机
host,
historyApiFallback: {
disableDotRule: true,
index: paths.publicUrlOrPath,
},
proxy,// proxy 还是用用户传进来的proxy
// 提供的啥方法,对文件做了什么什么处理....
onBeforeSetupMiddleware(devServer) {
devServer.app.use(evalSourceMapMiddleware(devServer));
if (fs.existsSync(paths.proxySetup)) {
require(paths.proxySetup)(devServer.app);
}
},
// 提供的啥方法,对文件做了什么什么处理....
onAfterSetupMiddleware(devServer) {
devServer.app.use(redirectServedPath(paths.publicUrlOrPath));
devServer.app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath));
},
};
};
react
的webpack
配置比vue
比起来,确实复杂了一点,在于他对模块的处理确实很细致,不管怎么样的配置都是能让我们去能够很好的开发项目。
附件
总结
通过两个框架的默认配置来对比webpack
,我们发现环境兼容
,代码压缩
,代码提取
,devServer
,optimization
都是他们两者共通
的,我们在学习配置的时候,也应该要明白,为什么要这样配置,下一章 >>> 重学webpack系列(十一) -- 重学webpack系列终结与展望
转载自:https://juejin.cn/post/7148756582061309959