webpack 手摸手學習系列之性能優化的 HRM、source-map 和 oneOf

一、webpack 性能優化之 HRM

  1. 創建空文件夾,通過 npm init 命令初始化 package.json 文件,通過 npm install webpack webpack-cli -g 命令全局下載 webpackwebpack-cli,通過 npm install webpack webpack-cli -D 命令本地下載 webpackwebpack-cli,通過 npm i style-loader css-loader -D 命令下載 style-loadercss-loader,通過 npm i less less-loader -D 命令下載 lessless-loader,通過 npm i html-webpack-plugin -D 命令下載 html-webpack-plugin 插件,通過 npm i url-loader file-loader -D 命令下載 url-loaderfile-loader,通過 npm i html-loader -D 命令下載 html-loader

  2. 創建 src 文件夾,在裏面創建 cssjsimgsmedia 文件夾,以及 index.htmlcss 文件夾中放 index.less 和字體圖標資源樣式,js 文件夾中放 index.jsprint.jsimgs 文件夾中放圖片資源,media 文件夾放字體圖標的相關資源,代碼如下所示:

  • index.less

    #box {
        width: 200px;
        height: 200px;
        background-image: url('../imgs/angular.jpg');
        background-repeat: no-repeat;
        background-size: 100% 100%;
    }
    
  • index.js

    // 引入
    import print from './print.js';
    import '../css/iconfont.css';
    import '../css/index.less';
    
    
    console.log('index.js 文件被加載')
    
    print()
    
    function add(x, y) {
      return x + y;
    }
    
    console.log(add(1, 2));
    
    if (module.hot) {
      module.hot.accept('./print.js', function () {
        print();
      })
    }
    
  • print.js

    // 引入
    import print from './print.js';
    import '../css/iconfont.css';
    import '../css/index.less';
    
    
    console.log('index.js 文件被加載')
    
    print()
    
    function add(x, y) {
      return x + y;
    }
    
    console.log(add(1, 2));
    
    if (module.hot) {
      module.hot.accept('./print.js', function () {
        print();
      })
    }
    
  • index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>開發環境</title>
    </head>
    <body>
        <h1>開發環境配置</h1>
        <span class="iconfont icon-icon-test"></span>
        <span class="iconfont icon-icon-test2"></span>
        <span class="iconfont icon-icon-test3"></span>
        <span class="iconfont icon-icon-test1"></span>
        <div id="box"></div>
        <img src="./imgs/vue.jpg" alt="vue">
        <img src="./imgs/react.png" alt="react">
    </body>
    </html>
    
  1. 創建 webpack.config.js 文件,通過 require 引入 pathhtml-webpack-pluginentry 是入口文件,output 是出口文件,filename 是打包輸出後的文件名,path 是輸出路徑。moduleloader 的配置,rules 是詳細的 loader 配置。第一個規則是處理 less 資源,匹配以 .less 結尾的文件,使用的 loaderstyle-loadercss-loaderless-loader。第二個規則是處理 css 資源, 匹配以 .css 結尾的文件,使用的 loaderstyle-loadercss-loader。第三個規則是處理圖片資源,匹配以 jpgpnggif 結尾的文件圖片資源,使用的 loaderurl-loader,進行 options 配置,limit 是限制圖片的大小小於8kb,就會被base64處理,給圖片做優化,name 給圖片做重命名,取 hash 值的前 10 位,取文件的原來擴展名,關閉 es6 模塊化,輸出路徑爲 imgs。第四個規則是 處理 html 中的 img 資源,匹配以 .html 結尾的文件,使用的 loaderhtml-loader。由於處理圖片資源採用的是 ES6 module 解析,而 html 文件中的 img 資源使用 commonJS 解析,會造成衝突,需要關閉 ES6 module。第五個規則是處理其它資源,使用 exclude 不包括上面的所有資源,html|css|js|less|jpg|png|gif,使用 file-loader,通過 options 配置 name 的值爲取 hash 值的前 10 位,取文件的原來擴展名,通過 outputPath 設置輸出路徑爲 mediaplugins 裏面是一些插件配置,通過 new HtmlWebpackPlugin(),複製裏面的文件,並自動引入打包輸出的所有資源(JS/CSS),設置 mode 模式爲 development 開發模式。在 devServer 中,contentBase 是項目構建後路徑,compress 是啓動 gzip 壓縮,port 是端口號,open 是自動打開瀏覽器,hot 是開啓 HRM 功能,代碼如下所示:
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: ['./src/js/index.js', './src/index.html'],
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          esModule: false,
          outputPath: 'imgs'
        }
      },
      {
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
        exclude: /\.(html|js|css|less|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]',
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    hot: true
  }
};

  1. HMRhot module replacement,熱模塊替換,也可以說是模塊熱替換。作用是一個模塊發生變化,只會重新打包這一個模塊,而不是打包所有模塊,極大提升構建速度,HRM 在樣式文件、JS 文件和 HTML 文件的不同應用:
  • 樣式文件:可以使用 HMR 功能,因爲 style-loader 內部實現了
  • JS 文件:默認不能使用 HMR 功能,需要修改 js 代碼,添加支持 HMR 功能的代碼。注意的是,HMR 功能對 js 的處理,只能處理非入口 js 文件的其他文件,添加代碼, 全局尋找 module 這個變量,有沒有 hot 這個屬性,一旦 module.hottrue,說明開啓了 HMR 功能,方法會監聽 print.js 文件的變化,一旦發生變化,其他模塊不會重新打包構建,會執行後面的回調函數,如下所示:
