build.js
1 #!/usr/bin/node 2 3 const start = Date.now() 4 5 const fs = require("fs") 6 const { readdir, copyFile, mkdir } = require("node:fs/promises") 7 const path = require("path") 8 9 const { build } = require("esbuild") 10 const { compile, preprocess } = require("svelte/compiler") 11 const sveltePreprocess = require("svelte-preprocess") 12 const { loadTsConfig } = require("load-tsconfig") 13 14 const { 15 default: TsconfigPathsPlugin, 16 } = require("@esbuild-plugins/tsconfig-paths") 17 const { nodeExternalsPlugin } = require("esbuild-node-externals") 18 19 const svelteCompilePlugin = { 20 name: "svelteCompile", 21 setup(build) { 22 // Compiles `.svelte` files into JS classes so that they can be directly imported into our 23 // Typescript packages 24 build.onLoad({ filter: /\.svelte$/ }, async args => { 25 const source = await fs.promises.readFile(args.path, "utf8") 26 const dir = path.dirname(args.path) 27 28 try { 29 const preprocessed = await preprocess( 30 source, 31 sveltePreprocess({ filename: args.path }) 32 ) 33 const { js } = compile(preprocessed.code, { 34 css: "injected", 35 generate: "server", 36 }) 37 38 return { 39 // The code placed in the generated file 40 contents: js.code, 41 // The loader this is passed to, basically how the above provided content is "treated", 42 // the contents provided above will be transpiled and bundled like any other JS file. 43 loader: "js", 44 // Where to resolve any imports present in the loaded file 45 resolveDir: dir, 46 } 47 } catch (e) { 48 return { 49 errors: [ 50 { 51 text: e && e.message ? e.message : String(e), 52 detail: e, 53 }, 54 ], 55 } 56 } 57 }) 58 }, 59 } 60 61 let { argv } = require("yargs") 62 63 async function runBuild(entry, outfile, opts = {}) { 64 const isDev = !process.env.CI 65 66 console.log(`Building in mode dev mode: ${isDev}`) 67 68 const tsconfig = argv["p"] || `tsconfig.build.json` 69 70 const { data: tsconfigPathPluginContent } = loadTsConfig( 71 process.cwd(), 72 tsconfig 73 ) 74 const external = Array.isArray(opts.external) ? opts.external : [] 75 76 const sharedConfig = { 77 entryPoints: [entry], 78 bundle: true, 79 minify: !isDev, 80 keepNames: true, 81 sourcemap: tsconfigPathPluginContent.compilerOptions.sourceMap, 82 sourcesContent: false, 83 tsconfig, 84 plugins: [ 85 svelteCompilePlugin, 86 TsconfigPathsPlugin({ tsconfig: tsconfigPathPluginContent }), 87 nodeExternalsPlugin({ 88 allowList: [ 89 "@budibase/frontend-core", 90 "svelte", 91 "chat", 92 "@chat-adapter/discord", 93 "@chat-adapter/slack", 94 "@chat-adapter/teams", 95 "@chat-adapter/state-ioredis", 96 "@chat-adapter/state-memory", 97 ], 98 }), 99 ], 100 preserveSymlinks: true, 101 metafile: true, 102 external, 103 } 104 105 await mkdir("dist", { recursive: true }) 106 107 const hbsFiles = (async () => { 108 const dir = await readdir("./", { recursive: true }) 109 const files = dir.filter( 110 entry => entry.endsWith(".hbs") || entry.endsWith("ivm.bundle.js") 111 ) 112 const fileCopyPromises = files.map(file => 113 copyFile(file, `dist/${path.basename(file)}`) 114 ) 115 116 await Promise.all(fileCopyPromises) 117 })() 118 119 const mainBuild = build({ 120 ...sharedConfig, 121 platform: "node", 122 outfile, 123 }) 124 125 await Promise.all([hbsFiles, mainBuild]) 126 127 if (isDev) { 128 fs.writeFileSync( 129 `dist/${path.basename(outfile)}.meta.json`, 130 JSON.stringify((await mainBuild).metafile) 131 ) 132 } 133 134 console.log( 135 "\x1b[32m%s\x1b[0m", 136 `Build successfully in ${(Date.now() - start) / 1000} seconds` 137 ) 138 } 139 140 if (require.main === module) { 141 const entry = argv["e"] || "./src/index.ts" 142 const outfile = `dist/${entry.split("/").pop().replace(".ts", ".js")}` 143 runBuild(entry, outfile) 144 } else { 145 module.exports = runBuild 146 }