搭建本身的React+Typescript環境(二)

前言

上一篇文章介紹了React+Typescript的基礎環境搭建,並無作任何優化配置,以及根據不一樣的開發環境拆分配置,這篇文章主要就是介紹這些,而且全部配置都是在上篇文章的基礎上,若是有什麼問題或者不對的地方,但願大佬們能及時指出,最後有項目地址~css

要用到的幾個依賴

  • webpack-merge:合併webpack配置
  • webpack.DefinePlugin:在編譯時建立一些全局變量
  • webpack.HotModuleReplacementPlugin:用於啓用局部模塊熱重載,開發環境用的
  • html-webpack-plugin:根據webpack打包生成的bundle,來生成html
  • add-asset-html-webpack-plugin:跟html-webpack-plugin配合使用,把資源文件引用到它生成的html中
  • mini-css-extract-plugin:把css抽取到不一樣的文件中
  • terser-webpack-plugin:新的壓縮js代碼插件
  • optimize-css-assets-webpack-plugin:在webpack打包時優化壓縮css代碼,主要使用 cssnano 壓縮器。
  • webpack.runtimeChunk:與持久化緩存有關
  • webpack.splitChunks:webpack 4 最大的改動就是廢除了 CommonsChunkPlugin 引入了 optimization.splitChunks,用來配置分包策略。
  • webpack.DllPlugin:將模塊預先編譯,它會在第一次編譯的時候將配置好的須要預先編譯的模塊編譯在緩存中,第二次編譯的時候,解析到這些模塊就直接使用緩存
  • webpack.DllReferencePlugin:將預先編譯好的模塊關聯到當前編譯中,當 webpack 解析到這些模塊時,會直接使用預先編譯好的模塊
  • webpack-bundle-analyzer:webpack打包分析器,能夠直觀看到各bundle佔比
  • clean-webpack-plugin:清理打包文件夾

公共配置

在上篇webpack.common.js中繼續添加和更新咱們的配置。html

定義可能用到的全局變量

有的時候須要在不一樣的環境定義不一樣的變量,就像vue-cli3建立的項目中的.env文件同樣。vue

首先在 build 文件夾下新建一個 env.json 文件夾,並在裏面寫上你可能用到的全局變量。node

{
  "dev": {
    "APP_ENVO": "dev",
    "BASEURL": "https://xxxx.xxxx.com/api/"
  },
  "test": {
    "APP_ENVO": "test",
    "BASEURL": "https://xxxx.xxxx.com/api/"
  },
  "pre": {
    "APP_ENVO": "pre",
    "BASEURL": "https://xxxx.xxxx.com/api/"
  },
  "prod": {
    "APP_ENVO": "prod",
    "BASEURL": "https://xxxx.xxxx.com/api/"
  }
}
複製代碼

接下來須要用到 yargs-parser 這個插件,yargs-parser: 用於將咱們的npm scripts中的命令行參數轉換成鍵值對的形式如 --mode development會被解析成鍵值對的形式mode: "development",便於在配置文件中獲取參數。react

而後在 package.json 中的scripts 腳本中加上咱們的環境參數 --env test 等,例如:webpack

"scripts": {
    "dev": "webpack-dev-server --config build/webpack.dev.js --mode development --open",
    "test-build": "webpack --config build/webpack.prod.js --mode production --env test",
    "pre-build": "webpack --config build/webpack.prod.js --mode production --env pre",
    "prod-build": "webpack --config build/webpack.prod.js --mode production --env prod"
  },
複製代碼

而後在 webpack.common.js 中拿到這個參數,並利用 webpack.DefinePlugin 這個插件將這些變量配置進去git

const argv = require('yargs-parser')(process.argv.slice(4))
const APP_ENV = argv.env || 'dev'

const env = require('./env.json')
const oriEnv = env[config.APP_ENV]
Object.assign(oriEnv, {
	APP_ENV: config.APP_ENV
})

const defineEnv = {}
for (let key in oriEnv) {
	defineEnv[`process.env.${key}`] = JSON.stringify(oriEnv[key])
}

module.exports={
  // ... 省略了其餘配置
  plugins: [
    new webpack.DefinePlugin(defineEnv)
  ]
}

複製代碼

以後在項目啓動後就能夠經過 process.env.${key} 對應的鍵,拿到相應的值了。github

修改輸出 output

修改咱們打包後的 js 輸出目錄以及名稱,讓它看起來清晰一些。web

module.exports={
  output: {
    filename: 'js/[name].[chunkhash].js',
    path: path.join(__dirname, '../dist')
  }
}
複製代碼

開發環境配置

