Webpack5 最佳實(shí)踐

先簡單回顧下 webpack 原理

Webpack 可以看做是模塊打包機(jī)最域,把解析的所有模塊變成一個對象谴分,然后通過入口模塊去加載我們的東西,然后依次實(shí)現(xiàn)遞歸的依賴關(guān)系镀脂,通過入口來運(yùn)行所有的文件牺蹄。由于 webpack 只認(rèn)識js,所以需要通過一系列的 loaderplugin 轉(zhuǎn)換成合適的格式供瀏覽器運(yùn)行薄翅。

  • loader 主要是對資源進(jìn)行加載/轉(zhuǎn)譯的預(yù)處理工作沙兰,其本質(zhì)是一個函數(shù),在該函數(shù)中對接收到的內(nèi)容進(jìn)行轉(zhuǎn)換翘魄,返回轉(zhuǎn)換后的結(jié)果鼎天。某種類型的資源可以使用多個 loader,執(zhí)行順序是從右到左暑竟,從下到上斋射。

  • plugin(插件)主要是擴(kuò)展 webpack 的功能,其本質(zhì)是監(jiān)聽整個打包的生命周期但荤。webpack 基于事件流框架 Tapable罗岖, 運(yùn)行的生命周期中會廣播出很多事件,plugin 可以監(jiān)聽這些事件腹躁,在合適的時機(jī)通過 webpack 提供的 API 改變輸出結(jié)果桑包。

本文主要介紹 webpack5 的配置,按步驟邊配置邊打包對比會印象更深~ 附上完整的源碼纺非。

webpack 安裝

新建一個目錄哑了,進(jìn)入目錄初始化 package.json赘方,并安裝 webpack 依賴

// 初始化包
npm init -y

// 安裝依賴
npm i webpack webpack-cli -D

基礎(chǔ)配置

webpack 默認(rèn)配置文件名字為 webpack.config.js,于是在項(xiàng)目根目錄下新建一個名為 webpack.config.js 的文件垒手,在配置文件里寫最簡單的單頁面配置:

let path = require("path");

module.exports = {
  mode: "development",
  entry: "./src/js/index.js", 
  output: {
    filename: "js/bundle.js", 
    path: path.resolve("dist"),
    publicPath: "http://cdn.xxxxx"
  }
}

配置詳解

  • mode - 打包模式
    • development 為開發(fā)模式蒜焊,打包后代碼不會被壓縮
    • production 為生產(chǎn)模式倒信,打包后代碼為壓縮代碼
  • entry - 入口文件
  • output - 打包文件配置
    • filename:打包后文件科贬,filename 的值可設(shè)置成帶 hash 戳的文件:js/bundle.[hash].js / js/bundle.[hash:8].js(只顯示 8 位 hash 戳)
    • path:打包文件路徑,需為絕對路徑
    • publicPath:上線的cdn地址

TIP: 上述代碼中 path 為內(nèi)置模塊鳖悠,無需安裝榜掌,直接引入即可。

新建后還需在項(xiàng)目根目錄下的 src/js 目錄下新建 index.js 文件乘综,然后隨便輸入一句 js 代碼憎账。

配置后可使用 webpack 命令嘗試打包,若報錯找不到命令可 npm i webpack -g 全局安裝后再打包卡辰,打包成功后會輸出到項(xiàng)目根目錄下的 dist 目錄胞皱。

項(xiàng)目目錄結(jié)構(gòu)大致如下

├─package.json
├─webpack.config.js
├─src
|  ├─js
|  | └index.js
├─dist

html 文件打包

由于 webpack 只認(rèn)識 js,因此需通過 html-webpack-plugin 插件打包 html 文件

npm i html-webpack-plugin -D

安裝后在 webpack.config.js 配置文件中

let HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    plugins: [
        new HtmlWebpackPlugin({
          template: "./src/index.html"
        })
    ]
}

production 模式下可以開啟 html 文件的壓縮配置:

plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      minify: { removeAttributeQuotes: true, collapseWhitespace: true }, 
      hash: true
    })
]

