跪拜 Guibai
← Back to the summary

Rust Rewrites HarmonyOS Builds: arkdown Cuts Cold Builds 8.4x and Memory 75%

Next-Generation HarmonyOS Build Toolchain: arkdown

Author: Ranger0929
Tags: HarmonyOS

Prologue

This article mainly documents something I’ve been working on recently: building a next-generation packaging/build toolchain for HarmonyOS (OpenHarmony / HarmonyOS) from scratch, based on the Rust ecosystem (OXC + Rolldown + tsrs) — named arkdown.

Currently arkdown has fully implemented the entire pipeline from ArkTS/TypeScript source parsing, type checking, UI syntax transformation, module bundling, C/C++ Native compilation, resource handling, bytecode (ABC) generation, to final HAP, HSP, HAR, APP signing and packaging.

The open-source repository mentioned in this article is at: arkdown

Before reading this article, the following background is recommended for the best experience:

  1. Some experience developing HarmonyOS native applications (ArkTS), with an understanding of basic project structure and development practices.
  2. A basic awareness of front-end engineering build tools (such as Rollup, Rolldown, OXC).
  3. A simple understanding of how the TypeScript compiler works internally.

1. Why Write a New Build Tool?

If you only write HarmonyOS demos or small applications with a few hundred lines of code, you might feel the official default build tool Hvigor works well enough, and see no compelling reason to switch.

But once you enter medium- to large-scale production HarmonyOS projects, or need to run build tasks frequently in CI/CD pipelines, the pain points of the traditional build chain hit you like a “slap in the face.”

Opening the HarmonyOS official build plugin developtools_ace_ets2bundle source code, we can see the current default build main chain is essentially: Node.js-based Hvigor task orchestration + a modified TypeScript 4.9.5 compiler + Rollup bundler + multi-Worker process pool.

In a real large project containing tens of thousands of .ts / .ets files, this JS/TS-based toolchain exposes several fatal problems:

  1. Extremely long cold build time In a production HarmonyOS app with 11,000+ source files, a cold build after clean often takes 10 minutes or more (620+ seconds). Most of that time is spent in the CompileArkTS phase’s single-threaded/multi-Worker AST parsing, full type checking, and Rollup module dependency graph scanning.
  2. Memory usage explodes, OOM is a frequent occurrence JS developers who have experienced memory overflow in large front-end project builds will understand. In HarmonyOS builds, because the customized TypeScript compiler and Rollup need to keep huge AST nodes, symbol tables, and SourceMap chains resident in Node.js memory, the peak physical memory (Physical Footprint) of the process tree during a large project cold build can spike to over 9.6 GiB. For developers still using 16 GB dev machines, or cloud build containers, running a few concurrent tasks can crash the machine.
  3. The toolchain is heavy and not “out-of-the-box” The traditional build process strongly couples complex task state machines, Daemon processes, and various implicit environment conventions. When we want to cleanly and lightly complete app packaging in a standalone command-line environment, automation scripts, or cross-platform toolchains, we often have to carry a whole heavy runtime baggage.

Similar to how the Web front-end toolchain has gone “Rust-ification” over the past two years (e.g., SWC, Rspack, Oxc, Rolldown), we naturally ask: Since ArkTS 1.1 is essentially a TypeScript superset with declarative UI syntax sugar, can we completely rewrite the HarmonyOS packager with a pure Rust toolchain?

This is the origin of arkdown.

This is also arkdown’s core engineering principle: use Hvigor and ets2bundle official source code as the sole behavioral benchmark, but do not keep any Hvigor compatibility wrapper layer internally. Rust directly implements every module from parsing to signing.

2. Technology Selection: Why OXC + Rolldown + tsrs?

If you want to rewrite the HarmonyOS build toolchain in Rust, you certainly can’t write every capability from scratch. For the core base, we ultimately chose the combination of OXC + Rolldown + tsrs.

Many might be curious: why exactly these three tools? What role does each play in arkdown?

1. Why OXC?

In the HarmonyOS build process, .ets files contain a large amount of non-standard TS declarative UI syntax (e.g., struct, @Component, @Builder, chained layout calls) as well as @interface annotations. The build tool needs to convert these to standard JS/TS code, generate precise SourceMaps, and complete ArkGuard source obfuscation in Release mode.

OXC (The JavaScript Oxidation Compiler) is currently one of the most performant and architecturally modern JS/TS compiler infrastructure in the Rust ecosystem:

Extending ArkTS syntax based on OXC means we can not only directly reuse its high-performance code generation and SourceMap capabilities, but also conveniently bring the official oxlint (static checking) and oxfmt (code formatting) into the HarmonyOS ecosystem.

2. Why Rolldown?

