本文介紹了在網頁開發中使用模組化的問題及解決方法,以及使用 Webpack 打包工具的過程和技巧。文章中詳細介紹了在瀏覽器中使用 ES6 的模組化語法的限制,以及使用 Webpack 可以解決的問題。此外,文章還講解了如何配置 Webpack 並使用其打包功能,以及如何使用 Webpack 的 loader 來處理 CSS、圖片等資源。
Module and CommonJS
在 ES6 (2015) 之前,JS 的模組化沒有任何標準或規則可以遵循,任何人都可以創建自己的 import/export
語法。例如在以下的例子中,我們使用 module.exports
與 require
來實現模組化。這種方式就是 CommonJS 的模組化規範,是 在 Node.js 的模組化規範。
// utils.js
function sum(a, b) {
return a + b;
}
module.exports = {
sum,
name: "utils",
};
// main.js
var obj = require("./utils");
console.log(obj.sum(1, 2)); // 3
console.log(obj.name); // utils
Browser Module and Browserify
我們在瀏覽器中執行的 JavaScript 沒有使用 CommonJS 或任何其他 import/export
語法的能力。於是 Browserify 在 2011 年被發明出來,並說:"Browserify lets you require('modules') in the browser by bundling up all of your dependencies."。
Browserify 簡單地將應用程式的進入點(main.js
)和所有的 模組(import/export) 自動打包成一個可以在瀏覽器中使用的 JavaScript 檔案(bundle.js
),讓你可以在瀏覽器中自由地使用 CommonJS。
npx browserify main.js -o bundle.js
ES6 ESM (import and export)
時間來到了 2015 年,ES6 為 JavaScript 建立了一個標準的模組化 import/export
語法(ESM),但它仍然無法直接與瀏覽器或 Node.js 相容。
// utils.js
function sum(a, b) {
return a + b;
}
export default {
sum,
name: "utils",
};
// main.js
import obj from "./utils";
console.log(obj.sum(1, 2)); // 3
console.log(obj.name); // utils
ES6 ESM in Node.js
如果你想要在 Node.js 中使用 ES6 的 import/export
語法,你有兩種選擇:
- 將你的檔案從
.js
改成.mjs
- 或者使用 babel 將 ES6 語法轉換成 ES5
ES6 ESM in Browser
如果你想要在瀏覽器中使用 ES6 的 import/export
語法,你必須照著以下步驟做:
- 在
<script>
標籤中加入type="module"
屬性
<head>
<script src="./main.js" type="module"></script>
</head>
- 在
import
的檔案路徑中加入副檔名.js
// main.js
import obj from "./utils.js";
console.log(obj.sum(1, 2)); // 3
console.log(obj.name); // utils
- 使用
http://
來執行你的應用程式,而不是file://
上面的步驟似乎已經解決了 ESM 的問題,但它仍然有兩個問題:
- ESM 在舊版的瀏覽器中不被支援,例如 IE
- ESM 很難從 npm 安裝並引入套件,例如
import pad from 'pad-left'
這樣的語法是不被支援的。你可能需要每次都使用import pad from './node_modules/pad-left/index.js
來引入套件。
Webpack
因為模組化的機制在瀏覽器中有很多問題(例如與不同瀏覽器和 npm 的相容性),我們需要一個工具來解決它。而這個工具就是 Webpack。 Webpack 與 Browserify 很相似。它不僅允許我們的瀏覽器 JavaScript 代碼輕鬆地利用 ES6 的 import/export
語法來使用模組,而且還附帶了許多有用的 loader
和 plugin
。讓我們可以做到像下面這樣的事情:
// main.js
import obj from "./utils.js";
import pad from "pad-left"; // npm install pad-left
console.log(obj.cal(30));
console.log(pad("4", 4, 0));
How to bundle with Webpack
在使用 Webpack 時,我們首先需要建立一個設定檔(webpack.config.js
)。在設定檔中,我們需要定義一些基本的參數,例如應用程式的進入點(./main.js
),以及產生的打包檔案的檔名和位置。Webpack 也有一個 mode
參數,可以在 development 和 production 之間切換。我們可以使用 development mode 讓打包的速度在 development 時變快。
// webpack.config.js
module.exports = {
mode: "development", // or 'production'
entry: "./main.js",
output: {
// __dirname means using the same folder as this config
path: __dirname,
filename: "webpack_bundle.js",
},
};
接著,我們可以使用 npx webpack
來執行 Webpack。Webpack 會自動讀取 webpack.config.js
並執行打包。我們也可以使用 npx webpack --config webpack.config.js
來執行 Webpack。
npm init -y
npm install webpack webpack-cli --save-dev
npx webpack --config webpack.config.js
最後,我們需要在 index.html
中加入打包好的檔案 webpack_bundle.js
就可以了。
使用 webpack 產生的 bundle 檔案可以不需要把 type=module
加在 <script>
標籤中。
<head>
<script src="./webpack_bundle.js"></script>
</head>
The power of webpack (loaders)
Webpack 除了解決模組化的問題之外,還可以讓我們直接在 JavaScript 中引入圖片或 CSS。這個功能與 JavaScript 或 ES6 完全無關,完全是由 webpack 將圖片和 CSS 模組化所達成的。
import Image from "./assets/banner.png";
import styles from "style.css";
為了達成這些功能,Webpack 定義了一些 loader
,不同的 loader
處理不同的資源。例如,你可以使用一個 SCSS loader 來載入 SCSS 檔案,Webpack 會幫你將它們編譯成純 CSS。或是你可以使用 ES10 來撰寫你的 JS,Webpack 會使用 babel-loader
將它轉換成 ES5。
Webpack 還可以在打包的過程中做到更多有趣的事情,例如:
- 將 JS 做 uglify
- 將 CSS 做 minify
- 為打包好的檔案加上 hash
- 將不同的檔案打包成不同的頁面,這樣你就不需要一次載入所有的檔案
- 在需要時 動態引入 JS
Review
在你閱讀完這篇文章之後,你應該可以回答這些問題:
1. 為什麼很多專案(例如 React)在部署之前都要經過建置(`build`)?
2. require/module.exports
與 import/export
有什麼不同?
require/module.exports
是支援 Node.js 的 CommonJS 語法,而 import/export
是 ES6 的語法,由 Node.js 和部分瀏覽器支援。3. 在瀏覽器中使用 ES6 的 import/export
有什麼限制嗎?
- 必須在 server 端執行
- 必須在
<script>
標籤中加上type=module
- 必須在
import
中指定檔案副檔名 - 必須在
import
中指定 npm 套件的絕對路徑
4. 為什麼我們要使用 webpack
?
- 我們想更容易地引入 ES6 模組(和 npm 套件)
- 我們想更容易地引入圖片和 CSS 檔案
- 我們想統一在一個地方處理 uglify、minify 和其他處理程序