esbuildによるTypeScript製Webアプリのビルド
次のツールを組み合わせてTypeScript製Webアプリをビルドする。
最低限の設定
依存パッケージのインストール
$ pnpm add -D esbuild esbuild-style-plugin esbuild-plugin-copy postcss autoprefixer tsx cross-env
code:json
{
"type": "module"
}
package.jsonにnpm scriptsを書き足し、npm runコマンドからビルドできるようにしておく。
code:json
{
"scripts": {
"prod:build": "cross-env NODE_ENV=production tsx ./scripts/prodBuild.ts",
"dev:start": "cross-env NODE_ENV=development tsx ./scripts/devStart.ts"
}
}
こうするとよい。
ビルド設定を組み立てるための関数を切り出す。
開発サーバーを実行するためのスクリプトや、プロダクションビルドを実行するためのスクリプトなどを分ける。
クライアント向けのビルド設定
/scripts/clientBuildOptionsFactory.tsに、クライアントをビルドする際のBuildOptionsを組み立てる関数を定義しておく。
code:clientBuildOptionsFactory.ts
/* eslint-disable import/no-extraneous-dependencies */
import path from 'node:path';
import autoprefixer from 'autoprefixer';
import { BuildOptions } from 'esbuild';
import copyPlugin from 'esbuild-plugin-copy';
import stylePlugin from 'esbuild-style-plugin';
import { createRequire } from 'module';
import tailwindcss, { Config } from 'tailwindcss';
const require = createRequire(import.meta.url);
const tailwindConfig = require('../tailwind.config.cjs') as unknown as Config;
interface IBuildOptionsFactoryParams {
isDev: boolean;
outDir: string;
appVersion: string;
}
export const clientBuildOptionsFactory = ({
isDev,
outDir,
appVersion,
}: IBuildOptionsFactoryParams): BuildOptions => ({
entryPoints: [
path.join('src', 'client', 'index.tsx'),
// path.join('src', 'client', 'serviceWorker.ts'),
],
outdir: path.join(outDir, 'client'),
assetNames: 'assets/name', loader: {
'.woff': 'file',
'.woff2': 'file',
},
platform: 'browser',
format: 'iife',
bundle: true,
define: {
'process.env.NODE_ENV': JSON.stringify(appVersion),
'process.env.APP_VERSION': JSON.stringify(appVersion),
},
minify: !isDev,
sourcemap: isDev,
plugins: [
stylePlugin({
// TailWind CSSを使用する場合はコメントを外す
// postcss: {
// },
}),
copyPlugin({
assets: {
from: 'src/client/static/*',
to: '.',
},
copyOnStart: true,
watch: true,
verbose: true,
}),
],
});
開発サーバーを実行するためのスクリプト
code:devStart.ts
/* eslint-disable import/no-extraneous-dependencies */
import { context } from 'esbuild';
import { createRequire } from 'module';
import { clientBuildOptionsFactory } from './clientBuildOptionsFactory.ts';
const require = createRequire(import.meta.url);
const packageJSON = require('../package.json') as unknown as Record<string, any>;
const APP_VERSION = process.env.APP_VERSION ?? (packageJSON.version as string) ?? '1.0.0';
const devStart = async () => {
const ctx = await context(
clientBuildOptionsFactory({
appVersion: APP_VERSION,
isDev: true,
outDir: 'development',
}),
);
await ctx.watch();
const { host, port } = await ctx.serve({
servedir: development/client,
});
console.log(Development server: http://${host}:${port});
};
devStart();
プロダクションビルドを実行するためのスクリプト
code:prodBuild.ts
/* eslint-disable import/no-extraneous-dependencies */
import esbuild from 'esbuild';
import { createRequire } from 'module';
import { clientBuildOptionsFactory } from './clientBuildOptionsFactory';
const require = createRequire(import.meta.url);
const packageJSON = require('../package.json') as unknown as Record<string, any>;
const APP_VERSION = process.env.APP_VERSION ?? (packageJSON.version as string) ?? '1.0.0';
const prodBuild = async () => {
await esbuild.build(
clientBuildOptionsFactory({
appVersion: APP_VERSION,
isDev: false,
outDir: 'production',
}),
);
};
prodBuild();