首先在 build 下新建一個 webpack.dev.js,而後須要安裝 webpack-merge 來合併配置。vuex

yarn add webpack-merge -D
複製代碼

接下來引入它以及公共配置文件,把以前的 devServer 移到這裏,並引入 webpack.HotModuleReplacementPlugin 用於啓用局部模塊熱重載方便咱們開發,若是要配置代理的話,須要配置 devServer 下的 proxy,具體每一個字段的意思,能夠參照官網

關於 source-map 的話,能夠理解它爲你的源碼與打包後代碼的一個映射,由於打包後的代碼都是通過壓縮的,尋找錯誤調試會很麻煩,因此須要它,這裏使用 eval-source-map ,對應的配置 devtool 選項。

const webpack=require('webpack')
const merge = require('webpack-merge')
const baseConfig=require('./webpack.common')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const devConfig={
  mode: 'development', 
  devtool: 'eval-source-map',
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'public/index.html',
      inject: true
    }),
    new webpack.HotModuleReplacementPlugin()
  ],
  devServer: {
    host: 'localhost',
    port: 3000,
    historyApiFallback: true,
    overlay: {//當出現編譯器錯誤或警告時,就在網頁上顯示一層黑色的背景層和錯誤信息
      errors: true
    },
    inline: true,
    hot: true,
    // proxy: {
    // '/api/v1': {
    // target: '',
    // ws: true,
    // changeOrigin: true,
    // pathRewrite: {
    // '^/api/v1': '/api/v1'
    // }
    // }
    // }
  },
}

module.exports=merge(baseConfig,devConfig)
複製代碼

而後在 package.json 中 scripts 添加咱們啓動開發環境的命令,以後就能夠啓動項目了。

"dev": "webpack-dev-server --config build/webpack.dev.js --mode development --open"
複製代碼

生產環境配置

首先在 build 下新建一個 webpack.prod.js,跟開發環境同樣,都須要引入公共配置,而後一點點的引入插件。

const merge = require('webpack-merge')
const baseConfig = require('./webpack.common')
const webpack = require('webpack')

const prodConfig = {
  mode: 'production',
  devtool: 'source-map'
}
module.exports = merge(baseConfig, prodConfig)
複製代碼

html-webpack-plugin

開頭介紹過它,用於自動生成html,並默認將打包生成的js、css引入到html文件中,其中minify 配置項有不少,具體能夠參照html-minifier

const HtmlWebpackPlugin = require('html-webpack-plugin')

const prodConfig = {
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'public/index.html',
      inject: true,
      minify: {
        removeComments: true, // 去掉註釋
        collapseWhitespace: true, // 去掉多餘空白
        removeAttributeQuotes: true // 去掉一些屬性的引號,例如id="moo" => id=moo
      }
    })
  ]
}
複製代碼

mini-css-extract-plugin

使用mini-css-extract-plugin來將css從js裏分離出來,而且支持chunk css。

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

// ...
const prodConfig = {
  // ...
  plugins: [
    // ...
    new MiniCssExtractPlugin({
      // Options similar to the same options in webpackOptions.output
      // both options are optional
      filename: assetsPath('css/[name].[contenthash].css'),
      chunkFilename: assetsPath('css/[name].[id].[contenthash].css')
    })
  ]
}
複製代碼

除此以外還要配置 webpack.commom.js, 把 style-loader 換成這個插件提供的 loader,固然也能夠區分一下環境,開發環境仍然使用 style-loader,以 css 文件爲例。

{
    test: /\.css$/, // 正則匹配文件路徑
    exclude: /node_modules/,
    use: [
      // 
      APP_ENV !== 'dev' ? MiniCssExtractPlugin.loader : 'style-loader',
      {
        loader: 'css-loader', // 解析 @import 和 url() 爲 import/require() 方式處理
        options: {
          importLoaders: 1 // 0 => 無 loader(默認); 1 => postcss-loader; 2 => postcss-loader, sass-loader
        }
      },
      'postcss-loader'
    ]
  }
複製代碼

clean-webpack-plugin

用於清除本地文件,在進行生產環境打包的時候,若是不清除dist文件夾,那麼每次打包都會生成不一樣的js文件或者css文件堆積在文件夾中,注意版本帶來的使用不一樣

const { CleanWebpackPlugin } = require('clean-webpack-plugin')

// ...
const prodConfig = {
  // ...
  plugins: [
    // ...
    new CleanWebpackPlugin(),
  ]
}
複製代碼

optimize-css-assets-webpack-plugin

在webpack打包時優化壓縮css代碼,主要使用 cssnano 壓縮器,這個就不是配置在 plugins 裏了,而是 optimization 下的 minimizer

const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')

// ...
const prodConfig = {
  // ...
  optimization: { // 性能配置
    // ...
    minimizer: [
      new OptimizeCssAssetsPlugin({
        cssProcessor: require('cssnano'), // 使用 cssnano 壓縮器
        cssProcessorOptions: {
          reduceIdents: false,
          autoprefixer: false,
          safe: true,
          discardComments: {
            removeAll: true
          }
        }
      })
    ]
  }
}
複製代碼

terser-webpack-plugin

optimize-css-assets-webpack-plugin 用於壓縮 css 代碼,而它用來壓縮 js 代碼,以前用到的是 uglifyjs-webpack-plugin 這一個,可是它好像須要 babel 的支持,並且如今官方推薦用 terser-webpack-plugin, 不過在使用上差很少,並且它不須要安裝。

const TerserPlugin = require('terser-webpack-plugin')

// ...
const prodConfig = {
  // ...
  optimization: { // 性能配置
    // ...
    minimizer: [
      new TerserPlugin({
        cache: true,
        // parallel: true,
        terserOptions: {
          compress: {
            warnings: true,
            drop_console: true,
            drop_debugger: true,
            pure_funcs: ['console.log'] // 移除console
          }
        },
        sourceMap: true
      }),
    ]
  }
}
複製代碼

webpack.RuntimeChunk

它能夠將包含chunks 映射關係的 list單獨從 app.js裏提取出來,由於每個 chunk 的 id 基本都是基於內容 hash 出來的,因此你每次改動都會影響它,若是不將它提取出來的話,等於app.js每次都會改變。緩存就失效了。在 webpack4 中,無需手動引入插件,配置 runtimeChunk 便可。

const prodConfig = {
  // ...
  optimization: { // 性能配置
    // ...
    {
      runtimeChunk: true;
    }
  }
}
複製代碼

打包生成的 runtime.js很是的小,gzip 以後通常只有幾 kb,但這個文件又常常會改變,咱們每次都須要從新請求它,它的 http 耗時遠大於它的執行時間了,因此建議不要將它單獨拆包,有關優化就是將他將它內聯到咱們的 index.html 之中。

這裏使用了 script-ext-html-webpack-plugin。

const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");

// 注意必定要在HtmlWebpackPlugin以後引用
// inline 的name 和你 runtimeChunk 的 name保持一致
new ScriptExtHtmlWebpackPlugin({
  //`runtime` must same as runtimeChunk name. default is `runtime`
  inline: /runtime\..*\.js$/
});
複製代碼

webpack.splitChunks

這個配置能讓咱們以必定規則抽離想要的包,webpack4 有一套默認的代碼分包策略。

  • 新的 chunk 是否被共享或者是來自 node_modules 的模塊
  • 新的 chunk 體積在壓縮以前是否大於 30kb
  • 按需加載 chunk 的併發請求數量小於等於 5 個
  • 頁面初始加載時的併發請求數量小於等於 3 個

關於按需加載跟頁面初始加載就對應到 webpack.splitChunks.chunks 它表示將選擇哪些塊進行優化,async 表示只優化動態導入的包,而 initial 表示初始加載時導入的包,還有一個值 all 表示都會優化,默認是 async,也就是說若是你動態導入了一個包,壓縮前大於30kb,而且你在代碼中有超過5個地方引用了它,那麼 webpack 就會將它單獨打包出來。

一般咱們須要將 node_modules 下的比較大的基礎類庫包抽出來,好比 vuex、vue之類的,或者像比較大的UI 組件庫,好比 antd、element-ui 之類的也抽出來,以及本身寫的可能會在多個頁面間用到屢次的組件。下面給一個我這裏的配置,注意:拆包的時候不要過度的追求顆粒化,資源的加載策略並沒什麼徹底的方案,都須要結合本身的項目找到最合適的拆包策略

const prodConfig = {
  // ...
  optimization: { // 性能配置
    // ...
    splitChunks: {
      chunks: 'async', // 提取的 chunk 類型,all: 全部,async: 異步,initial: 初始
      // minSize: 30000, // 默認值,新 chunk 產生的最小限制 整數類型(以字節爲單位)
      // maxSize: 0, // 默認值,新 chunk 產生的最大限制,0爲無限 整數類型(以字節爲單位)
      // minChunks: 1, // 默認值,新 chunk 被引用的最少次數
      // maxAsyncRequests: 5, // 默認值,按需加載的 chunk,最大數量
      // maxInitialRequests: 3, // 默認值,初始加載的 chunk,最大數量
      // name: true, // 默認值,控制 chunk 的命名
      cacheGroups: { // 配置緩存組
        vendor: {
          name: 'vendor',
          chunks: 'initial',
          priority: 10, // 優先級
          reuseExistingChunk: false, // 容許複用已經存在的代碼塊
          test: /node_modules\/(.*)\.js/, // 只打包初始時依賴的第三方
        },
        common: {
          name: 'common',
          chunks: 'initial',
          // test: resolve("src/components"), // 可自定義拓展你的規則
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true
        }
      }
    }
  }
}
複製代碼

