初學者應該看的 Webpack 完整指南(2020)

做者:Valentino Gagliardi
譯者:前端小智
來源:valentinog
點贊再看,微信搜索 【大遷世界】 關注這個沒有大廠背景,但有着一股向上積極心態人。本文 GitHub https://github.com/qq44924588... 上已經收錄,文章的已分類,也整理了不少個人文檔,和教程資料。

咱們應該學習 webpack 嗎 ?

現在,CLI工具(如create-react-appVue -cli)已經爲咱們抽象了大部分配置,並提供了合理的默認設置。css

即便那樣,瞭解幕後工做原理仍是有好處的,由於咱們早晚須要對默認值進行一些調整。html

在本文中中,咱們會知道 webpack能夠作什麼,以及如何配置它以知足咱們的平常需求。前端

什麼是 webpack?

做爲前端開發人員,咱們應該熟悉 module 概念。 你可能據說過 AMD模塊UMDCommon JS還有ES模塊node

webpack是一個模塊綁定器,它對模塊有一個更普遍的定義,對於webpack來講,模塊是:react

  • Common JS modules
  • AMD modules
  • CSS import
  • Images url
  • ES modules

webpack 還能夠從這些模塊中獲取依賴關係webpack

webpack 的最終目標是將全部這些不一樣的源和模塊類型統一塊兒來,從而將全部內容導入JavaScript代碼,並最生成能夠運行的代碼。git

entry

Webpackentry(入口點)是收集前端項目的全部依賴項的起點。 實際上,這是一個簡單的 JavaScript 文件。es6

這些依賴關係造成一個依賴關係圖github

Webpack 的默認入口點(從版本4開始)是src/index.js,它是可配置的。 webpack 能夠有多個入口點。web

Output

output是生成的JavaScript和靜態文件的地方。

Loaders

Loaders 是第三方擴展程序,可幫助webpack處理各類文件擴展名。 例如,CSS,圖像或txt文件。

Loaders的目標是在模塊中轉換文件(JavaScript之外的文件)。 文件成爲模塊後,webpack能夠將其用做項目中的依賴項。

Plugins

插件是第三方擴展,能夠更改webpack的工做方式。 例如,有一些用於提取HTML,CSS或設置環境變量的插件。

Mode

webpack 有兩種操做模式:開發(development)生產(production)。 它們之間的主要區別是生產模式自動生成一些優化後的代碼。

Code splitting

代碼拆分延遲加載是一種避免生成較大包的優化技術。

經過代碼拆分,開發人員能夠決定僅在響應某些用戶交互時加載整個JavaScript塊,好比單擊或路由更改(或其餘條件)。

被拆分的一段代碼稱爲 chunk

Webpack入門

開始使用webpack時,先建立一個新文件夾,而後進入該文件中,初始化一個NPM項目,以下所示:

mkdir webpack-tutorial && cd $_

npm init -y

接着安裝 webpackwebpack-cliwebpack-dev-server

npm i webpack webpack-cli webpack-dev-server --save-dev

要運行 webpack,只須要在 package.json 配置以下命令便可:

"scripts": {
    "dev": "webpack --mode development"
  },

經過這個腳本,咱們指導webpack在開發模式下工做,方便在本地工做。

Webpack 的第一步

在開發模式下運行 webpack:

npm run dev

運行完後會看到以下錯誤:

ERROR in Entry module not found: Error: Can't resolve './src'

webpack 在這裏尋找默認入口點src/index.js,因此咱們須要手動建立一下,並輸入一些內容:

mkdir src

echo 'console.log("Hello webpack!")' > src/index.js

如今再次運行npm run dev,錯誤就沒有了。 運行的結果生成了一個名爲dist/的新文件夾,其中包含一個名爲main.js的 JS 文件:

dist
└── main.js

這是咱們的第一個webpack包,也稱爲output

配置 Webpack

對於簡單的任務,webpack無需配置便可工做,可是很快咱們就會遇到問題,一些文件若是沒有指定的 loader 是無法打包的。因此,咱們須要對 webpack進行配置,對於 webpack 的配置是在 webpack.config.js 進行的,因此咱們須要建立該文件:

touch webpack.config.js

Webpack 用 JavaScript 編寫,並在無頭 JS 環境(例如Node.js)上運行。 在此文件中,至少須要一個module.exports,這是的 Common JS 導出方式:

module.exports = {
  //
};

webpack.config.js中,咱們能夠經過添加或修改來改變webpack的行爲方式

  • entry point
  • output
  • loaders
  • plugins
  • code splitting

例如,要更改入口路徑,咱們能夠這樣作

const path = require("path");

module.exports = {
  entry: { index: path.resolve(__dirname, "source", "index.js") }
};

如今,webpack 將在source/index.js中查找要加載的第一個文件。 要更改包的輸出路徑,咱們能夠這樣作:

