Announcing the Motoko Dev Server: live-reloading for Web3 dapps
We are excited to introduce mo-dev, a flexible live-reload server for quickly building and testing Motoko services on the Internet Computer.
Background
Live reloading (or more specifically, hot module replacement) is a well-established technology known to massively improve the productivity of web developers. I highly recommend checking out this blog post for a great explanation of how it works.
This is a solved problem in the world of conventional web development. Robust solutions such as the Vite, Next.js, and Webpack dev servers make it possible to change a line of frontend code and immediately see the result in your browser, usually without even refreshing the page.
However, this feature is almost nonexistent when developing a decentralized application (or “dapp”) running on a blockchain. Several options exist — such as ZepKit for Solidity smart contracts — but the current state of Web3 live reloading leaves much to be desired given the sky-high expectations from Web2.
This is where the Motoko programming language comes in. With quick compilation times, module-based imports, and stable variable semantics, Motoko is the ideal candidate for a game-changing live reload workflow.
Over the past six months, DFINITY has adopted full-stack live reloading in our internal Motoko projects, saving a huge amount of development time and allowing us to quickly try lots of different ideas to improve the end-user experience of our Internet Computer dapps.
This was made possible using the Motoko Dev Server (or mo-dev
for short), a command-line tool which facilitates a live-reload workflow for Motoko dapps and smart contracts.
mo-dev
is already used in a wide range of projects such as the playground, Developer Experience Feedback Board, and even the language’s own base library.
Each use case requires different live-reload capabilities, so we decided to create a Swiss Army knife (if you will) of features which you can select depending on your project:
- Deploy canisters to the local replica
- Generate language bindings
- Run unit tests (files ending with
.test.mo
) - Execute commands
- Any combination of the above
For a complete list of features, check out the project’s GitHub repository at github.com/dfinity/motoko-dev-server.
Now that you know why you’d want to use mo-dev
, here are a few ways to get started:
Online Demo
If you’re curious to try mo-dev
without downloading anything, here’s an online example project which you can run entirely in your browser (source code).
Installation
Run the following command in your terminal (requires Node.js ≥ 16):
npm install -g mo-dev
Alternatively, you can download a standalone binary from the project’s GitHub releases.
Once you’ve installed the tool, run mo-dev --help
to view usage examples and descriptions for each available feature.
Candid UI
Let’s say you’re developing a Motoko smart contract using the Candid UI. Here’s a command which will redeploy the canister on file change:
mo-dev --deploy -y
The -y
flag automatically responds “yes” to prompts from dfx about upgrading the canister interface (potentially clearing canister data). Feel free to include or omit this depending on your use case.
Full-Stack Dapp
mo-dev
is specifically designed to play well with popular frontend build tools such as Vite and Create React App.
If you want to start a new project, consider using the Vite + React + Motoko template (or the even simpler plain JavaScript version).
Otherwise, this is a good starting point for live reloading a backend Motoko canister:
mo-dev --generate --deploy -y
The --generate
flag automatically creates and updates JavaScript language bindings whenever you make changes to your Motoko source code.
It’s also possible to use this feature by itself (mo-dev --generate
).
Advanced Usage
For those wanting to plug the Motoko Dev Server into an existing webapp, here is a scalable Vite project configuration we’ve adopted at DFINITY:
- Run
npm install -D npm-run-all mo-dev
in your project’s root directory - Add a frontend npm script which runs
vite
- Add a backend npm script which runs
mo-dev
with relevant flags - Change the start npm script to
run-p frontend backend
With this project configuration, npm start
will run the Vite and Motoko dev servers with a seamlessly integrated console output. You can split the outputs via npm run frontend
and npm run backend
in separate terminals.
Another benefit of this configuration is that anyone can run the dev server without needing to globally install the mo-dev
command, which is great for encouraging open-source contributions or working as part of a team.
This project configuration will be included by default in the upcoming changes to the dfx new command.
mo-dev
ships with a test runner compatible with third-party libraries such as motoko-matchers.
To register a unit test, create a file with the extension *.test.mo
anywhere in your project. For example, here is a basic unit test which asserts that 2vxsx-fae
is the anonymous principal:
import { print } "mo:base/Debug";
import Principal "mo:base/Principal";
let myPrincipal = Principal.fromText("2vxsx-fae");
print("Anonymous?");
assert Principal.isAnonymous(myPrincipal);
print("Yep!");
Once you’ve created a *.test.mo
file, run the following command:
mo-dev --test
This will run all unit tests whenever you change a Motoko source file.
Note that when using --test
with --deploy
, the dev server will wait until all relevant tests succeed before redeploying your canister.
If you want to run tests without the live-reloading functionality, you can use the mo-test
command which is automatically installed alongside mo-dev
:
mo-test --help
You can also include the -f
flag to filter specific test files:
mo-test -f Foo # Run test files starting with "Foo"
mo-test -f Foo.test.mo # Only run test files named "Foo.test.mo"
mo-test -f Foo -f Bar # Run test files starting with "Foo" or "Bar"
By default, the test runner uses the Motoko interpreter. If performance is critical, you can switch to a compiled WASI runtime by installing Wasmtime and adding --testmode wasi
to the test command.
The Motoko VS Code extension also includes a UI for the test runner:
Continuous Integration
The Motoko Dev Server can be used in CI environments such as GitHub Actions, Travis, and CircleCI. In these environments, mo-dev
will automatically terminate instead of waiting for file changes.
You can achieve the same effect on your local system by setting the CI environment variable to true
or 1
:
CI=true mo-dev --generate --deploy
Final Thoughts
Here are a few miscellaneous tips which might come in handy while working with mo-dev
:
- Live reloading works when switching Git branches, which is useful to keep in mind for code reviews and pair programming.
- If you’ve used dfx generate, you might have encountered a common catch-22 where both
dfx generate
anddfx deploy
require running each other first.mo-dev --generate --deploy
automatically handles this situation for you. - You can define custom live-reload logic using
mo-dev --exec <command>
.
Feedback is welcome! If you find a bug or want to see a new feature, please feel free to reach out on GitHub (or give us a ⭐ to support the project) at github.com/dfinity/motoko-dev-server.
Otherwise, consider submitting a request on our Developer Experience Feedback Board, which itself was built with mo-dev
! If you're curious, you can find the source code at github.com/dfinity/feedback.
Thanks for reading. Cheers!