配置詳解

  • plugins - webpack 插件配置
    • html-wepack-plugin配置
      • template - html 模板文件的相對/絕對路徑
      • minify - 壓縮配置
        • removeAttributeQuotes:刪除屬性雙引號
        • collapseWhitespace:代碼壓縮成一行
      • hash - 引入文件帶上hash戳

TIP: 如果不指定模板 template 配置九妈,將是插件默認(rèn)的 html文件反砌,而不是項(xiàng)目中的 html 文件

開啟服務(wù)

webpack 通過安裝 webpack-dev-server 開啟服務(wù)

npm i webpack-dev-server -D

配置 webpack.config.js

devServer: {
    port: 5000,
    compress: true,
    open: true,
    client: { progress: true }
}

配置詳解

  • devServer - webpack-dev-server 配置
    • port - 端口號
    • compress - 開啟 gzip 壓縮
    • open - 啟動后自動把頁面打開
    • client
      • progress:在瀏覽器中以百分比顯示編譯進(jìn)度

配置好可運(yùn)行 webpack-dev-server 命令查看效果,若找不到命令可 npm i webpack-dev-server -g 全局安裝下

跨域

開發(fā)過程中容易遇到接口跨域問題萌朱,可通過 devServer.proxy 配置解決

假設(shè)接口地址為 http://localhost:3000/api/users宴树,對 /api/users 的請求可如下配置

devServer: {
    proxy: {
      '/api': 'http://localhost:3000',
    },
},

但實(shí)際項(xiàng)目中接口的地址有很多種可能,一般不會有 /api 目錄晶疼,即一般接口地址為http://localhost:3000/users酒贬,因此枚舉配置會很麻煩,可通過代理請求解決

即先請求 http://localhost:3000/api/users 接口地址翠霍,然后通過 devServer 代理到 http://localhost:3000/users

本文通過 express 開啟接口服務(wù)锭吨,接口地址為 http://localhost:3000/user,接口代碼不再贅述寒匙,后期上傳完整的源碼零如,可通過 node "項(xiàng)目路徑\webpack5\src\js\server.js" 啟動接口服務(wù),然后配置 webpack.config.js

devServer: {
    proxy: {
      "/api": {
        target: "http://localhost:3000/",
        pathRewrite: {
          "/api": ""
        },
      },
    }
}

devServer 配置詳解

  • proxy - 代理配置
    • target - 接口域名
    • pathRewrite - 接口路徑重寫蒋情,把請求代理到接口服務(wù)器上

mock 接口數(shù)據(jù)

當(dāng)后端接口沒有寫好埠况,又不希望被阻塞進(jìn)度,可以通過 mock 前期跟后端約定好的接口數(shù)據(jù)格式來模擬調(diào)試頁面棵癣≡玻可使用有自定義函數(shù)和應(yīng)用自定義中間件的能力的配置 devServer.setupMiddlewares,在 middlewares.unshift 中的回調(diào)函數(shù)使用 res.send 把需要 mock 的數(shù)據(jù)傳遞進(jìn)去:

devServer: {
    setupMiddlewares: (middlewares, devServer) => {
        if (!devServer) {
        throw new Error("webpack-dev-server is not defined");
        }
        
        middlewares.unshift({
            name: "user-info",
            // `path` 是可選的狈谊,接口路徑
            path: "/user",
            middleware: (req, res) => {
              // mock 數(shù)據(jù)模擬接口數(shù)據(jù)
              res.send({ name: "moon mock" });
            },
        });

        return middlewares;
    },
}

樣式處理

樣式處理需要用到的 loader 及其作用:

  • less-loader:加載和轉(zhuǎn)譯 LESS 文件
  • postcss-loader:使用 PostCSS 加載和轉(zhuǎn)譯 CSS/SSS 文件喜命,如可以處理 autoprefixer css 包沟沙,為css添加瀏覽器前綴
  • css-loader:解析 @import and url() 語法,使用 import 加載解析后的css文件壁榕,并且返回 CSS 代碼
  • mini-css-extract-pluginloader:抽取出 css 文件矛紫,通過 link 標(biāo)簽引入 html 文件

