# Performance 組建流程優化 Build Process Optimization- Code Splitting
在開發流程中我們會使用到不同的工具,在組建流程我們會使用到 Bundler 這個工具。
> What is bundler? Why we need bundler?
那先來 Javascript 的 Module 的歷史吧~ 會在這個過程中發現 bundle 是怎麼開始的~
# **A History of JavaScript Modules and Bundling**
### In the Beginning, Multi-Page Websites With Global Scripts
當 user 訪問不同網頁路徑時,user 會向 server request 這一個網頁路徑的靜態語言(HTML+CSS+JS),不同的路徑會有各自的程式碼。
當時我們會寫多個 js 檔,以 script tag 的方式引入。

像是上的的程式碼,`main.js` 依賴 `calculate.js`, `calculate.js` 依賴 `add.js`,script 必須按照正確的順序,而且變數都被加到 **global namespace**! 全域的變數很容易被修改或是混淆。
它們還是 synchronous and blocking,如下圖當 user 端訪問網頁後,瀏覽器在執行 script tag 那一行時,它會發送另一個 request,從 server 下載 `js` 檔。執行 HTML頁面的過程會暫停,直到 script 下載完成。

在 Bundling 時代開始前,有了一些解決方案,但只是 Workarounds 並不是真的解決的問題。 像是 “Module Pattern" (IIFE), 或是`jQuery` 的 `$` 符號。
+ IIFE, Immediately Invoked Function Expression
Module Pattern provide a way to create private variables and expose a controlled set of public methods or properties.
```javascript
var shoppingCartModule = (function() {
// Private variables
var cart = [];
// Private functions
function addToCart(item) {
cart.push(item);
console.log("Added to cart: " + item);
}
function removeFromCart(item) {
var index = cart.indexOf(item);
if (index !== -1) {
cart.splice(index, 1);
console.log("Removed from cart: " + item);
} else {
console.log(item + " not found in cart.");
}
}
// Public API
return {
addItem: function(item) {
addToCart(item);
},
removeItem: function(item) {
removeFromCart(item);
},
getCart: function() {
return cart.slice(); // Return a copy of the cart
},
getTotalItems: function() {
return cart.length;
}
};
})();
```
## And Then Came Bundling
為了模組化,開發者將程式碼分成多個檔案, 這樣的結果是 browser 會向 server request 多個小的 js 檔。那何不把所有的 js 檔 Bundle 在一起,這樣 browser 只需要 request 一個 js 檔!
剛開始開發者會手動將檔案 `cat`\-ing 在一起,有麻煩事的時候就會解答~ 隨後出現了 minifying 或 uglifying 的工具,JSMin from Douglas Crockford.
## **Node's Modules Introduce Backend Runtime**
2009年,Ryan Dahl 創建了 Node,像其他語言一樣,Node 需要一個packaging system。這個 packaging system 使不同的 package(programs)能夠被共享並被其他人使用,很快地,大家開始製作 code package, 這些 code package 也就是 node modules,而 `npm` 被創建用於管理這些 node modules。
```javascript
var otherModule = require('./otherModule');
```
```javascript
module.exports = MyModule;
```
其中透過 CommonJS 的標準來實踐,⚠️ JavaScript 本身並不原生支持require/module.exports syntax,Node compiler 和 runtime 能夠讀取並執行它們。同時 Browsers 也不懂這個 syntax,不能在 JavaScript 中使用後端 Node module libraries。
### **Modules For the Browser - AMD**
前端羨慕嫉妒恨,Node 有自己的 module system,也為瀏覽器創建了可以解析執行 module syntax 的工具。其中最著名的是 [RequireJS](https://requirejs.org/), Asynchronous Module Definition (AMD) system 非同步模組定義,
```javascript
define(['calculate, alert'], function(calculate, alert){
var values = [ 1, 3, 9 ];
var calculation = calculate(values)
alert('Hello')
document.getElementById("calculation").innerHTML = calculation;
})
```
FYI, 在 modern HTML,`<script>` 有也 asynchronous imports the [`async` or `defer` script attributes](https://levelup.gitconnected.com/html-script-element-attributes-async-vs-defer-vs-type-module-610b50a79dbd).)
AMD module syntax 已經是一大進步了,我們不用在手動的組織依賴關係。\
但!是!問題又來了~
1. Browser 為每一個 dependency 向 serever 發送 requests,dependency 的 dependency…等,很多很多小的 server requests.
2. RequireJS 僅適用於 AMD 語法,不適用於 CommonJS/Node 語法,所以很多 Node modules,frontend 沒辦法使用,所以必須要寫不同的兩個版本,用 if/else 去判斷環境 (Node or browser)。
> 救星也來了~ Browserify 引入 bundling software 來解決這兩個問題~
### **Browserify - Bundling Node Modules For Browsers**
他讓前端可以使用 CommonJS `require/export` syntax,並使用他們所需要的任何 node modules!在 server 執行 `build` 後,Browserify 會將所有的 `require` statements,通過 dependency paths 進行爬取(包含 Node modules),並建構出一個 bundle 檔(一大包沒有 require statements 的 js 檔),讓瀏覽器可以直接讀取。
他還將 Node built-ins (基本上是整個 library) 加上 polyfills 和 shims(replacement JS code),來兼容每個瀏覽器和瀏覽器版本。
`<script src="bundle.js"></script>`
> 現在我們來到了 modern JavaScript bundling world~\
> 執行一個 bundler program,然後 Build Process 中優化它!
## Webpack and Single-Page Web Apps
Webpack 在 2015年創建,並被 React 採用,它迅速取代 Browserify。

官網:Webpack is a bundler for modules. The main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset
他為網站所有的資源(JS, CSS, images, SVGs, HTML) 創建了一個 dependency graph,將資源打包成一個或多個綜合性的文件。
就像 Browserify 一樣,它可以能夠解析使用 AMD 或 CommonJS syntax 的 js,創建出 bundle 檔,再從 HTML 中使用`<script src=bundle.js></script>`.
SPA (React)崛起,從原本的根據每個路徑像 server 發 request, 或是根據 dependency。現在, 整個 App 會在 user 第一次 request 時被下載,這意味著 App 第一次加載和響應的時間很長!

那重點來了!問題來了~救星也來了~終於來到優化組建流程了!
# Build Process Optimization
試想,\
當我們只是改一行程式碼,但整個 App 和所有的 dependencies 都再重新 ship 給使用者,即使有些 dependencies 不常變動? 或是 user 只造訪 A 頁面,但 B,C,D 的程式碼也都被載入了!
## Code splitting
**MDN: Code splitting** is the practice of splitting the code a web application depends on — including its own code and any third-party dependencies — into separate bundles that can be loaded independently of each other. **This allows an application to load only the code it actually needs at a given point in time, and load other bundles on demand.** This approach is used to improve application performance, especially on initial load.
簡單來說,需要的時候再載入就好!
# Code splitting third party
開發中,我們會在不同畫面中使用相同的程式碼 (libraries, frmework,.. ) 這些是你的 vendor bundle,有可能是需要預先載入的程式碼,例如 lodash, react,… (common enough that every single gonna buy it),可以將這些 vendor 打包成另一個 chuck。
拆出 Vendor Bundle 的好處是,因為它變動的頻率相對較低,可以被 cache,browser只需要載入其他的程式碼,加快了網站的載入速度。控制 chuck 的命名是重要的!

+ Webpack:把所有的 node_modules 打包成一包 myVendors
```javascript
// webpack.config.js
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'myVendors',
},
},
},
},
```
Before:

After:

## Code splitting with Dynamic Import
範例一:lazy, Suspense, import
```javascript
import { useState, lazy, Suspense } from 'react';
import './App.css';
// lazy load component
const MyDefaultComponent = lazy(() => import('./MyDefaultComponent'));
function App() {
const [name, setName] = useState('');
const onLoad = () => {
// lazy load utilities
import('./names').then((module) => {
console.log('module', module);
setName(module.default);
});
};
const onLoadLodash = async () => {
// lazy load packages
const { default:_} = await import ('lodash');
setName(_.capitalize(name));
}
return (
<>
<button onClick={onLoad}>Load Name</button>
<button onClick={onLoadLodash}>Load Lodash</button>
<div>{name}</div>
{name && (
<Suspense fallback={<h1>Loading...</h1>}>
<MyDefaultComponent />
</Suspense>
)}
</>
);
}
export default App;
```
範例二:您只需要在 user 登錄後才使用 Firestore

範例三: Route code-splitting

[`當 bundle 的檔案超過 500 KiB`](https://github.com/vitejs/vite/discussions/9440), Webpack 或 Vite 會提醒開發者!

<br>
> 開放討論:大家有做 code splitting 嗎?當遇到檔案超過 500 KiB,要不要 code splitting?
<br>

Btw, [Gmail for Mobile HTML5 Series: Reducing Startup Latency](https://googlecode.blogspot.com/2009/09/gmail-for-mobile-html5-series-reducing.html)
Gmail 在很久以前就在 code splitting 了~....結束