2025-05-07 01:21:27 +07:00
|
|
|
import { parse } from 'path'
|
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-05-19 01:42:47 +07:00
|
|
|
import { exists, mkdir, readdir, rm, writeFile } from 'fs/promises'
|
2025-03-21 19:59:16 +07:00
|
|
|
import { type OutputChunk, type RolldownPlugin, rolldown } from 'rolldown'
|
2025-05-22 00:06:36 +07:00
|
|
|
import { aliasPlugin, importGlobPlugin } from 'rolldown/experimental'
|
2025-03-21 19:59:16 +07:00
|
|
|
|
2025-05-01 01:29:55 +07:00
|
|
|
import pkg from '../package.json'
|
|
|
|
|
|
2025-05-19 01:42:47 +07:00
|
|
|
const AssetsDir = 'src/assets'
|
|
|
|
|
const GeneratedAssetsDir = 'dist/assets/generated'
|
|
|
|
|
|
|
|
|
|
await rm(GeneratedAssetsDir, { recursive: true, force: true })
|
|
|
|
|
.then(() => {
|
|
|
|
|
console.debug(chalk.gray('\u{1F5BB} Deleted old assets'))
|
|
|
|
|
})
|
|
|
|
|
.catch()
|
|
|
|
|
|
2025-03-21 19:59:16 +07:00
|
|
|
// If this file is being run directly, build the project
|
|
|
|
|
if (main === import.meta.filename) build()
|
|
|
|
|
|
|
|
|
|
export default async function build(dev = false, log = true) {
|
|
|
|
|
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-03-21 19:59:16 +07:00
|
|
|
const bundle = await rolldown({
|
|
|
|
|
input: 'src/index.ts',
|
|
|
|
|
platform: 'neutral',
|
2025-04-30 20:20:16 +07:00
|
|
|
experimental: {
|
|
|
|
|
strictExecutionOrder: true,
|
|
|
|
|
},
|
2025-04-30 20:19:51 +07:00
|
|
|
resolve: {
|
|
|
|
|
tsconfigFilename: 'tsconfig.json',
|
|
|
|
|
},
|
2025-03-21 19:59:16 +07:00
|
|
|
treeshake: true,
|
|
|
|
|
keepNames: true,
|
|
|
|
|
moduleTypes: {
|
|
|
|
|
'.webp': 'dataurl',
|
|
|
|
|
},
|
|
|
|
|
define: {
|
2025-05-01 01:29:55 +07:00
|
|
|
__BUILD_VERSION__: JSON.stringify(pkg.version),
|
2025-05-01 18:54:58 +07:00
|
|
|
__BUILD_COMMIT__: JSON.stringify((await $`git rev-parse HEAD`.text()).trim().substring(0, 7)),
|
|
|
|
|
__BUILD_BRANCH__: JSON.stringify((await $`git rev-parse --abbrev-ref HEAD`.text()).trim()),
|
2025-03-21 19:59:16 +07:00
|
|
|
__BUILD_ENV__: JSON.stringify(dev ? 'development' : 'production'),
|
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',
|
|
|
|
|
replacement: './shims/react~jsx-runtime.ts',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}),
|
2025-05-22 00:06:36 +07:00
|
|
|
importGlobPlugin(),
|
|
|
|
|
swcPlugin(),
|
2025-03-21 19:59:16 +07:00
|
|
|
hermesCPlugin({
|
|
|
|
|
flags: [
|
|
|
|
|
dev ? '-Og' : '-O',
|
|
|
|
|
'-eager',
|
|
|
|
|
'-finline',
|
|
|
|
|
'-fno-static-require',
|
|
|
|
|
'-Wno-direct-eval',
|
|
|
|
|
'-Wno-undefined-variable',
|
|
|
|
|
],
|
2025-05-07 02:53:31 +07:00
|
|
|
before() {
|
|
|
|
|
if (log) console.debug(chalk.cyanBright('\u{1F5CE} JS compilation finished...'))
|
|
|
|
|
if (log) console.debug(chalk.gray('\u{1F5CE} Compiling bytecode...'))
|
|
|
|
|
},
|
|
|
|
|
after() {
|
|
|
|
|
if (log) console.debug(chalk.cyanBright('\u{1F5CE} Bytecode compilation finished'))
|
2025-03-21 19:59:16 +07:00
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await bundle.write({
|
2025-05-21 07:26:14 +07:00
|
|
|
minify: {
|
|
|
|
|
deadCodeElimination: true,
|
|
|
|
|
removeWhitespace: false,
|
|
|
|
|
compress: true,
|
|
|
|
|
mangle: true,
|
|
|
|
|
},
|
2025-03-21 19:59:16 +07:00
|
|
|
file: 'dist/revenge.js',
|
|
|
|
|
format: 'iife',
|
|
|
|
|
footer: '//# sourceURL=Revenge',
|
|
|
|
|
inlineDynamicImports: true,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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: {
|
|
|
|
|
id: /\.[cm]?[jt]sx?$/,
|
|
|
|
|
},
|
|
|
|
|
handler(code) {
|
|
|
|
|
return transform(code, {
|
|
|
|
|
jsc: {
|
|
|
|
|
transform: {
|
|
|
|
|
react: {
|
|
|
|
|
runtime: 'automatic',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
parser: {
|
|
|
|
|
syntax: 'typescript',
|
|
|
|
|
tsx: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
env: {
|
|
|
|
|
// https://github.com/discord/hermes/blob/main/doc/Features.md
|
|
|
|
|
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-05-07 02:53:31 +07:00
|
|
|
function hermesCPlugin({ after, before, flags }: { flags?: string[]; before?: () => void; after?: () => void } = {}) {
|
2025-03-21 19:59:16 +07:00
|
|
|
const paths = {
|
|
|
|
|
win32: 'hermesc.exe',
|
2025-05-05 01:45:35 +07:00
|
|
|
darwin: 'hermesc',
|
|
|
|
|
linux: 'hermesc',
|
2025-03-21 19:59:16 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(process.platform in paths)) throw new Error(`Unsupported platform: ${process.platform}`)
|
|
|
|
|
|
|
|
|
|
const binPath = paths[process.platform as keyof typeof paths]
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
name: 'hermesc',
|
|
|
|
|
generateBundle(_, bundle) {
|
2025-05-07 02:53:31 +07:00
|
|
|
if (before) before()
|
|
|
|
|
|
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')
|
|
|
|
|
|
|
|
|
|
const cmd = Bun.spawnSync(
|
|
|
|
|
[
|
|
|
|
|
`./node_modules/@unbound-mod/hermesc/${process.platform}/${binPath}`,
|
|
|
|
|
'-emit-binary',
|
|
|
|
|
...(flags ?? []),
|
|
|
|
|
],
|
|
|
|
|
{
|
|
|
|
|
stdin: new Blob([file.code]),
|
|
|
|
|
stdout: 'pipe',
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const buf = cmd.stdout
|
|
|
|
|
if (!buf.length) throw new Error('No output from hermesc')
|
|
|
|
|
|
|
|
|
|
this.emitFile({
|
|
|
|
|
type: 'asset',
|
|
|
|
|
fileName: `${file.fileName.split('.')[0]!}.bundle`,
|
|
|
|
|
source: buf,
|
|
|
|
|
})
|
|
|
|
|
|
2025-05-07 02:53:31 +07:00
|
|
|
if (after) after()
|
2025-03-21 19:59:16 +07:00
|
|
|
},
|
|
|
|
|
} satisfies RolldownPlugin
|
|
|
|
|
}
|
2025-05-07 01:21:27 +07:00
|
|
|
|
|
|
|
|
async function generateAssets() {
|
|
|
|
|
if (!(await exists(GeneratedAssetsDir))) await mkdir(GeneratedAssetsDir, { recursive: true })
|
|
|
|
|
|
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-05-07 02:53:07 +07:00
|
|
|
promises.push(
|
|
|
|
|
writeFile(
|
|
|
|
|
path2,
|
2025-05-10 02:06:40 +07:00
|
|
|
`const ${name}=require('@revenge-mod/assets').registerAsset({name:'${name}',type:'${ext.slice(1)}',uri:require('~/../${path}')})/**@type {ReturnType<import('@revenge-mod/assets').registerAsset>}*/\nexport default ${name}`,
|
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
|
|
|
}
|