通過webpack從零開始搭建React項目

從零開始搭建React項目

1. Webpack基礎概念

webpack主要作用:

  • 代碼轉換:TypeScript 編譯成 JavaScript、SCSS 編譯成 CSS 等。
  • 文件優化:壓縮 JavaScript、CSS、HTML 代碼,壓縮合並圖片等。
  • 代碼分割:提取多個頁面的公共代碼、提取首屏不需要執行部分的代碼讓其異步加載。
  • 模塊合併:在採用模塊化的項目裏會有很多個模塊和文件,需要構建功能把模塊分類合併成一個文件。
  • 自動刷新:監聽本地源代碼的變化,自動重新構建、刷新瀏覽器。
  • 代碼校驗:在代碼被提交到倉庫前需要校驗代碼是否符合規範,以及單元測試是否通過。
  • 自動發佈:更新完代碼後,自動構建出線上發佈代碼並傳輸給發佈系統。

Webpack 有以下幾個核心概念:

  • Entry:入口,Webpack 執行構建的第一步將從 Entry 開始,可抽象成輸入。
  • Module:模塊,在 Webpack 裏一切皆模塊,一個模塊對應着一個文件。Webpack 會從配置的 Entry 開始遞歸找出所有依賴的模塊。
  • Chunk:代碼塊,一個 Chunk 由多個模塊組合而成,用於代碼合併與分割。
  • Loader:模塊轉換器,用於把模塊原內容按照需求轉換成新內容。
  • Plugin:擴展插件,在 Webpack 構建流程中的特定時機會廣播出對應的事件,插件可以監聽這些事件的發生,在特定時機做對應的事情。
  • Output:輸出結果,在 Webpack 經過一系列處理並得出最終想要的代碼後輸出結果。

構建過程:

  1. 從 Entry 裏配置的 Module 開始遞歸解析 Entry 依賴的所有 Module。
  2. 每找到一個 Module, 就會根據配置的 Loader 去找出對應的轉換規則。
  3. 對 Module 進行轉換後,再解析出當前 Module 依賴的 Module。
  4. 這些模塊會以 Entry 爲單位進行分組,一個 Entry 和其所有依賴的 Module 被分到一個組也就是一個 Chunk。
  5. 最後 Webpack 會把所有 Chunk 轉換成文件輸出。
  6. 在整個流程中 Webpack 會在恰當的時機執行 Plugin 裏定義的邏輯。

2. 項目需求

使用Webpack 4.x 搭建項目,滿足以下需求:

  1. 使用ES6語言
  2. 使用React框架
  3. 自動生成HTML
  4. webpack-dev-server
  5. 加載樣式(CSS、SCSS)
  6. 加載靜態資源(圖片、字體)
  7. 使用第三方庫
  8. 其他(clean,merge,source map)

3. 實戰

3.1 基礎環境

NodeJS版本10.4.1。

  1. 初始化生成一個 package.json 文件。

    npm init -y
    
  2. 本地安裝webpack

    webpack4版本命名行相關的功能獨立到Webpack-cli。

    npm i webpack webpack-cli -D
    
  3. 新建webpack.config.js 配置文件。

    webpack默認會查找 webpack.config.js 作爲配置文件。
    
    自定義配置文件名稱:
    webpack --config xx.js
    
  4. 基礎配置

    webpack配置採用commonJS規範,通過module.export導出一個描述如何構建的 Object 對象。
    
    module.exports={
        entry:{},     //入口配置*
        output:{},    //出口配置*
        modules:{},   //module.rules loader
        plugins:[],   //插件
        devServer:{}   //開發服務器
    }
    

3.2 ES6

3.2.1 前置步驟

  1. 按如下結構創建相應文件與目錄:

    │  package.json
    │  webpack.config.js
    ├─dist
    │      index.html        
    └─src
            main.js
    
  2. main.js 內容:

    class Ui {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
    
        coding() {
            return `${this.name} is coding`;
        }
    }
    
    let ui= new Ui('hqz','18');
    
    document.getElementById('app').innerText=ui.coding();
    
  3. index.html 內容:

    <html>
    <head>
    	<meta charset="UTF-8">
    	<title>ES6</title>
    </head>
    <body>
        <div id="app"></div>
        <script src="bundle.js"></script>    //手動引入打包後的JS
    </body>
    </html>
    
  4. webpack配置

    const path=require("path");
    
    module.exports={
        entry:'./src/main.js',     //入口文件
        output:{                   //出口配置
            filename: 'bundle.js', //出口文件名
            path: path.resolve(__dirname, 'dist')  //出口文件路徑
        },
        module: {},    //module.rules loader
        plugins:[],    //插件
        devServer:{}   //開發服務器
    };
    

