美文网首页前端大宝剑
11-脚手架create react app源码分析(1)

11-脚手架create react app源码分析(1)

作者: 七玄之主 | 来源:发表于2019-07-16 20:42 被阅读1次

    最近抽时间阅读了下 create-react-app 源码,里面所使用到的有用的插件,会不断分析扩展到项目中。当然源码内容过多,可能会摘选出基于自身考虑需要优化的内容加以介绍。

    准备

    我们可以通过yarn create react-app helloreact创建基于 create-react-app 的脚手架工程,然后执行yarn eject命令将原有的已经封装好的配置暴露出来,之后我们就可以便利的阅读及扩展了,不过需要注意的是这个命令是不可逆的,一旦执行后就不能恢复原状态了。大致的项目结构如下所示:

    paths.js分析

    而对于所有项目中的关键路径,create-react-app 都全部抽象到 paths.js 中统一定义,例如包括srcpackage.json等文件的路径等。并且,通过以下代码实现了正确解析所有的相对路径:

    // Make sure any symlinks in the project folder are resolved:
    // https://github.com/facebook/create-react-app/issues/637
    const appDirectory = fs.realpathSync(process.cwd());
    const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
    

    通过fs.realpathSync(process.cwd())获取到当前nodejs执行的工作目录后,就能够在任意层级的配置中解析项目对应的相对路径了。如果我们需要实现类似 create-react-app 这种动态配置,可以采用这种方式来实现。

    env.js分析

    我们在实际开发中,会使用到类似远程数据库访问用户名,密码或者部署容器的用户名,密码等敏感信息,这些信息暴露出去以后是相当危险的,并且对于这些类似的信息我们可能不同环境需要指定不同的值,例如开发环境和产品环境的数据库配置肯定是不同的。如此我们需要能够根据环境不同,能够自定义需要的变量,该文件即实现了此功能。

    以下列表展示了实现加载环境变量的主要插件:

    • dotenv 加载指定的 env*类的环境定义到 nodejsprocess.env 环境变量中。
    • dotenv-expand 使dotenv可以定义变量。使用方法如下所示,最终获取到的 BASE_URL 变量值为 BASE_URL: 'http://127.0.0.1:8080/'
    PORT=8080
    IP=127.0.0.1
    BASE_URL = http://${IP}:${PORT}/
    

    如下代码指定了对应环境变量所加载的定义文件及顺序。NODE_ENV 变量需要我们根据不同环境指定,例如开发development产品production测试test[].filter(Boolean)是移除所有的 false 类型元素 (false, null, undefined, 0, NaN or an empty string) 的一个简写方式。

    var dotenvFiles = [
      `${paths.dotenv}.${NODE_ENV}.local`,
      `${paths.dotenv}.${NODE_ENV}`,
      // Don't include `.env.local` for `test` environment
      // since normally you expect tests to produce the same
      // results for everyone
      NODE_ENV !== 'test' && `${paths.dotenv}.local`,
      paths.dotenv,
    ].filter(Boolean);
    

    根据不同环境,循环按序加载环境变量定义文件。

    dotenvFiles.forEach(dotenvFile => {
      if (fs.existsSync(dotenvFile)) {
        require('dotenv-expand')(
          require('dotenv').config({
            path: dotenvFile,
          })
        );
      }
    });
    

    支持基于 NODE_PATH 来解析程序模块,将 NODE_PATH 里定义的相对路径,转换为基于应用程序的绝对路径。如何项目想使用非标准布局,可以考虑使用 NODE_PATH 来解析。

    const appDirectory = fs.realpathSync(process.cwd());
    process.env.NODE_PATH = (process.env.NODE_PATH || '')
      .split(path.delimiter)
      .filter(folder => folder && !path.isAbsolute(folder))
      .map(folder => path.resolve(appDirectory, folder))
      .join(path.delimiter);
    

    过滤出 REACT_APP_ 开头的环境变量后,与 NODE_ENVPUBLIC_URL 一起提供给 WebpackDefinePlugin。应用程序中可以随意通过process.env.REACT_APP_*的方式使用定义的变量。

    const REACT_APP = /^REACT_APP_/i;
    function getClientEnvironment(publicUrl) {
      const raw = Object.keys(process.env)
        .filter(key => REACT_APP.test(key))
        .reduce(
          (env, key) => {
            env[key] = process.env[key];
            return env;
          },
          {
            // Useful for determining whether we’re running in production mode.
            // Most importantly, it switches React into the correct mode.
            NODE_ENV: process.env.NODE_ENV || 'development',
            // Useful for resolving the correct path to static assets in `public`.
            // For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
            // This should only be used as an escape hatch. Normally you would put
            // images into the `src` and `import` them in code to get their paths.
            PUBLIC_URL: publicUrl,
          }
        );
      // Stringify all values so we can feed into Webpack DefinePlugin
      const stringified = {
        'process.env': Object.keys(raw).reduce((env, key) => {
          env[key] = JSON.stringify(raw[key]);
          return env;
        }, {}),
      };
    
      return { raw, stringified };
    }
    

    webpack.config.js分析

    看文件名就可以知道,肯定和 Webpack 打包有关,create-react-app中是通过定义一个 Webpack 工厂函数来实现开发和产品环境区分的,通过之间传入对应的环境参数不同,生成不同环境的打包配置。

    module.exports = function(webpackEnv) {
      const isEnvDevelopment = webpackEnv === 'development';
      const isEnvProduction = webpackEnv === 'production';
      ...
    }
    

    getStyleLoaders 函数定义了处理 Css 所需要的 loaders。

    • style-loader 通过注入<style>标签将CSS添加到DOM,建议将 style-loadercss-loader 结合使用。
    • css-loader 解释 @importurl() ,会 import/require() 后再解析它们,主要用于将 CSS 转换为JS模块。
    • postcss-loader 启用 postcss 来处理 Css,需要另外配置。通过配置不同插件,可以完成非常强大的功能。postcss
    • sass-loader 加载 SASS / SCSS 文件并将其编译为 CSS。

    产品环境中,使用的 MiniCssExtractPlugin 插件将每个JS中包含的CSS提取为独立文件。

    以下我们重点关注一些新增的配置或插件。

    bail

    编译遇到错误立即终止打包过程

    output.pathinfo

    告诉 webpack 在 bundle 中引入「所包含模块信息」的相关注释

    optimization.minimize

    Webpack4 启动的优化配置,一般只在产品环境设置。

    optimization.splitChunks

    根据注释理解嗯,如下配置会自动开启 vendorcommons 的分割。

    splitChunks: {
       chunks: 'all',
       name: false,
    },
    

    但实际执行效果和手动配置有差异。

    // 代码块分割配置
        splitChunks: {
          cacheGroups: {
            vendor: {
              // 抽取出来文件的名字,默认为 true,表示自动生成文件名
              name: "vendor",
              // 表示从所有chunks里面抽取代码, 可选值为initial、async、all,也可以自定义函数
              chunks: "all",
              // 表示要过滤 modules, 这里限制为 node_modules
              test: /node_modules/,
              // 表示抽取权重,数字越大表示优先级越高。
              priority: 20,
              // 表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的
              reuseExistingChunk: true
            },
            commons: {
              // 抽取出来文件的名字,默认为 true,表示自动生成文件名
              name: "commons",
              // 从初始chunks里面抽取代码
              chunks: "initial",
              // 表示被引用次数,默认为1
              minChunks: 2,
              // 表示抽取出来的文件在压缩前的最小大小
              minSize: 0,
              // 表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的
              reuseExistingChunk: true
            }
          }
        },
    

    terser-webpack-plugin插件(仅限产品环境)

    在之前的配置中使用的 uglifyjs-webpack-plugin 插件不支持 es6 语法的解析,需要配合 Babel 一起使用,现在通过 terser-webpack-plugin 插件可以直接完成。

    optimize-css-assets-webpack-plugin插件(仅限产品环境)

    优化及压缩CSS的插件。并且配置使用 postcss-safe-parser 这个能修复语法错误的 PostCSS 的容错CSS解析器。

    pnp-webpack-plugin插件

    添加支持由Yarn 团队开发的 PnP 特性。解决现有的依赖管理方式效率太低,引用依赖时慢,安装依赖时也慢的痛点。该特性还比较新,实际尝试了下,安装体验确实很大改善,不过相对来讲如果想要查看对应的源码就相当麻烦了,同时想添加区别于全局的特定版本也需要额外操作。详情可以参考此博文 Yarn 的 Plug'n'Play 特性

    react-dev-utils/ModuleScopePlugin插件

    react-dev-utils 工具集提供的插件,禁止导入 srcnode_modules 文件夹以外的模块。

    module.strictExportPresence

    使缺少的导出出现错误而不是警告

    module.rules里的oneOf

    后接 loader 数组,会遍历所有 loader 直到有一个符合要求,最终缺少 loader 的情况下,会由最后的 file-loader 完成解析。

    node

    nodejs 标准模块的mock。使 nodejs 编写的程序能够在类似浏览器等环境运行。

    performance

    关闭 bundle 文件大小提示,create react app 使用了自带的 react-dev-utils/FileSizeReporter 插件。

    html-webpack-plugin插件

    相对之前的配置,针对产品环境,增加了压缩处理。

    react-dev-utils/InterpolateHtmlPlugin

    react-dev-utils 工具集提供的插件,与 HtmlWebpackPlugin 一起使用,以在index.html中嵌入值。

    react-dev-utils/ModuleNotFoundPlugin

    react-dev-utils 工具集提供的插件,创建模块未找到而错误的上下文环境。

    case-sensitive-paths-webpack-plugin插件

    强制所有需要的模块的整个路径匹配磁盘上实际路径的具体情况。适用于 window 环境和 osx 环境共同开发的情况。

    react-dev-utils/WatchMissingNodeModulesPlugin

    react-dev-utils 工具集提供的插件,Webpack 在缺少相关包时会抛出错误。
    如果执行 yarn install 后,除非重启 devServer,否则通常无法识别该包。该插件会在安装新包时,自动识别它而无需重新启动 devServer

    webpack-manifest-plugin插件

    生成项目的清单文件,包含所有资产的引用。要开启 PWA 功能时,会使用到该清单文件。

    fork-ts-checker-webpack-plugin插件

    使用专门线程来进行 ts 类型检查,目的就是运用多核资源来提升编译的速度。

    IgnorePlugin

    指定不加载某些第三方包的资源。例如忽略moment 2.18的本地化内容。

    优化配置

    基于以上的分析我们可以对原有程序做做优化。

    新增 config/webpack/getModuleRules.js

    // 将每个JS中包含的CSS提取为独立文件
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    // 允许通过读取browserslist配置来部分加载 normalize.css或sanitize.css
    const postcssNormalize = require("postcss-normalize");
    const path = require("path");
    const fs = require("fs");
    
    // 获取nodejs执行的工作目录
    const appDirectory = fs.realpathSync(process.cwd());
    // 获取相对于工作目录的相对路径的真实路径
    const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
    
    // 定义正则匹配
    const cssRegex = /\.css$/;
    const cssModuleRegex = /\.module\.css$/;
    const sassRegex = /\.(scss|sass)$/;
    const sassModuleRegex = /\.module\.(scss|sass)$/;
    
    const getCssRules = webpackEnv => {
      // 是否为开发环境
      const isEnvDevelopment = webpackEnv === "development";
      // 是否为产品环境
      const isEnvProduction = webpackEnv === "production";
      // 启用/禁用 Sourcemap 开发环境启用/产品环境禁用
      const shouldUseSourceMap = isEnvDevelopment ? true : false;
      // 根据环境,获取style相关loader数组
      const getStyleLoaders = (cssOptions, preProcessor) => {
        const loaders = [
          // 开发环境使用style-loader
          isEnvDevelopment && require.resolve("style-loader"),
          // 生产环境使用MiniCssExtractPlugin.loader
          isEnvProduction && {
            loader: MiniCssExtractPlugin.loader
          },
          // 解释 @import 和 url() ,会 import/require() 后再解析它们,主要用于将 CSS 转换为JS模块
          {
            loader: require.resolve("css-loader"),
            options: cssOptions
          },
          {
            // 启用postcss
            loader: require.resolve("postcss-loader"),
            options: {
              // 解决引用外部css出现的异常
              // https://github.com/facebook/create-react-app/issues/2677
              ident: "postcss",
              plugins: () => [
                require("postcss-flexbugs-fixes"),
                // 允许你使用未来的 CSS 特性
                require("postcss-preset-env")({
                  // 自动添加前缀
                  autoprefixer: {
                    flexbox: "no-2009"
                  },
                  // 填充语法允许使用标准stage3阶段
                  stage: 3
                }),
                postcssNormalize()
              ],
              sourceMap: shouldUseSourceMap
            }
          }
        ].filter(Boolean);
        // 添加其他loader sass或less等
        if (preProcessor) {
          loaders.push({
            loader: require.resolve(preProcessor),
            options: {
              sourceMap: shouldUseSourceMap
            }
          });
        }
        return loaders;
      };
    
      return [
        {
          test: cssRegex,
          exclude: cssModuleRegex,
          use: getStyleLoaders({
            // 用于配置css-loader作用于 @import的资源之前有多少个loader
            importLoaders: 1,
            // 是否开启sourceMap
            sourceMap: shouldUseSourceMap
          }),
          sideEffects: true
        },
        {
          test: cssModuleRegex,
          use: getStyleLoaders({
            importLoaders: 1,
            sourceMap: shouldUseSourceMap,
            modules: true
          })
        },
        {
          test: sassRegex,
          exclude: sassModuleRegex,
          use: getStyleLoaders(
            {
              importLoaders: 2,
              sourceMap: shouldUseSourceMap
            },
            require.resolve("sass-loader")
          ),
          sideEffects: true
        },
        {
          test: sassModuleRegex,
          use: getStyleLoaders(
            {
              importLoaders: 2,
              sourceMap: shouldUseSourceMap,
              modules: true
            },
            require.resolve("sass-loader")
          )
        }
      ];
    };
    
    // 获取完整的模块处理规则
    const getModuleRules = webpackEnv => {
      return [
        // 解析图片资源
        {
          test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
          loader: require.resolve("url-loader"),
          options: {
            limit: 10000,
            name: "static/media/[name].[hash:8].[ext]"
          }
        },
        // babel-loader解析typescript
        {
          test: /\.(ts|tsx|js|jsx)$/,
          exclude: /node_modules/,
          include: resolveApp("src"),
          use: {
            loader: require.resolve("babel-loader")
          }
        },
        // css解析相关loaders
        ...getCssRules(webpackEnv),
        // 其他文件解析
        {
          loader: require.resolve("file-loader"),
          // Exclude `js` files to keep "css" loader working as it injects
          // its runtime that would otherwise be processed through "file" loader.
          // Also exclude `html` and `json` extensions so they get processed
          // by webpacks internal loaders.
          exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
          options: {
            name: "static/media/[name].[hash:8].[ext]"
          }
        }
      ];
    };
    
    module.exports = getModuleRules;
    

    通过传递不同环境参数,组合 WebpackoneOf 所需要使用的模块解析规则。

    新增 config/webpack/getEnvVariables.js

    const fs = require("fs");
    
    const getEnvVariables = webpackEnv => {
      // 默认仅加载 .env.production | .env.development | .env.test格式的变量定义文件
      const dotenvFiles = [`.env.${webpackEnv}`].filter(Boolean);
      // 将计算机自身和自定义变量加载到nodejs环境中
      dotenvFiles.forEach(dotenvFile => {
        if (fs.existsSync(dotenvFile)) {
          require("dotenv-expand")(
            require("dotenv").config({
              path: dotenvFile
            })
          );
        }
      });
    
      // 假定应用程序中所使用的环境变量都是以 APP_ 开头
      const VAR_PREFIX = /^APP_/i;
      // 生成需要注入的变量
      const raw = Object.keys(process.env)
        .filter(key => VAR_PREFIX.test(key))
        .reduce(
          (env, key) => {
            env[key] = process.env[key];
            return env;
          },
          {
            // 增加环境变量
            NODE_ENV: webpackEnv
          }
        );
      // 需要注入的变量字符串化
      const stringified = {
        "process.env": Object.keys(raw).reduce((env, key) => {
          env[key] = JSON.stringify(raw[key]);
          return env;
        }, {})
      };
      return { raw, stringified };
    };
    
    module.exports = getEnvVariables;
    

    项目可以在根目录添加类似 .env.development.env.production.env.test的区分不同环境的变量,推荐将 .env.development 加入 git 管理,以便协作的同学清楚工程中环境变量的定义。

    比如我们新增 .env.development

    APP_DB_URL=127.0.0.1
    APP_DB_USERNAME=admin
    APP_DB_PASSWORD=12345
    

    具体的 webpack 配置文件调整分别如下所示:
    webpack.common.js

    const { CleanWebpackPlugin } = require("clean-webpack-plugin");
    const ManifestPlugin = require("webpack-manifest-plugin");
    const path = require("path");
    const webpack = require("webpack");
    
    module.exports = {
      // 入口文件
      entry: "./src/index.tsx",
      // 需要解析的文件后缀名
      resolve: {
        extensions: [".tsx", ".ts", ".js"],
        modules: ["node_modules", path.resolve(__dirname, "src")],
      },
      // 管理插件,通过插件实现增强功能
      plugins: [
        // 自动清理dist
        new CleanWebpackPlugin(),
        // 生成清单目录
        new ManifestPlugin({
          fileName: "asset-manifest.json",
          generate: (seed, files) => {
            const manifestFiles = files.reduce(function(manifest, file) {
              manifest[file.name] = file.path;
              return manifest;
            }, seed);
    
            return {
              files: manifestFiles
            };
          }
        }),
        // 忽略moment 2.18的本地化内容
        new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
      ],
      // 配置项目处理的不同文件及模块
      module: {
        // 使缺少的导出出现错误而不是警告
        strictExportPresence: true,
        rules: [
          // Disable require.ensure as it's not a standard language feature.
          { parser: { requireEnsure: false } },
          {
            enforce: "pre",
            test: /\.(ts|tsx)$/,
            exclude: /node_modules/,
            include: path.resolve(__dirname, "src"),
            loader: "eslint-loader"
          }
        ]
      },
      // 管理输出
      output: {
        // 定义输出文件名路径
        path: path.resolve(__dirname, "dist"),
        publicPath: "/"
      },
      optimization: {
        // 代码块分割配置
        splitChunks: {
          cacheGroups: {
            vendor: {
              // 抽取出来文件的名字,默认为 true,表示自动生成文件名
              name: "vendor",
              // 表示从所有chunks里面抽取代码, 可选值为initial、async、all,也可以自定义函数
              chunks: "all",
              // 表示要过滤 modules, 这里限制为 node_modules
              test: /node_modules/,
              // 表示抽取权重,数字越大表示优先级越高。
              priority: 20,
              // 表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的
              reuseExistingChunk: true
            },
            commons: {
              // 抽取出来文件的名字,默认为 true,表示自动生成文件名
              name: "commons",
              // 从初始chunks里面抽取代码
              chunks: "initial",
              // 表示被引用次数,默认为1
              minChunks: 2,
              // 表示抽取出来的文件在压缩前的最小大小
              minSize: 0,
              // 表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的
              reuseExistingChunk: true
            }
          }
        },
        // manifest分割配置
        runtimeChunk: true
      },
    };
    

    webpack.dev.js

    const merge = require("webpack-merge");
    const common = require("./webpack.common.js");
    const webpack = require("webpack");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const getModuleRules = require("./config/webpack/getModuleRules");
    const getEnvVariables = require("./config/webpack/getEnvVariables");
    // 开发环境
    const webpackDev = "development";
    // 定义模块解析规则
    const rules = getModuleRules(webpackDev);
    // 获取环境变量定义
    const env = getEnvVariables(webpackDev);
    // 将环境定义注入到Nodejs中,后续Babel等配置会使用该变量
    process.env.NODE_ENV = webpackDev;
    
    module.exports = merge(common, {
      // 标识配置为开发用
      mode: webpackDev,
      // 控制是否生成,以及如何生成 source map
      devtool: "cheap-module-source-map",
      // 管理开发服务器
      devServer: {
        // 开启服务器路由支持,默认定位根目录index.html
        historyApiFallback: true,
        // 查找文件路径
        contentBase: "dist",
        // 启用 HMR
        hot: true
      },
      plugins: [
        // 当开启 HMR 的时候使用该插件会显示模块的相对路径,建议用于开发环境
        new webpack.NamedModulesPlugin(),
        // 启用 HMR 热更新,建议用于开发环境
        new webpack.HotModuleReplacementPlugin(),
        // 预设程序执行环境
        new webpack.DefinePlugin(env.stringified),
        // 根据模板生成html
        new HtmlWebpackPlugin({
          title: "My App",
          template: "./src/index.html"
        })
      ],
      // 管理输出
      output: {
        // 定义输出文件名规则
        filename: "static/js/bundle.js",
        // 定义非入口(non-entry) chunk 文件的名称
        chunkFilename: "static/js/[name].chunk.js",
        // 告诉 webpack 在 bundle 中引入「所包含模块信息」的相关注释
        pathinfo: true
      },
      // 配置项目处理的不同文件及模块
      module: {
        // 配置项目处理模块规则
        rules: [
          {
            oneOf: rules
          }
        ]
      }
    });
    

    webpack.prod.js,生成环境去掉了之前采用的 UglifyJSPlugin JS压缩插件。

    const merge = require("webpack-merge");
    const common = require("./webpack.common.js");
    // const UglifyJSPlugin = require("uglifyjs-webpack-plugin");
    const webpack = require("webpack");
    const TerserPlugin = require("terser-webpack-plugin");
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
    const safePostCssParser = require("postcss-safe-parser");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    
    const getModuleRules = require("./config/webpack/getModuleRules");
    const getEnvVariables = require("./config/webpack/getEnvVariables");
    // 生产环境
    const webpackDev = "production";
    // 定义模块解析规则
    const rules = getModuleRules(webpackDev);
    // 获取环境变量定义
    const env = getEnvVariables(webpackDev);
    // 将环境定义注入到Nodejs中,后续Babel等配置会使用该变量
    process.env.NODE_ENV = webpackDev;
    
    module.exports = merge(common, {
      // 标识配置为生产用
      mode: webpackDev,
      // 编译遇到错误立即终止打包过程
      bail: true,
      // 控制是否生成,以及如何生成 source map
      devtool: false,
      plugins: [
        // 预设程序执行环境
        new webpack.DefinePlugin(env.stringified),
        new MiniCssExtractPlugin({
          filename: "static/css/[name].[contenthash:8].css",
          chunkFilename: "static/css/[name].[contenthash:8].chunk.css"
        }),
        // 根据模板生成html
        new HtmlWebpackPlugin({
          title: "My App",
          template: "./src/index.html",
          minify: {
            removeComments: true,
            collapseWhitespace: true,
            removeRedundantAttributes: true,
            useShortDoctype: true,
            removeEmptyAttributes: true,
            removeStyleLinkTypeAttributes: true,
            keepClosingSlash: true,
            minifyJS: true,
            minifyCSS: true,
            minifyURLs: true
          }
        })
      ],
      // 管理输出
      output: {
        // 定义输出文件名规则
        filename: "static/js/[name].[contenthash:8].js",
        // 定义非入口(non-entry) chunk 文件的名称
        chunkFilename: "static/js/[name].[contenthash:8].chunk.js"
      },
      // 代码分割配置
      optimization: {
        // 启用js代码压缩,生产环境默认为true
        minimize: true,
        // 指定自定义压缩插件
        minimizer: [
          // Terser配置
          new TerserPlugin({
            terserOptions: {
              parse: {
                ecma: 8
              },
              compress: {
                ecma: 5,
                warnings: false,
                comparisons: false,
                inline: 2
              },
              mangle: {
                safari10: true
              },
              output: {
                ecma: 5,
                comments: false,
                ascii_only: true
              }
            },
            // 开启多线程,加快编译速度
            parallel: true,
            // 开启文件缓存
            cache: true,
            // 关闭sourceMap
            sourceMap: false
          }),
          // 优化及压缩CSS
          new OptimizeCSSAssetsPlugin({
            cssProcessorOptions: {
              parser: safePostCssParser,
              map: false
            }
          })
        ]
      },
      // 配置项目处理的不同文件及模块
      module: {
        // 配置项目处理模块规则
        rules: [
          {
            oneOf: rules
          }
        ]
      }
    });
    

    相关文章

      网友评论

        本文标题:11-脚手架create react app源码分析(1)

        本文链接:https://www.haomeiwen.com/subject/qmrdkctx.html