NodeJS 與 CommonJS 到 ESM 與 Webpack


Posted by hoyi-23 on 2021-08-17


本篇文章從 TechBridge學習並參考。這裡只是自己的學習筆記喲!


ESM 全名是 「ES6 Modules or JavaScript Modules」。

ES6 Modules or JavaScript Module

為了因應 JavaScript 越來越多工、越寫越複雜,開發者覺得要將 JavaScript 模組化 才行阿!

什麼是 模組化 ?

假設今天有一個網頁,有好幾個相同的頁面都會渲染畫面的資料,檔案方別為 app1.js 與 app2.js,在沒有模組化前遇到這個狀況,就必須在不同檔案間重複撰寫。

模組化讓我們將有重複性且可以一直利用的程式碼片段抽出來當作模組使用。


關於Node.js模組

說到模組可以先來了解一下Node.js。
Node.js裡面可以使用require把內建的模組引入,同時可以使用module.exports自己做一個模組輸出。
範例: 在utils.js內有一個很常會用到的計算函式:

// utils.js
function calculate(n) { 
  return (n * 0.88) + 5  // 計算價格公式
}

module.exports = calculate // 把這個函式 export 出去

到另一個檔案app.js就可以使用import引入:

// app.js
var calculate = require('./utils')
console.log(calculate(100))

除了函式可以輸出引入,物件也可以:

// utils.js
function calculate(n) { 
  return (n * 0.88) + 5  // 計算價格公式
}
module.exports = { cal: calculate}, name: 'money'}
// app.js
var obj = require('./utils')
console.log(obj.cal(100));
console.log(obj.name)

記得一個最簡單的觀念: module.exports 出什麼,import進來就會是一樣的東西!

Node.js 最基本的模組使用概念: module.exports 導出, require 引入。


什麼是 CommonJS?

等等怎麼橫空插出一個 CommonJS ?
在 ESM 成熟前,流傳著不同的模組化撰寫標準,而CommonJS就是其中一種。

CommonJS的標準: module.exports 導出, require 引入。

後來這個標準就被Node.js採用,所以上面Node.js的模組基本概念就是從CommonJS來的


好的,那上面提到Node.js基於CommonJS有了模組標準,但是這樣的寫法瀏覽器原生不支援這個東西(也就是瀏覽器不能用 module.exports 導出, require 引入)。
但是!!!瀏覽器可以借助其他工具來達成目的。


browserify 介紹

在2011年時,browserify出現了! browserify 是一個可以讓你在瀏覽器上使用reauire的指令。
指令npx browserify /*entry point*/ -o /*output*/
可以在終端機上使用指令 npx browserify main.js -o bundle.js,來打包main.js 與 utils.js。

如何運作?

在bundle.js內,把的程式碼用一個 function 包住,提供一個叫做 require 的 function 以及一個叫做 module 的物件來使用」,
下次再來好好探討browserify的運作!

那除了 browserify 外,還有一個在前端更有名的 webpack。


webpack

webpack和browserify 其實是相似的! browserify 使用指令來指定Entry point 與 Output。
而webpack是將這些寫成一個設定檔案:webpack.config.js

module.exports = {
  mode: 'development' //開發模式(另外還有生產模式 production)
  entry: './main.js', //入口點
  output: {
    path: __dirname, //輸出路徑
    filename: 'webpack_bundle.js' //輸出的檔案名稱
  }
}

接著在終端機輸入指令來安裝並執行

npm init -y
npm install webpack webpack-cli --save-dev
npx webpack --config webpack.config.js

Webpack的整體作法與 browserify 類似,那為什麼要使用webpack?因為要在瀏覽器上面使用 CommonJS 的模組機制,就必須使用工具先把程式碼打包才能做到


ES6 的 import 與 export (ESM)

ok! 現在我們知道 webpack 幫助我們打包程式碼然後讓JS模組化並可以使用require,那ES6中的importexport呢?
前面有提到在 ES6 出現以前,JavaScript 並沒有一個標準的模組化規範。Node.js 支援 CommonJS,所以才可以用require跟module.exports,但是瀏覽器原生沒有支援,所以才需要像是 browserify 以及 webpack 這種工具。

而 ES6 出來之後,有了正式的規範( import 與 export ),我們可以把之前的 main.js 與 utils.js 改成 import 與 export 的形式:

// main.js
import obj from './utils'
console.log(obj.cal(30))
console.log(obj.name)
//utils.js
function calculate(n) { 
  return ((n * 100 + 20 - 4)) % 10 + 3  // 計算價格公式
}

export default {
  cal: calculate,
  name: 'hello'
}