3.2.2 配置Babel

目前瀏覽器對ES6的標準支持不全,所以我們需要把ES6轉換成ES5,包含以下兩件事:

  1. 把新的 ES6 語法用 ES5 實現。
  2. 給新的 API 注入 polyfill。

babel可以方便的完成以上2件事。 babel-preset-env的工作方式類似於babel-preset-latest,但它允許您指定環境並僅轉換該環境中缺少的功能。

babel 7.X的主要變更:

  • 刪除對未維護的 Node 版本的支持:0.10,0.12,2,5
  • 刪除“Stage” & 年度預設(preset-es2015 等), 用@babel/preset-env 取代。
  • 對部分軟件包進行重命名(e.g. babel-core–>@babel/core)

簡單升級:

  1. 利用babel-upgrade

    npm i babel-upgrade -g
    babel-upgrade --write
    
  2. 重新安裝包

  3. 修改配置文件中的包名

配置:

  1. 本地安裝Babel

    npm i -D @babel/core @babel/preset-env babel-loader
    @babel/plugin-transform-object-rest-spread @babel/plugin-transform-export-extensions @babel/plugin-transform-class-properties @babel/plugin-syntax-dynamic-import
    
  2. 配置webpack

    在module.rules中添加如下:
    module: {
            rules: [{
                test: /\.js$/,    //使用正則匹配所有需要使用babel-loader的文件
                use: {
                    loader: "babel-loader",  //指明要使用的loader
                    options: {               //傳入loader的參數
                        presets: [           //用於解析一組語法特性
                            [
                                "@babel/preset-env",       //包含當前所有 ECMAScript 標準裏的最新特性
                                {
                                    "targets": {   //指定需要兼容的瀏覽器類型和版本
                                        "browsers": [
                                            "> 1%",     //支持市場份額超過1%的瀏覽器。
                                            "ie >= 9"   //支持IE9以上的版本
                                        ]
                                    }
                                }
                            ]
                        ],
                        plugins: [         //用於解析某個語法特性
                            "@babel/plugin-proposal-object-rest-spread", //解析對象的擴展運算符(ES2018)
                            "@babel/plugin-proposal-export-default-from",  //解析額外的export語法:export v from "xx/xx"
                            "@babel/plugin-proposal-export-namespace-from", //解析額外的export語法:export v as vv from "xx/xx";
                            "@babel/plugin-proposal-class-properties",   //解析class中的靜態屬性
                            "@babel/plugin-syntax-dynamic-import"         //解析import方法
                        ]
                    }
                }
    
            }]
        },
    
  3. 執行編譯

    package.json 的 script字段添加如下:
    "build": "webpack"
    
    執行 npm run build
    

    編譯完成後,dist文件夾下多出一個bundle.js文件,打包成功。

3.3 React

3.3.1 前置步驟

  1. 本地安裝react

    npm i -D react react-dom
    
  2. mian.js

    import React, { Component } from 'react';
    import ReactDOM from 'react-dom';
    
    class App extends Component {
        render() {
            return <h1>Hello word!</h1>
        }
    }
    ReactDOM.render(<App />, document.getElementById('app'));
    

3.3.2 配置Babel

Babel也可用於解析JSX,需要使用babel-preset-react。

  1. 本地安裝babel-preset-react

    npm i -D @babel/preset-react
    
  2. 配置webpack (在ES6環境的基礎上)

    在module.rules中添加如下:
    module: {
            rules: [{
                test: /\.js$/,   
                use: {
                    loader: "babel-loader", 
                    options: {               
                        presets: ["@babel/preset-env","@babel/preset-react"]  //用於解析ES6+React
                    }
                }
    
            }]
        },
    

3.4 自動生成HTML

3.4.1 單個頁面