if (module.hot) {
  module.hot.accept('./print.js', function () {
    print();
  })
}
  • HTML 文件:默認不能使用 HMR 功能.同時會導致問題,html 文件不能熱更新了,不用做 HMR 功能。如果需要做,修改 entry 入口,將 html文件引入,代碼如下所示:
entry: ['./src/js/index.js', './src/index.html']
  1. 在命令行輸入 npx webpack-dev-server 命令,項目就會自動打包編譯。每次在更改完代碼後,自動編譯看到最新的顯示內容。在 build 打包文件中,也可以看到打包後的 cssjsimgsmedia 文件夾,以及 index.html

二、webpack 性能優化之 source-map

  1. 在上面 HRM 熱模塊替換的項目中,修改 webpack.config.js 文件,添加 devtool 來指定不同的 source-map,代碼如下所示:

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: ['./src/js/index.js', './src/index.html'],
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          esModule: false,
          outputPath: 'imgs'
        }
      },
      {
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
        exclude: /\.(html|js|css|less|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]',
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    hot: true
  },
  devtool: 'eval-source-map'
};

  1. source-map 是一種 提供源代碼到構建後代碼映射 技術,如果構建後代碼出錯了,通過映射可以追蹤源代碼錯誤,不同 source-map 之間的比較,如下所示:
類型 類型描述 類型方式
source-map 錯誤代碼準確信息和源代碼的錯誤位置 外部
inline-source-map 只生成一個內聯source-map,錯誤代碼準確信息和源代碼的錯誤位置 內聯
hidden-source-map 錯誤代碼錯誤原因,但是沒有錯誤位置,不能追蹤源代碼錯誤,只能提示到構建後代碼的錯誤位置 外部
eval-source-map 每一個文件都生成對應的source-map,都在eval,錯誤代碼準確信息 和源代碼的錯誤位置 內聯
nosources-source-map 錯誤代碼準確信息, 但是沒有任何源代碼信息 外部
cheap-source-map 錯誤代碼準確信息 和 源代碼的錯誤位置,只能精確的行 外部
cheap-module-source-map 錯誤代碼準確信息 和 源代碼的錯誤位置,module會將loader的source map加入 外部
  1. 內聯和外部的區別:
  • 外部生成了文件,內聯沒有
  • 內聯構建速度更快
  1. 開發環境需要速度快,調試更友好。在速度快中,eval>inline>cheap>...,而 eval-source-map 優於 eval-cheap-souce-map。在調試友好中,cheap-souce-map 優於 cheap-module-souce-map。所以,在開發環境中,最快調試,調試更加友好,可以選擇 eval-source-map 或者是 eval-cheap-module-souce-map

  2. 在生產環境中,需要考慮源代碼的隱藏和調試的友好。內聯會讓代碼體積變大,所以在生產環境不用內聯。nosources-source-map 是全部隱藏, hidden-source-map 是隻隱藏源代碼,會提示構建後代碼錯誤信息。所以,在生成環境中,可以選擇 source-map 或者是 cheap-module-souce-map

  3. 在命令行輸入 npx webpack-dev-server 命令,項目就會自動打包編譯。每次在更改完代碼後,自動編譯看到最新的顯示內容。在 build 打包文件中,也可以看到打包後的 cssjsimgsmedia 文件夾,以及 index.html