安裝依賴,若使用的是 sass牌里,則把 less less-loader 換成 node-sass sass-loader 即可

npm i mini-css-extract-plugin css-loader postcss-loader autoprefixer less-loader less -D

配置 webpack.config.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 

module.exports = {
    plugins: [
        new MiniCssExtractPlugin({
          filename: "css/main.css", // 抽離的css文件名
        })
    ],
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
            },
            {
                test: /\.less$/,
                use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"],
            },
        ]
    }
}

還需新建并配置 postcss.config.js

module.exports = {
  plugins: [require("autoprefixer")]
};

上述文件配置好后颊咬,打包后會發(fā)現(xiàn) css3 樣式還是沒有添加前綴,還需配置 package.jsonbrowserlist 才能生效

"browserslist": [
    "last 1 version",
    "> 1%",
    "IE 10"
],

js 處理及語法校驗(yàn)

es6 或更高級的語法需轉(zhuǎn)化成 es5牡辽,并使用 eslint 規(guī)范代碼:

  • babel-loader:加載 ES2015+ 代碼喳篇,然后使用 Babel 轉(zhuǎn)譯為 ES5
  • @babel/preset-env:基礎(chǔ)的ES語法分析包,各種轉(zhuǎn)譯規(guī)則的統(tǒng)一設(shè)定态辛,目的是告訴loader要以什么規(guī)則來轉(zhuǎn)化成對應(yīng)的js版本
  • @babel/plugin-transform-runtime:解析 generator 等高級語法麸澜,但不包含 include 語法,include 語法需安裝 @babel/polyfill奏黑。官方文檔說上線需帶上 @babel/runtime 這個補(bǔ)丁炊邦,該包還做了一些方法抽離的優(yōu)化,如 class 語法的抽離(抽離出 classCallCheck 方法)
  • @babel/polyfill:解析更高級的語法熟史,如 promise馁害,include 等,在js文件中 require 引入即可
  • eslint-loader:校驗(yàn) js 是否符合規(guī)范以故,可自行在 eslint 網(wǎng)站上配置下載

安裝依賴

npm i @babel/core babel-loader @babel/preset-env @babel/plugin-transform-runtime -@babel/polyfill -D

npm i @babel/runtime eslint-loader eslint -S

webpack.config.js

{
    test: /\.js$/,
    use: {
      loader: "eslint-loader",
      options: {
        enforce: "pre", // 定義為前置loader蜗细,在normal的loader前執(zhí)行
      },
    },
},
{
    test: /\.js$/, // enforce 默認(rèn)為 normal 普通loader
    use: {
      loader: "babel-loader", 
      options: {
        presets: ["@babel/preset-env"], // 把es6轉(zhuǎn)成es5
        plugins: ["@babel/plugin-transform-runtime"], //作用?
      },
    },
    include: path.resolve(__dirname, "src"),
    exclude: /node_modules/,
},

配置 source-map

源碼映射配置 source-map 的值:

  • source-map 映射源碼 會單獨(dú)生成source-map文件 出錯了會標(biāo)識當(dāng)前報錯的行和列 大而全
  • eval-source-map 不會產(chǎn)生單獨(dú)的文件怒详,可顯示行和列
  • cheap-module-source-map 不會標(biāo)識列炉媒,會生成單獨(dú)的映射文件
  • cheap-module-eval-source-map 不會產(chǎn)生文件 集成在打包后的文件中 不會產(chǎn)生列

webpack.config.js

  devtool: "eval-source-map",

引入js全局變量

有三種方式可以引入全局變量

expose-loader

可把變量暴露到 window 全局對象上,以 jquery 為例昆烁,先安裝依賴

npm i jquery expose-loader -D

然后在 webpack.config.js 中配置 loader吊骤,把 $ 暴露到 window 全局對象上

module: {
  rules: [{
    test: require.resolve('jquery'),
    use: [{
      loader: 'expose-loader',
      options: '$'
    }]
  }]
}