const path = require("path");

module.exports = {
  output: {
    path: path.resolve(__dirname, "build")
  }
}

這樣,webpack將把最終生成包放在build中,而不是dist.(爲了簡單起見,在本文中,咱們使用默認配置)。

打包 HTML

沒有HTML頁面的Web應用程序幾乎沒有用。 要在webpack中使用 HTML,咱們須要安裝一個插件html-webpack-plugin

npm i html-webpack-plugin --save-dev

一旦插件安裝好,咱們就能夠對其進行配置:

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

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "src", "index.html")
    })
  ]
};

這裏的意思是讓 webpack,從 src/index.html 加載 HTML 模板。

html-webpack-plugin的最終目標有兩個:

  • 加載 html 文件
  • 它將bundle注入到同一個文件中

接着,咱們須要在 src/index.html 中建立一個簡單的 HTML 文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Webpack tutorial</title>
</head>
<body>

</body>
</html>

稍後,咱們會運行這個程序。

webpack development server

在本文第一部分中,咱們安裝了webpack-dev-server。若是你忘記安裝了,如今能夠運行下面命令安裝一下:

npm i webpack-dev-server --save-dev

webpack-dev-server 可讓開發更方便,不須要改動了文件就去手動刷新文件。 配置完成後,咱們能夠啓動本地服務器來提供文件。

要配置webpack-dev-server,請打開package.json並添加一個 「start」 命令:

"scripts": {
  "dev": "webpack --mode development",
  "start": "webpack-dev-server --mode development --open",
},

有了 start 命令,咱們來跑一下:

npm start

運行後,默認瀏覽器應打開。 在瀏覽器的控制檯中,還應該看到一個 script 標籤,引入的是咱們的 main.js

clipboard.png

使用 webpack loader

Loader是第三方擴展程序,可幫助webpack處理各類文件擴展名。 例如,有用於 CSS,圖像或 txt 文件的加載程序。

下面是一些 loader 配置介紹:

module.exports = {
  module: {
    rules: [
      {
        test: /\.filename$/,
        use: ["loader-b", "loader-a"]
      }
    ]
  },
  //
};

相關配置以module 關鍵字開始。 在module內,咱們在rules內配置每一個加載程序組或單個加載程序。

對於咱們想要做爲模塊處理的每一個文件,咱們用testuse配置一個對象

{
    test: /\.filename$/,
    use: ["loader-b", "loader-a"]
}

test 告訴 webpack 「嘿,將此文件名視爲一個模塊」。 use 定義將哪些 loaders 應用於些打包的文件。

打包 CSS

要 在webpack 中打包CSS,咱們須要至少安裝兩個 loader。Loader 對於幫助 webpack 瞭解如何處理.css文件是必不可少的。

要在 webpack 中測試 CSS,咱們須要在 src 下建立一個style.css文件:

h1 {
    color: orange;
}

另外在 src/index.html 添加 h1 標籤

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Webpack tutorial</title>
</head>
<body>
<h1>Hello webpack!</h1>
</body>
</html>

最後,在src/index.js 中加載 CSS:

在測試以前,咱們須要安裝兩個 loader:

  • css-loader: 解析 css 代碼中的 url、@import語法像importrequire同樣去處理css裏面引入的模塊
  • style-loader:幫咱們直接將css-loader解析後的內容掛載到html頁面當中

安裝 loader:

npm i css-loader style-loader --save-dev

而後在webpack.config.js中配置它們

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

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "src", "index.html")
    })
  ]
};

如今,若是你運行npm start,會看到樣式表加載在HTML的頭部:

clipboard.png

一旦CSS Loader 就位,咱們還可使用MiniCssExtractPlugin提取CSS文件

Webpack Loader 順序很重要!

在webpack中,Loader 在配置中出現的順序很是重要。如下配置無效:

//

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["css-loader", "style-loader"]
      }
    ]
  },
  //
};

此處,「style-loader」出如今 「css-loader」 以前。 可是style-loader用於在頁面中注入樣式,而不是用於加載實際的CSS文件。

相反,如下配置有效:

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  },
  //
};

webpack loaders 是從右到左執行的。

打包 sass

要在 webpack 中測試sass,一樣,咱們須要在 src 目錄下建立一個 style.scss 文件:

@import url("https://fonts.googleapis.com/css?family=Karla:weight@400;700&display=swap");

$font: "Karla", sans-serif;
$primary-color: #3e6f9e;

body {
  font-family: $font;
  color: $primary-color;
}

另外,在src/index.html中添加一些 Dom 元素:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Webpack tutorial</title>
</head>
<body>
  <h1>Hello webpack!</h1>
  <p>Hello sass!</p>
</body>
</html>

最後,將 sass 文件加載到src/index.js中:

import "./style.scss";
console.log("Hello webpack!");

