普通文組 2.5

捨棄 create-react-app 之餘還架了個 astro blog 昭告天下:打包與預覽

webpack 設定已經備妥,現在剩下啟動打包的指令、以及你可能需要一點小工具幫你把放在 ./public 中的靜態資源(比如 favicon)複製一份到輸出資料夾中。今天就來介紹個人慣用的腳本內容。

打包

Makefile 腳本

Terminal window
.PHONY: build
build:
node -r esbuild-runner/register ./script/build.ts

script/build.ts 內容

個人基本上會將打包邏輯放在 ./script/build.ts 中,而這個檔案會處理以下三件事情:

  1. 啟動 webpack 進行打包
  2. ./public 中的靜態資源複製一份到打包完的目的資料夾
  3. (在專案內容比較簡單的時候)手動產生一份 sitemap.xml 並灌到打包完的資料夾中

先看看第一步「透過 webpack 進行打包」的功能:

import webpackProductionConfig from "@/config/webpack.config.production";
import webpack from "webpack";
function webpackBuild(): Promise<void> {
return new Promise((resolve, reject) => {
const compiler = webpack(webpackProductionConfig);
compiler.run((error, stats) => {
if (error) {
console.error(error);
return reject(error);
}
if (stats) {
// 將毫秒轉回秒
console.info("build time: ", (stats.endTime - stats.startTime) / 1000);
// 印出打包過程 log
console.info("build log: ", stats.toString());
}
if (stats && stats.hasErrors()) {
return reject(stats.compilation.errors);
}
compiler.close((closeError) => {
if (closeError) {
console.error(closeError);
return reject(closeError);
} else {
return resolve();
}
});
});
});
}

webpackProductionConfig 傳入 webpack() 建立打包用的 compiler 並啟動之。在有錯誤發生時,執行 reject() 把錯誤拋出。中間讀取 stats.endTime / stats.startTime 來顯示打包時長。其他打包過程中出現的資訊則透過 stats.toString() 直接輸出到終端。

如果萬事順利,則進入 compiler.close() callback 中的的 return resolve(); 分之,結束 webpack 打包流程。


接著來將 ./public 中除了 index.html 以外的內容都複製一份到打包完畢的目的地資料夾:

import env from "@/config/data/env";
import type { EnvForBuildApp } from "@/config/data/types";
import { resolvePath } from "@/tool/resolvePath";
import fs from "fs-extra";
const ENV_FOR_BUILD_APP = env["process.env"] as EnvForBuildApp;
const BUILD_DESTINATION_PATH = resolvePath(
JSON.parse(ENV_FOR_BUILD_APP.BUILD_DESTINATION),
);
function copyPublicAsset() {
fs.copySync(resolvePath("public"), BUILD_DESTINATION_PATH, {
filter: (file) => file !== resolvePath("public/index.html"),
});
console.info("copy public folder done.");
}

透過 filter 跳過 index.html 是因為這份檔案已經透過 webpack 的 html-webpack-plugin 處理了,我們不需要在這裡再複製一份。關於 fx-extra 中的 copySync 詳細說明可參考官方文件


而在 React app 結構較簡單的情況下,個人會在處理打包時順便將 sitemap 一起做掉。

function generateSiteMap() {
const content = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
<lastmod>${new Date().toISOString()}</lastmod>
</url>
</urlset>`;
fs.writeFileSync(`${BUILD_DESTINATION_PATH}/sitemap_index.xml`, content);
console.log("sitemap.xml generated done.");
}

上方 xml 內容參考自 Google Search Center: Build and submit a sitemap

sitemap 的功能基本上就是在告訴搜尋引擎「這個網站包含哪些內容」。需要更近一步的介紹可看看 Google 的 Learn about sitemaps 一文。


最後,把這三個功能包起來執行即可:

async function buildProduction() {
console.info("build start.");
await webpackBuild();
copyPublicAsset();
generateSiteMap();
console.info("build end.");
}
buildProduction();

預覽

個人習慣在打包完畢後,檢查包完的內容是否真的符合預期(比如檔案間互相引用的路徑在透過 webpack 處理後是否還正確、樣式是否能正常顯示)。以下是 Makefile 腳本:

Terminal window
# 預覽打包後的結果
.PHONY: preview
preview:
npx http-server $(BUILD_DESTINATION) -p $(APP_PORT)

BUILD_DESTINATIONAPP_PORT 都是引用自 .evn 的內容,忘記這邊是怎麼來的可以回頭參考第 6 天內容)

在終端輸入 make preview 即可在本機試玩打包後的成果。


恭喜,你的專案已經交給 devOps 來準備上線了 (*゚ー゚)b