除了上述方法外還可以在入口 js 文件中暴露

require("expose-loader?$!jquery");

providePlugin

可使用 webapck 內(nèi)置插件 providePlugin 給每個模塊中注入變量,還是以 jquery 為例

webapck.config.js 中配置

const webpack = require("webpack");
module.exports = {
    plugins: [
        new webpack.ProvidePlugin({
          $: 'jquery'
        });
    ]
}

然后在任意js模塊中可以直接使用$調(diào)用静尼,無需引入jquery包

// in a module
$('#item'); // <= works
// $ is automatically set to the exports of module "jquery"

通過 cdn 引入

還可以通過 cdn 鏈接的方式引入全局變量白粉,但如果此時js文件中多寫了 import $ from 'jquery',就會把 jquery 也打包進(jìn)去鼠渺,可使用 external 防止將某些 import 的包(package)打包到 bundle 中

index.html

<script
  src="https://code.jquery.com/jquery-3.1.0.js"
  integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
  crossorigin="anonymous"
></script>

webpack.config.js

module.exports = {
  //...
  externals: {
    jquery: 'jQuery',
  },
};

這樣就剝離了那些不需要改動的依賴模塊鸭巴,換句話,下面展示的代碼還可以正常運(yùn)行:

import $ from 'jquery';

$('.my-element').animate(/* ... */);

上面的例子拦盹。屬性名稱是 jquery鹃祖,表示應(yīng)該排除 import $ from 'jquery' 中的 jquery 模塊。為了替換這個模塊普舆,jQuery 的值將被用來檢索一個全局的 jQuery 變量恬口。換句話說校读,當(dāng)設(shè)置為一個字符串時,它將被視為全局的(定義在上面和下面)祖能。

樣式壓縮和 js 壓縮

production 模式下需壓縮 css 可使用插件 css-minimizer-webpack-plugin歉秫,但使用了此插件壓縮 css, 會導(dǎo)致 js 不壓縮,所以需要安裝 js 壓縮插件 terser-webpack-plugin

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
    optimization: {
        minimize: true,
        minimizer: [
          new CssMinimizerPlugin(),
          // 壓縮js
          new TerserPlugin({ test: /\.js(\?.*)?$/i }),
        ],
    },
}

圖片處理

需要 loader 解析圖片資源:

  • file-loader:將文件的import/require()解析為url养铸,并將文件發(fā)送到輸出文件夾(dist文件夾)雁芙,并返回(相對)URL
  • url-loader:像 file-loader 一樣工作,但如果文件小于限制揭厚,可以返回 data URL却特,即把圖片變成base64
  • html-loader:可以解析html標(biāo)簽引入的圖片,可以通過查詢參數(shù) attrs,指定哪個標(biāo)簽屬性組合(tag-attribute combination)應(yīng)該被處理,默認(rèn)值:attrs=img:src

安裝依賴

npm i file-loader url-loader html-loader -D

配置 webpack.config.js

module: {
    rules: [
      {
        test: /\.jpg|png|jpeg$/,
        use: {
          loader: "file-loader", 
          options: {
            outputPath: "images/",
            name: "[name].[ext]", // 如果不寫文件名,則會生成隨機(jī)名字
            // publicPath: "http://cdn.xxx.com/images", // 可配置生產(chǎn)環(huán)境的cdn地址前綴
          },
        },
      },
      {
        test: /\.(html)$/,
        use: {
          loader: "html-loader",
          options: {
            esModule: false, 
          },
        },
      },
    ]
}

TIP: url-loader可以使用 options.limit 限制抱究,小于多少k時使用base64轉(zhuǎn)換幌衣,大于這個體積使用file-loader打包

html-loader 配置報錯問題

html-loader 需關(guān)閉 es6 模塊化,使用commonjs解析掌桩,否則會報錯。原因主要是兩個 loader 解析圖片的方式不一樣。

項(xiàng)目目錄結(jié)構(gòu)大致如下

