likes
comments
collection
share

重学webpack系列(十) -- webpack与框架的结合实践

作者站长头像
站长
· 阅读数 19

前面几章重学webpack系列(零) -- 全局概况我们依次讲解了webpack核心机制运行原理高级特性性能优化,但是种种来说还是需要去学习一下比较权威的webpack配置,那么这一章我们就来探索一下在Vue2.xReact里面的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启动了一个本地服务器,来访问页面。

  • 页面效果

重学webpack系列(十) -- webpack与框架的结合实践

基本配置

build目录下我们可以看到三个webpack.base.conf.jswebpack.dev.conf.jswebpack.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帮我们启动一个服务来访问项目。

  • 页面效果

重学webpack系列(十) -- webpack与框架的结合实践

基本配置

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.

解包之后我们在项目目录中可以看到configscript文件夹,那么我们依次来看看吧。

  • 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));
    },
  };
};

reactwebpack配置比vue比起来,确实复杂了一点,在于他对模块的处理确实很细致,不管怎么样的配置都是能让我们去能够很好的开发项目。

附件

config源码地址

总结

通过两个框架的默认配置来对比webpack,我们发现环境兼容代码压缩代码提取devServeroptimization都是他们两者共通的,我们在学习配置的时候,也应该要明白,为什么要这样配置,下一章 >>> 重学webpack系列(十一) -- 重学webpack系列终结与展望

转载自:https://juejin.cn/post/7148756582061309959
评论
请登录