三、webpack 性能優化之 oneOf

  1. 創建空文件夾,通過 npm init 命令初始化 package.json 文件,通過 npm install webpack webpack-cli -g 命令全局下載 webpackwebpack-cli,通過 npm install webpack webpack-cli -D 命令本地下載 webpackwebpack-cli,通過 npm i html-webpack-plugin mini-css-extract-plugin optimize-css-assets-webpack-plugin -D 命令下載 html-webpack-pluginmini-css-extract-pluginoptimize-css-assets-webpack-plugin,通過 npm i eslint-loader eslint -D 命令下載 eslint-loadereslint,通過 npm i eslint-config-airbnb-base eslint-plugin-import eslint -D 命令下載 eslint-config-airbnb-baseeslint-plugin-importeslinteslint-config-airbnb-base 依賴於 eslint-plugin-importeslint,通過 npm i postcss-loeader postcss-preset-env -D 命令下載 postcss-loeaderpostcss-preset-env,通過 npm i css-loader less-loader -D 命令下載 css-loaderless-loader,通過 npm i url-loader file-loader html-loader -D 命令下載 url-loaderfile-loaderhtml-loader,通過 npm i babel-loader @babel/core @babel/preset-env -D 命令下載 babel-loader@babel/core@babel/preset-env,通過 npm i @babel/polyfill core-js -D 命令下載 @babel/polyfillcore-js

  2. 創建 src 文件夾,在裏面創建 index.jsindex.htmla.cssb.css 文件,代碼如下所示:

  • index.js
    import '../css/a.css';
    import '../css/b.css';
    
    function add(x, y) {
      return x + y;
    }
    
    console.log(add(2, 5));
    
    
  • index.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>webpack</title>
    </head>
    <body>
        <h1>hello webpack</h1>
        <div id="box1"></div>
        <div id="box2"></div>
    </body>
    </html>
    
  • a.css
    #box1 {
        width: 100px;
        height: 100px;
        background-color: pink;
    }
    
  • b.css
    #box2 {
        width: 200px;
        height: 200px;
        background-color: deeppink;
    }
    
  1. 創建 webpack.config.js 文件,通過 require 引入 pathhtml-webpack-pluginmini-css-extract-pluginoptimize-css-assets-webpack-plugin。通過 process.env.NODE_ENV 定義 nodejs 環境變量:決定使用 browserslist 的哪個環境。定義一個複用 loader,在下面匹配 .css.less 文件的時候,需要重複使用。通過 MiniCssExtractPlugin.loader 提取 css 單獨文件,使用 css-loader,使用 postcss-loadercss 的兼容性處理,同時使用 postcss-preset-envcss 的按需加載的兼容性處理,optionspostcss-loader 的配置項,ident 是固定寫法,plugins 是使用 postcss-preset-env 這個插件。entry 是入口文件,output 是出口文件,filename 是輸出的文件名,path 是文件的輸出路徑。moduleloader 的配置,rules 是詳細的 loader 配置。在 rules 中使用 oneOf,以下的 loader 只會匹配一個,不能有兩個配置處理同一種類型文件,如果有需要提取出來。第一個規則是匹配以 .css 結尾的文件,通過 use 使用 commonCssLoader 這個抽出複用的 loader。第二個規則是匹配以 .less 結尾的文件,通過 use 使用 commonCssLoader 這個抽出複用的 loaderless-loader。第三個規則是匹配以 .js 結尾的文件,通過 exclude 不包括 node_modules,通過 enforce 優先執行,通過 loader 使用 eslint-loader,通過 options 設置自動修復 eslint 的錯誤。第四個規則是匹配以 .js 結尾的文件,通過 exclude 不包括 node_modules,通過 loader 使用 babel-loader,通過 options 進行配置,presets 爲預設,指示 babel 做怎麼樣的兼容性處理,使用 babel/preset-env 這個預設。通過 useBuiltIns 按需加載,通過 corejs 指定 core-js 版本,通過 targets 指定兼容性做到哪個版本瀏覽器。js 兼容性處理需要使用到 babel-loader@babel/core@babel/preset-env。如果是基本 js 兼容性處理,需要使用 @babel/preset-env,但是隻能轉換基本語法,如promise高級語法不能轉換。如果使用全部 js 兼容性處理,需要使用 @babel/polyfill ,但是隻要解決部分兼容性問題,但是將所有兼容性代碼全部引入,體積太大了,所有就需要做兼容性處理的就做做兼容性處理的就做,使用 core-js。第五個規則是匹配以 .jpgpnggif 格式的圖片資源,通過 loader 使用 url-loaderurl-loader 依賴於 file-loader。進行 options 配置,設置 limit8 * 1024,圖片大小小於 8kb,就會被 base64 處理,給圖片做優化。這樣做的優點是減少請求數量(減輕服務器壓力),缺點是圖片體積會更大(文件請求速度更慢)。設置 esModulefalse,由於 url-loader 默認使用 es6 模塊化解析,而 html-loader 引入圖片是 commonjs ,解析時會出問題爲 [object Module],所以關閉 url-loaderes6 模塊化,使用commonjs 解析。設置 name[hash:10].[ext],給圖片進行重命名,因爲圖片值爲 hash[hash:10] 取圖片的 hash 的前 10 位,[ext]取文件原來擴展名。第三個規則是匹配以 .html 結尾的文件,使用 loaderhtml-loader,處理 html 文件的 img 圖片(負責引入 img,從而能被 url-loader 進行處理)。第六個規則是匹配以 .html 結尾的文件,通過 loader 使用 html-loader。第七個規則是匹配其它資源,通過 loader 使用 file-loader,通過 options 進行輸出路徑配置,輸出文件夾爲 mediaplugins 裏面是一些插件配置,通過 MiniCssExtractPlugin 插件提取 css 以及對輸出的 css 文件進行重命名,通過 OptimizeCssAssetsWebpackPlugincss 文件進行壓縮,通過 HtmlWebpackPlugin 複製裏面的文件,並自動引入打包輸出的所有資源(JS/CSS),進行 minify 配置,通過 collapseWhitespace 移除空格,通過 removeComments 移除註釋,壓縮 html 文件。設置 modeproduction,在生產環境下 webpack 會自動壓縮 js 代碼,代碼如下所示:

const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

process.env.NODE_ENV = 'production'


const commonCssLoader = [
    MiniCssExtractPlugin.loader,
    'css-loader',
    {
        loader: 'postcss-loader',
        options: {
            ident: 'postcss',
            plugins: () => [
                require('postcss-preset-env')()
            ]
        }
    }
]

module.exports = {
    entry: 'src/js/index.js',
    output: {
        filename: 'built.js',
        path: resolve(__dirname, 'build')
    },
    module: {
        rules:[
            {
                test: /\.js$/,
                exclude: /node_modules/,
                enforce: 'pre',
                loader: 'eslint-loader',
                options: {
                    fix: true
                }
            },
            {
                oneOf: [
                    {
                        test: /\.css$/,
                        use: [...commonCssLoader]
                    },
                    {
                        test: /\.less$/,
                        use: [...commonCssLoader, 'less-loader']
                    },
                    {
                        test: /\.js$/,
                        exclude: /node_modules/,
                        loader: 'babel-loader',
                        options: {
                            presets: [
                                '@babel/preset-env',       
                                {
                                    useBuiltIns: 'usage',
                                    corejs: {
                                        version: 3
                                    },
                                    targets: {
                                        chrome: '60',
                                        firefox: '60',
                                        ie: '9',
                                        safari: '10',
                                        edge: '17'
                                    }
                                }
                            ]
                        }
                    },
                    {
                        test: /\.(jpg|png|gif)/,
                        loader: 'url-loader',
                        options: {
                            limit: 8 * 1024,
                            name: '[hash:].[ext]',
                            outputPath: 'imgs',
                            esModule: false
                        }
                    },
                    {
                        test: /\.html$/,
                        loader: 'html-loader'
                    },
                    {
                        test: /\.(js|css|less|html|jpg|png|gif)/,
                        loader: 'file-loader',
                        options: {
                            outputPath: 'media'
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'css/built.css'
        }),
        new OptimizeCssAssetsWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            minify: {
                collapseWhitespace: true,
                removeComments: true
            }
        })
    ],
    mode: 'production'
}
  1. package.json 這個文件中,進行配置 browserslist,在 css 兼容處理的時候,需要幫 postcss 找到 package.jsonbrowserslist 裏面的配置,通過配置加載指定的css兼容性樣式,development 爲開發環境配置,last 1 chrome version 表示兼容最近的 chrome 版本,last 1 firefox version 表示兼容最近的 firefox 版本,last 1 safari version 表示兼容最近的 safari 版本。production 爲生產環境配置,>0.2% 表示大於 99.8% 的瀏覽器,not dead 表示不要已經死的瀏覽器,not op_mini all 表示不要使用 op_mini all 瀏覽器。 eslint 並不知道要檢查什麼,所有就有 airbnb 風格指南,airbnb 需要使用 eslint-config-airbnb-base 這個庫,這個庫依賴於 eslint-plugin-importeslint。所有需要在 package.jsoneslintConfig 中設置檢查規則,設置 extendsairbnb-base,代碼如下所示:
"browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  },
  "eslintConfig": {
    "extends": "airbnb-base"
  }
  1. 在命令行輸入 webpack 命令,資源就會進行打包,以及相應的壓縮代碼。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章