Files
tibi-svelte-starter/esbuild.config.js

250 lines
8.3 KiB
JavaScript

const fs = require("fs")
const { execSync } = require("child_process")
const postcssPlugin = require("esbuild-postcss")
// Resolve version at build time via git describe (tag + hash), fallback to env var or "dev"
let gitHash = "dev"
try {
gitHash = process.env.GIT_HASH || execSync("git describe --tags --always --dirty").toString().trim()
} catch (_) {
// .git not available (e.g. Docker build without .git context)
}
// Generate buildInfo module that can be imported by frontend code
function writeBuildInfo() {
const info = {
gitHash,
buildTime: new Date().toISOString(),
}
fs.writeFileSync(
__dirname + "/frontend/src/lib/buildInfo.ts",
`// AUTO-GENERATED by esbuild.config.js \u2013 do not edit\nexport const gitHash = ${JSON.stringify(info.gitHash)}\nexport const buildTime = ${JSON.stringify(info.buildTime)}\n`
)
// Write same buildInfo for backend hooks (X-Build-Time / X-Release headers)
fs.writeFileSync(
__dirname + "/api/hooks/lib/buildInfo.js",
`// AUTO-GENERATED by esbuild.config.js \u2013 do not edit\nmodule.exports = { gitHash: ${JSON.stringify(info.gitHash)}, buildTime: ${JSON.stringify(info.buildTime)} }\n`
)
}
// NOTE: writeBuildInfo() is NOT called here at top-level.
// It is called by esbuild-wrapper.js before each build.
// This prevents the server build from overwriting the frontend's timestamp
// (which would cause a version mismatch and spurious auto-reload).
const resolvePlugin = {
name: "resolvePlugin",
setup(build) {
let path = require("path")
// url in css does not resolve via esbuild-svelte correctly
build.onResolve({ filter: /.*/, namespace: "fakecss" }, (args) => {
// console.log(args)
if (args.path.match(/^\./)) return { path: path.dirname(args.importer) + "/" + args.path }
// return { path: path.join(args.resolveDir, "public", args.path) }
})
},
}
// When MOCK is disabled, replace the mock module with a no-op stub so
// esbuild can tree-shake all mock data out of the production bundle.
const mockPlugin = {
name: "mockPlugin",
setup(build) {
if (process.env.MOCK !== "1") {
build.onResolve({ filter: /\/mock$/ }, (args) => {
if (args.importer.includes("api.ts") || args.importer.includes("api.js")) {
return { path: "mock-noop", namespace: "mock-noop" }
}
})
build.onLoad({ filter: /.*/, namespace: "mock-noop" }, () => ({
contents: "export function mockApiRequest() { return null }",
loader: "ts",
}))
}
},
}
////////////////////////// esbuild-svelte
const sveltePlugin = require("esbuild-svelte")
const frontendDir = "./frontend"
const distDir = frontendDir + "/dist"
// console.log("copy public dir...")
// const copydir = require("copy-dir")
// copydir.sync(__dirname + "/public", __dirname + "/" + distDir)
/*copydir.sync(
__dirname + "/public/index.html",
__dirname + "/" + distDir + "/template.html"
)*/
const svelteConfig = require("./svelte.config")
const esbuildSvelte = sveltePlugin({
compilerOptions: {
css: "external",
dev: (process.argv?.length > 2 ? process.argv[2] : "build") !== "build",
},
preprocess: svelteConfig.preprocess,
cache: true,
filterWarnings: (warning) => {
// filter out a11y
if (warning.code.match(/^a11y/)) return false
return true
},
})
const options = {
logLevel: "info",
color: true,
entryPoints: ["./frontend/src/index.ts"],
outfile: distDir + "/index.mjs",
metafile: true,
format: "esm",
minify: process.argv[2] == "build",
bundle: true,
splitting: false,
define: {
__MOCK__: process.env.MOCK === "1" ? "true" : "false",
},
plugins: [esbuildSvelte, postcssPlugin(), resolvePlugin, mockPlugin],
loader: {
".woff2": "file",
".woff": "file",
".eot": "file",
".svg": "file",
".ttf": "file",
},
sourcemap: true,
target: ["es2022", "chrome100", "firefox100", "safari15", "edge100"],
}
const bsMiddleware = []
if (process.argv[2] == "start") {
const { createProxyMiddleware } = require("http-proxy-middleware")
const dotEnv = fs.readFileSync(__dirname + "/.env", "utf8")
const TIBI_NAMESPACE = dotEnv.match(/TIBI_NAMESPACE=(.*)/)[1]
const apiBase = process.env.API_BASE || "http://localhost:8080/api/v1/_/" + TIBI_NAMESPACE
bsMiddleware.push(
createProxyMiddleware({
pathFilter: "/api",
target: apiBase,
pathRewrite: { "^/api": "" },
changeOrigin: true,
logLevel: "debug",
})
)
// Sentry tunnel proxy: forwards /_s requests to Sentry ingest to avoid ad-blockers
const https = require("https")
const http = require("http")
bsMiddleware.push(function (req, res, next) {
if (req.url !== "/_s" || req.method !== "POST") return next()
let body = []
req.on("data", (chunk) => body.push(chunk))
req.on("end", () => {
const payload = Buffer.concat(body).toString()
const firstLine = payload.split("\n")[0]
let envelope
try {
envelope = JSON.parse(firstLine)
} catch {
res.writeHead(400)
res.end()
return
}
const dsn = envelope.dsn || ""
let sentryUrl
try {
sentryUrl = new URL(dsn)
} catch {
res.writeHead(400)
res.end()
return
}
const projectId = sentryUrl.pathname.replace(/\//g, "")
const ingestUrl = `${sentryUrl.protocol}//${sentryUrl.host}/api/${projectId}/envelope/`
const transport = ingestUrl.startsWith("https") ? https : http
const proxyReq = transport.request(
ingestUrl,
{
method: "POST",
headers: { "Content-Type": "application/x-sentry-envelope" },
},
(proxyRes) => {
res.writeHead(proxyRes.statusCode || 200)
proxyRes.pipe(res)
}
)
proxyReq.on("error", () => {
res.writeHead(502)
res.end()
})
proxyReq.write(payload)
proxyReq.end()
})
})
if (process.env.SSR) {
bsMiddleware.push(
createProxyMiddleware({
pathFilter: function (path, req) {
return !path.match(/\./)
},
target: apiBase,
changeOrigin: true,
logLevel: "debug",
pathRewrite: function (path, req) {
return "/ssr?url=" + encodeURIComponent(path)
},
})
)
}
}
module.exports = {
sveltePlugin: sveltePlugin,
resolvePlugin: resolvePlugin,
writeBuildInfo: writeBuildInfo,
options: options,
distDir,
watch: {
path: [__dirname + "/" + frontendDir + "/src"],
},
serve: {
onRequest(args) {
console.log(args)
},
},
browserSync: {
server: {
baseDir: frontendDir,
middleware: [
require("morgan")("dev"),
...bsMiddleware,
require("connect-history-api-fallback")({
index: "/spa.html",
// Allow dots in URL paths (e.g. /version-3.7.1) to fall through to SPA,
// but only if they don't look like actual file requests.
rewrites: [
{
from: /\.(js|css|mjs|map|woff2?|ttf|eot|svg|png|jpe?g|gif|webp|ico|json|pdf)(\?.*)?$/i,
to: function (context) {
return context.parsedUrl.pathname
},
},
{
from: /./,
to: "/spa.html",
},
],
// verbose: true,
}),
],
},
open: false,
// logLevel: "debug",
ghostMode: false,
},
}