|
| 1 | +# Platform Isolation |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This project supports multiple runtime platforms (Browser, Node.js, React Native, and Universal), with separate entry points for each. To ensure the build artifacts work correctly, platform-specific code must not be mixed. |
| 6 | + |
| 7 | +## Platform Declaration |
| 8 | + |
| 9 | +**Every non-test source file MUST export a `__platforms` array** to declare which platforms it supports. This is enforced by ESLint and validated at build time. |
| 10 | + |
| 11 | +### Export Declaration (Required) |
| 12 | + |
| 13 | +All files must include a `__platforms` export: |
| 14 | + |
| 15 | +**For universal files (all platforms):** |
| 16 | +```typescript |
| 17 | +export const __platforms = ['__universal__']; |
| 18 | +``` |
| 19 | + |
| 20 | +**For platform-specific files:** |
| 21 | +```typescript |
| 22 | +export const __platforms = ['browser']; // Browser only |
| 23 | +export const __platforms = ['node']; // Node.js only |
| 24 | +export const __platforms = ['react_native']; // React Native only |
| 25 | +``` |
| 26 | + |
| 27 | +**For multi-platform files:** |
| 28 | + |
| 29 | +```typescript |
| 30 | +// lib/utils/web-features.ts |
| 31 | +export const __platforms = ['browser', 'react_native']; |
| 32 | + |
| 33 | +// Your code that works on both browser and react_native |
| 34 | +export function makeHttpRequest() { |
| 35 | + // Implementation that works on both platforms |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +Valid platform identifiers: `'browser'`, `'node'`, `'react_native'`, `'__universal__'` |
| 40 | + |
| 41 | +**Important**: Only files that explicitly include `'__universal__'` in their `__platforms` array are considered universal. Files that list all concrete platforms (e.g., `['browser', 'node', 'react_native']`) are treated as multi-platform files, NOT universal files. They must still ensure imports support all their declared platforms. |
| 42 | + |
| 43 | +### File Naming Convention (Optional) |
| 44 | + |
| 45 | +While not enforced, you should use file name suffixes for clarity: |
| 46 | +- `.browser.ts` - Typically browser-specific |
| 47 | +- `.node.ts` - Typically Node.js-specific |
| 48 | +- `.react_native.ts` - Typically React Native-specific |
| 49 | +- `.ts` (no suffix) - Typically universal |
| 50 | + |
| 51 | +**Note:** The validator currently enforces only the `__platforms` export declaration. File naming is informational and not validated. The `__platforms` export is the source of truth. |
| 52 | + |
| 53 | +## Import Rules |
| 54 | + |
| 55 | +Each platform-specific file can **only** import from: |
| 56 | + |
| 57 | +1. **Universal files** (no platform restrictions) |
| 58 | +2. **Compatible platform files** (files that support ALL the required platforms) |
| 59 | +3. **External packages** (node_modules) |
| 60 | + |
| 61 | +A file is compatible if: |
| 62 | +- It's universal (no platform restrictions) |
| 63 | +- For single-platform files: The import supports at least that platform |
| 64 | +- For multi-platform files: The import supports ALL of those platforms |
| 65 | + |
| 66 | +### Compatibility Examples |
| 67 | + |
| 68 | +**Core Principle**: When file A imports file B, file B must support ALL platforms that file A runs on. |
| 69 | + |
| 70 | +**Universal File (`__platforms = ['__universal__']`)** |
| 71 | +- ✅ Can import from: universal files (with `__universal__`) |
| 72 | +- ❌ Cannot import from: any platform-specific files, even `['browser', 'node', 'react_native']` |
| 73 | +- **Why**: Universal files run everywhere, so all imports must explicitly be universal |
| 74 | +- **Note**: Listing all platforms like `['browser', 'node', 'react_native']` is NOT considered universal |
| 75 | + |
| 76 | +**Single Platform File (`__platforms = ['browser']`)** |
| 77 | +- ✅ Can import from: universal files, files with `['browser']`, multi-platform files that include browser like `['browser', 'react_native']` |
| 78 | +- ❌ Cannot import from: files without browser support like `['node']` or `['react_native']` only |
| 79 | +- **Why**: The import must support the browser platform |
| 80 | + |
| 81 | +**Multi-Platform File (`__platforms = ['browser', 'react_native']`)** |
| 82 | +- ✅ Can import from: universal files, files with `['browser', 'react_native']`, supersets like `['browser', 'node', 'react_native']` |
| 83 | +- ❌ Cannot import from: files missing any platform like `['browser']` only or `['node']` |
| 84 | +- **Why**: The import must support BOTH browser AND react_native |
| 85 | + |
| 86 | +**All-Platforms File (`__platforms = ['browser', 'node', 'react_native']`)** |
| 87 | +- ✅ Can import from: universal files, files with exactly `['browser', 'node', 'react_native']` |
| 88 | +- ❌ Cannot import from: any subset like `['browser']`, `['browser', 'react_native']`, etc. |
| 89 | +- **Why**: This is NOT considered universal - imports must support all three platforms |
| 90 | +- **Note**: If your code truly works everywhere, use `['__universal__']` instead |
| 91 | + |
| 92 | +### Examples |
| 93 | + |
| 94 | +✅ **Valid Imports** |
| 95 | + |
| 96 | +```typescript |
| 97 | +// In lib/index.browser.ts (Browser platform only) |
| 98 | +import { Config } from './shared_types'; // ✅ Universal file |
| 99 | +import { BrowserRequestHandler } from './utils/http_request_handler/request_handler.browser'; // ✅ browser + react_native (supports browser) |
| 100 | +import { uuid } from 'uuid'; // ✅ External package |
| 101 | +``` |
| 102 | + |
| 103 | +```typescript |
| 104 | +// In lib/index.node.ts (Node platform only) |
| 105 | +import { Config } from './shared_types'; // ✅ Universal file |
| 106 | +import { NodeRequestHandler } from './utils/http_request_handler/request_handler.node'; // ✅ Same platform |
| 107 | +``` |
| 108 | + |
| 109 | +```typescript |
| 110 | +// In lib/vuid/vuid_manager_factory.react_native.ts (React Native platform only) |
| 111 | +import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; // ✅ Compatible (react_native only) |
| 112 | +``` |
| 113 | + |
| 114 | + |
| 115 | + |
| 116 | +```typescript |
| 117 | +// In lib/event_processor/event_processor_factory.browser.ts (Browser platform only) |
| 118 | +import { Config } from '../shared_types'; // ✅ Universal file |
| 119 | +import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; // ✅ Compatible (browser + react_native, includes browser) |
| 120 | +``` |
| 121 | + |
| 122 | +```typescript |
| 123 | +// In lib/event_processor/event_dispatcher/default_dispatcher.browser.ts (Multi-platform: browser + react_native) |
| 124 | +import { Config } from '../../shared_types'; // ✅ Universal file |
| 125 | +import { BrowserRequestHandler } from '../../utils/http_request_handler/request_handler.browser'; // ✅ Compatible (also browser + react_native) |
| 126 | +``` |
| 127 | + |
| 128 | +❌ **Invalid Imports** |
| 129 | + |
| 130 | +```typescript |
| 131 | +// In lib/index.browser.ts (Browser platform only) |
| 132 | +import { NodeRequestHandler } from './utils/http_request_handler/request_handler.node'; // ❌ Node-only file |
| 133 | +``` |
| 134 | + |
| 135 | +```typescript |
| 136 | +// In lib/index.node.ts (Node platform only) |
| 137 | +import { BrowserRequestHandler } from './utils/http_request_handler/request_handler.browser'; // ❌ browser + react_native, doesn't support node |
| 138 | +``` |
| 139 | + |
| 140 | +```typescript |
| 141 | +// In lib/shared_types.ts (Universal file) |
| 142 | +import { AsyncStorageCache } from './utils/cache/async_storage_cache.react_native'; // ❌ React Native only, universal file needs imports that work everywhere |
| 143 | +``` |
| 144 | + |
| 145 | +```typescript |
| 146 | +// In lib/event_processor/event_dispatcher/default_dispatcher.browser.ts |
| 147 | +import { NodeRequestHandler } from '../../utils/http_request_handler/request_handler.node'; // ❌ Node-only, doesn't support browser or react_native |
| 148 | +// This file needs imports that work in BOTH browser AND react_native |
| 149 | +``` |
| 150 | + |
| 151 | +## Automatic Validation |
| 152 | + |
| 153 | +Platform isolation is enforced automatically during the build process. |
| 154 | + |
| 155 | +### Running Validation |
| 156 | + |
| 157 | +```bash |
| 158 | +# Run validation manually |
| 159 | +npm run validate-platform-isolation |
| 160 | + |
| 161 | +# Validation runs automatically before build |
| 162 | +npm run build |
| 163 | +``` |
| 164 | + |
| 165 | +### How It Works |
| 166 | + |
| 167 | +The validation script (`scripts/validate-platform-isolation.js`): |
| 168 | + |
| 169 | +1. Scans all TypeScript/JavaScript files configured in the in the `.platform-isolation.config.js` config file. |
| 170 | +2. **Verifies every file has a `__platforms` export** - fails immediately if any file is missing this |
| 171 | +3. **Validates all platform values** - ensures values in `__platforms` arrays are valid (read from Platform type) |
| 172 | +4. Parses import statements using TypeScript AST (ES6 imports, require, dynamic imports) |
| 173 | +5. **Checks import compatibility**: For each import, verifies that the imported file supports ALL platforms that the importing file runs on |
| 174 | +6. Fails the build if violations are found or if any file lacks `__platforms` export |
| 175 | + |
| 176 | +**ESLint Integration:** The `require-platform-declaration` ESLint rule also enforces the `__platforms` export requirement during development. |
| 177 | + |
| 178 | +### Build Integration |
| 179 | + |
| 180 | +The validation is integrated into the build process: |
| 181 | + |
| 182 | +```json |
| 183 | +{ |
| 184 | + "scripts": { |
| 185 | + "build": "npm run validate-platform-isolation && tsc --noEmit && ..." |
| 186 | + } |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +If platform isolation is violated, the build will fail with a detailed error message showing: |
| 191 | +- Which files have violations |
| 192 | +- The line numbers of problematic imports |
| 193 | +- What platform the file belongs to |
| 194 | +- What platform it's incorrectly importing from |
| 195 | + |
| 196 | + |
| 197 | + |
| 198 | +## Creating New Modules |
| 199 | + |
| 200 | +### Universal Code |
| 201 | + |
| 202 | +For code that works across all platforms, use `['__universal__']`: |
| 203 | + |
| 204 | +**Example: Universal utility function** |
| 205 | + |
| 206 | +```typescript |
| 207 | +// lib/utils/string-helpers.ts |
| 208 | +export const __platforms = ['__universal__']; |
| 209 | + |
| 210 | +// Pure JavaScript that works everywhere |
| 211 | +export function capitalize(str: string): string { |
| 212 | + return str.charAt(0).toUpperCase() + str.slice(1); |
| 213 | +} |
| 214 | + |
| 215 | +export function sanitizeKey(key: string): string { |
| 216 | + return key.replace(/[^a-zA-Z0-9_]/g, '_'); |
| 217 | +} |
| 218 | +``` |
| 219 | + |
| 220 | +### Platform-Specific Code |
| 221 | + |
| 222 | +**Single Platform** |
| 223 | + |
| 224 | +1. **Add `__platforms` export** declaring the platform (e.g., `export const __platforms = ['browser'];`) |
| 225 | +2. Name the file with a platform suffix for clarity (e.g., `my-feature.browser.ts`) |
| 226 | +3. Only import from universal or compatible platform files |
| 227 | + |
| 228 | +**Example:** |
| 229 | + |
| 230 | +```typescript |
| 231 | +// lib/features/my-feature.ts (universal interface) |
| 232 | +export const __platforms = ['__universal__']; |
| 233 | + |
| 234 | +export interface MyFeature { |
| 235 | + doSomething(): void; |
| 236 | +} |
| 237 | + |
| 238 | +// lib/features/my-feature.browser.ts |
| 239 | +export const __platforms = ['browser']; |
| 240 | + |
| 241 | +export class BrowserMyFeature implements MyFeature { |
| 242 | + doSomething(): void { |
| 243 | + // Browser-specific implementation |
| 244 | + } |
| 245 | +} |
| 246 | + |
| 247 | +// lib/features/my-feature.node.ts |
| 248 | +export const __platforms = ['node']; |
| 249 | + |
| 250 | +export class NodeMyFeature implements MyFeature { |
| 251 | + doSomething(): void { |
| 252 | + // Node.js-specific implementation |
| 253 | + } |
| 254 | +} |
| 255 | +``` |
| 256 | + |
| 257 | +**Multiple Platforms (But Not Universal)** |
| 258 | + |
| 259 | +For code that works on multiple platforms but is not universal, use the `__platforms` export to declare the list of supported platforms: |
| 260 | + |
| 261 | +**Example: Browser + React Native only** |
| 262 | + |
| 263 | +```typescript |
| 264 | +// lib/utils/http-helpers.ts |
| 265 | +export const __platforms = ['browser', 'react_native']; |
| 266 | + |
| 267 | +// This code works on both browser and react_native, but not node |
| 268 | +export function makeRequest(url: string): Promise<string> { |
| 269 | + // XMLHttpRequest is available in both browser and react_native |
| 270 | + return new Promise((resolve, reject) => { |
| 271 | + const xhr = new XMLHttpRequest(); |
| 272 | + xhr.open('GET', url); |
| 273 | + xhr.onload = () => resolve(xhr.responseText); |
| 274 | + xhr.onerror = () => reject(new Error('Request failed')); |
| 275 | + xhr.send(); |
| 276 | + }); |
| 277 | +} |
| 278 | +``` |
| 279 | + |
| 280 | +## Benefits |
| 281 | + |
| 282 | +- ✅ Prevents runtime errors from platform-incompatible code |
| 283 | +- ✅ Catches issues at build time, not in production |
| 284 | +- ✅ Makes platform boundaries explicit and maintainable |
| 285 | +- ✅ Ensures each bundle only includes relevant code |
0 commit comments