├─.eslintrc.json
├─package-lock.json
├─package.json
├─postcss.config.js
├─webpack.config.js
├─src
|  ├─index.html
|  ├─js
|  | ├─index.js
|  | ├─server.js
|  | └test.js
|  ├─image
|  |   └logo.png
|  ├─css
|  |  ├─a.css
|  |  └index.css
├─dist

resolve 配置

resolve 常用的屬性配置:

  • modules:告訴 webpack 解析模塊時應(yīng)該搜索的目錄提岔。絕對路徑和相對路徑都能使用,但是要知道它們之間有一點(diǎn)差異笋敞。
    • 使用絕對路徑碱蒙,將只在給定目錄中搜索。使用相對路徑夯巷,通過查看當(dāng)前目錄以及祖先路徑赛惩。
    • 如果想要優(yōu)先于某個目標(biāo)目錄搜索,則需把該目錄放到目標(biāo)目錄前面趁餐,可詳看官網(wǎng)例子
  • alias:設(shè)置別名喷兼,方便使用,下面的例子應(yīng)用于 src 目錄下的路徑使用
  • mainFields:當(dāng)從 npm 包中導(dǎo)入模塊時(例如后雷,import * as D3 from 'd3')季惯,此選項(xiàng)將決定在 package.json 中使用哪個字段導(dǎo)入模塊。根據(jù) webpack 配置中指定的 target 不同臀突,默認(rèn)值也會有所不同勉抓。這里 browser 屬性是最優(yōu)先選擇的,因?yàn)樗?mainFields 的第一項(xiàng)
  • extensions:嘗試按順序解析這些后綴名候学。當(dāng)引入的文件不帶后綴名藕筋,且有多個文件有相同的名字,但后綴名不同盒齿,webpack 會解析列在數(shù)組首位的后綴的文件 并跳過其余的后綴念逞。
let path = require("path");

module.exports = {
    resolve: {
        modules: [path.resolve("node_modules")], 
        alias: {
          "@": path.resolve(__dirname, "src"),
        },
        mainFields: ["browser", "module", "main"], 
        extensions: [".js", ".json", ".vue"], 
    },
}

多頁面配置

多頁面顧名思義就是多個 html 頁面困食,因此一般也會有多個 js 入口文件。

下面的配置中 entry 的 key 值對應(yīng)的是 output 屬性的 [name] 值翎承,HtmlWebpackPlugin 中的屬性 chunks 表示引入 [name] 對應(yīng)的 js 代碼文件硕盹,不指定 chunks 值將引入所有打包出來的 js 文件。

即本例的 [name] 分別為 homeother叨咖,即打包出來是 home.js 和 other.js瘩例,最終打包的效果是 home.html 引入的是 home.jsother.html 引入的是 other.js 文件

配置 webpack.config.js

let path = require("path");
let HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "development",
  entry: {
      home: "./src/js/index.js",
      other: "./src/js/other.js",
  }, 
  output: {
    filename: "js/[name].js", 
    path: path.resolve("dist")
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      filename: "home.html",
      chunks: ['home']
    }),
    new HtmlWebpackPlugin({
      template: "./src/other.html",
      filename: "other.html",
      chunks: ['other']
    }),
  ],
}

webpack 小插件應(yīng)用

clean-webpack-plugin

清除插件甸各,可用于清除上一次的打包文件垛贤,清除目錄為 output.path 的值

安裝依賴

npm i clean-webpack-plugin -D

配置 webpack.config.js

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

module.exports = {
    plugins: [
        new CleanWebpackPlugin(),
    ]
}

copy-webpack-plugin

拷貝插件,把某個文件夾導(dǎo)出到打包文件夾中趣倾,如文檔文件夾(如 doc 文件夾)

安裝依賴

npm i copy-webpack-plugin -D

配置 webpack.config.js

const CopyWebpackPlugin = require("copy-webpack-plugin"); // 拷貝文件

module.exports = {
    plugins: [
        new CopyWebpackPlugin({
          patterns: [
            {
              from: "./doc",
              to: "./doc", 
            },
          ],
        }),
    ]
}