在測試以前,咱們須要安裝幾個 loader:

  • sass-loader:加載 SASS / SCSS 文件並將其編譯爲 CSS
  • css-loader: 解析 css 代碼中的 url、@import語法像importrequire同樣去處理css裏面引入的模塊
  • style-loader:幫咱們直接將css-loader解析後的內容掛載到html頁面當中

安裝 loader:

npm i css-loader style-loader sass-loader sass --save-dev

而後在webpack.config.js中配置它們:

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

module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ["style-loader", "css-loader", "sass-loader"]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "src", "index.html")
    })
  ]
};

注意loader的出現順序:首先是sass-loader,而後是css-loader,最後是style-loader

如今,運行npm start,你應該會在HTML的頭部看到加載的樣式表:

clipboard.png

打包現代 JavaScrip

webpack 自己並不知道如何轉換JavaScript代碼。 該任務已外包給babel的第三方 loader,特別是babel-loader

babel是一個JavaScript編譯器和「編譯器」。 babel 能夠將現代JS(es6, es7...)轉換爲能夠在(幾乎)任何瀏覽器中運行的兼容代碼。

一樣,要使用它,咱們須要安裝一些 Loader:

  • babel-core :把 js 代碼分析成 ast ,方便各個插件分析語法進行相應的處理
  • babel-preset-env:將現代 JS 編譯爲ES5
  • babel-loader :用於 webpack

引入依賴關係

npm i @babel/core babel-loader @babel/preset-env --save-dev

接着,建立一個新文件babel.config.json配置babel,內容以下:

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

最後在配置一下 webpack :

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

module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ["style-loader", "css-loader", "sass-loader"]
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ["babel-loader"]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "src", "index.html")
    })
  ]
};

要測試轉換,能夠在 src/index.js中編寫一些現代語法:

import "./style.scss";
console.log("Hello webpack!");

const fancyFunc = () => {
  return [1, 2];
};

const [a, b] = fancyFunc();

如今運行npm run dev來查看dist中轉換後的代碼。 打開 dist/main.js並搜索「fancyFunc」:

\n\nvar fancyFunc = function fancyFunc() {\n  return [1, 2];\n};\n\nvar _fancyFunc = fancyFunc(),\n    _fancyFunc2 = _slicedToArray(_fancyFunc, 2),\n    a = _fancyFunc2[0],\n    b = _fancyFunc2[1];\n\n//# sourceURL=webpack:///./src/index.js?"

沒有babel,代碼將不會被轉譯:

\n\nconsole.log(\"Hello webpack!\");\n\nconst fancyFunc = () => {\n  return [1, 2];\n};\n\nconst [a, b] = fancyFunc();\n\n\n//# sourceURL=webpack:///./src/index.js?");

注意:即便沒有babel,webpack也能夠正常工做。 僅在執行 ES5 代碼時才須要進行代碼轉換過程。

在 Webpack 中使用 JS 的模塊

webpack 將整個文件視爲模塊。 可是,請不要忘記它的主要目的:加載ES模塊

ECMAScript模塊(簡稱ES模塊)是一種JavaScript代碼重用的機制,於2015年推出,一經推出就受到前端開發者的喜好。在2015之年,JavaScript 尚未一個代碼重用的標準機制。多年來,人們對這方面的規範進行了不少嘗試,致使如今有多種模塊化的方式。

你可能據說過AMD模塊,UMD,或CommonJS,這些沒有孰優孰劣。最後,在ECMAScript 2015中,ES 模塊出現了。

咱們如今有了一個「正式的」模塊系統。

要在 webpack 使用 ES module ,首先建立 src/common/usersAPI.js 文件:

const ENDPOINT = "https://jsonplaceholder.typicode.com/users/";

export function getUsers() {
  return fetch(ENDPOINT)
    .then(response => {
      if (!response.ok) throw Error(response.statusText);
      return response.json();
    })
    .then(json => json);
}

src/index.js中,引入上面的模塊:

import { getUsers } from "./common/usersAPI";
import "./style.scss";
console.log("Hello webpack!");

getUsers().then(json => console.log(json));

生產方式

如前所述,webpack有兩種操做模式:開發(development )和(production)。 到目前爲止,咱們僅在開發模式下工做。

在開發模式中,爲了便於代碼調試方便咱們快速定位錯誤,不會壓縮混淆源代碼。相反,在生產模式下,webpac k進行了許多優化:

  • 使用 TerserWebpackPlugin 進行縮小以減少 bundle 的大小
  • 使用ModuleConcatenationPlugin提高做用域

在生產模式下配 置webpack,請打開 package.json 並添加一個「 build」 命令:

如今運行 npm run build,webpack 會生成一個壓縮的包。

Code splitting

代碼拆分(Code splitting)是指針對如下方面的優化技術:

  • 避免出現一個很大的 bundle
  • 避免重複的依賴關係

