2025-03-21 19:59:16 +07:00
import { transform } from '@swc/core'
2025-04-30 20:21:59 +07:00
import { $ , main } from 'bun'
2025-03-21 19:59:16 +07:00
import chalk from 'chalk'
2025-06-17 19:12:04 +07:00
import { existsSync } from 'fs'
2025-05-19 01:42:47 +07:00
import { exists , mkdir , readdir , rm , writeFile } from 'fs/promises'
2025-06-03 16:24:58 +07:00
import { parse } from 'path'
import { rolldown } from 'rolldown'
2025-05-22 00:06:36 +07:00
import { aliasPlugin , importGlobPlugin } from 'rolldown/experimental'
2025-05-01 01:29:55 +07:00
import pkg from '../package.json'
2025-06-03 16:24:58 +07:00
import type { OutputChunk , RolldownPlugin } from 'rolldown'
2025-05-01 01:29:55 +07:00
2025-06-01 13:50:21 +07:00
const ShimsDir = ` ${ import . meta . dir } /../shims `
const AssetsDir = ` ${ import . meta . dir } /../src/assets `
const GeneratedAssetsDir = ` ${ import . meta . dir } /../dist/assets/generated `
2025-05-19 01:42:47 +07:00
await rm ( GeneratedAssetsDir , { recursive : true , force : true } )
2025-06-15 17:43:27 +07:00
. then ( ( ) = >
console . debug ( chalk . gray ( '\u{1F5BB} Deleted old generated assets' ) ) ,
)
2025-05-19 01:42:47 +07:00
. catch ( )
2025-06-15 17:43:27 +07:00
const Dev =
process . argv . includes ( '--dev' ) || process . env . NODE_ENV === 'development'
2025-06-15 18:01:30 +07:00
// If this file is being run directly, build the project
if ( main === import . meta . filename ) build ( )
2025-06-15 17:43:27 +07:00
export default async function build ( dev = Dev , log = true ) {
2025-03-21 19:59:16 +07:00
const start = performance . now ( )
2025-05-07 02:53:31 +07:00
if ( log ) console . info ( chalk . gray ( '\u{1F5BB} Generating assets...' ) )
2025-05-07 01:21:27 +07:00
await generateAssets ( )
2025-05-07 02:53:31 +07:00
if ( log ) console . info ( chalk . cyanBright ( '\u{1F5BB} Assets generated' ) )
if ( log ) console . info ( chalk . gray ( '\u{1F5CE} Compiling JS...' ) )
2025-05-07 01:21:27 +07:00
2025-07-12 02:50:48 +07:00
const COMMIT = ( await $ ` git rev-parse HEAD ` . text ( ) ) . trim ( ) . substring ( 0 , 7 )
const REPO = 'revenge-mod/revenge-bundle-next'
2025-03-21 19:59:16 +07:00
const bundle = await rolldown ( {
input : 'src/index.ts' ,
platform : 'neutral' ,
2025-07-30 20:15:58 +07:00
optimization : {
2025-08-13 00:46:50 +07:00
// ! BROKEN, DO NOT USE!
// inlineConst: true,
2025-07-30 20:15:58 +07:00
// @ts-expect-error: Option not documented, but added in 1.0.0-beta.30
pifeForModuleWrappers : true ,
} ,
preserveEntrySignatures : false ,
transform : {
assumptions : {
pureGetters : true ,
setPublicClassFields : true ,
noDocumentAll : true ,
// TODO: "Not implemented"
// objectRestNoSymbols: true,
// ignoreFunctionLength: true,
} ,
2025-04-30 20:20:16 +07:00
} ,
2025-08-25 19:19:22 +07:00
tsconfig : 'tsconfig.json' ,
2025-03-21 19:59:16 +07:00
treeshake : true ,
keepNames : true ,
moduleTypes : {
'.webp' : 'dataurl' ,
} ,
define : {
2025-07-12 02:50:48 +07:00
__BUILD_DISCORD_SERVER_URL__ : JSON.stringify (
'https://discord.com/invite/ddcQf3s2Uq' ,
) ,
__BUILD_SOURCE_REPOSITORY_URL__ : JSON.stringify (
` https://github.com/ ${ REPO } ` ,
2025-06-03 16:24:58 +07:00
) ,
2025-07-12 02:50:48 +07:00
__BUILD_LICENSE_URL__ : JSON.stringify (
` https://raw.githubusercontent.com/ ${ REPO } / ${ COMMIT } /LICENSE ` ,
) ,
__BUILD_VERSION__ : JSON.stringify ( pkg . version ) ,
__BUILD_COMMIT__ : JSON.stringify ( COMMIT ) ,
2025-06-03 16:24:58 +07:00
__BUILD_BRANCH__ : JSON.stringify (
( await $ ` git rev-parse --abbrev-ref HEAD ` . text ( ) ) . trim ( ) ,
) ,
2025-07-10 12:39:25 +07:00
__DEV__ : String ( dev ) ,
2025-06-12 23:25:10 +07:00
2025-08-11 17:40:50 +07:00
// See types/build.d.ts for what these flags do
2025-06-15 17:57:33 +07:00
__BUILD_FLAG_DEBUG_MODULE_LOOKUPS__ : String ( dev ) ,
2025-07-08 11:24:48 +07:00
__BUILD_FLAG_DEBUG_MODULE_WAITS__ : String ( dev ) ,
2025-09-04 01:26:26 +07:00
__BUILD_FLAG_DEBUG_LAZY_VALUES__ : 'false' ,
2025-05-05 19:13:05 +07:00
__BUILD_FLAG_LOG_PROMISE_REJECTIONS__ : String ( dev ) ,
2025-03-21 19:59:16 +07:00
} ,
plugins : [
2025-05-01 12:11:33 +07:00
aliasPlugin ( {
entries : [
{
find : 'react/jsx-runtime' ,
2025-06-01 13:50:21 +07:00
replacement : ` ${ ShimsDir } /react~jsx-runtime.ts ` ,
2025-05-01 12:11:33 +07:00
} ,
2025-06-08 19:01:33 +07:00
// Do not move React to the top!
// If you do that, react/jsx-runtime would resolve to ${ShimsDir}/react.ts/jsx-runtime instead.
{
find : 'react' ,
replacement : ` ${ ShimsDir } /react.ts ` ,
} ,
{
find : 'react-native' ,
replacement : ` ${ ShimsDir } /react-native.ts ` ,
} ,
2025-07-25 02:19:06 +07:00
{
find : '@react-navigation/native' ,
replacement : ` ${ ShimsDir } /@react-navigation~native.ts ` ,
} ,
{
find : '@react-navigation/stack' ,
replacement : ` ${ ShimsDir } /@react-navigation~stack.ts ` ,
} ,
{
find : '@shopify/flash-list' ,
replacement : ` ${ ShimsDir } /@shopify~flash-list.ts ` ,
} ,
2025-05-01 12:11:33 +07:00
] ,
} ) ,
2025-05-22 00:06:36 +07:00
importGlobPlugin ( ) ,
swcPlugin ( ) ,
2025-03-21 19:59:16 +07:00
hermesCPlugin ( {
flags : [
dev ? '-Og' : '-O' ,
2025-07-08 22:14:22 +07:00
dev ? '-g3' : '-g1' ,
'-reuse-prop-cache' ,
'-optimized-eval' ,
'-strict' ,
2025-03-21 19:59:16 +07:00
'-finline' ,
] ,
2025-06-17 19:12:04 +07:00
before ( ver ) {
if ( log ) {
2025-06-03 16:24:58 +07:00
console . debug (
chalk . cyanBright (
'\u{1F5CE} JS compilation finished...' ,
) ,
)
2025-06-17 19:12:04 +07:00
2025-06-03 16:24:58 +07:00
console . debug (
2025-06-17 19:12:04 +07:00
chalk . gray (
` \ u{1F5CE} Compiling bytecode with ${ ver } ... ` ,
) ,
2025-06-03 16:24:58 +07:00
)
2025-06-17 19:12:04 +07:00
}
2025-05-07 02:53:31 +07:00
} ,
after() {
2025-06-03 16:24:58 +07:00
if ( log )
console . debug (
chalk . cyanBright (
'\u{1F5CE} Bytecode compilation finished' ,
) ,
)
2025-03-21 19:59:16 +07:00
} ,
} ) ,
] ,
} )
await bundle . write ( {
2025-08-25 20:10:25 +07:00
minify : 'dce-only' ,
2025-07-30 20:15:58 +07:00
exports : 'none' ,
esModule : false ,
2025-07-11 00:17:18 +07:00
minifyInternalExports : true ,
2025-07-30 20:15:58 +07:00
hoistTransitiveImports : false ,
2025-03-21 19:59:16 +07:00
file : 'dist/revenge.js' ,
format : 'iife' ,
} )
if ( log )
console . info (
chalk . greenBright (
2025-05-07 02:53:31 +07:00
` \ u{2714} Compiled successfully! ${ chalk . gray ( ` (took ${ ( performance . now ( ) - start ) . toFixed ( 2 ) } ms) ` ) } ` ,
2025-03-21 19:59:16 +07:00
) ,
)
}
function swcPlugin() {
return {
name : 'swc' ,
transform : {
filter : {
2025-07-08 22:14:22 +07:00
moduleType : [ 'js' , 'jsx' , 'ts' , 'tsx' ] ,
2025-03-21 19:59:16 +07:00
} ,
handler ( code ) {
return transform ( code , {
jsc : {
transform : {
react : {
runtime : 'automatic' ,
} ,
} ,
parser : {
syntax : 'typescript' ,
tsx : true ,
} ,
} ,
env : {
2025-06-03 16:24:58 +07:00
// https://github.com/facebook/hermes/blob/main/doc/Features.md
2025-03-21 19:59:16 +07:00
targets : 'fully supports es6' ,
include : [
2025-04-30 18:35:50 +07:00
'transform-async-generator-functions' ,
2025-03-21 19:59:16 +07:00
'transform-block-scoping' ,
'transform-classes' ,
2025-04-30 18:35:50 +07:00
'transform-duplicate-named-capturing-groups-regex' ,
2025-03-21 19:59:16 +07:00
'transform-named-capturing-groups-regex' ,
] ,
exclude : [
2025-04-30 18:35:50 +07:00
// Async functions are supported, only async arrow functions aren't
// Source: https://github.com/facebook/hermes/issues/1395
'transform-async-to-generator' ,
2025-03-21 19:59:16 +07:00
'transform-exponentiation-operator' ,
2025-04-30 18:35:50 +07:00
'transform-logical-assignment-operators' ,
2025-03-21 19:59:16 +07:00
'transform-nullish-coalescing-operator' ,
2025-04-30 18:35:50 +07:00
'transform-numeric-separator' ,
2025-03-21 19:59:16 +07:00
'transform-object-rest-spread' ,
2025-04-30 18:35:50 +07:00
'transform-optional-catch-binding' ,
2025-03-21 19:59:16 +07:00
'transform-optional-chaining' ,
2025-04-30 18:35:50 +07:00
'transform-parameters' ,
'transform-template-literals' ,
2025-03-21 19:59:16 +07:00
] ,
} ,
} )
} ,
} ,
} satisfies RolldownPlugin
}
2025-06-17 19:12:04 +07:00
async function hermesCPlugin ( {
2025-06-03 16:24:58 +07:00
after ,
before ,
flags ,
} : {
flags? : string [ ]
2025-06-17 19:12:04 +07:00
before ? : ( v : string ) = > void
after ? : ( v : string ) = > void
2025-06-03 16:24:58 +07:00
} = { } ) {
2025-03-21 19:59:16 +07:00
const paths = {
2025-06-17 19:12:04 +07:00
win32 : 'win64-bin/hermesc.exe' ,
darwin : 'osx-bin/hermesc' ,
2025-06-17 19:57:34 +07:00
linux : 'linux64-bin/hermesc' ,
2025-03-21 19:59:16 +07:00
}
2025-06-03 16:24:58 +07:00
if ( ! ( process . platform in paths ) )
throw new Error ( ` Unsupported platform: ${ process . platform } ` )
2025-03-21 19:59:16 +07:00
2025-06-17 19:12:04 +07:00
const sdksDir = './node_modules/react-native/sdks'
const binPath = ` ${ sdksDir } /hermesc/ ${ paths [ process . platform as keyof typeof paths ] } `
if ( ! existsSync ( binPath ) )
throw new Error (
` Hermes compiler not found at ${ binPath } . Please ensure you have react-native installed. ` ,
)
const ver = await Bun . file ( ` ${ sdksDir } /.hermesversion ` ) . text ( )
2025-03-21 19:59:16 +07:00
return {
name : 'hermesc' ,
generateBundle ( _ , bundle ) {
2025-06-17 19:12:04 +07:00
if ( before ) before ( ver )
2025-05-07 02:53:31 +07:00
2025-03-21 19:59:16 +07:00
const file = bundle [ 'revenge.js' ] as OutputChunk
if ( ! file || ! file . code ) throw new Error ( 'No code to compile' )
2025-06-09 23:35:50 +07:00
// TODO(scripts/build): Remove this when we have a better way to add sourceURL
file . code += ` //# sourceURL=Revenge `
2025-06-17 19:12:04 +07:00
const cmdlist = [ binPath , '-emit-binary' , . . . ( flags ? ? [ ] ) ]
2025-05-22 02:49:24 +07:00
2025-07-08 22:14:22 +07:00
const cmd = Bun . spawnSync < 'pipe' , 'pipe' > ( cmdlist , {
2025-06-01 13:50:21 +07:00
// @ts-expect-error: Types are incorrect, but this works
2025-05-22 02:49:24 +07:00
stdin : new Blob ( [ file . code ] ) ,
stdout : 'pipe' ,
} )
2025-03-21 19:59:16 +07:00
2025-07-08 22:14:22 +07:00
if ( cmd . exitCode ) {
if ( cmd . stderr . length )
throw new Error (
` Got error from hermesc: ${ cmd . stderr . toString ( ) } ` ,
)
else
throw new Error ( ` hermesc exited with code: ${ cmd . exitCode } ` )
}
2025-03-21 19:59:16 +07:00
const buf = cmd . stdout
2025-05-22 02:49:24 +07:00
if ( ! buf . length )
throw new Error (
` No output from hermesc. Probably a compilation error. \ nTry running the command manually: ${ cmdlist . join ( ' ' ) } ` ,
)
2025-03-21 19:59:16 +07:00
this . emitFile ( {
type : 'asset' ,
fileName : ` ${ file . fileName . split ( '.' ) [ 0 ] ! } .bundle ` ,
source : buf ,
} )
2025-06-17 19:12:04 +07:00
if ( after ) after ( ver )
2025-03-21 19:59:16 +07:00
} ,
} satisfies RolldownPlugin
}
2025-05-07 01:21:27 +07:00
async function generateAssets() {
2025-06-03 16:24:58 +07:00
if ( ! ( await exists ( GeneratedAssetsDir ) ) )
await mkdir ( GeneratedAssetsDir , { recursive : true } )
2025-05-07 01:21:27 +07:00
2025-05-07 02:53:07 +07:00
const promises : Promise < void > [ ] = [ ]
2025-05-07 01:21:27 +07:00
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
2025-06-07 09:59:30 +07:00
// 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 ) )
2025-05-07 02:53:07 +07:00
promises . push (
writeFile (
path2 ,
2025-07-13 19:52:32 +07:00
` import{registerAsset}from'@revenge-mod/assets';import uri from ${ uriPath } ;const ${ name } =registerAsset({name:' ${ name } ',type: ${ type } ,uri});export { ${ name } as default } ` ,
2025-05-07 02:53:07 +07:00
) ,
2025-05-07 01:21:27 +07:00
)
}
2025-05-07 02:53:07 +07:00
await Promise . all ( promises )
2025-05-07 01:21:27 +07:00
}