插件配置屬性

  • patterns
    • from: 源文件聘惦,相對于當(dāng)前目錄路徑
    • to:目標(biāo)文件,相對于output.path文件路徑儒恋,會生成到 dist/doc 目錄下

webpack.BannerPlugin

版權(quán)聲明插件善绎,webpack 內(nèi)置插件,無需安裝

配置 webpack.config.js

const webpack = require("webpack");

module.exports = {
    plugins: [
        new webpack.BannerPlugin("copyright by Moon in 2022"),
    ]
}

打包后的文件開頭會帶上版權(quán)聲明大概如下:

[圖片上傳失敗...(image-1584af-1651569545228)]

watch

可以監(jiān)聽文件變化诫尽,當(dāng)它們修改后會重新編譯禀酱,可以用在實(shí)時打包的場景下

配置 webpack.config.js

watch: true,
watchOptions: {
  poll: 1000, //每秒檢查一次變動
  aggregateTimeout: 600, // 防抖
  ignored: /node_modules/,
},

配置屬性

  • watchOptions 監(jiān)聽參數(shù)
    • poll: 每n毫秒檢查一次變動
    • aggregateTimeout:防抖,當(dāng)?shù)谝粋€文件更改牧嫉,會在重新構(gòu)建前增加延遲剂跟。這個選項(xiàng)允許 webpack 將這段時間內(nèi)進(jìn)行的任何其他更改都聚合到一次重新構(gòu)建里。以毫秒為單位
    • ignored:對于某些系統(tǒng)酣藻,監(jiān)聽大量文件會導(dǎo)致大量的 CPU 或內(nèi)存占用曹洽。可以使用正則排除像 node_modules 如此龐大的文件夾

配置后在命令窗口輸入 npm run build 就可以進(jìn)行監(jiān)控并實(shí)時打包了臊恋,如圖實(shí)時打包了一次

[圖片上傳失敗...(image-7a8f7a-1651569545229)]

環(huán)境變量

通過 webpack 內(nèi)置插件 DefinePlugin 定義 DEV 環(huán)境變量衣洁。

還可以把開發(fā)和生產(chǎn)模式不同的 webpack 配置抽離出來,即把 webpack.config.js 文件一分為三

  • 公共配置放在 webpack.config.base.js 文件
  • 開發(fā)模式配置放在 webpack.config.dev.js 文件抖仅,通過 webpack-merge 合并webpack.config.base.js 文件和 webpack.config.dev.js 文件的配置
  • 生產(chǎn)模式配置放在webpack.config.prod.js 文件 (和開發(fā)模式配置文件邏輯一致)

webpack.config.dev.js 文件完整代碼如下:

let { merge } = require("webpack-merge");
let base = require("./webpack.config.base.js");
let HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require("webpack");

module.exports = merge(base, {
  mode: "development",
  devtool: "eval-source-map",
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
    new webpack.DefinePlugin({
      ENV: JSON.stringify("dev"),
    }),
  ],
  devServer: {
    compress: true,
    client: { progress: true },
    port: 5000,
    
    // mock數(shù)據(jù)
    setupMiddlewares: (middlewares, devServer) => {
      if (!devServer) {
        throw new Error("webpack-dev-server is not defined");
      }

      middlewares.unshift({
        name: "fist-in-array",
        // `path` 是可選的
        path: "/user",
        middleware: (req, res) => {
          res.send({ name: "moon mock" });
        },
      });

      return middlewares;
    },
  },
});

使用環(huán)境變量后目錄結(jié)構(gòu)大致如下

├─.eslintrc.json
├─package-lock.json
├─package.json
├─postcss.config.js
├─webpack.config.base.js
├─webpack.config.dev.js
├─webpack.config.prod.js
├─src
|  ├─index.html
|  ├─js
|  | ├─index.js
|  | ├─server.js
|  | └test.js
|  ├─image
|  |   └logo.png
|  ├─css
|  |  ├─a.css
|  |  └index.css
├─doc
|  └notes.md
├─dist

更改配置文件后坊夫,打包命令也要做適當(dāng)調(diào)整,打包時需要指定配置文件:

