搭建自己的React+Typescript環境(二)


前言

上一篇文章介紹了React+Typescript的基礎環境搭建,並沒有做任何優化配置,以及根據不同的開發環境拆分配置,這篇文章主要就是介紹這些,並且所有配置都是在上篇文章的基礎上,如果有什么問題或者不對的地方,希望大佬們能及時指出,最后有項目地址~

要用到的幾個依賴

  • 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中繼續添加和更新我們的配置。

定義可能用到的全局變量

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

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

{
  "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",便於在配置文件中獲取參數。

然后在 package.json 中的scripts 腳本中加上我們的環境參數 --env test 等,例如:

  "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 這個插件將這些變量配置進去

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} 對應的鍵,拿到相應的值了。

修改輸出 output

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

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

開發環境配置

首先在 build 下新建一個 webpack.dev.js,然后需要安裝 webpack-merge 來合並配置。

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)) } } } 復制代碼

最后

到這里生產開發環境的配置基本上就結束了,如果有漏掉的或者配置不對的地方,希望大佬指出。

最后附上地址 項目地址,如果有不對的地方希望各位指出,感謝。

參考的文章:


作者:GLaDOS
鏈接:https://juejin.im/post/5d1424aef265da1ba9158ed0
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM