From d9d6942775db809cd28249de74ca6411abadf8d0 Mon Sep 17 00:00:00 2001 From: Angel de la Torre Date: Thu, 30 Nov 2023 14:27:52 -0800 Subject: [PATCH] feat(build): add logic for non-bundling builds For Docker builds, we don't want to bundle the config files into the dist/index.js file. By bundling them, we lose the ability to change them without rebuilding the image. Rebuilding the image every time we want to change the config files is not ideal and would prevent us from using the image for other sites. Solving this problem is a bit tricky because we want to use the same build script for both Docker and non-Docker builds, keep the independent core and scripts bundling, and retain the path aliasing functionality. To achieve all of this, I used the following: - tsup's 'external' option to exclude the "@config" modules from the bundle - The tsc-alias package to resolve the "@config" paths to a relative path - The process.env.DOCKER variable to determine whether to use tsup's 'external' option or not (this variable is set in the Dockerfile). By doing all of this, we can now pass the config files into the container as volumes and change them without rebuilding the image. At the same time, we can still use the same build script for local development and retain the simplicity of standalone builds. The use of tsc-alias was discovered in this issue: https://github.com/evanw/esbuild/issues/394#issuecomment-1537247216. Without it, "@config/*" paths would remain in the bundle and would cause errors as there is no way for "@config" to be resolved as a local path. --- build.config-alias-replacer.cjs | 23 ++++++++++++++++++++ build.js | 23 ++++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 37 +++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 build.config-alias-replacer.cjs diff --git a/build.config-alias-replacer.cjs b/build.config-alias-replacer.cjs new file mode 100644 index 0000000..969cebe --- /dev/null +++ b/build.config-alias-replacer.cjs @@ -0,0 +1,23 @@ +/** + * File must be named with a .cjs extension to be recognized by tsc-alias and + * work with the type: "module" setting in package.json. + * @see https://github.com/justkey007/tsc-alias/discussions/73#discussioncomment-4416038 + */ +const fs = require('fs'); + +/** + * This script replaces '@config/' aliases with relative paths './' in the + * provided file. + * This is necessary for non-bundling scenarios like Docker, where we want to + * use files from the host machine. + * Without this, the referenced files would be bundled, which is not the + * desired behavior in this case. + */ +function configAliasReplace({ orig, file }) { + const fileContents = fs.readFileSync(file, 'utf8'); + const newContents = fileContents.replace(/@config\//g, './'); + fs.writeFileSync(file, newContents, 'utf8'); + return orig; +} + +exports.default = configAliasReplace; \ No newline at end of file diff --git a/build.js b/build.js index 3efff20..16d1ab5 100644 --- a/build.js +++ b/build.js @@ -1,8 +1,10 @@ import { build as tsupBuild } from 'tsup'; +import { replaceTscAliasPaths } from 'tsc-alias'; import yargs from 'yargs'; const CORE_DIR = 'dist'; const SCRIPTS_DIR = 'bin'; +const isDocker = process.env.DOCKER === 'true'; const argv = yargs(process.argv.slice(2)).options({ target: { @@ -33,6 +35,7 @@ const config = { format: 'esm', silent: argv.silent, clean: argv.clean, + external: isDocker ? ['@config'] : [], // Don't resolve @config modules when building in docker } const CORE_BUILD = { @@ -49,9 +52,29 @@ const SCRIPTS_BUILD = { entry: ['src/scripts/*'], } +/** + * Resolve paths after the build process because ESBuild only supports path + * resolution natively during bundling. + * + * "@config/" paths are replaced with relative-path "./" to allow for + * non-bundling scenarios like Docker. + */ +async function replaceConfigAliasPaths(outDir) { + await replaceTscAliasPaths({ + configFile: 'tsconfig.json', + outDir: outDir, + watch: false, + replacers: ['build.config-alias-replacer.cjs'] + }); + } + async function build(buildConfig, buildTarget) { try { await tsupBuild(buildConfig); + + if (isDocker) { + await replaceConfigAliasPaths(buildConfig.outDir); + } } catch (e) { console.error(`Failed to build ${buildTarget}:`, e); process.exit(1); diff --git a/package.json b/package.json index 1879124..8b4ea60 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@tsconfig/node18": "^18.2.1", "@types/node": "^20.5.9", "@types/yargs": "^17.0.28", + "tsc-alias": "^1.8.8", "tsup": "^8.0.1", "typescript": "^5.2.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb20869..2057ec8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,6 +31,9 @@ devDependencies: '@types/yargs': specifier: ^17.0.28 version: 17.0.28 + tsc-alias: + specifier: ^1.8.8 + version: 1.8.8 tsup: specifier: ^8.0.1 version: 8.0.1(typescript@5.2.2) @@ -801,6 +804,11 @@ packages: engines: {node: '>= 6'} dev: true + /commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + dev: true + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true @@ -1563,6 +1571,11 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + /mylas@2.1.13: + resolution: {integrity: sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==} + engines: {node: '>=12.0.0'} + dev: true + /mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} dependencies: @@ -1704,6 +1717,13 @@ packages: engines: {node: '>= 6'} dev: true + /plimit-lit@1.6.1: + resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} + engines: {node: '>=12'} + dependencies: + queue-lit: 1.5.2 + dev: true + /postcss-load-config@4.0.2: resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} engines: {node: '>= 14'} @@ -1832,6 +1852,11 @@ packages: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} dev: false + /queue-lit@1.5.2: + resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==} + engines: {node: '>=12'} + dev: true + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true @@ -2091,6 +2116,18 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true + /tsc-alias@1.8.8: + resolution: {integrity: sha512-OYUOd2wl0H858NvABWr/BoSKNERw3N9GTi3rHPK8Iv4O1UyUXIrTTOAZNHsjlVpXFOhpJBVARI1s+rzwLivN3Q==} + hasBin: true + dependencies: + chokidar: 3.5.3 + commander: 9.5.0 + globby: 11.1.0 + mylas: 2.1.13 + normalize-path: 3.0.0 + plimit-lit: 1.6.1 + dev: true + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: false