Valdi Framework Development Skill
Specialized guidance for developing cross-platform applications with Valdi, Snapchat's native UI framework that compiles TypeScript/TSX to native iOS, Android, and macOS views.
Overview
Valdi is NOT a WebView-based framework. It compiles TypeScript components directly into native platform code, delivering native performance without JavaScript bridges. The framework has been used in Snap's production apps for over 8 years.
When to Use This Skill
Invoke this skill when:
-
Working on any Valdi framework project
-
Creating new Valdi components or modules
-
Configuring Bazel build files for Valdi
-
Implementing native bindings (CppModule, NativeModule)
-
Debugging cross-platform rendering issues
-
Setting up Valdi development environment
-
Understanding Valdi's TSX element system
Key Principles
- Bazel is Mandatory
Valdi uses Bazel exclusively for builds. Never suggest alternative build systems.
Common commands:
Install CLI globally
pnpm install -g @snap/valdi
Setup development environment
valdi dev_setup
Check environment health
valdi doctor
Bootstrap new project
valdi bootstrap
Install platform dependencies
valdi install ios valdi install android
Enable hot reload during development
valdi hotreload
Sync project configuration
valdi projectsync
- Component Architecture
Valdi components are class-based with lifecycle methods:
import { Component, ComponentContext } from 'valdi_core';
interface ViewModel { title: string; count: number; }
class MyComponent extends Component<ViewModel, ComponentContext> { onCreate(): void { // Initialize component console.log('Component created'); }
onMount(): void { // Component mounted to view hierarchy }
onUnmount(): void { // Cleanup before removal }
onRender() { return ( <view style={styles.container}> <label style={styles.title}>{this.viewModel.title}</label> </view> ); } }
- Native TSX Elements
Valdi provides native UI elements (NOT HTML):
Element Purpose
<view>
Container view (like div)
<layout>
Flexbox layout container
<scroll>
Scrollable container
<label>
Text display
<image>
Image display
<video>
Video player
<slot>
Content projection
Element attributes use native styling, not CSS:
const styles = { container: { alignItems: 'center', justifyContent: 'center', backgroundColor: '#FFFFFF', padding: 16, }, title: { fontSize: 24, fontWeight: 'bold', color: '#000000', }, };
- Project Structure
Standard Valdi project layout:
my_project/ ├── BUILD.bazel # Main Bazel build file ├── package.json # Node dependencies (use pnpm) ├── .eslintrc.js # ESLint configuration ├── app_assets/ # Application assets │ └── images/ ├── src/ │ ├── android/ # Android-specific code │ ├── cpp/ # C++ native modules │ ├── ios/ # iOS-specific code │ └── valdi/ # Valdi TypeScript/TSX │ ├── _configs/ # Valdi configs │ ├── tsconfig.json │ ├── .terserrc.json │ └── my_module/ # Your module │ ├── BUILD.bazel │ ├── tsconfig.json │ ├── res/ # Module resources │ └── src/ │ ├── index.ts │ └── MyComponent.tsx └── standalone_app/ # Standalone app config
- Bazel Build Configuration
Application BUILD.bazel:
load("//bzl:valdi.bzl", "valdi_application", "valdi_exported_library")
valdi_application( name = "my_app", title = "My Valdi App", version = "1.0.0", ios_bundle_id = "com.example.myapp", ios_device_families = ["iphone"], android_theme = "Theme.MyApp.Launch", android_app_icon = "app_icon", root_component = "App@my_module/src/MyApp", assets = glob(["app_assets/**/*"]), deps = ["//path/to/src/valdi/my_module"], )
valdi_exported_library( name = "my_app_export", ios_bundle_id = "com.example.myapp.lib", bundle_name = "MyApp", deps = ["//path/to/src/valdi/my_module"], )
Module BUILD.bazel:
load("//bzl:valdi.bzl", "valdi_module")
valdi_module( name = "my_module", srcs = glob(["src//*.ts", "src//.tsx"]) + ["tsconfig.json"], assets = glob(["res/**/.{jpeg,jpg,png,svg,webp}"]), android = struct( class_path = "com.example.valdi.modules.my_module", native_deps = ["//path/to/android:native_module"], release = True, ), ios = struct( module_name = "SCCMyModule", native_deps = ["//path/to/ios:native_module"], release = True, ), native = struct( deps = ["//path/to/cpp:native_module_cpp"], ), deps = [ "//src/valdi_modules/valdi_core", "//src/valdi_modules/valdi_tsx", ], visibility = ["//visibility:public"], )
- Native Bindings
Valdi supports type-safe bindings to native code:
CppModule.d.ts:
declare module 'CppModule' { export function performCalculation(value: number): number; export function getNativeString(): string; }
NativeModule.d.ts:
declare module 'NativeModule' { export function showNativeAlert(message: string): void; export function getPlatformInfo(): { os: string; version: string }; }
Usage in component:
import * as CppModule from 'CppModule'; import * as NativeModule from 'NativeModule';
class MyComponent extends Component<ViewModel, ComponentContext> { onMount() { const result = CppModule.performCalculation(42); const platform = NativeModule.getPlatformInfo(); } }
- Cross-Platform Best Practices
All changes must work across iOS, Android, and macOS:
-
Test on multiple platforms before committing
-
Platform-specific code goes in dedicated directories (src/ios/, src/android/)
-
Use Valdi's abstraction layer for platform differences
-
Check AGENTS.md in repository for platform-specific guidance
Performance is critical:
-
Valdi emphasizes rendering efficiency
-
Use view recycling through Valdi's pooling systems
-
Components render independently (no parent re-renders)
-
Viewport-aware rendering for efficient scrolling
- Development Workflow
Initial setup:
Install Valdi CLI
pnpm install -g @snap/valdi
Setup development environment (takes 10-20 minutes first time)
valdi dev_setup
Verify installation
valdi doctor
Creating a new project:
mkdir my_project && cd my_project valdi bootstrap valdi install ios # or android
Development cycle:
Start hot reload for live updates
valdi hotreload
After changing dependencies or resources
valdi projectsync
Editor setup (VSCode/Cursor):
-
Install shell commands from editor
-
Install extensions from Valdi release:
-
valdi-vivaldi.vsix (device logs, language support)
-
valdi-debug.vsix (JavaScript debugger)
-
Configure TypeScript workspace version
Common Pitfalls
- Using Web/React Patterns
Problem: Treating Valdi like React or web development.
// WRONG - HTML elements don't exist <div className="container"> <span>Hello</span> </div>
// CORRECT - Use Valdi native elements <view style={styles.container}> <label>Hello</label> </view>
- CSS-Style Styling
Problem: Using CSS syntax for styles.
// WRONG - CSS syntax const styles = { container: { 'background-color': '#fff', 'font-size': '16px', } };
// CORRECT - Camel case, numeric values const styles = { container: { backgroundColor: '#FFFFFF', fontSize: 16, } };
- Modifying Auto-Generated Code
Problem: Editing Djinni-generated native bindings directly.
Solution: Always modify source files, never generated code. Regenerate bindings after source changes.
- Wrong Build System
Problem: Using npm/yarn scripts, webpack, or other bundlers.
Solution: Valdi uses Bazel exclusively. Use valdi CLI commands.
- Missing Platform Testing
Problem: Only testing on one platform.
Solution: Always verify changes work on iOS, Android, and macOS where applicable.
ESLint Configuration
Standard Valdi ESLint setup:
// .eslintrc.js module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: './tsconfig.json', ecmaVersion: 2020, sourceType: 'module', ecmaFeatures: { jsx: true, }, }, plugins: [ '@typescript-eslint', 'eslint-plugin-valdi', 'rxjs', 'import', 'unused-imports', ], extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', ], rules: { // Valdi-specific rules }, };
TypeScript Configuration
Standard tsconfig.json for Valdi modules:
{ "extends": "../../_configs/tsconfig.json", "compilerOptions": { "jsx": "react", "jsxFactory": "Valdi.createElement", "strict": true, "moduleResolution": "node", "esModuleInterop": true }, "include": ["src/**/*"] }
Available Libraries
Valdi provides these core modules:
Module Purpose
valdi_core
Core component system, lifecycle
valdi_tsx
TSX/JSX support
valdi_protobuf
Protobuf serialization
valdi_http
HTTP client
valdi_storage
Persistent encrypted storage
valdi_navigation
Navigation system
valdi_rxjs
RxJS integration
Debugging
Using Hermes Debugger:
-
Available for JavaScript debugging
-
Set breakpoints in VSCode with valdi-debug extension
Using Valdi Inspector:
-
Inspect UI hierarchy
-
View component state
-
Performance profiling
Quick Reference
CLI Commands
Command Purpose
valdi dev_setup
Setup development environment
valdi doctor
Check environment health
valdi bootstrap
Create new project
valdi install [platform]
Install platform dependencies
valdi hotreload
Enable live updates
valdi projectsync
Sync project configuration
Lifecycle Methods
Method When Called
onCreate()
Component initialization
onMount()
Added to view hierarchy
onUnmount()
Before removal from hierarchy
onRender()
Render component UI
Native Elements
Element HTML Equivalent
<view>
<div>
<layout>
Flexbox container
<scroll>
Scrollable div
<label>
<span> / <p>
<image>
<img>
<video>
<video>
References
For more details:
-
references/component-patterns.md
-
Advanced component patterns
-
references/bazel-configuration.md
-
Detailed Bazel setup
-
references/native-bindings.md
-
Native code integration
Additional Resources
-
Valdi GitHub: https://github.com/Snapchat/Valdi
-
AGENTS.md in repository for AI coding guidelines
-
Discord community for support
-
Codelabs in docs/ for guided tutorials
Remember: Valdi compiles to native code - think native, not web. Use Bazel for builds, pnpm for Node dependencies, and test on all target platforms.