看起來好像很好~但是真的要只使用ES6 的 import 與 export其實是滿麻煩的!

  1. 需要將引入的script加上type="module"
  2. 不能直接用檔案開啟(也就是不能直接用雙擊HTML檔開啟專案=>檔案開頭file:///)。必須使用伺服器的方式開啟才行(在同一個目錄底下輸入指令(python -m SimpleHTTPServer 8080,接著就可以打開:http://localhost:8080。)
  3. import的檔案附檔名要明確(不能只寫./utilis,要寫./utilis.js)
  4. 支援度問題(IE步行用啊~)
  5. 如果像要使用npm寫入其他人的套件呢?需要使用import pad from './node_modules/pad-left/index.js'這樣的方式引入嗎?QQ 這樣的寫法維護性相當差,若是模組的入口點變了,你就必須改寫所有 import 的地方。而且難道要把整個 node_modules 資料夾一起傳上去嗎?

主要只使用 ESM 的問題就是相容性、無法兼容 npm 等等,所以我們就要再回來看看 WEBPACK 啦!


Webpack

前面提到設定一個路徑檔案webpack.config.js

module.exports = {
  mode: 'development' //開發模式(另外還有生產模式 production)
  entry: './main.js', //入口點
  output: {
    path: __dirname, //輸出路徑
    filename: 'webpack_bundle.js' //輸出的檔案名稱
  }
}

接著在終端機輸入指令來安裝並執行

npm init -y
npm install webpack webpack-cli --save-dev
npx webpack --config webpack.config.js

我們可以試著載入一個套件

npm install pad-left

並在 main.js 裡面引入套件並且使用:

import obj from './utils.js'
import pad from 'pad-left'
console.log(obj.cal(100))
console.log(pad('4', 4, 0))

然後一樣按照之前教過的流程打包檔案,config 檔之前已經寫過了,所以直接下指令就好:

npx webpack --config webpack.config.js

接著打開 index.html,更改引入的 script,因為有 webpack 的關係所以不需要 type=module 了:

<html>
<head>
</head>
<body>
<script src="./webpack_bundle.js"></script>
</body>
</html>

最後打開 index.html,看到 console 上面出現 9 跟 0004 這兩個輸出,就代表有打包成功了。


使用 webpack 的好處之一,就是我們能把使用 npm 安裝的模組一併打包,就跟打包自己寫的模組一樣,不需要多做其他事情。這件事是原生的瀏覽器沒辦法做到的。

webpack除了將JS打包模組化外,也將範圍延伸到像CSS或是IMG,任何資源都可以 import 進來使用。為了要支援這樣的功能,webpack 定義了許多 loader(載入器),不同的資源有不同的 loader,要透過 loader 處理才能把資源載入。而這個 loader 也是它強大的地方。例如說你可以用 scss loader 載入 scss 的檔案,在引入資源的時候就會幫你順便編譯成 CSS,所以你不用自己做這件事。JS 也是一樣,你可以寫最新最潮的語法,然後在載入時用 babel-loader 把 ES10 的語法轉成 ES5。


Q:為什麼很多專案(例如說 React)在部署前都要先 build?這個步驟在幹嘛你知道嗎?
因為原始碼沒辦法直接放上去瀏覽器(會沒有辦法執行),所以一定要經過 webpack 打包處理,打包完的檔案才能讓瀏覽器執行。

Q:你知道 require/module.exports 與 import/export 的差別嗎?
require/module.exports 是一套叫做 CommonJS 的規範,Node.js 有支援,瀏覽器沒有。

import/export 是 ES6 的規範,Node.js 部分支援,瀏覽器也是部分支援。

Q:你知道 import 與 export 這兩個語法在瀏覽器上面不是隨便就能使用嗎?
有使用限制,例如說要加上 type=module,而且也沒辦法直接引入 npm 裡的模組,要把路徑寫死才能使用。而瀏覽器支援度也是一個考量,IE11 並不支援此種寫法。

Q:你知道為什麼要用 webpack 嗎?
原因有很多,例如說:

我想使用 npm 上的第三方模組
我想把圖片當作資源 import 進來
我想把 CSS 當作資源 import 進來
我想在一個地方就處理好 uglify 與 minify
不過重點其實是第一個,因為瀏覽器原生的 ES6 模組支援度沒那麼高,尤其是引入第三方模組,所以才需要透過 webpack 或其他打包工具幫我們處理好這一段。

Q:你知道 webpack 為什麼要有 loader 嗎?
因為把圖片或是 CSS 當作資源引入這並不是正式的規範,而是 webpack 自己延伸的定義。為了支援這些資源,就必須特別寫一個 loader 去載入,否則預設的 loader 只能載入 JavaScript。


#ESM #nodejs #CommonJS #Webpack







Related Posts

Web開發學習筆記15 — 呼叫堆疊、同步與非同步、Promise、Async/Await、Conditional ternary operator

Web開發學習筆記15 — 呼叫堆疊、同步與非同步、Promise、Async/Await、Conditional ternary operator

jQuery 與 Ajax

jQuery 與 Ajax

N4.1_Webpack 是什麼及為什麼要用它?

N4.1_Webpack 是什麼及為什麼要用它?


Comments