// 開發(fā)模式
webpack --config webpack.config.dev.js

// 生產(chǎn)模式
webpack --config webpack.config.prod.js

生產(chǎn)模式配置文件和公共配置文件源碼后期上傳

熱更新

webpack 的熱更新又稱熱替換(Hot Module Replacement)撤卢,縮寫為 HMR环凿。這個機(jī)制可以做到不用刷新瀏覽器而將新變更的模塊替換掉舊的模塊。默認(rèn)啟用熱更新放吩,無需配置智听,它會自動應(yīng)用 webpack.HotModuleReplacementPlugin,這是啟用 HMR 所必需的。

優(yōu)化

下面的配置代碼都是在 webpack 配置文件中到推,不再贅述

縮小構(gòu)建范圍

include/exclude 選其一即可

module: {
    rules: [
        {
            test: /\.js$/,
            include: path.resolve(__dirname, "src"),
            // exclude: /node_modules/,
        },
    ]
}

module.noParse

由于webpack會通過入口文件解析 import, require 引用的包考赛,還會去分析包的依賴,但有些包是沒有依賴的莉测,因此可以通過 noParse 不解析某個引用包中的依賴關(guān)系颜骤,來提高構(gòu)建性能。適合沒有依賴項(xiàng)的包捣卤,如 jquery

module: {
    noParse: /jquery/,
}

webpack.IgnorePlugin

webpack 內(nèi)置插件 IgnorePlugin 可以阻止生成用于導(dǎo)入的模塊忍抽,或要求調(diào)用與正則表達(dá)式或篩選函數(shù)匹配的模塊。如 moment 包內(nèi)引入了很多語言包董朝,這些語言包都放在 locale 文件夾下鸠项,但大部分實(shí)際場景只會引用一個的語言包,因此打包時可忽略 moment 目錄下的 locale 語言包

 new webpack.IgnorePlugin({
  resourceRegExp: /^\.\/locale$/,
  contextRegExp: /moment$/,
}), 

忽略后再重新再js文件中引入某個語言包就能正常使用了

import "moment/locale/zh-cn";
moment.locale("zh-cn");

抽離公共代碼

一般用在多頁應(yīng)用場景或者是單個 js 文件太大子姜,請求需要很長時間祟绊,需要拆成幾個js文件,優(yōu)化請求速度闲询,使用 optimization 的 splitChunks 屬性來優(yōu)化久免。

splitChunks.cacheGroups 緩存組可以繼承和/或覆蓋來自 splitChunks.* 的任何選項(xiàng)。但是 test扭弧、priorityreuseExistingChunk 只能在緩存組級別上進(jìn)行配置。將它們設(shè)置為 false以禁用任何默認(rèn)緩存組记舆。

看下面配置之前先了解splitChunks的幾個屬性:

  • priority:抽離代碼的優(yōu)先級鸽捻,值越高越先被抽離,防止某些模塊在前面的模塊抽離完了后面沒被抽離到泽腮,在本例中是防止 vendor 模塊被 common 模塊抽離完了沒被抽離到
  • name:每個模塊(chunk)的文件名御蒲,不定義將是隨機(jī)名字
  • test:匹配目錄
  • chunks:選擇哪些 chunk 進(jìn)行優(yōu)化
    • initial:從入口處開始提取代碼,若有異步模塊考慮后面兩個值
    • async:異步模塊
    • all:可以存在異步和非異步模塊
  • minSize:生成 chunk 的最小體積诊赊,此處為方便測試設(shè)置為 0
  • minChunks:拆分前必須共享模塊的最小 chunks 數(shù)厚满,當(dāng)前代碼塊引用多少次才被抽離,此處為方便設(shè)置設(shè)置為 1

本例中分割了 common 和 vendor 兩個 chunk

optimization: {
    // 分割代碼塊
    splitChunks: {
      // 緩存組
      cacheGroups: {
        // 公共模塊
        commons: {
          name: "common",
          chunks: "initial", 
          minSize: 0, 
          minChunks: 1, 
        },
        vendor: {
          name: "vendor",
          priority: 1, 
          test: /[\\/]node_modules[\\/]/,
          chunks: "all", //包括異步和非異步代碼塊
        },
      },
    },
  },

