website/blog/2025-09-01_moon-v1.40.mdx
It's been a while since our last release, as we've been busy working on new JavaScript ecosystem WASM toolchains, which are now available!
<!--truncate-->Porting the legacy Bun and Node.js toolchains to WASM has been a non-trivial amount of work, as the JavaScript ecosystem is quite convoluted compared to other languages. The paradigms required by JavaScript simply don't exist in other languages, and as such, we've had to build custom functionality into our toolchain plugin system to support it properly.
On top of that, Bun and Node.js share a lot of functionality, and if you've been using both legacy
toolchains in parallel, you may have noticed a handful of issues because of this, such as
overlapping dependency installs, conflicting alias/task extraction, or over-reading of
package.json files. Additionally, this doesn't even take Deno into account, which has its own set
of problems to solve for interoperability.
To solve these problems, we've reimagined how JavaScript toolchains work in moon with the following goals in mind:
And the result of this rework is 6 new toolchains! Continue reading for more details.
unstable_javascriptA new JavaScript toolchain has been introduced, called unstable_javascript. This toolchain serves
as the foundation for all other JavaScript-related toolchains, providing a shared core of
functionality and settings. It implements tier 1 and tier 2 features, and is in charge of
the following:
package.json and node_modules information.package.json workspaces).unstable_javascript:
packageManager: 'yarn'
inferTasksFromScripts: false
syncPackageManagerField: true
syncProjectWorkspaceDependencies: true
If you're familar with the legacy Bun or Node.js toolchains, this should feel very familiar, as this is a combination of their functionality. Learn more about its settings:
unstable_bun and unstable_nodeJavaScript is a unique language in that it has multiple runtimes, primarily Node.js, Bun, and Deno.
We support Node.js through the new unstable_node toolchain, and Bun through the unstable_bun
toolchain, with Deno support coming soon. These toolchains only implement tier 1 and tier 3
features, as tier 2 is handled by the core unstable_javascript toolchain.
Their primary role is to provide settings for runtime execution (task child processes), and for
downloading and installing the runtime tool into the proto toolchain (when the version setting is
defined).
The runtime that will be utilized in the action graph is defined by the new
unstable_javascript.packageManager setting (bun = bun, npm/pnpm/yarn = node), but that doesn't
stop you from using multiple runtimes in parallel.
unstable_node:
version: '24.0.0'
executeArgs: ['--preserve-symlinks']
Learn more about their settings:
unstable_npm, unstable_pnpm, and unstable_yarnAll JavaScript package managers (including Bun) are now their own toolchain, with their own
configuration, and are no longer nested within the Node.js toolchain. These toolchains only
implement tier 1 and tier 3 features, as tier 2 is handled by the core
unstable_javascript toolchain.
Their primary role is to provide settings for the unstable_javascript toolchain when installing
and syncing dependencies, and for downloading and installing the package manager tool into the proto
toolchain (when the version setting is defined).
Since there are multiple package managers, which do you need to configure? Only the one associated
with the new unstable_javascript.packageManager setting!
unstable_pnpm:
version: '10.15.0'
installArgs: ['--frozen-lockfile']
Learn more about their settings:
Migrating from the legacy toolchains to these new modern WASM toolchains is rather straightforward, as most of the existing settings have been ported over. Follow these steps:
node.npm, node.pnpm, and node.yarn configuration to its own top-level unstable_
toolchain.bun.version or node.version setting to an unstable_bun or unstable_node toolchain
respectively. If not using version, set an empty object.node.addEnginesConstraint setting.node or bun toolchain to unstable_javascript.node.binExecArgs setting to unstable_node.executeArgs.node.rootPackageOnly setting to unstable_javascript.rootPackageDependenciesOnly.As an example, here's a before and after of our repository.
# Before
node:
version: '22.14.0'
packageManager: 'yarn'
yarn:
version: '4.8.0'
inferTasksFromScripts: false
syncPackageManagerField: true
syncProjectWorkspaceDependencies: true
# After
unstable_javascript:
packageManager: 'yarn'
inferTasksFromScripts: false
syncPackageManagerField: true
syncProjectWorkspaceDependencies: true
unstable_node:
version: '22.14.0'
unstable_yarn:
version: '4.8.0'
Because these new toolchains are built around a plugin system, and not hard-coded into core like the legacy toolchains, there are some backwards incompatibilities, changes, and caveats to be aware of:
node/bun toolchains are now spread across multiple new toolchains, instead of
1 toolchain, any configuration of the task toolchain setting
may now be inaccurate, as this setting overrides all detected/inherited toolchains. We suggest
omitting this field unless you want full control and understand what you are doing.# Invalid
tasks:
build:
# ...
toolchain: 'node'
# Valid
tasks:
build:
# ...
toolchain: ['unstable_javascript', 'unstable_node', 'unstable_npm']
# Or simply
toolchain: ['javascript', 'node', 'npm']
moon project and moon task
commands.If either of these issues, or other unexpected issues arise, please report it so we can fix it, or provide a work around!
Tasks have always supported a cache option for toggling caching on
and off. With the introduction of remote cache, we're expanding these options to provide more
flexibility and control. Instead of supporting only a simple boolean flag, we're introducing new
local and remote values, which will only cache locally or remotely, respectively.
This is useful for tasks that need caching, but should not persist in the remote cache, and vice versa.
tasks:
build:
# ...
options:
cache: 'local'
Adoption of our new remote cache solution has been going great, and we've heard positive feedback from users about its performance and reliability. However, it's not perfect and can always be improved!
And as such, we're introducing a new
unstable_remote.cache.localReadOnly setting, which will
only read (download) outputs from the remote cache when in local development, but will not write
(upload) outputs. This is useful for teams that want to share cache between CI and local, but don't
want the overhead of uploading in-development outputs.
unstable_remote:
cache:
localReadOnly: true
View the official release for a full list of changes.
moon query touched-files to default to comparing against remote branches when in CI, and
local when not in CI. This aligns with the other moon query commands.PATH.MOON_TASK_HASH environment variable for the current hash,
which can be read from child processes.With toolchains plugins being stabilized more, we'd like to focus on some other areas.
unstable_deno toolchain