捨棄 create-react-app 之餘還架了個 astro blog 昭告天下:打包與預覽
webpack 設定已經備妥,現在剩下啟動打包的指令、以及你可能需要一點小工具幫你把放在 ./public
中的靜態資源(比如 favicon
)複製一份到輸出資料夾中。今天就來介紹個人慣用的腳本內容。
打包
Makefile 腳本
.PHONY: build
build:
node -r esbuild-runner/register ./script/build.ts
script/build.ts 內容
個人基本上會將打包邏輯放在 ./script/build.ts
中,而這個檔案會處理以下三件事情:
- 啟動
webpack
進行打包 - 將
./public
中的靜態資源複製一份到打包完的目的資料夾 - (在專案內容比較簡單的時候)手動產生一份
sitemap.xml
並灌到打包完的資料夾中
先看看第一步「透過 webpack 進行打包」的功能:
import webpack from 'webpack';
import webpackProductionConfig from '@/config/webpack.config.production';
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 fs from 'fs-extra';
import { resolvePath } from '@/tool/resolvePath';
import env from '@/config/data/env';
import type { EnvForBuildApp } from '@/config/data/types';
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
腳本:
# 預覽打包後的結果
.PHONY: preview
preview:
npx http-server $(BUILD_DESTINATION) -p $(APP_PORT)
(BUILD_DESTINATION
與 APP_PORT
都是引用自 .evn
的內容,忘記這邊是怎麼來的可以回頭參考第 6 天內容)
在終端輸入 make preview
即可在本機試玩打包後的成果。
恭喜,你的專案已經交給 devOps 來準備上線了 (*゚ー゚)b