webpack.DllPlugin 與 webpack.DllReferencePlugin

像 React 相關基礎運行環境,將這些基礎模塊打到一個包裏,只要這些包的包的版本沒升級,之後每次打包就不須要再編譯這些模塊,提升打包的速率,這裏咱們就能夠用到 webpack.DllPlugin,而後使用 webpack.DllReferencePlugin 將這個 dll 包關聯到當前的編譯中去。

在 build 文件夾下新建一個 webpack.dll.js 文件,並寫入下面的配置

const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  mode:'production',
  entry: {
    // 還有redux 之類的也能夠放進來
    vendor: ['react', 'react-dom', 'react-router-dom']
  },
  output: {
    filename: '[name].dll.[hash:8].js',
    path: path.join(__dirname, '../dll'),
    // 連接庫輸出方式 默認'var'形式賦給變量
    libraryTarget: 'var',
    // 全局變量名稱 導出庫將被以var的形式賦給這個全局變量 經過這個變量獲取到裏面模塊
    library: '_dll_[name]_[hash:8]'
  },
  plugins: [
    // 每次運行時清空以前的 dll 文件
    new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: [path.join(__dirname, '../dll/**/*')]
    }),
    new webpack.DllPlugin({
      // path 指定manifest文件的輸出路徑
      path: path.join(__dirname, '../dll/[name].manifest.json'),
      // 和library 一致,輸出的manifest.json中的name值
      name: '_dll_[name]_[hash:8]'
    })
  ]
}
複製代碼

下面修改 webpack.prod.js 使用DllReferencePlugin告訴 Webpack 使用了哪些動態連接庫,而後並使用下面介紹的 add-asset-html-webpack-plugin 將其放入資源列表 html webpack插件注入到生成的 html 中。

其中 vendor.manifest.json 是由 DllPlugin 生成出,用於描述動態連接庫文件中包含哪些模塊。

// ...
const prodConfig = {
  // ...
  plugins: [
    // ...
    // 告訴 Webpack 使用了哪些動態連接庫
    new webpack.DllReferencePlugin({
      manifest: path.join(__dirname, `../dll/vendor.manifest.json`)
    })
  ]
}
複製代碼

以後在 package.json 中scripts再加一個命令

"scripts": {
    "dll": "webpack --config build/webpack.config.dll.js",
  }
複製代碼

而後運行它,就能夠發現根目錄下dll生成了兩個文件 vendor.dll.xxxxxxxx.js,vendor.manifest.json

add-asset-html-webpack-plugin

咱們使用它來將給定的靜態資源css或者js引入到html-webpack-plugin生成的html文件中。

const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')

// ...
const prodConfig = {
  // ...
  plugins: [
    // ...
    new AddAssetHtmlPlugin({
      filepath: resolve(`${DLL_PATH}/**/*.js`),
      includeSourcemap: false
    }),
  ]
}
複製代碼

webpack-bundle-analyzer

若是你想看你webpack打包以後輸出文件的大小佔比,可使用這個插件,在webpack.prod.js 中加入以下配置,若是你想控制這個插件是否引入,可使用一個變量:

if (config.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  prodConfig.plugins.push(new BundleAnalyzerPlugin())
}
複製代碼

這樣在打包結束後,會自動打開一個瀏覽器窗口,並展現輸出文件的大小佔比。

性能提示

若是想要在打包或者開發過程當中展現一些性能提示,能夠在 webpack.common.js 中加入以下配置。

module.exports={
   // ...
   performance: { // 性能提示,能夠提示過大文件
    hints: "warning", // 性能提示開關 false | "error" | "warning"
    maxAssetSize: 100000, // 生成的文件最大限制 整數類型(以字節爲單位)
    maxEntrypointSize: 100000, // 引入的文件最大限制 整數類型(以字節爲單位)
    assetFilter: function(assetFilename) {
        // 提供資源文件名的斷言函數
        return (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(assetFilename))
    }
  } 
}
複製代碼

最後

到這裏生產開發環境的配置基本上就結束了,若是有漏掉的或者配置不對的地方,但願大佬指出。

最後附上地址 項目地址,若是有不對的地方但願各位指出,感謝。

參考的文章:

相關文章
相關標籤/搜索