目录
  • 引言
  • 准备工作
  • 启动命令
    • start.js
    • build.js
  • 目录结构
    • 配置解析
      • weback.config.js
        • 1.entry
    • 结语

      引言

      create-react-app(以下简称cra)作为react官方提供的脚手架工具,是目前生成react项目一个非常常用和主流的工具。很多企业级的应用搭建也是基于这个脚手架工具上二次开发。最近这段正好最近学习了webpack打包配置工程化的一些内容,索性就以cra的配置为例,对这段时间的学习做一个总结。

      准备工作

      首先,我们要用cra创建一个项目。这个没啥好说,有手就行。

      create-react-app cra-config-project
      

      这样初始化后创建出来项目的配置信息是隐藏在node_modules中的react-scripts中的。为了更直观的看到配置信息和修改,使用
      eject命令将配置弹射出来。

      yarn eject
      

      完成后,我们项目配置的目录结构变成这样。

      create-react-app项目配置全解析

      启动命令

      打开package.json 文件,在scripts中看到以下三条命令

        "scripts": {
          "start": "node scripts/start.js",
          "build": "node scripts/build.js",
          "test": "node scripts/test.js"
        },
      

      很明显,这分别是项目的启动开发环境,构建,测试的命令。我们重点看一下scripts中开发和构建的脚本。

      start.js

      在大概115行的位置,我们看到这样一段代码

          const devServer = new WebpackDevServer(serverConfig, compiler);
          // Launch WebpackDevServer.
          devServer.startCallback(() => {
          ...
      

      很明显,这就是启动开发服务器的关键代码。在开发环境的时候,我们通过webpack-dev-server来启动一个本地的服务器,然后把随时构建出来的项目放在这个服务器下面运行。实例化这个devServer对象时候传的第一个参数是服务器的配置项,包括端口号,代理,静态资源目录等,具体见https://webpack.docschina.org/configuration/dev-server/;第二个参数是webpack的相关配置。如下所示:

        compiler = webpack(config);
      

      build.js

      构建脚本直接输出打包结果,自然不再需要启动本地服务。因此在获取了编译结果后,直接运行即可。因此在140行中

       compiler.run((err, stats) => {
         //...
       }
      

      在代码中我们可以看到构建时,编译过程通过promise封装,对各种错误情况进行了处理。

      目录结构

      在看具体的配置之前,让我们回到这张图,看一下eject命令都弹射出了哪些配置放到了项目目录中来。

      create-react-app项目配置全解析

      比起初使状态,现在的项目目录中除了装有启动脚本文件的目录scripts外,另外增加的就是config目录。打开config目录,webpack.config.js和webpackDevServer.config.js赫然在目,根据这个文件名我们可以很明显得知,这两个文件一个是webpack的配置,一个是开发服务器devServer 的配置。接下来,我们就可以从这两个文件按图索骥,学习cra的基本配置了。

      配置解析

      weback.config.js

      在webpack.config.js中,默认导出了一个接受一个环境变量作为返回一个配置对象的方法。那传这个环境变量的目的不言而喻,一定有很多配置开发和生产环境是不同的。接下来重头戏来了,让我们来一条一条地学习下react官方对react开发环境是怎么配置的吧。

      1.entry

       entry: paths.appIndexJs,
      

      也就是 src目录下的index.js,因为cra构建的是单页应用,只有一个入口文件

      2.output

       output: {
            path: paths.appBuild, // 打包后文件目录 在config目录中path.js中配置
            pathinfo: isEnvDevelopment, // webpack 在 bundle 中是否引入「所包含模块信息」的相关注释 开发环境打开 生产环境关闭
            filename: isEnvProduction
              ? 'static/js/[name].[contenthash:8].js'
              : isEnvDevelopment && 'static/js/bundle.js',//打包后文件名,生产环境根据name放在不同文件,开发环境放在一个bundle.js文件中
           chunkFilename: isEnvProduction
              ? 'static/js/[name].[contenthash:8].chunk.js'
              : isEnvDevelopment && 'static/js/[name].chunk.js',//chunk文件名称,生产环境和开发环境的区别是文件名中加上了hash
            assetModuleFilename: 'static/media/[name].[hash][ext]',//打包后的静态资源目录和文件名规则,如不指定直接放在打包后的根目录中
             publicPath: paths.publicUrlOrPath,//打包后的文件部署的url地址
             devtoolModuleFilenameTemplate: isEnvProduction
              ? info =>
                  path
                    .relative(paths.appSrc, info.absoluteResourcePath)
                    .replace(/\\/g, '/')
              : isEnvDevelopment &&
                (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),//自定义source-map文件数组使用名称
      }
      

      3.target

          target: ['browserslist'],
      

      构建目标,从 browserslist-config 中推断出平台和 ES 特性 默认是browserslist 如果browserslist不存在,为web(cra项目中browserslist在package.json中)

      4.bail

          bail: isEnvProduction,
      

      错误出现时是否立即退出,生产环境下打开

      5.devtool

      devtool: isEnvProduction
            ? shouldUseSourceMap
              ? 'source-map'
              : false
            : isEnvDevelopment && 'cheap-module-source-map',
      

      生成sourceMap方式,cra配置为生产环境source-map,开发环境为cheap-module-source-map。这两者的区别source-map调试时会显示列信息。devtool的配置有很多种,具体见https://webpack.docschina.org/configuration/devtool/#root

      6.cache

          cache: {
            type: 'filesystem',//缓存生成的 webpack 模块和 chunk,来改善构建速度 开发环境下默认为type:'memory' 生产环境下关闭
            version: createEnvironmentHash(env.raw),
            cacheDirectory: paths.appWebpackCache,//缓存目录
            store: 'pack',
            buildDependencies: {
              defaultWebpack: ['webpack/lib/'],
              config: [__filename],
              tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f =>
                fs.existsSync(f)
              ),
            },
          },
      

      7.optimization 优化项

       optimization: {
            minimize: isEnvProduction, //只在生产环境下开启
            minimizer: [
              //js TerserPlugin开启代码压缩
              new TerserPlugin({
                terserOptions: {
                  parse: {
                    ecma: 8,
                  },
                  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(),
            ],
          },
      

      8.resolve 解析

      resolve: {
           modules: ['node_modules', paths.appNodeModules].concat(
             modules.additionalModulePaths || []
           ),//解析模块时应该搜索的目录
           extensions: paths.moduleFileExtensions
             .map(ext => `.${ext}`)
             .filter(ext => useTypeScript || !ext.includes('ts')),//如果有多个文件有相同的名字,但后缀名不同时webpack按顺序解析这些后缀名,使用户在引入模块时不带扩展名
           alias: {      
             'react-native': 'react-native-web',
             ...(isEnvProductionProfile && {
               'react-dom$': 'react-dom/profiling',
               'scheduler/tracing': 'scheduler/tracing-profiling',
             }),
             ...(modules.webpackAliases || {}),
           },//创建 import 或 require 的别名,来确保模块引入变得更简单。可以给utils之类的文件色之后
           plugins: [
             new ModuleScopePlugin(paths.appSrc, [
               paths.appPackageJson,
               reactRefreshRuntimeEntry,
               reactRefreshWebpackPluginRuntimeEntry,
               babelRuntimeEntry,
               babelRuntimeEntryHelpers,
               babelRuntimeRegenerator,
             ]),
           ],
         },//应该使用的额外的解析插件列表
      

      9.performance

      performance: false,
      

      关闭了webpack本身的性能提示,cra本身提供了FileSizeReporter来计算和报告文件大小

      10. module

      终于进入到我们这个比较重要的module配置项,module配置决定了webpack如何解析非js的模块,项目中的各种静态资源,样式文件,乃至于ts tsx jsx等loader配置都是在这个模块中配置。

      • source-map loader
              shouldUseSourceMap && {
                enforce: 'pre',
                exclude: /@babel(?:\/|\\{1,2})runtime/,
                test: /\.(js|mjs|jsx|ts|tsx|css)$/,
                loader: require.resolve('source-map-loader'),
              },
      
      • 静态资源loader
                  {
                    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)$/],
                    },
                  },
      

      对各种格式的图片,svg文件的处理

      • 样式文件loader
                  {
                    test: cssRegex,
                    exclude: cssModuleRegex,
                    use: getStyleLoaders({
                      importLoaders: 1,
                      sourceMap: isEnvProduction
                        ? shouldUseSourceMap
                        : isEnvDevelopment,
                      modules: {
                        mode: 'icss',
                      },
                    }),
                    sideEffects: true,
                  },
                  {
                    test: cssModuleRegex,
                    use: getStyleLoaders({
                      importLoaders: 1,
                      sourceMap: isEnvProduction
                        ? shouldUseSourceMap
                        : isEnvDevelopment,
                      modules: {
                        mode: 'local',
                        getLocalIdent: getCSSModuleLocalIdent,
                      },
                    }),
                  },
                  {
                    test: sassRegex,
                    exclude: sassModuleRegex,
                    use: getStyleLoaders(
                      {
                        importLoaders: 3,
                        sourceMap: isEnvProduction
                          ? shouldUseSourceMap
                          : isEnvDevelopment,
                        modules: {
                          mode: 'icss',
                        },
                      },
                      'sass-loader'
                    ),
                    sideEffects: true,
                  },
                  {
                    test: sassModuleRegex,
                    use: getStyleLoaders(
                      {
                        importLoaders: 3,
                        sourceMap: isEnvProduction
                          ? shouldUseSourceMap
                          : isEnvDevelopment,
                        modules: {
                          mode: 'local',
                          getLocalIdent: getCSSModuleLocalIdent,
                        },
                      },
                      'sass-loader'
                    ),
                  },
      

      对样式文件的处理, 主要是scss和css,cra 为什么没有配置less文件loader呢?开发环境下直接将所有样式注入head中style中,生产环境下结合下面要介绍的miniCssExtractPlugin插件抽出后放入不同css文件。另外,这里cra还对以.module.css 和 .module.sass后缀结尾的文件进行了css module处理,如果开发者需要对样式文件要用modules规则,可以将文件的后缀写成这两种。
       

      11. 插件

      • htmlWebpackPlugin
            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
              )
            ),
      

      没啥好说的,地球人都知道的一个插件,把打包好的js文件注入到html中去,要注意的是在生产环境了开启了移除注释,合并空格一系列优化配置

      • InlineChunkHtmlPlugin
       isEnvProduction &&
              shouldInlineRuntimeChunk &&
              new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
      

      这个插件辅助将一些chunk出来的模块内联到html中,比如runtime的代码,代码量不大。生产环境下开启

      • InterpolateHtmlPlugin
            new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
      

      HtmlWebpackPlugin的辅助插件,可以在html文件中加入变量

      • ModuleNotFoundPlugin
      • ReactRefreshWebpackPlugin
          isEnvDevelopment &&
              shouldUseReactRefresh &&
              new ReactRefreshWebpackPlugin({
                overlay: false,
              }),
      

      热更新 react 组件,开发环境下开启

      • MiniCssExtractPlugin
       isEnvProduction &&
              new MiniCssExtractPlugin({
                filename: 'static/css/[name].[contenthash:8].css',
                chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
              }),
      

      抽离css文件插件,生产环境下开启

      • WebpackManifestPlugin
      • ForkTsCheckerWebpackPlugin
            useTypeScript &&
              new ForkTsCheckerWebpackPlugin({
                async: isEnvDevelopment,
                typescript: {
                  typescriptPath: resolve.sync('typescript', {
                    basedir: paths.appNodeModules,
                  }),
                  configOverwrite: {
                    compilerOptions: {
                      sourceMap: isEnvProduction
                        ? shouldUseSourceMap
                        : isEnvDevelopment,
                      skipLibCheck: true,
                      inlineSourceMap: false,
                      declarationMap: false,
                      noEmit: true,
                      incremental: true,
                      tsBuildInfoFile: paths.appTsBuildInfoFile,
                    },
                  },
                  context: paths.appPath,
                  diagnosticOptions: {
                    syntactic: true,
                  },
                  mode: 'write-references',
                },
                issue: {
                  include: [
                    { file: '../**/src/**/*.{ts,tsx}' },
                    { file: '**/src/**/*.{ts,tsx}' },
                  ],
                  exclude: [
                    { file: '**/src/**/__tests__/**' },
                    { file: '**/src/**/?(*.){spec|test}.*' },
                    { file: '**/src/setupProxy.*' },
                    { file: '**/src/setupTests.*' },
                  ],
                },
                logger: {
                  infrastructure: 'silent',
                },
              }),
      

      强制ts类型检查,如果项目使用了typescript编写的话使用

      • webpack.definePlugin
            new webpack.DefinePlugin(env.stringified),
      

      wepack内置插件,在浏览器环境中定义环境变量

      • webpack.ignorePlugin
          new webpack.IgnorePlugin({
              resourceRegExp: /^\.\/locale$/,
              contextRegExp: /moment$/,
            }),
      

      wepack内置插件,可以在打包时有选择的忽略一些内容,这里的配置是在打包moment的时候忽略moment的本地化内容

       isEnvDevelopment && new CaseSensitivePathsPlugin(),
      

      解决为了解决mac系统中文件名大小写不敏感导致的打包不报错的问题,详见https://github.com/facebook/create-react-app/issues/240

      结语

      对于工程化经验特别少的开发者来说,webpack的配置浩如烟海,宛如一本百科全书让人望而兴叹。但是掌握webpack可以说是前端开发者进阶的必经之路。在学习的过程中,可以自己多搞一些demo,多去尝试和实践,就会渐渐的对它熟悉起来。之后,笔者计划对webpack打包的性能优化从配置项的各个维度做一个总结,请拭目以待。

      以上就是create-react-app项目配置全解析的详细内容,更多关于create-react-app项目配置的资料请关注其它相关文章!

      声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。