使用html-webpack-plugin 自動生成HTML,並引入相應文件。

  1. 將index.html移到src目錄下,並重命名:

    │  package.json
    │  webpack.config.js
    ├─dist     
    └─src
            main.js
            template.html
    
  2. template.html 內容:

    <html>
    <head>
    	<meta charset="UTF-8">
    	<title>自動生成HTML</title>
    </head>
    <body>
        <div id="app"></div>     //去掉手動引入script
    </body>
    </html>
    
  3. 本地安裝

    npm i -D html-webpack-plugin
    
  4. webpack配置

    const HtmlWebpackPlugin = require('html-webpack-plugin');  //引入插件
    
    plugins:[
        new HtmlWebpackPlugin({
        	template:'./src/template.html',  //html模板
        })
    ]
    
  5. 編譯

  6. 編譯後生成的HTML

    <html>
    <head>
      <meta charset="UTF-8">
      <title>自動生成HTML</title>
    </head>
    <body>
    <div id="app"></div>
    <script type="text/javascript" src="bundle.js"></script>   //自動引入JS文件
    
    </body>
    </html>
    

3.4.2 多個頁面

如果單頁應用中需要多個頁面入口,或者多頁應用時配置多個html時,那麼就需要實例化該插件多次。

  1. 目錄結構要求:

    │  package.json
    │  webpack.config.js
    │  template.html
    ├─dist
    └─src                   
        ├─A                 
        │      A.js 
        └─B
               B.js
    
  2. 配置webpack

    const HtmlWebpackPlugin = require('html-webpack-plugin');  //引入插件
    
    entry: {                           //多入口使用對象形式配置,chunk名稱爲key值
            A: './src/pages/A/A.js',
            B: './src/pages/B/B.js',
    },
    output:{                   
            filename:'[name].bundle.js',  //[name]代表chunk名稱
            path: path.resolve(__dirname, 'dist')
    },
        
    plugins:[
        new HtmlWebpackPlugin({
            chunks: ['A'],           //要引入的chunk
            filename:'A.html',       //生成的文件名
            template:'template.html',  //模板文件
        }),
        new HtmlWebpackPlugin({
            chunks:['B'],
            filename:'B.html',
            template:'template.html'
        }),
    ]
    
  3. 編譯後的目錄結構

    │  package.json
    │  webpack.config.js
    │  template.html
    ├─dist
    |	A.bundle.js
    |	A.html
    │  	B.bundle.js
    │  	B.html
    └─src                   
        ├─A                 
        │      A.js 
        └─B
               B.js
    

3.5 Webpack-dev-server

webpack-dev-server提供了一個簡單的服務器,用於訪問 webpack 構建好的靜態文件,我們日常開發時可以使用它來調試前端代碼。 webpack-dev-server將構建好的項目存在內存中。

DevServer 支持模塊熱替換, 可在不刷新整個網頁的情況下實時預覽頁面。 原理是當一個源碼發生變化時,只重新編譯發生變化的模塊,再用新輸出的模塊替換掉瀏覽器中對應的老模塊。

3.5.1 devServer

  1. 本地安裝devServer

    npm i -D webpack-dev-server
    
  2. 配置webpack:

    const webpack = require('webpack');
    
    plugins: [
            new webpack.HotModuleReplacementPlugin()  //啓用 HMR (webpack 4)
    ],
    
    devServer:{
            hot: true,                 //開啓模塊熱替換
            contentBase: './dist',     //將dist目錄下的文件,作爲額外可訪問文件
            host: '0.0.0.0',           //DevServer 服務監聽的地址,默認是localhost。
    當需要同個局域網可訪問你的服務時,可設成0.0.0.0
            port: 3000,                //DevServer 服務監聽的端口,默認8080
            https: false,              //是否使用HTTPS服務
            open: true                 //自動打開網頁,地址是host:port
    },
    
    只有在通過 DevServer 去啓動 Webpack 時配置文件裏 devServer 纔會生效,
    因爲這些參數所對應的功能都是 DevServer 提供的,Webpack 本身並不認識 devServer 配置項。 
    
  3. 執行編譯

    package.json 的 script字段添加如下:
    "start": "webpack-dev-server"
    
    執行 npm start
    
  4. http://192.168.1.87:3000/A.html

3.5.2 open-browser-webpack-plugin

  1. 本地安裝

    npm i -D open-browser-webpack-plugin
    
  2. 配置webpack

    const OpenBrowserPlugin = require('open-browser-webpack-plugin');
    
    plugins: [
        new OpenBrowserPlugin({ url: 'http://192.168.1.87:3000/A.html' })  //開啓服務後,自動打開的地址
    ]
    
  3. 執行 npm start後,會自動打開http://192.168.1.87:3000/A.html頁。

