valdi
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
1. 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
2. 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>
);
}
}
3. 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',
},
};
4. 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
5. 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"],
)
6. 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();
}
}
7. 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
8. 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
1. 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>
2. 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,
}
};
3. Modifying Auto-Generated Code
Problem: Editing Djinni-generated native bindings directly.
Solution: Always modify source files, never generated code. Regenerate bindings after source changes.
4. Wrong Build System
Problem: Using npm/yarn scripts, webpack, or other bundlers.
Solution: Valdi uses Bazel exclusively. Use valdi CLI commands.
5. 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 patternsreferences/bazel-configuration.md- Detailed Bazel setupreferences/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.