11-腳手架create react app源碼分析(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
      }
    ]
  }
});
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。