3.6 加載樣式

webpack本身只認得JS文件,其他非JS文件需要用loader進行轉換。

3.6.1 加載CSS

處理css文件,需要用到以下兩個loader:

  • css-loader 負責解析 CSS 代碼,主要是爲了處理 CSS 中的依賴,例如 @import 和 url() 等引用外部文件的聲明。
  • style-loader 會將 css-loader 解析的結果轉變成 JS 代碼,運行時動態插入 style 標籤來讓 CSS 代碼生效。
  1. 本地安裝loader

    npm i -D css-loader style-loader
    
  2. 配置webpack

    module: {
            rules: [
                {
                    test: /\.css$/,    //使用正則匹配所有需要使用此loader的文件
                    use: [             //先由css-loader處理後,在交給style-loader處理
                        'style-loader',
                        {
                            loader:'css-loader',
                            options:{           //傳入css-loader的參數
                                minimize:true,  //是否壓縮css代碼
                                module:true     //是否使用css module
                            }
                        }
                    ]
                },
            ]
        },
    

3.6.2 加載SCSS

先將SCSS轉成CSS,後續處理同上。

  1. 本地安裝

    npm i -D sass-loader node-sass 
    
  2. 配置webpack

    module: {
            rules: [
                {
                    test: /\.scss$/,    //使用正則匹配所有需要使用此loader的文件
                    use: ['style-loader','css-loader', 'sass-loader']  
                            //處理順序:sass-loader->css-loader->style-loader
                },
            ]
        },
    

3.7 加載靜態資源

3.7.1 加載圖片&字體

file-loader, url-loader可用於處理圖片,字體等靜態資源。

url-loader封裝了file-loader:

  • 文件大小小於limit參數時,url-loader將會把文件轉爲DataURL。
  • 文件大小大於limit,url-loader會調用file-loader進行處理。
  1. 本地安裝loader

    npm i -D file-loader url-loader
    
  2. 配置webpack

    module: {
        rules: [
            {
                    test: /\.(png|svg|jpg|gif)$/,
                    use: [{
                        loader: 'url-loader',
                        options: {
                            limit: 1024 * 30,         //30KB 以下的文件採用 url-loader
                            fallback: 'file-loader',  //否則採用 file-loader,默認值就是 file-loader
                            outputPath: 'images',     //圖片輸出路徑,相對於output.path
                        }
                    }]
                },
                {
                    test: /\.(eot|ttf|woff|svg)$/,
                    use: [{
                        loader: 'url-loader',
                        options: {
                            limit: 1024 * 30,         //30KB 以下的文件採用 url-loader
                            fallback: 'file-loader',  //否則採用 file-loader,默認值就是 file-loader
                            outputPath: 'fonts',      //字體輸出路徑
                        }
                    }]
                },
        ]
    },
    
  3. 編譯後:

    • 大於30KB的資源,用file-loader處理,複製到dist/images目錄下。
    • 小於30KB的資源,和js一起打包,形成dataURL形式。

3.7.2 copy-webpack-plugin

將不需要webpack處理的靜態資源(e.g. favicon),原樣輸出到指定目錄下。

  1. 本地安裝

    npm i copy-webpack-plugin -D
    
  2. 配置webpack

    const CopyWebpackPlugin = require('copy-webpack-plugin');
    
    plugins:[
        new CopyWebpackPlugin([{
            from: './src/assets/public',  // 將此目錄下的文件
            to:'./public'                 // 輸出到此目錄,相對於output.path目錄
        }])
    ]
    
  3. 編譯後 src/assets/public 下的文件將原封不動的輸出到 dist/public 目錄下。

3.8 第三方庫

通過ProvidePlugin引用某些模塊作爲應用運行時的變量,從而不必每次都用 require 或者 import, 是內置的插件。

  1. 安裝jquery

    npm i -D jquery
    
  2. 配置webpack

    plugins:[
        new webpack.ProvidePlugin({
          $: 'jquery', 
        })
    ]
    
    
  3. 在JS文件中就可直接使用jquery,不用導入。

3.9 其他

3.9.1 clean-webpack-plugin

