前言
CommonJS 為當初最早設計用來解決 JavaScript 模組化設計的規範,使用簡單的幾個語法,即可達到模組化的效果。本篇不會探討較新標準的其他規範,比如 ES6 Module 等等,將會把焦點放在如何以 CommonJS 規範針對 Gulp 進行模組化設計,以及 CommonJS 規範中最常被人拿來討論的 module.exports 與 exports 語法兩者差別。
筆記重點
- CommonJS 初始環境建構
- CommonJS 規範相關語法
- Gulp 相關套件安裝
- Gulp 模組化設計
CommonJS 初始環境建構
專案結構:
1 | commonjs/ |
在之後講解到關於 CommonJS 規範相關語法時,都會以上面這一個專案結構做為測試目的。
CommonJS 規範相關語法
CommonJS 規範相關語法:
module.exports:導出模塊(推薦寫法)exports:導出模塊require:引入模塊
內部原理:
exports=module.exports={}= 空物件exports是module.exports的一個捷徑變數exports.xxx,相當於在匯出物件上新增屬性,該屬性對呼叫模組直接可見exports = xxx,相當於給exports物件重新賦值,require無法訪問exports物件及其屬性require引用模組後,返回給呼叫者的是module.exports而不是exports- 由於一個模組只有一個
exports,當有多個物件需要exports時,可利用新增屬性方式掛載到物件上,最後統一導出
exports = module.exports = {} = 空物件
路徑:./module.js
1 | console.log(module.exports); // {} |
從上面範例可以得知,module.exports 與 exports 本身是一個物件,而 exports 本身是 module.exports 的捷徑變數,兩者指向記憶體位址是一樣的,也就代表不管是操作 module.exports 還是 exports 物件,其實都是操作同一個物件。
exports 是 module.exports 的一個捷徑變數
路徑:./module.js
1 | // 以物件方式增加屬性 |
前面有講解到 exports 是 module.exports 的捷徑變數,但需要注意的是,這邊說的捷徑變數是指 module.exports 初始物件,如果 module.exports 有任何賦值動作,exports 只會透過捷徑映射到初始物件,如同上面範例。
exports.xxx,相當於在匯出物件上新增屬性,該屬性對呼叫模組直接可見
路徑:./module.js
1 | const fun = () => { |
路徑:./main.js
1 | /* --- 導入模塊 ---*/ |
在我們導出模塊時,可透過物件新增屬性的方式掛載內容,接收方只需要使用 require 導入模塊即可載入目標模塊的物件以及掛載內容,前面有提到 exports 是 module.exports 的捷徑變數,我們可改寫導出模塊方式:
路徑:./module.js
1 | const fun = () => { |
exports = xxx,相當於給 exports 物件重新賦值,require 無法訪問 exports 物件及其屬性
路徑:./module.js
1 | const fun = () => { |
路徑:./main.js
1 | /* --- 導入模塊 ---*/ |
前面我們都是針對 module.exports 本身物件新增屬性,之後再導出模塊,事實上,我們可以直接針對 module.exports 重新賦值,這樣子的作法在導入模塊時,就不需要以物件方式拿取裡面的內容,可以直接進行取用,可能有人就在想,既然 exports 是 module.exports 的捷徑變數,那我們是否可針對 exports 重新賦值?如下面寫法:
路徑:./module.js
1 | const fun = () => { |
答案是不行的,下段介紹會有說明。
require 引用模組後,返回給呼叫者的是 module.exports 而不是 exports
路徑:./module.js
1 | const fun = () => { |
路徑:./main.js
1 | /* --- 導入模塊 ---*/ |
前面有提到,使用 exports 重新賦值時,require 接收到的或是一個空物件,造成此結果的原因在於 require 引用模組時,返回給呼叫者的是 module.exports 而不是 exports,exports 預設是 module.exports 的捷徑變數,代表兩者指向記憶體位址相同,當我們針對 exports 物件新增屬性時,module.exports 也會接連變動,但當我們針對 exports 重新賦值後,exports 就與 module.exports 無任何關係,兩個是完全不一樣的東西,這才導致使用 require 時,接收到的會是一個空物件,因為 module.exports 物件匯出時沒有任何掛載屬性,簡單來講,exports 只適合用以掛載屬性導出模塊,如果你想要避免兩者的使用陷阱,module.exports 是最好的選擇。
由於一個模組只有一個 exports,當有多個物件需要 exports 時,可利用新增屬性方式掛載到物件上,最後統一導出
路徑:./module.js
1 | const variable = '變數'; |
路徑:./main.js
1 | const obj = require('./module'); |
由於一個 .js 檔案就等於一個 module.exports,如果有多個物件、變數、函式需要做導出時,可使用物件新增屬性方式掛載內容,如上面範例。此時也可以搭配物件解構直接取用掛載內容:
路徑:./main.js
1 | const { variable, fun, people } = require('./module'); |
Gulp 相關套件安裝
套件連結:gulp-sass
1 | $ npm install gulp-sass |
此次範例會使用 gulp-sass 套件,請先進行安裝
Gulp 模組化設計
初始專案結構:
1 | gulp-demo/ |
請注意 Gulp 主檔案位置,原本我們都是使用 gulpfile.js 作為依據,設計模組化應用時,建議將同應用檔案放在同一個資料夾,所以我們新增了一個 gulpfile.js 資料夾,裡面新增了一個 index.js 檔案,這一個就是我們的 Gulp 主檔案,而 compile.js 是我們 SCSS 相關套件的模組檔案,下面會有說明。
Gulp 主檔案:
1 | const gulp = require('gulp'); |
Gulp 模組檔案:
1 | const gulp = require('gulp'); |
之前我們都是使用 Task 名稱作為執行任務參數依據,在 Gulp 4 中可接受函式作為參數依據,就如同上面範例所演示,Gulp 主檔案使用 gulp.series 非同步方式執行任務,而這一個任務以往都是字串形式的 Task 名稱,但現在我們使用函式方式導入,而這一個函示就是 ./compile.js 所宣告的函式,我們可以將任務內容寫到函式裡頭,最後再將這一個函式導出,即可完成模組化應用,相同方式可套用在其他模組,只需要了解 exports 與 require 原理,就能夠拆分出任何形式的模組。