Development workflow: quickly test code modifications

As I’m writing more and more Motoko, it would be nice to have a playground to quickly test code. The quickest way I’ve found to test a function is to run this after each modification:

dfx deploy && dfx canister call my_can myFunc

Even with minimal code, this takes roughly 30 seconds before the output of myFunc is reported.

Is there a faster way to test changes to my code?

4 Likes

If you’ve only made changes to a single canister you can run:
dfx build my_can && dfx canister install my_can -m upgrade && dfx canister call my_can myFunc

You can check the options for any of these commands with the --help flag too, eg:
dfx canister install --help

2 Likes

This might help:

4 Likes

We are going to release a web-based playground for Motoko to allow you deploy canisters online soon :slight_smile:

The live edits in the SDK website is a lightweight version of the playground, if you want to explore small Motoko code snippets.

8 Likes

Excellent

Something like Remix would reach Solidity devs;

https://remix.ethereum.org/

3 Likes

@Ori For some reason, when I try to build just the one canister I’m working on, it fails with

import error, canister alias "credits" not defined

even though I do have a canister named credits in the project that can be built successfully with

dfx build credits

Everything builds and runs successfully when I use dfx build or dfx deploy, so I’m not sure why building just a single canister fails when it is importing another canister.

Have you tried running dfx deploy once first, then using the commands I suggested for any further single canister changes?

Yup. The first command, dfx build serve fails, complaining that credits is not found. serve is one canister that imports the credits canister which is in the same project and both exist in the canister_ids.json file:

"credits": {
  "local": "rwlgt-iiaaa-aaaaa-aaaaa-ca
},
"serve": {
  "local": "ryjl3-tyaaa-aaaaa-aaaba-cai"
},

My output:

% dfx build serve
Building canisters...
The build step failed for canister 'ryjl3-tyaaa-aaaaa-aaaba-cai' with an embedded error: The command '"/Users/mikem/.cache/dfinity/versions/0.6.16/moc" "/Users/mikem/OneDrive/Projects/Web/videate/credits/src/serve/main.mo" "-o" "/Users/mikem/OneDrive/Projects/Web/videate/credits/.dfx/local/canisters/serve/serve.did" "--idl" "--actor-idl" "/Users/mikem/OneDrive/Projects/Web/videate/credits/.dfx/local/canisters/idl/" "--actor-alias" "serve" "ryjl3-tyaaa-aaaaa-aaaba-cai" "--package" "base" "/Users/mikem/.cache/dfinity/versions/0.6.16/base"' failed with exit status 'exit code: 1'.
Stdout:

Stderr:
/Users/mikem/OneDrive/Projects/Web/videate/credits/src/serve/main.mo:19.1-19.34: import error, canister alias "credits" not defined

serve uses import Credits "canister:credits"; to import the credits canister and the credits code is parallel to the serve code. Both are able to be deployed with dfx deploy commands; building works just fine in that case.

1 Like

Ah, you’ll need to add the credits canister as a dependency of the serve canister in your dfx.json, like this:

"serve": {
    "main": "src/serve/main.mo",
    "type": "motoko",
    "dependencies": [
        "credits"
    ]
},

Then the above steps should work, so…

First run only:
dfx deploy

Subsequent runs for single canister changes:
dfx build serve && dfx canister install serve -m upgrade && dfx canister call serve myFunc

2 Likes

Oh interesting. Odd that the deploy command works without the dependency, but the build command doesn’t. I thought deploy just used build under the hood. Something else is going on?

dfx deploy will be doing the same as dfx build --all, which will work too, but to build an individual canister it needs the dependencies listed.

2 Likes

Hi all! I’d like to revive this thread now that a year and a half has gone by to see what the community is doing to quickly test code changes.

Is it still best practice to use dfx build myCanister && dfx canister install myCanister -m upgrade each time I make changes to myCanister? It certainly works a lot faster than dfx deploy myCanister.

How about testing frontend code, for example React, that lives in a canister? Just use the above set of commands to quickly rebuild and upgrade the asset canister? Is that the fastest way to see quick UI changes, like if you change a color and want to see how it looks, or change some javascript/tsx to modify UI behavior, without having to do the upgrade as above and then refresh the page (actually refreshing never works since it always ends up going to the root so I always need to manually load the proper URL) and go through the process of logging in with Internet Identity and navigating to the page with the changes? Has anyone found a way to skip all that and maybe even have the site hot reload to see the changes immediately on save without requiring logging in again or navigating to the correct page? Any other tips to speed up development time, especially when working with UIs?