webpack打包的文件都放在dist文件夾下,但webpack無法追蹤到哪些文件是實際項目中用到的。所以建議在每次構建前都清理下dist文件夾。

  1. 本地安裝插件

    npm i -D clean-webpack-plugin
    
  2. 配置webpack

    const CleanWebpackPlugin=require('clean-webpack-plugin');
    
    new CleanWebpackPlugin(['dist'])
    

3.9.2 Webpack-merge

實際項目開發中,一般會有三份配置文件:

  • webpack.dev.js 開發環境:devserver配置,生成SourceMap
  • webpack.prod.js 生產環境:壓縮代碼
  • webpack.common.js 公共配置

可使用webpack-merge合併配置。

  1. 本地安裝

    npm i -D webpack-merge
    
  2. 拆分webpack配置

    webpack.prod.js:
    
    const merge = require("webpack-merge");
    const common = require("./webpack.common.js");     //引入公共配置
    const CleanWebpackPlugin = require("clean-webpack-plugin");
    
    module.exports = merge(common, {                   //合併配置
            plugins: [
                new CleanWebpackPlugin(["dist"])
            ],
    });
    
  3. 修改package.json的script字段

    "build": "webpack --config webpack.prod.js",
    "start": "webpack-dev-server --config webpack.dev.js"
    

3.9.3 source map

React, ES6等經過webpack轉換後,代碼可讀性非常差,不利於在瀏覽器中調試代碼。可通過加載 Source Map 文件,在瀏覽器中調試源碼。

各種source map的差異見:https://github.com/webpack/webpack/tree/master/examples/source-map

devtool: "cheap-module-eval-source-map"  //開發環境

4. 優化

4.1 模式

我們在日常的前端開發工作中,一般都會有開發&生產兩套構建環境。

webpack 4.X新增用mode字段指定當前環境,並啓用相應模式下的webpack內置的優化。

module.exports = {
  mode: 'production'
};
選項 描述
development (開發環境) process.env.NODE_ENV =development
並啓用以下插件:
NamedChunksPlugin , NamedModulesPlugin
production (生產環境) process.env.NODE_ENV =production
並啓用以下插件:
FlagDependencyUsagePlugin , FlagIncludedChunksPlugin , ModuleConcatenationPlugin , NoEmitOnErrorsPlugin , OccurrenceOrderPlugin , SideEffectsFlagPlugin , UglifyJsPlugin

可通過optimization字段,手動配置或覆蓋mode配置。

4.2 代碼壓縮

4.2.1 壓縮JS文件

將mode設置成production,在此模式下optimization.minimize默認爲true, webpack 會自動調用UglifyjsWebpackPlugin壓縮JS代碼。

若不需要壓縮,可將optimization.minimize設置爲false。

  1. 配置webpack.prod.js

    mode: "production"
    
    optimization: {
            // minimize:false,     //若不需要進行壓縮,可使用此句禁用
            // minimizer: [         //用於指定使用的壓縮插件或自定義UglifyjsWebpackPlugin插件配置,覆蓋默認配置。
            //     new UglifyJsPlugin({ /* your config */ })
            // ]
    },
    

4.2.2 壓縮CSS文件

3.7章節介紹瞭如何加載樣式,但加載後的樣式是寫在JS文件中的。隨着項目越來越大,js文件也會越來越大,所以,我們就需要對css文件進行分離並壓縮。

  1. 本地安裝插件

    npm i -D mini-css-extract-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin
    
  2. 配置webpack:分離css文件

    webpack.common.js:
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    
    module.exports = {
      plugins: [
        new MiniCssExtractPlugin({
          filename: "[name].css",    // [name]爲chunk名稱
        })
      ],
      module: {
        rules: [
          {
            test: /\.(sc|c)ss$/,
            use: [
                MiniCssExtractPlugin.loader,   // style-loader改用MiniCssExtractPlugin
                'css-loader',
                'sass-loader',
            ],
          }
        ]
      }
    }
    
    注:在Webpack4上用extract-text-webpack-plugin會出錯,可以安裝beta版本extract-text-webpack-plugin@next。
    
  3. 配置webpack:壓縮css文件

    webpack.prod.js:
    const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); //用於壓縮CSS代碼
    const UglifyJsPlugin = require("uglifyjs-webpack-plugin");  //用於壓縮JS代碼
    
    optimization: {
            minimizer: [     
                new UglifyJsPlugin({}),
                new OptimizeCSSAssetsPlugin({})  //壓縮css
            ]
        },
    