webpack 社區考慮到應用程序的初始 bundle 的最大大小有一個限制:200KB

在 webpack 中有三種激活 code splitting 的主要方法:

  • 有多個入口點
  • 使用 optimization.splitChunks 選項
  • 動態導入

第一種基於多個入口點的技術適用於較小的項目,可是從長遠來看它是不可擴展的。這裏咱們只關注第二和第三種方式。

Code splitting 與 optimization.splitChunks

考慮一個使用Moment.js 的 JS 應用程序,Moment.js是流行的時間和日期JS庫。

在項目文件夾中安裝該庫:

npm i moment

如今清除src/index.js的內容,並引入 moment 庫:

import moment from "moment";

運行 npm run build 並查看控制的輸出內容:

main.js    350 KiB       0  [emitted]  [big]  main

整個 moment 庫都綁定到了 main.js 中這樣是很差的。藉助optimization.splitChunks,咱們能夠從主包中移出moment.js

要使用它,須要在 webpack.config.js 添加 optimization 選項:

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

module.exports = {
  module: {
  // ...
  },
  optimization: {
    splitChunks: { chunks: "all" }
  },
  // ...
};

運行npm run build 並查看運行結果:

main.js   5.05 KiB       0  [emitted]         main
vendors~main.js    346 KiB       1  [emitted]  [big]  vendors~main

如今,咱們有了一個帶有moment.js 的vendors〜main.js,而主入口點的大小更合理。

注意:即便進行代碼拆分,moment.js仍然是一個體積較大的庫。 有更好的選擇,如使用luxondate-fns

Code splitting 與 動態導入

Code splitting的一種更強大的技術使用動態導入來有條件地加載代碼。 在ECMAScript 2020中提供此功能以前,webpack 提供了動態導入。

這種方法在 Vue 和 React 之類的現代前端庫中獲得了普遍使用(React有其本身的方式,可是概念是相同的)。

Code splitting 可用於:

  • 模塊級別
  • 路由級別

例如,你能夠有條件地加載一些 JavaScript 模塊,以響應用戶的交互(例如單擊或鼠標移動)。 或者,能夠在響應路由更改時加載代碼的相關部分。

要使用動態導入,咱們先清除src/index.html,並寫入下面的內容:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Dynamic imports</title>
</head>
<body>
<button id="btn">Load!</button>
</body>
</html>

src/common/usersAPI.js中:

const ENDPOINT = "https://jsonplaceholder.typicode.com/users/";

export function getUsers() {
  return fetch(ENDPOINT)
    .then(response => {
      if (!response.ok) throw Error(response.statusText);
      return response.json();
    })
    .then(json => json);
}

src/index.js

const btn = document.getElementById("btn");

btn.addEventListener("click", () => {
  //
});

若是運行npm run start查看並單擊界面中的按鈕,什麼也不會發生。

如今想象一下,咱們想在某人單擊按鈕後加載用戶列表。 「原生」的方法可使用靜態導入從src/common /usersAPI.js加載函數:

import { getUsers } from "./common/usersAPI";

const btn = document.getElementById("btn");

btn.addEventListener("click", () => {
  getUsers().then(json => console.log(json));
});

問題在於ES模塊是靜態的,這意味着咱們沒法在運行時更改導入的內容。

經過動態導入,咱們能夠選擇什麼時候加載代碼

const getUserModule = () => import("./common/usersAPI");

const btn = document.getElementById("btn");

btn.addEventListener("click", () => {
  getUserModule().then(({ getUsers }) => {
    getUsers().then(json => console.log(json));
  });
});

這裏咱們建立一個函數來動態加載模塊

const getUserModule = () => import("./common/usersAPI");

如今,當你第一次使用npm run start加載頁面時,會看到控制檯中已加載 js 包:

clipboard.png

如今,僅在單擊按鈕時才加載/common/usersAPI

clipboard.png

對應的 chunk 是 0.js

經過在導入路徑前面加上魔法註釋/ * webpackChunkName:「 name_here」 * /,能夠更改塊名稱:

const getUserModule = () =>
  import(/* webpackChunkName: "usersAPI" */ "./common/usersAPI");

const btn = document.getElementById("btn");

btn.addEventListener("click", () => {
  getUserModule().then(({ getUsers }) => {
    getUsers().then(json => console.log(json));
  });
});

clipboard.png

人才們的 【三連】 就是小智不斷分享的最大動力,若是本篇博客有任何錯誤和建議,歡迎人才們留言,最後,謝謝你們的觀看。


代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

原文:https://www.sitepoint.com/web...

交流

文章每週持續更新,能夠微信搜索 【大遷世界 】 第一時間閱讀,回覆 【福利】 有多份前端視頻等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已經收錄,歡迎Star。

相關文章
相關標籤/搜索