I found that using a pre-built Internet Identity Wasm will allow for quicker testing by using the II_DUMMY_CAPTCHA and II_DUMMY_AUTH feature flags to speed up the Internet Identity account creation and login process (see the Development or Test prebuilt Wasm modules), but I feel like there must be some more tips out there to further speed up UI development and iteration time.

Maybe I’m expecting too much at this point, but I would love to have a page just hot reload with my code changes. Developing for mobile devices using Google’s Flutter has spoiled me, I guess. How close can we get to that when developing on the IC?

During development I run a separate dev server that has hot reload enabled. I’m using esbuild. I think dfx new sets up a webpack dev server.

Oh cool! If I run my canisters using npm start, I can access the frontend canisters using port 3000, which will hot reload when I save a .tsx file.

My next challenge: Internet Identity now doesn’t work due to the port mismatch, so I can’t test using hot reload on any pages accessible only after successful login.

After logging in with Internet Identity, when I’m returned to my page, the following error prints out in the console that ran npm start:

[webpack-dev-server] [HPM] Error occurred while proxying request localhost:3000/api/v2/status to http://localhost:8000/ [ECONNREFUSED] (https://nodejs.org/api/errors.html#errors_common_system_errors)

Seems like webpack can’t connect to my local Internet Identity canister. It didn’t have a problem loading the Internet Identity login page on port 8000, but for some reason seems to be trying to proxy it through 3000 along with my frontend stuff.

The solution to this similar issue was to run the application server in one terminal and the webpack (via yarn) in another, but I am already doing that: I ran dfx start in one terminal (which makes port 8000 work for the backend canisters) and I ran npm start in another (which makes port 3000 work for the frontend canisters).

I also noticed that when I use npm start, I no longer need to specify the canisterId for my asset canister. It loads my asset canister no matter what I specify for the canisterId query parameter.

Anyone know how to resolve this port issue? What’s the proper workflow for being able to hot reload your pages that are only available after logging in through a locally running Internet Identity canister?

@kpeacock you do a lot of IC frontend development, surely you are able to hot reload pages post-login? How are you able to get around the port issue described above and succeed with the login?

Yes, I do this and can still access parts of my site that require users to be logged in.

I’ll try to respond tomorrow with info on how I do it.

1 Like

@paulyoung I’d love to see your process, as I’m still facing this issue. My current workaround is to develop the pages by placing them before sending the user to login, then moving them after they look good.

I’m not sure how to help other than to describe what I’m doing;

Then in my app I use the above URL for Internet Identity when running locally.

Localhost domains are supported in Chromium browsers, so I’m using Brave.

I’m currently struggling with the following error when trying to do the proxying by modifying webpack.config.js:

Error occurred while proxying request r7inp-6aaaa-aaaaa-aaabq-cai.localhost:3000/api/v2/status to http://r7inp-6aaaa-aaaaa-aaabq-cai.localhost:8000/ [ENOTFOUND] (https://nodejs.org/api/errors.html#errors_common_system_errors)

If I navigate to http://r7inp-6aaaa-aaaaa-aaabq-cai.localhost:8000/api/v2/status directly it responds successfully, but not when proxied through webpack I guess.

In addition to the default webpack that was set up for me (which I assumed is there to do exactly the thing I’m trying to do), i.e. the following:

  // proxy /api to port 8000 during development
  devServer: {
    proxy: {
      "/api": {
        target: "http://localhost:8000",
        changeOrigin: true,
        pathRewrite: {
          "^/api": "/api",
        },
      },
    ...

I’ve tried a million variations including explicit things like:

  devServer: {
    proxy: {
      '/api/**': {
        target: 'http://r7inp-6aaaa-aaaaa-aaabq-cai.localhost:8000',
        secure: false,
      },
    ...

I’ll have to pick this up tomorrow. It seems my next steps might be to use esbuild or icx-proxy as you do. I have no experience working with those, but I’ll play around tomorrow. Thanks for your help!

I think I saw something about this being broken on nodejs 17, in case you’re using that.