4.2.3 壓縮HTML文件

HtmlWebpackPlugin支持壓縮輸出的HTML文件。

 plugins:[
    new HtmlWebpackPlugin({
    	template:'src/index.html', 
    	minify:{                     //壓縮輸出
            collapseWhitespace:true   //摺疊空白區域
            minifyCSS: true, 	// 壓縮 HTML 中出現的 CSS 代碼
        	minifyJS: true 		// 壓縮 HTML 中出現的 JS 代碼
    	}
    })
]

4.2.4 壓縮圖片

  1. 本地安裝

    npm i -D image-webpack-loader
    
  2. 配置webpack

    {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
            {
                loader: 'url-loader',
                options: {
                    limit: 1024 * 30,         
                    fallback: 'file-loader',  
                    outputPath: 'images',    
                }
            },
        	'image-webpack-loader'        // 壓縮圖片
        ]
    },
    

4.3 Tree Sharing

Tree Shaking 可以用來剔除 JavaScript 中用不上的代碼。

Tree Shaking要求:

  • 必須遵循 ES6 的模塊規範(即 import 和 export)。
  • 在項目 package.json 文件中,添加一個 “sideEffects” 屬性。作用於引入重導出的模塊。
  • 引入一個能夠刪除未引用代碼(dead code)的壓縮工具(minifier)(例如 UglifyJSPlugin)。
  1. 新建util.js

    export funcA from './TESTA'
    export funcB from './TESTB'
    
    TESTA.js:
    import React from 'react';
    export default function funcA() {
      return <h1>I'm funcA</h1>;
    }
    console.log("funcAfuncAfuncA")   // 副作用代碼
    
    TESTB.js:
    import React from 'react';
    export default function funcB() {
      return <h1>I'm funcB</h1>;
    }
    console.log("funcBfuncBfuncB")  // 副作用代碼
    
  2. 在A.js中引用util.funcA,則funcB就是用不上的代碼。

    import {funcA} from '../util';
    
  3. 修改babel-loader配置。

    presets: [ 
        [
            "env",
            {modules: false}  // 關閉Babel的模塊轉換功能,保留原本的ES6模塊化語法
        ],
        "react"
    ]
    
  4. package.json添加sideEffects字段: 將文件標記爲無副作用

    "sideEffects": ["*.css","*.scss"]  //避免樣式文件被刪除
    
    注:此字段是webpack4新增的。
    
  5. 開啓壓縮,參照4.2.1

    執行編譯後,A.bundle.js無funcB相關代碼。

4.4 提取公共代碼

提取公共代碼的原理:用戶第一次訪問頁面後,頁面公共代碼的文件已經被瀏覽器緩存起來。用戶切換到其它頁面時,存放公共代碼的文件就不會再重新加載,而是直接從緩存中獲取。 加快了其他頁面的訪問速度,減少了網絡傳輸流量。

3種拆分方案瀏覽器的加載情況:

  • 未拆分代碼:所有代碼被打包到一個JS文件中,當修改了代碼,瀏覽器就得重新加載所有代碼。
  • 拆分公共代碼:代碼被打包成兩個JS文件(公共代碼 & 其餘代碼),修改其中一個文件的代碼,瀏覽器只需要重新加載修改的代碼文件。但第三方庫的代碼也屬於公共代碼,若修改了非第三方庫的公共代碼,那麼瀏覽器也會重新加載不常變更的第三方庫代碼。
  • 拆分公共代碼&第三方庫:代碼被打包成3個以上的JS文件。

mode=production時,webpack 4.X 會默認對代碼進行拆分,拆分的規則是:

  • 模塊被重複引用,或者是來自node_modules中的模塊。
  • 文件大於30kb。
  • 在按需加載時,請求數量小於等於5。
  • 在初始化加載時,請求數量小於等於3。

注:webpack 4.x 通過optimization.splitChunks配置, webpack 4.x之前可用CommonsChunkPlugin。

默認配置:

optimization: {
    splitChunks: {
      chunks: "async", //異步加載的模塊
      minSize: 30000, //模塊大於30k會被抽離到公共模塊
      minChunks: 1, //被引用超過1次
      maxAsyncRequests: 5, //異步模塊,一次最多隻能被加載5個
      maxInitialRequests: 3, //入口模塊最多隻能加載3個
    },
  },
}

