import chalk from 'chalk' import { execSync } from 'child_process' import { mkdir, readdir, rm, writeFile } from 'fs/promises' import { dirname, parse } from 'path' import { rolldown } from 'rolldown' import { importGlobPlugin } from 'rolldown/experimental' import { fileURLToPath } from 'url' import pkg from '../package.json' with { type: 'json' } import { exists } from './_shared' import asRequire from './plugins/as-require' import hermesSwcPlugin from './plugins/hermes-swc' import hermesCPlugin from './plugins/hermesc' import importDefer from './plugins/import-defer' import shimAliases from './plugins/shim-aliases' const __dirname = dirname(fileURLToPath(import.meta.url)) const ShimsDir = `${__dirname}/../shims` const AssetsDir = `${__dirname}/../src/assets` const GeneratedAssetsDir = `${__dirname}/../dist/assets/generated` await rm(GeneratedAssetsDir, { recursive: true, force: true }) .then(() => console.debug(chalk.gray('\u{1F5BB} Deleted old generated assets')), ) .catch() const Dev = process.argv.includes('--dev') || process.env.NODE_ENV === 'development' // If this file is being run directly, build the project if (import.meta.main) build() export default async function build(dev = Dev, log = true) { const start = performance.now() if (log) console.info(chalk.gray('\u{1F5BB} Generating assets...')) await generateAssets() if (log) console.info(chalk.cyanBright('\u{1F5BB} Assets generated')) if (log) console.info(chalk.gray('\u{1F5CE} Compiling JS...')) const COMMIT = execSync('git rev-parse HEAD') .toString() .trim() .substring(0, 7) const REPO = 'revenge-mod/revenge-bundle-next' const bundle = await rolldown({ input: 'src/index.ts', platform: 'neutral', external: [/^node:/], optimization: { inlineConst: { mode: 'smart', pass: 5, }, pifeForModuleWrappers: true, }, experimental: { lazyBarrel: true, }, preserveEntrySignatures: false, transform: { define: { __BUILD_DISCORD_SERVER_URL__: JSON.stringify( 'https://discord.com/invite/ddcQf3s2Uq', ), __BUILD_SOURCE_REPOSITORY_URL__: JSON.stringify( `https://github.com/${REPO}`, ), __BUILD_LICENSE_URL__: JSON.stringify( `https://raw.githubusercontent.com/${REPO}/${COMMIT}/LICENSE`, ), __BUILD_VERSION__: JSON.stringify(pkg.version), __BUILD_COMMIT__: JSON.stringify(COMMIT), __BUILD_BRANCH__: JSON.stringify( execSync('git rev-parse --abbrev-ref HEAD') .toString() .trim(), ), __DEV__: String(dev), __BUILD_DEFAULT_PLUGIN_REPOSITORY_URL__: 'undefined', __BUILD_DONATE_URL__: 'undefined', // See types/build.d.ts for what these flags do __BUILD_FLAG_DEBUG_MODULE_LOOKUPS__: String(dev), __BUILD_FLAG_DEBUG_MODULE_WAITS__: String(dev), __BUILD_FLAG_DEBUG_LAZY_VALUES__: 'false', __BUILD_FLAG_LOG_PROMISE_REJECTIONS__: String(dev), }, }, tsconfig: 'tsconfig.json', // propertyReadSideEffects: false works around a Rolldown bug where // property reads on tree-shaken import bindings are kept as "side // effects" while their imports are removed, producing free-variable // references (e.g. `FibonacciHeap.MinFibonacciHeap` from mnemonist's // barrel) that throw ReferenceError at runtime. treeshake: true, moduleTypes: { '.webp': 'dataurl', }, plugins: [ asRequire(), shimAliases(ShimsDir), importGlobPlugin(), hermesSwcPlugin(), importDefer(), hermesCPlugin({ flags: [ dev ? '-Og' : '-O', dev ? '-g3' : '-g1', '-reuse-prop-cache', '-optimized-eval', '-strict', '-finline', ], before(ver) { if (log) { console.debug( chalk.cyanBright( '\u{1F5CE} JS compilation finished...', ), ) console.debug( chalk.gray( `\u{1F5CE} Compiling bytecode with ${ver}...`, ), ) } }, after() { if (log) console.debug( chalk.cyanBright( '\u{1F5CE} Bytecode compilation finished', ), ) }, }), ], }) await bundle.write({ minify: dev ? 'dce-only' : { codegen: { removeWhitespace: false, }, mangle: { keepNames: true, }, compress: { joinVars: true, keepNames: { class: true, function: true, }, unused: true, }, }, esModule: false, minifyInternalExports: true, hoistTransitiveImports: false, file: 'dist/revenge.js', format: 'iife', keepNames: true, postFooter: `//# sourceURL=Revenge`, topLevelVar: true, }) if (log) console.info( chalk.greenBright( `\u{2714} Compiled successfully! ${chalk.gray(`(took ${(performance.now() - start).toFixed(2)}ms)`)}`, ), ) } async function generateAssets() { if (!(await exists(GeneratedAssetsDir))) await mkdir(GeneratedAssetsDir, { recursive: true }) const promises: Promise[] = [] for (const file of await readdir(AssetsDir)) { const { name, ext } = parse(file) const path = `${AssetsDir}/${file}` const path2 = `${GeneratedAssetsDir}/${name}.js` if (await exists(path2)) continue // We attempt to sanitize the path, but not the name as it should fail if name contains invalid characters const uriPath = JSON.stringify(path) const type = JSON.stringify(ext.slice(1)) promises.push( writeFile( path2, `import{registerAsset}from'@revenge-mod/assets';import uri from${uriPath};const ${name}=registerAsset({name:'${name}',type:${type},uri});export { ${name} as default }`, ), ) } await Promise.all(promises) }