1. 程式人生 > >從0搭建自己的webpack開發環境(五)

從0搭建自己的webpack開發環境(五)

往期回顧:

從0搭建自己的webpack開發環境(一)

從0搭建自己的webpack開發環境(二)

從0搭建自己的webpack開發環境(三)

從0搭建自己的webpack開發環境(四)

前四篇文章我們已經掌握了webpack各種常見的配置,這一片文章我們來看看如何實現webpack中的優化。

我們先來編寫最基本的webpack配置,然後依次實現其中的各種優化。

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = mode => {
  return {
    mode: mode,
    entry: "./src/main.js",
    output: {
      filename: "bundle.js",
      path: path.resolve(__dirname, "dist")
    },
    module: {
      rules: [
        {
          test: /\.(png|jpg|gif)$/,
          use: "file-loader"
        },
        {
          test: /\.js$/,
          use: "babel-loader" // .babelrc已經配置支援react
        },
        {
          test: /\.css$/,
          use: [
            mode !== "development"
              ? MiniCssExtractPlugin.loader
              : "style-loader",
            "css-loader"
          ]
        }
      ]
    },
    plugins: [
      new PurgecssPlugin({
        paths: glob.sync(`${path.join(__dirname, "src")}/**/*`, { nodir: true }) // 不匹配目錄,只匹配檔案
      }),
      mode !== "development" &&
        new MiniCssExtractPlugin({
          filename: "css/[name].css"
        }),
      new HtmlWebpackPlugin({
        template: "./src/template.html",
        filename: "index.html"
      })
    ].filter(Boolean)
  };
};

.babelrc配置檔案

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react"
    ]
}

1.刪除無用的Css樣式

先來看看編寫的程式碼

import './style.css'
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(<div>hello</div>,document.getElementById('root'));
body{
    background: red
}
.class1{
    background: red
}

這裡的.class1顯然是無用的,我們可以搜尋src目錄下的檔案,刪除無用的樣式

const glob = require('glob');
const PurgecssPlugin = require('purgecss-webpack-plugin');

// 這裡需要配合mini-css-extract-plugin外掛
mode !== "development" && new PurgecssPlugin({
    paths: glob.sync(`${path.join(__dirname, "src")}/**/*`, { nodir: true }) // 不匹配目錄,只匹配檔案
}),

2.圖片壓縮外掛

打包後的圖片進行優化

npm install image-webpack-loader --save-dev

file-loader之前使用壓縮圖片外掛

loader: "image-webpack-loader",
options: {
  mozjpeg: {
    progressive: true,
    quality: 65
  },
  // optipng.enabled: false will disable optipng
  optipng: {
    enabled: false,
  },
  pngquant: {
    quality: [0.90, 0.95],
    speed: 4
  },
  gifsicle: {
    interlaced: false,
  },
  // the webp option will enable WEBP
  webp: {
    quality: 75
  }
}

可以發現圖片大小是否有了明顯的變化

3.CDN載入檔案

我們希望通過cdn的方式引入資源

const AddAssetHtmlCdnPlugin = require('add-asset-html-cdn-webpack-plugin')
new AddAssetHtmlCdnPlugin(true,{
    'jquery':'https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js'
})

但是在程式碼中還希望引入jquery來獲得提示

import $ from 'jquery'
console.log('$',$)

但是打包時依然會將jquery進行打包

externals:{
  'jquery':'$'
}

在配置檔案中標註jquery是外部的,這樣打包時就不會將jquery進行打包了

4.Tree-shaking && Scope-Hoisting

4.1 Tree-shaking

顧名思義就是將沒用的內容搖晃掉,來看下程式碼:

main.js

import { minus } from "./calc";
console.log(minus(1,1));

calc.js

import {test} from './test';
export const sum = (a, b) => {
  return a + b + 'sum';
};
export const minus = (a, b) => {
  return a - b + 'minus';
};

test.js

export const test = ()=>{
    console.log('hello')
}
console.log(test());

觀察上述程式碼會發現我們主要使用minus方法,test.js程式碼其實是有副作用的!

預設mode:production時,會自動tree-shaking,但是打包後'hello'依然會被打印出來,這時候我們需要配置規避副作用!

package.json中配置

"sideEffects":false,

如果這樣設定,預設就不會匯入css檔案啦,因為我們引入css也是通過import './style.css'的;

這裡重點就來了,tree-shaking主要針對es6模組,我們可以使用require語法匯入css,但是這樣用起來又有點格格不入,所以我們配置css檔案不起副作用。

"sideEffects":[
    "**/*.css"
]

在開發環境下預設tree-shaking不會生效,可以配置標識提示。

optimization:{
  usedExports:true
}

4.2 Scope Hoisting

作用域提升,可以減少程式碼體積,節約記憶體

let a = 1;
let b = 2;
let c = 3;
let d = a+b+c;
export default d;
// 引入d
import d from './d';
console.log(d);

最終打包後的結果會變成 console.log(6)

  • 程式碼量明顯減少
  • 減少多個函式後記憶體佔用也將減少

5.DllPlugin && DllReferencePlugin

每次構建時第三方模組都需要重新構建,這個效能消耗比較大,我們可以先把第三方庫打包成動態連結庫,以後構建時只需要查詢構建好的庫就好了,這樣可以大大節約構建時間。

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(<h1>hello</h1>,document.getElementById('root'))

5.1 DllPlugin

這裡我們可以先將reactreact-dom單獨進行打包

單獨打包建立webpack.dll.js

const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
module.exports = {
    entry:['react','react-dom'],
    mode:'production',
    output:{
        filename:'react.dll.js',
        path:path.resolve(__dirname,'dll'),
        library:'react'
    },
    plugins:[
        new DllPlugin({
            name:'react',
            path:path.resolve(__dirname,'dll/manifest.json')
        })
    ]
}

執行"webpack --config webpack.dll.js命令,可以看到dll目錄下建立了兩個檔案分別是manifest.jsonreact.dll.js

關係是這個醬紫的,到時候我們會通過manifest.json來找到react.dll.js檔案中的模組進行載入。

5.2 DllReferencePlugin

在我們的專案中可以引用剛才打包好的動態連結庫

const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
// 構建時會引用動態連結庫的內容
new DllReferencePlugin({
  manifest:path.resolve(__dirname,'dll/manifest.json')
}),
// 需要手動引入react.dll.js
new AddAssetHtmlWebpackPlugin(
  { filepath: path.resolve(__dirname,'dll/react.dll.js') }
)

使用DllPlugin可以大幅度提高構建速度

6.動態載入

實現點選後動態載入檔案

let btn = document.createElement('button');
btn.innerHTML = '點選載入視訊';
btn.addEventListener('click',()=>{
    import('./video').then(res=>{
        console.log(res.default);
    });
});
document.body.appendChild(btn);

給動態引入的檔案增加名字

output:{
  chunkFilename:'[name].min.js'
}
import(/* webpackChunkName: "video" */ './video').then(res=>{
    console.log(res.default);
})

這樣打包後的結果最終的檔案就是 video.min.js

7.打包檔案分析工具

安裝webpack-bundle-analyzer外掛

npm install --save-dev webpack-bundle-analyzer

安裝後使用外掛

const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer');
mode !== "development" && new BundleAnalyzerPlugin()

預設就會展現當前應用的分析圖表

8.SplitChunks

我們再來看下SplitChunks這個配置,他可以在編譯時抽離第三方模組、公共模組。

先將專案配置成多入口檔案

entry:{
  a:'./src/a.js',
  b:'./src/b.js'
}

我們讓a,b兩個模組同時引用jquery,這裡別忘了去掉之前的externals配置

analyze

配置SplitChunks外掛

預設配置在此,我們一個個描述下含義