拆分公共代碼配置:

optimization: {
    splitChunks: {
      chunks: "all", // 所有的 chunks 代碼公共的部分分離出來成爲一個單獨的文件
    },
  },
}

拆分公共代碼&第三方庫配置:

  • 根據所使用的技術棧,找出所有頁面都需要用到的基礎庫。如react、react-dom 等庫,把它們提取到一個單獨vendor.js文件。
  • 在剔除了各個頁面中被 vendor.js 包含的部分代碼外,再找出所有頁面都依賴的公共部分的代碼提取出來放到 common.js中去。
  • 再爲每個頁面都生成一個單獨的文件,這個文件中不再包含 vendor.js 和 common.js中包含的部分,而只包含各個頁面單獨需要的部分代碼。
optimization: {
    splitChunks: {
            cacheGroups: {           //緩存組
                commons: {           //提取入口文件之間的公共代碼
                    chunks: 'all',   //塊的範圍,有三個可選值:initial、async、all,默認爲all
                    minChunks: 2,    //被引用次數
                    minSize: 0,      //文件大小
                    name: "common"   //拆分出來塊的名字
                },
                vendor: {
                    chunks: "all",
                    test: /node_modules/,//控制哪些模塊被這個緩存組匹配到
                    name: "vendor",
                    priority: 10,
                },
            },
        }
}
new HtmlWebpackPlugin({
    chunks: ['A','common','vendor'],           //引入拆分出來的chunk
    filename:'A.html',      
    template:'template.html', 
    minify:{                 
    	collapseWhitespace:true  
    }
}),

編譯完成後dist目錄結構如下:

│  A.bundle.js
│  A.css
│  A.html
│  B.bundle.js
│  B.css
│  B.html
│  common.bundle.js           //A和B之間的公共代碼
│  vendor.bundle.js           //node_modules下的第三方依賴代碼
├─images
│      230280f0ff880c8273b99fdae150dc96.png
└─public
        ico.png

4.5 優化loader配置

使用 Loader 時可以通過 test 、 include、 exclude 三個配置項來命中 Loader 要應用規則的文件。 爲了儘可能少的讓文件被 Loader 處理,可以通過 include && exclude 去命中/排除文件。

示例配置:

babel-loader:
exclude: path.resolve(__dirname, 'node_modules')

url-loader:
include: path.resolve(__dirname, 'src/assets')

4.6 Resolve

Webpack 在啓動後會從配置的入口模塊出發找出所有依賴的模塊,Resolve 配置 Webpack 如何尋找模塊所對應的文件。

模塊引入方式:

import * as m from './index.css'   // 相對路徑
import React from 'react'         // 模塊名
  • 解析相對路徑
    1. 查找相對當前模塊的路徑下是否有對應文件或文件夾
    2. 是文件則直接加載
    3. 是文件夾則繼續查找文件夾下的 package.json 文件
    4. 有 package.json 文件則按照文件中 browser/module/main 字段的文件名來查找文件 (配置項:resolve.mainFields )
    5. 無 package.json 或者無 browser/module/main字段則查找 index.js 文件 (配置項:resolve.mainFiles)
  • 解析模塊名
    1. 查找當前文件目錄下,父級目錄及以上目錄下的 node_modules 文件夾,看是否有對應名稱的模塊 (配置項:resolve.modules)

4.6.1 resolve.modules

resolve.modules 用於配置 Webpack 去哪些目錄下尋找第三方模塊。

默認值是 [‘node_modules’],作用:

  • 先去當前目錄下的node_modules目錄下查找第三方模塊。
  • 如果沒找到,就去上級目錄的node_modules目錄下查找。
  • 還是沒找到,再去上級的node_modules目錄下查找。
  • 直到根目錄。

通常安裝的第三方模塊都放在項目根目錄下的 node_modules 目錄下,就沒有必要按照默認的方式去一層層的尋找,可以指明存放第三方模塊的絕對路徑,以減少尋找。

配置如下:

module.exports = {
  resolve: {
    // 使用絕對路徑指明第三方模塊存放的位置,以減少搜索步驟
    modules: [path.resolve(__dirname, 'node_modules')]
  },
};

