看了很多webpack的教程,大多是上來就講一堆配置,一堆插件的使用。這種文章看起來有點類似於官方文檔,或者新華字典。我想回歸初心,換一種方式,基於實際使用出發,一步一步介紹webpack。
從一個最簡單的例子開始,這個例子只爲了描述最簡單的webpack功能,實現一個最簡單的需求:在瀏覽器上顯示一段文字。
傳統方式
首先我們有一個index.html
,這個html中只是引入了index.js
,a.js
和b.js
。
<!Doctype html>
<html>
<head>
<meta name="charset" content="utf-8">
<title>webpack</title>
</head>
<body>
<script type="text/javascript" src="./a.js"></script>
<script type="text/javascript" src="./b.js"></script>
<script type="text/javascript" src="./index.js"></script>
</body>
</html>
在a.js
中聲明瞭變量var a = 1;
,在b.js
中聲明瞭變量var b = 2;
。由於這種方式下,作用域是共享的,a和b相當於都是掛載在window上,所以在index.js
中可以直接訪問到a和b的值。
在index.js
中,我們創建一個DOM並掛載到HTML上:
var dom = document.createElement('p');
dom.innerHTML = `a=${a}; b=${b}`;
document.body.appendChild(dom);
最後,在瀏覽器上就會顯示a=1; b=2
。
這種使用方式雖然簡單,但是會有潛在的問題:
- 瀏覽器在加載時,會先加載html文件,然後根據HTML裏面的script標籤去依次加載每個js文件。這樣對於每個js文件,瀏覽器都會向服務器發送一次請求。如果引入的文件數很多,那麼發送的請求次數就會過多,對服務器造成一定的壓力。而且,單個文件可能並不大,相對於瀏覽器對每次請求都需要建立鏈接、斷開鏈接的成本來說很不划算。要解決這個問題,就需要打包,將多個js文件打包成單個。
- 不能嚴格保證js文件的加載順序,比如
index.js
加載完了,但它所依賴的a.js
還沒加載。當然,這個問題可以用require.js
解決。 - 不同script標籤引入的js代碼,會污染全局作用域,比如
a.js
中聲明的a就直接掛載到了window上,其他文件中如果再聲明a變量,就會有衝突。這個問題可以用立即執行函數的方式解決。
雖然有辦法解決,但總感覺不是那麼完美,治標而不治本。迴歸到js的運行環境上,這都是因爲js代碼需要在瀏覽器中運行。如果是在node環境中運行,那可以直接使用CommonJS規範,每個文件就是一個模塊,各個模塊之間的作用域是獨立的,通過require可以解決模塊依賴和加載問題。甚至還可以在node中利用ES6的模塊機制,也同樣可以解決這個問題。顯然,這種寫代碼的方式要簡單很多,但只能在node環境下。而webpack的一個重要作用,就是可以讓你這種方式寫出來的代碼能在瀏覽器中運行。
webpack方式
a.js
和b.js
分別作爲2個模塊,通過ES6的export導出變量a和b,在index.js中通過import引入:
//a.js
export var a = 1;
//b.js
export var b = 2;
//index.js
import {a} from './a.js';
import {b} from './b.js';
var dom = document.createElement('p');
dom.innerHTML = `a=${a}; b=${b}`;
document.body.appendChild(dom);
最後,我們希望用webpack,將其打包成一個單獨的文件,直接掛載到index.html
中。從零開始,安裝webpack。
- 新建一個文件夾,在這個文件夾中
npm init
,初始化。 - 安裝
webpack
和webpack-cli
,運行npm install webpack webpack-cli -D
。webpack-cli
爲webpack提供了命令行工具,讓我們可以直接在命令行中使用webpack - 建立src文件夾,將
a.js
,b.js
和index.js
存放在src文件夾下。這個文件夾存放的是原始文件 - 建立dist文件夾,用來存放編譯後的文件,也就是打包後的單個文件
-
將
index.html
放到src文件夾下,這時引用的不是index.js
,而是打包後的位於dist目錄下的bundle.js
文件<script type="text/javascript" src="../dist/bundle.js"></script>
-
配置webpack。webpack的配置就是在根目錄下直接新建一個
webpack.config.js
,配置如下:const path = require('path'); module.exports = { entry: './src/index.js', output: { path: path.join(__dirname, 'dist'), filename: 'bundle.js' } }
- entry是打包的入口文件,也就是告訴webpack打包哪個文件,這裏指定的是
index.js
。由於在index.js
中import
了a.js
和b.js
,所以webpack在打包時會同時將a.js
和b.js
引入。從這裏可以看到,只用告訴webpack入口文件即可,所有的依賴文件webpack會自己尋找和解決。 - output是告訴webpack,打包後的文件放哪裏。path指定了打包後的文件路徑,filename指定了打包後的文件名。綜合起來,打包後的文件就是dist目錄下的
bundle.js
- entry是打包的入口文件,也就是告訴webpack打包哪個文件,這裏指定的是
-
在
package.json
中的script下做個配置:"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack" },
- 直接運行
npm run build
,node就會自動執行webpack,這時在dist目錄下就可以看到生成的js文件(只有一個),將index.html
放到瀏覽器中,就會看到最後顯示的效果。
不止於此
從上面的例子看到,使用了webpack之後,我們解決了傳統方式裏面遇到的各種問題。當然,webpack能做的,遠不止這些。比如在寫代碼時,可能還會有這些需求:
- 代碼轉換:將 TypeScript 編譯成JavaScript、將 SCSS 編譯成 CSS等。
- 文件優化:壓縮JavaScript、CSS、HTML 代碼,壓縮合並圖片等。
- 代碼分割:提取多個頁面的公共代碼,提取首屏不需要執行部分代碼讓其異步記在。
- 模塊合併:在採用模塊化的項目裏會有很多個模塊和文件,需要通過構建功能將模塊分類合併成一個文件。
- 自動刷新:監聽本地源代碼變化,自動重新構建、刷新瀏覽器。
- 代碼校驗:在代碼被提交到倉庫前需要校驗代碼是否符合規範,以及單元測試是否通過。
- 自動發佈:更新代碼後,自動構建出線上發佈代碼並傳輸給發佈系統。
我們需要一個工具來幫我們解決這些問題,完成整個構建流程。使用構建工具的目的,是爲了讓我們寫代碼更加方便,可以用更新的特性而不用過多關心瀏覽器的兼容問題;讓我們可以省去很多機械重複性的工作,比如修改代碼後瀏覽器會自動刷新,提高我們的開發效率。
當然,在webpack之前,已經有很多優秀的構建工具了,比如grunt、gulp等。稱webpack是當下最流行的構建工具毫不爲過。webpack強大不僅在其本身,還在於很多基於webpack的插件,提供了一個強大的生態系統。webpack能做的事情還有很多,將在後面一步步繼續學習。