If you’ve looked into ets2bundle internals or reverse-engineered hvigor source code, you’ll know that HarmonyOS Stage/ESMODULE mode compilation is heavily built on Rollup, and exposes a set of Rollup-compatible plugin hooks (such as buildStart, resolveId, load, transform, moduleParsed, buildEnd) as a compilation plugin mechanism.

Rolldown, as the next-generation Rust bundler, happens to have two core advantages:

  1. Naturally compatible with Rollup plugins and module graph models: This lets us seamlessly inherit existing custom TS/JS compilation plugins in HarmonyOS projects with minimal adaptation cost at the JS/N-API boundary.
  2. Thoroughly same-origin with OXC: Rolldown uses OXC’s Parser, AST, and Resolver underneath. Choosing Rolldown allows syntax transformation and module graph scanning to work within the same AST system, avoiding repeated parsing and serialization overhead between different tools.

3. Why Still Need tsrs?

Before discussing tsrs, let’s clarify a question many will first ask: Since we already have OXC and Rolldown, why not just use OXC for single-file syntax erasure and transformation like we do in normal TS projects, and then package it? Why introduce a full TypeScript type checker?

Answer: You cannot.

The most critical reasons are two-fold:

  1. HarmonyOS builds include extremely strict syntax restrictions and SDK compliance checks In the CompileArkTS phase, in addition to basic TS type checking, you must also execute the full ArkTS 1.1 Linter (such as prohibiting any, dynamic property restrictions, Sendable concurrency safety checks, etc.), as well as SDK JSDoc-based API checks (such as @since version gates, @syscap system capabilities, @permission permissions, @crossplatform cross-platform checks, etc.).
  2. ArkUI code transformations strongly depend on real cross-file type information This is the easiest place to trip up. When converting ArkUI to runtime code, many transformation logic is not purely syntactic; you must know the actual derived type behind a certain identifier. For example, when handling the @ObjectLink decorator, the transformer must accurately determine whether the property type is a nullable union type, basic type, @ObservedV2 class, or function type. Similarly, cross-file @Builder recognition, constant folding of enum members, @interface retention policies, etc., all require full cross-file type inference rather than single-file AST guessing. Otherwise the generated bytecode will definitely fail on real devices.

We also took a detour in this selection:

Earlier, we integrated tsgo, a Go-language port, but although tsgo solved native compilation speed, it inevitably brought cross-process IPC/RPC communication overhead as an independent Go binary. Worse, publishing npm packages required cross-platform building and distributing independent compiler executables, and process lifecycle management was cumbersome.

Because of that, when the Rust-native TypeScript 7 compiler port tsrs appeared, we decisively migrated the checker entirely to tsrs.

The biggest engineering benefit of migrating to tsrs is: we can directly link tsrs_api, tsrs_core, and tsrs_vfs as Cargo dependencies into arkdown’s N-API native dynamic library.

This way, we no longer need to start external subprocesses or do JSON-RPC cross-process communication, nor distribute separate executables. All source snapshots, type inference queries, and diagnostics happen within the same Rust process.

3. What Core Changes Did We Make to Upstream Source?

Of course, the upstream open-source OXC, Rolldown, and tsrs only understand standard JavaScript/TypeScript and have no idea about HarmonyOS-specific .ets syntax and build rules.

To make these three core libraries truly run the full HarmonyOS build pipeline, we carried out deep custom modifications in the ohos-rs organization. Let me briefly share a few of the most important changes.

1. Modifying OXC: Native ArkTS / ArkUI Syntax Tree

Our modifications to ohos-rs/oxc center on oxc_ast, oxc_parser, oxc_codegen, and supporting toolchain:

2. Modifying Rolldown: A “Low-Memory Scan Mode” for Bytecode Compilation

While modifying ohos-rs/rolldown, we found a critical architectural difference:

In traditional web front-end builds, the bundler (Rollup/Rolldown) links thousands of small modules (symbol binding, scope hoisting, tree-shaking) and concatenates them into a final JS bundle file (generateBundle). So Rolldown keeps all modules’ EcmaAst, statement analysis info (stmt_infos), and symbol reference database (symbol_ref_db) in memory for the entire build cycle, for the later Link stage.

But in HarmonyOS Stage/ESMODULE build mode, the situation is completely different. In the upstream ets2bundle, generateModuleGraphOnly and disableGenerateBundle are enabled by default. Simply put, HarmonyOS doesn’t need the bundler to concatenate code into one big JS file. Instead, the bundler is used to run through Resolve → Load → Transform → module dependency graph scan, then the transformed per-file source and module mapping table are handed to the SDK’s es2abc to directly compile into Ark bytecode (modules.abc).