splitChunks: {
  chunks: 'async', // 分割非同步模組
  minSize: 30000, // 分割的檔案最小大小
  maxSize: 0,
  minChunks: 1, // 引用次數
  maxAsyncRequests: 5, // 最大非同步請求數
  maxInitialRequests: 3, // 最大初始化請求數
  automaticNameDelimiter: '~', // 抽離的命名分隔符
  automaticNameMaxLength: 30, // 名字最大長度
  name: true,
  cacheGroups: { // 快取組
    vendors: { // 先抽離第三方
      test: /[\\/]node_modules[\\/]/,
      priority: -10
    },
    default: {
      minChunks: 2,
      priority: -20, // 優先順序
      reuseExistingChunk: true
    }
  }
}

我們將async改為initial

analyze

我們在為每個檔案動態匯入lodash庫,並且改成async

import('lodash')

analyze

為每個入口引入c.js,並且改造配置檔案

splitChunks: {
  chunks: 'all',
  name: true,
  cacheGroups: {
    vendors: {
      test: /[\\/]node_modules[\\/]/,
      priority: -10
    },
    default: {
      minSize:1, // 不是第三方模組,被引入兩次也會被抽離
      minChunks: 2,
      priority: -20,
    }
  }
}

analyze

這樣再反過來看chunks的引數是不是就瞭然於胸了呢?

9.熱更新(不是林更新)

模組熱替換(HMR - Hot Module Replacement)是webpack提供的最有用的功能之一。它允許在執行時替換、新增、刪除各種模組,而無需進行完全重新整理重新載入整個頁面。

  • 保留在完全重新載入頁面時丟失的應用程式的狀態;
  • 只更新改變的內容,以節省開發時間;
  • 調整樣式更加快速,幾乎等同於就在瀏覽器偵錯程式中更改樣式。

啟用熱更新,預設樣式可以支援熱更新,如果不支援熱更新則可以採用強制重新整理。

devServer:{
  hot:true
}
new webpack.NamedModulesPlugin(),

js支援熱更新

import sum from './sum';
console.log(sum(1,2));
if(module.hot){ // 如果支援熱更新
    module.hot.accept(); // 當入口檔案變化後重新執行當前入口檔案
}

10.IgnorePlugin

配置忽略 importrequire語法

new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)

11.費時分析

可以計算每一步執行的執行速度

const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin();
module.exports =smw.wrap({
  ...
});

12.noParse

module.noParse,對類似jq這類的依賴庫,內部不會引用其他庫,那麼我們在打包的時候就沒有必要再去解析,這樣能夠增加打包速率。

noParse:/jquery/

13.resolve

resolve: {
  extensions: [".js",".jsx",".json",".css"],
  alias:{},
  modules:['node_modules']
},

14.include/exclude

在使用loader時,可以指定哪些檔案不通過loader,或者指定哪些檔案必須通過loader

{
  test: /\.js$/,
  use: "babel-loader",
  // include:path.resolve(__dirname,'src'),
  exclude:/node_modules/
},

15.happypack

多執行緒打包,我們可以將不同的邏輯交給不同的執行緒來處理。

npm install --save-dev happypack

使用外掛

const HappyPack = require('happypack');
rules:[
  {
    test: /\.js$/,
    use: 'happypack/loader?id=jsx'
  },

  {
    test: /\.less$/,
    use: 'happypack/loader?id=styles'
  },
]
new HappyPack({
  id: 'jsx',
  threads: 4,
  loaders: [ 'babel-loader' ]
}),

new HappyPack({
  id: 'styles',
  threads: 2,
  loaders: [ 'style-loader', 'css-loader', 'less-loader' ]
})

16.總結

至此,我們五篇連載《從0搭建自己的webpack開發環境》先要告一段落了,希望大家可以通過這些文章對webpack能有全新的認識,對工作能更有幫助。

有什麼想要了解的前端知識,歡迎關注留言!