為方便大家理解碧磅,獻(xiàn)上打包后的目錄樹結(jié)構(gòu)

├─index.html
├─js
| ├─common.js
| ├─common.js.LICENSE.txt
| ├─main.js
| ├─main.js.LICENSE.txt
| ├─vendor.js
| └vendor.js.LICENSE.txt
├─images
|   └logo.png
├─doc
|  └notes.md
├─css
|  └main.css

這一塊比較難理解碘箍,建議多試幾次打包對比差異就懂了

懶加載

通過 es6 的 import() 語法實(shí)現(xiàn)懶加載,通過 jsonp 實(shí)現(xiàn)動態(tài)加載文件鲸郊,import 函數(shù)返回的是 promise 對象丰榴。vue 懶加載,react 懶加載都是這樣實(shí)現(xiàn)的秆撮。舉個簡單的栗子四濒,某些 js 文件在按鈕點(diǎn)擊后再請求加載。

此處省略獲取 button dom元素對象的代碼

button.addEventListener('click', function(){
    import('./test.js').then(data => {
        console.log(data);
    })
})

除了以上的優(yōu)化方法之外,還有dll預(yù)構(gòu)建盗蟆,多線程構(gòu)建/壓縮戈二,利用緩存提升二次構(gòu)建速度,動態(tài) polyfill 等等喳资,可根據(jù)實(shí)際情況自行選擇優(yōu)化方案觉吭,這里不一一贅述

webpack自帶優(yōu)化

tree-shaking

使用 import 語法在生產(chǎn)環(huán)境下沒用到的代碼不會被打包, 即 tree shaking, require 語法不支持tree-shaking

scope hosting

scope hosting(作用域提升),舉個栗子:

let a = 1
let b = 2
let c = 3
let d = a+b+c
console.log(d)

代碼打包出來只有最后一句, webpack打包會自動省略一些可以簡化的代碼

手寫簡易less-loader

less-loader.js

/loaders/less-loader.js 目錄文件中引入 less 插件

const less = require("less");

function loader(source) {
  let css = "";
  less.render(source, function (err, res) {
    css = res.css;
  });
}
module.exports = loader;

webpack.config.js

寫入以下配置

resolveLoader: {
    alias: {
        "lessLoader": path.resolve(__dirname, "loaders", "less-loader"))
    }
},
module: {
    rules: [
        {
            test: /\.less/,
            use: ["style-loader", "lessLoader"]
        }
    ]
}

最后

最后附上完整源碼骨饿,若有錯漏之處望指出~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亏栈,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子宏赘,更是在濱河造成了極大的恐慌绒北,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件察署,死亡現(xiàn)場離奇詭異闷游,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)贴汪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門脐往,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人扳埂,你說我怎么就攤上這事业簿。” “怎么了阳懂?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵梅尤,是天一觀的道長。 經(jīng)常有香客問我岩调,道長巷燥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任号枕,我火速辦了婚禮缰揪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘葱淳。我一直安慰自己钝腺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布蛙紫。 她就那樣靜靜地躺著拍屑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坑傅。 梳的紋絲不亂的頭發(fā)上僵驰,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼蒜茴。 笑死星爪,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的粉私。 我是一名探鬼主播顽腾,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诺核!你這毒婦竟也來了抄肖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤窖杀,失蹤者是張志新(化名)和其女友劉穎漓摩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體入客,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡管毙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了桌硫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夭咬。...
    茶點(diǎn)故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖铆隘,靈堂內(nèi)的尸體忽然破棺而出卓舵,到底是詐尸還是另有隱情,我是刑警寧澤膀钠,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布边器,位于F島的核電站,受9級特大地震影響托修,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恒界,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一睦刃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧十酣,春花似錦涩拙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至虾宇,卻和暖如春搓彻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工旭贬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怔接,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓稀轨,卻偏偏與公主長得像扼脐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子奋刽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容