With this in mind, we made two core changes in Rolldown:

  1. Added a lightweight scan_module_sources dedicated channel We exposed interfaces for module-source-graph scanning directly from Bundle and ScanStage, skipping the entire heavy Link and Chunk generation phase, and directly output structured ModuleSourceScanOutput (including each module’s transformed source, export types, and imports dependency list with precise source ranges).
  2. Introduced a retain_link_output memory immediate-release mechanism In the ModuleLoader task loop, when in module scanning mode, once each file completes AST dependency scanning, we immediately drop its EcmaAst, stmt_infos, and local_symbol_ref_db in the current Worker, and no longer retain unused transformation-stage SourceMap chains.
// In module scan mode, immediately release AST and symbol table memory after extracting dependency graph info
if self.retain_link_output {
    *self.intermediate_normal_modules.index_ecma_ast.get_mut(runtime.id()) = Some(ast);
    *self.intermediate_normal_modules.stmt_infos.get_mut(runtime.id()) = Some(stmt_infos);
    self.symbol_ref_db.store_local_db(runtime.id(), local_symbol_ref_db);
} else {
    drop((ast, stmt_infos, local_symbol_ref_db));
}

This change directly cuts the resident memory overhead of AST and symbol tables for tens of thousands of files during the bundling phase. It is one of the core reasons arkdown can keep memory usage extremely low.

3. Modifying tsrs: Complete Port of HarmonyOS TS 4.9 Semantics and In-Process Embedded Engine

The modification of ohos-rs/tsrs is the largest and most complex part of the entire project. Core pieces include:

  1. Complete port of dynamic ArkTS syntax, module resolution, and 1003 Linter rules We referenced OpenHarmony’s official third_party_typescript and ets2bundle source code, and fully implemented parsing and binding of .ets/.d.ets, virtual component node construction, ohpm module resolution (supporting oh_modules, oh-package.json5, .so mappings), semantic-level auto-expansion of @kit.* imports, and the full ArkTS 1.1 Linter. To ensure correctness, we built an automated differential testing framework: 1,003/1,003 test cases from the official Linter corpus and 243/243 full-project diagnostic tests captured from the official compiler achieved 100% alignment in error codes, line/column numbers, error messages, and diagnostic order.
  2. Accurate restoration of OH TypeScript 4.9 compatibility gates Since tsrs upstream is based on latest TypeScript 7 semantics, while the HarmonyOS SDK is based on TypeScript 4.9.5, many new TS type narrowing rules would cause false positives in HarmonyOS projects. We added TS 4.9 compatibility gates specifically for HarmonyOS mode in tsrs (e.g., narrowing rules in equality checks for numeric enums, enum flow analysis with computed members, regex literal scanning differences, and generic common supertype inference order).
  3. Built the tsrs_api::embedded in-process snapshot service and SDK plugin callbacks We added an embedded::Compiler interface to tsrs_api, which runs in a fixed noEmit mode, allowing arkdown to create and incrementally update immutable source snapshots (ProgramSnapshot) directly in the Rust process, and batch-query types needed for OXC transformations. For the external API-check plugins written in CommonJS in the HarmonyOS SDK (e.g., checking @since, @syscap), we implemented a thread-safe ohsdkplugin callback router, and strictly aligned with the upstream LinterRunner lifecycle—triggering JSDoc callbacks only once in the normal semantic phase. This single fix directly reduced redundant callback executions in real large projects by over 65% (more than 850,000 calls).

4. What Are the Final Benefits?

After all that low-level architecture and source-code modification, the most important thing is: what is the actual performance in real projects?

We can look at the benefits from three dimensions: performance and memory, artifact correctness, and developer experience.

1. Cold Build Speedup 8.4 to 10x, Physical Memory Down 75%

We ran strict end-to-end benchmarks on a real large production HarmonyOS app.

Test environment: Apple M2 Pro (12-core CPU, 16 GiB RAM), macOS 15.6.1, DevEco Studio 26.0.0.821 / HarmonyOS API 26. Test project: a real large commercial app with 11,627 TypeScript files + 23 ETS files, building a Debug HAP that preserves full native debug symbols (about 1.5 GB).

The test results are shown below:

Build tool & mode Cold build time after clean No-change second build Peak process tree RSS (cold/hot) Peak physical memory Footprint (cold/hot)
Hvigor 6.26.4 629.69 s 5.96 s 2.90 / 0.54 GiB 9.68 / 0.49 GiB
Arkdown (full Checker) 75.36 s 17.28 s 1.71 / 0.42 GiB 2.39 / 0.34 GiB
Arkdown (--skip-checker) 62.94 s 15.84 s 1.68 / 0.43 GiB 2.37 / 0.34 GiB

From the measured data we can directly see:

Of course, looking at the table honestly, you’ll also notice: for a second build with no source changes (Hot Rebuild), arkdown needs 15-17 seconds, which is slower than Hvigor’s 5.96 seconds.

