Contributing a project to ICP Ninja
We recommend to build your example directly within ICP Ninja, such that it starts out with the correct tooling, structure and configs. If you do that, your project will naturally be in the correct format and can be easily added to ICP Ninja. Once the example is done, you can download the source files from ICP Ninja. Alternatively, you can start with an existing Ninja project from this repo (see CODEOWNERS file for Ninja examples) and modify it.
Ideally, your project should have a frontend and backend.
Where to place your example
/motokofor a Motoko project/rustfor a Rust project/hostingfor a frontend-only project
Compilation requirements
- Make sure the project compiles and runs with
dfx deployinside the latestghcr.io/dfinity/icp-dev-env-slimcontainer. - Make sure
npm run devworks and the canister can be called through the browser (if applicable). - Make sure II login works (if applicable).
- If you use Rust, make sure the project has
ic_cdk::export_candid!();in thelib.rsfile, such that the Candid interface can be auto derived. - If you use Motoko, use Mops as the package manager.
Frontend tooling requirements
- React
- Vite for the build system
- Tailwind CSS for styling (recommended), or plain CSS
Preparing the PR
- Add your project in the
CODEOWNERSfile, with your team plus@dfinity/ninja-devsas reviewers. - Add your project to the matrix in
.github/workflows/ninja_pr_checks.ymlto run PR tests. - Add a
README.mdfile, copy theBUILD.mdanddevcontainer.jsonfiles.
Submit a PR to ICP Ninja
- Add your newly added project to
frontend/public/projects.json - Bump the commit hash in
submodules/examplesto a commit hash after your PR has been merged into the examples repo.
- ask the Ninja team or AI to give you a beautiful image for your project
Templates
Recommended dfx.json for a Rust canister:
{
"canisters": {
"backend": {
"candid": "backend/backend.did",
"type": "custom",
"shrink": true,
"gzip": true,
"wasm": "target/wasm32-unknown-unknown/release/backend.wasm",
"build": [
"cargo build --target wasm32-unknown-unknown --release -p backend",
"candid-extractor target/wasm32-unknown-unknown/release/backend.wasm > backend/backend.did"
],
"metadata": [
{
"name": "candid:service"
}
]
}
},
"output_env_file": ".env"
}
Recommended dfx.json for a Motoko canister:
{
"canisters": {
"backend": {
"main": "backend/app.mo",
"type": "motoko",
"args": "--enhanced-orthogonal-persistence"
},
},
"output_env_file": ".env",
"defaults": {
"build": {
"packtool": "mops sources"
}
}
}
Recommended dfx.json for a frontend/asset canister:
{
"canisters": {
"frontend": {
"dependencies": ["backend"],
"frontend": {
"entrypoint": "frontend/index.html"
},
"source": ["frontend/dist"],
"type": "assets"
}
},
"output_env_file": ".env"
}
Recommended vite.config.js
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import { fileURLToPath, URL } from 'url';
import environment from 'vite-plugin-environment';
export default defineConfig({
base: './',
plugins: [react(), environment('all', { prefix: 'CANISTER_' }), environment('all', { prefix: 'DFX_' })],
envDir: '../',
define: {
'process.env': process.env
},
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis'
}
}
},
resolve: {
alias: [
{
find: 'declarations',
replacement: fileURLToPath(new URL('../src/declarations', import.meta.url))
}
]
},
server: {
proxy: {
'/api': {
target: 'http://127.0.0.1:4943',
changeOrigin: true
}
},
host: '127.0.0.1'
}
});