4.6.2 resolve.extensions

resolve.extensions 用於配置在嘗試過程中用到的後綴列表,默認是:

extensions: ['.js', '.json']

如果這個列表越長,或者正確的後綴在越後面,就會造成嘗試的次數越多。

建議:

  • 後綴嘗試列表要儘可能的小,不要把項目中不可能存在的情況寫到後綴嘗試列表中。
  • 頻率出現最高的文件後綴要優先放在最前面,以做到儘快的退出尋找過程。
  • 在源碼中寫導入語句時,要儘可能的帶上後綴,從而可以避免尋找過程。例如在你確定的情況下把 require('./data') 寫成 require('./data.json')

相關 Webpack 配置如下:

module.exports = {
  resolve: {
    // 儘可能的減少後綴嘗試的可能性
    extensions: ['.js'],
  },
};

4.6.3 resolve.alias

resolve.alias 配置項通過別名來把原導入路徑映射成一個新的導入路徑,可簡化import/require路徑。

resolve:{
  alias:{
    'assets': path.resolve(__dirname, 'src','assets')   
                //把導入語句裏的 assets 關鍵字替換成 根目錄/src/assets/
  }
}

import img from 'assets/images/1.png' ==> import img from 'xx/src/assets/images/1.png'

css中的url不支持。

解決在webstorm中無法解析alias後的路徑:

  1. 在webpack settings中指定webpack配置文件的路徑。
  2. 由於我們腳手架的webpack配置中resolve.alias配置是直接寫在alias字段下的,webstorm無法解析。所以需要在配置文件中配置resolve.alias字段。

4.7 Hash

在打包出來的文件名上加上文件內容的hash是目前最常見的有效使用瀏覽器長緩存的方法,js文件如果有內容更新,hash就會更新,瀏覽器請求路徑變化所以更新緩存,如果js內容不變,hash不變,直接用緩存。

hash類型:

  • hash 跟整個項目的構建相關,只要項目裏有文件更改,整個項目構建的hash值都會更改,並且全部文件都共用相同的hash值。
  • chunkhash 和hash不一樣,它根據不同的入口文件(Entry)進行依賴文件解析、構建對應的chunk,生成對應的哈希值。
  • contenthash 更細緻地根據內容更改,生成對應的哈希值。爲css文件生成獨立的hash,讓css文件不受js文件的影響。

webpack配置:

output:{                
        filename:'[name].[chunkhash:8].bundle.js', 
        path: path.resolve(__dirname, 'dist') 
    },
plugins:[
        new MiniCssExtractPlugin({
            filename: "[name].[contenthash:8].css",
        }),
    ],

4.8 遺留

4.8.1 熱更新 (react-hot-loader)

  1. 安裝

    yarn add react-hot-loader
    
  2. webpack配置

    1.使用HotModuleReplacementPlugin插件
        plugins: [
            new webpack.HotModuleReplacementPlugin(),
        ],
    2.開啓devServer的模塊熱替換
        devServer:{
            hot: true,                 //開啓模塊熱替換
        },
    3.在babel-loader中使用react-hot-loader/babel插件
        plugins: ["react-hot-loader/babel"             
    
  3. 入口文件設置

    import React, { Component } from 'react';
    import { AppContainer } from 'react-hot-loader';  
    import ReactDOM from 'react-dom';
    require('./a.css');
    import Test from './Test';
    
    
    function render(RootElement) {
      ReactDOM.render(
        <AppContainer>
          <RootElement />
        </AppContainer>,
        document.getElementById('app')
      );
    }
    
    render(Test);
    
    if (module.hot) {
      module.hot.accept('./Test', () => {
        render(Test);
      });
    }
    

4.8.2 Proxy

proxy 用於配置 webpack-dev-server 將特定 URL 的請求代理到另外一臺服務器上。例如:

proxy: {
    '/api/test': {
        target: "http://localhost:3000", // 將URL中帶有/api/test的請求代理到本地的3000端口的服務上
        pathRewrite: { '^/api': '' }, // 把URL中path部分的api移除掉
    },
}
  1. 請求到 /api/test 會被代理到請求 http://localhost:3000/api/users
  2. 請求到 /api/test 會被代理到請求 http://localhost:3000/users

 


想要整理更多的碎片知識,掃碼關注下面的公衆號,讓我們在哪裏接着嘮!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章