The reason: arkdown’s cross-process cache already covers OXC transformation, checker, ABC bytecode, and packaging/signing phases (hot build Bundling only takes 0.46s, ABC is 0s), but because upstream Rolldown does not yet provide a safe structured disk module graph deserialization interface, each new process startup still spends about 6.9 seconds on project snapshot preparation, and about 7.3 seconds hashing the 1.5 GB large package for content verification. This is the next focus for optimization.

2. 37/37 Strict Artifact Parity (Strict Hvigor Parity)

For a build tool, being fast is a bonus, but producing packages that are 100% identical to the official ones, and that install and run reliably on real devices, is the fundamental requirement.

During arkdown development, we established a regression test matrix that directly compares artifact-by-artifact against real hvigorw:

ARKDOWN_PARITY_HVIGORW=/path/to/command-line-tools/bin/hvigorw \
ARKDOWN_PARITY_STRICT=1 \
cargo test -p arkdown-napi --test hvigor_parity -- --test-threads=1

In ARKDOWN_PARITY_STRICT=1 mode, there is no allowlist. Currently covering HAP, HSP, HAR, multi-HAP+HSP APP, hot-reload HQF, ArkGuard source and bytecode obfuscation, and real signature verification, 37/37 core Fixture tests all pass with zero differences.

3. Out-of-the-Box and More Practical Features

In terms of usage experience, arkdown is truly clean and out-of-the-box. There’s no need to install any extra Go environment or standalone compiler; just install via npm:

npm install @ohos-rs/arkdown

Then you only need one command to directly compile an existing HarmonyOS project:

npx arkdown build --project ./my-app --target hap --mode debug

On top of that, we also added several practical features to improve daily development experience:

5. Future Iteration Plans

Although arkdown has already gotten production-grade large app builds working and achieved impressive performance gains, there is still much to iterate on as a complete build tool.

I will focus on the following next:

  1. Optimizing cross-process hot rebuild performance and implementing disk module graph cache As mentioned earlier, the current no-change second build time is mainly bottlenecked by new-process module scanning and large artifact verification. We will push forward structured cross-process module graph caching (Disk Graph Cache) and finer-grained bytecode invalidation strategies to further reduce incremental build time.
  2. Completing pure Rust native HAR signing capability Currently, arkdown signs HAP, HSP, HQF, and APP natively via our Rust-written hapsigner-rs, which is embedded directly; only HAR’s directory signing protocol still uses the SDK’s sdk-sign-tool-full.jar as a fallback. Next we will complete the native implementation of that protocol in hapsigner-rs and remove the last Java dependency.
  3. Following up on the official ArkTS 1.2 toolchain and more edge cases arkdown has already architecturally completed build support for ArkTS 1.2 (supporting pure static, pure dynamic, and mixed modules that contain both modules_static.abc and modules.abc). As the official SDK releases the full ets/static and static bytecode obfuscation components, we will immediately verify on real devices and the full alignment matrix.
  4. Improving the HarmonyOS peripheral development toolchain based on OXC + tsrs To build arkdown well, we’ve already completed the most important parts—ohos-rs/oxc (ArkTS parsing/formatting/Lint) and ohos-rs/tsrs (ArkTS native type checking). Their value goes far beyond packaging: whether it’s millisecond-level ArkTS code formatting tools (oxfmt), static checking tools (oxlint), or low-memory, high-performance ArkTS language server support (LSP) for lightweight editors like Zed and VS Code, all will evolve on this foundation.

Epilogue

This article mainly introduced and reviewed arkdown, the next-generation HarmonyOS build toolchain I’ve been working on, including why I built this wheel, why I chose OXC + Rolldown + tsrs, the core upstream modifications we made, and the performance gains we achieved on a real large project.

Looking back at the whole process—from initially trying to parse .ets files in Rust, to gradually integrating OXC, Rolldown, tsrs, and hapsigner-rs, to fighting every byte of artifact alignment with the official ets2bundle source—there were countless pitfalls, but seeing a 10-minute build compressed to about one minute, with memory reduced by three-quarters, was very rewarding. Above all, it has greatly helped my daily development by significantly reducing compile-time overhead.

If you’re suffering from slow builds or memory overflows in large HarmonyOS projects, or are interested in Rust front-end infrastructure and HarmonyOS toolchains, you’re very welcome to try it out, file issues, or submit PRs.

I also want to thank the Void team for their high-performance open-source implementation, without which we wouldn’t have been able to transform the HarmonyOS toolchain. And of course, I shamelessly hope you can give arkdown a ⭐️. Hope this article helps!

Comments

Top 1 from juejin.cn, machine-translated. The original thread is authoritative.

Ying影

Like