Skip to main content

Javascript Runtime Implementation

Roku devices do not natively support JavaScript; instead, they use BrightScript as their primary language. Hosanna is unique in that the Hosanna Compiler and runtime polyfill essential JavaScript features, enabling developers to write code in TypeScript and run it on Roku devices.

The Hosanna Compiler is optimized specifically for BrightScript and structures the generated code in the most efficient way possible for Roku hardware. While many JavaScript features are supported, not everything can be implemented or is advisable for performance reasons. Hosanna includes diagnostics to alert you when you use unsupported features or code patterns that may perform poorly. In most cases, it will also suggest suitable workarounds to help you adapt your code for the Roku environment.

Hosanna Compiler Diagnostics

The Hosanna compiler (hsc) outputs diagnostics to the terminal during the build process. These diagnostics help identify unsupported features, potential performance issues, or code that may not behave as expected on Roku devices. When using VSCode, the provided tasks and launch commands are configured to inject these diagnostics and display them directly in the IDE for a streamlined development experience.

Filtering Diagnostics

You can filter out specific diagnostics in your hsconfig.json file using their diagnostic codes. For example:

"diagnosticFilters": [
"HS-1038",
"HS-1058"
]

Additionally, you can suppress diagnostics for a specific line in your code using a comment, similar to TypeScript:

// hs:disable-next-line
someUnsupportedOperation();

// hs:disable-next-line HS-1038 HS-1058
anotherOperation();

This allows you to either disable all diagnostics for the next line or target specific diagnostic codes.

Advice on Diagnostics

We strongly recommend reviewing all warning diagnostics. The best way to do this is to use the source map feature to view the generated BrightScript (.brs) code side by side with your original code. This helps you determine if a diagnostic can be safely ignored or if your code has been rewritten for better performance or accuracy. Note that some diagnostics may be silent, and some code may be automatically transformed to improve compatibility or efficiency.

Conditional Compilation

hsconfig.json can define build-time flags:

{
"buildFlags": {
"DEV": true,
"ROKU": true,
"WEB": false
}
}

Flags are referenced from source as double-underscore identifiers:

declare const __DEV__: boolean;
declare const __ROKU__: boolean;
declare const __WEB__: boolean;

if (__DEV__ && __ROKU__) {
console.info('Roku development path');
}

The Hosanna Compiler evaluates flag-only conditions and removes unreachable branches. Supported flag expressions include &&, ||, !, and parentheses.

Important constraints:

  • DEV maps to __DEV__, ROKU maps to __ROKU__, and so on.
  • __ROKU__ defaults from the source generation platform when no explicit flag is supplied.
  • __PROD__ defaults to the opposite of DEV when DEV is set.
  • Do not mix build flags with runtime values in the same condition.
  • Do not use else or elseif on flag-only conditional compilation blocks.
// Supported
if (__ROKU__ && !__DEV__) {
useDeviceOnlyPath();
}

// Not supported: mixed build-time and runtime values
if (__ROKU__ && user.isSignedIn) {
startPlayback();
}

// Not supported: else with flag-only conditional compilation
if (__WEB__) {
startWebPath();
} else {
startRokuPath();
}

Hosanna Compiler Directives

Use current hs:* comment directives:

// hs:no-module

// hs:exclude-from-platform roku

// hs:disable-next-line HS-1038

hs:exclude-from-platform roku can exclude a whole file when placed in the comment-only prefix before executable code. It can also exclude the next node or line when placed immediately before code.

Use hs_native_roku only for small BrightScript snippets:

hs_native_roku(`
print "Roku-only code"
`);

Interop and tasks

Hosanna's JS runtime interoperates with Roku SceneGraph tasks and nodes. When passing data or behavior across boundaries, avoid capturing closures or complex references that cannot be serialized.

Passing function references across node/task boundaries

Use an AsyncFunctionPointer to pass function references across async boundaries. Functions are automatically serialized as string pointers and resolved when invoked, allowing safe execution across node/task boundaries.

Critical: Must Be Exported Functions

AsyncFunctionPointer cannot be an inline or anonymous function — it must be an exported function. The function must be defined at module scope and exported so it can be serialized as a string pointer and resolved across boundaries.

Where AsyncFunctionPointer Is Used

The most common use of AsyncFunctionPointer is in HsFetchOptions (for example, postProcessFunction and transformFunction on HsFetch). These callbacks are passed into Roku Tasks or other background threads, so they must be exported functions that can be serialized and resolved safely.

// Correct: use an exported function
import { processRows } from './transformFunctions';

this.dispatch(Http.Get, {
url: 'https://api.example.com/data',
transformFunction: processRows // Exported function reference
});

// Incorrect: inline arrow functions are not supported
this.dispatch(Http.Post, {
url: 'https://api.example.com/submit',
postProcessFunction: (response) => {
console.info('Response received:', response);
}
});

Correct pattern: Define and export your function in a separate module:

// transformFunctions.ts
export function processRows(response: IHsFetchResponse) {
console.info('Response received:', response);
// ... processing logic
}

Type definitions:

// hosanna-api
declare type AsyncFunctionPointer = (...args: any[]) => any;
declare function hs_resolveAsyncFunctionPointer(ptr: AsyncFunctionPointer): Function;
How Serialization Works

When you pass a function as an AsyncFunctionPointer, the Hosanna Compiler automatically serializes it as a string pointer. The runtime resolves it using hs_resolveAsyncFunctionPointer() before execution in the background context. This avoids closure capture issues and enables safe cross-boundary execution.

Resolving Function Pointers

The hs_resolveAsyncFunctionPointer() function converts a serialized function pointer back into a callable function:

// In your globals.d.ts or type definitions
declare function hs_resolveAsyncFunctionPointer(ptr: AsyncFunctionPointer): Function;

// Example usage (typically handled automatically by the framework)
// Correct: export the function first
export function myFunc(data: any) {
console.info('Processed:', data);
}

const ptr: AsyncFunctionPointer = myFunc;
// ... after serialization/deserialization ...
const resolved = hs_resolveAsyncFunctionPointer(ptr);
resolved({ result: 'success' }); // Calls myFunc with the data
Important Constraints
  • Must be exported functions: AsyncFunctionPointer cannot be inline or anonymous functions — they must be exported functions defined at module scope.
  • Parameter limit: Functions support up to 5 parameters maximum. Functions with more than 5 parameters will throw an error when resolved and called.
  • Module scope: Functions must be accessible from module scope (not nested functions, closures, or inline arrow functions).
  • Serialization: Functions are serialized as string pointers, so closures and this context are not captured directly.
  • Type safety: TypeScript will type-check the function signature, but runtime validation is limited.
  • Linter diagnostics: Hosanna's linter reports diagnostics if you pass an inline function or use .bind(this) where an AsyncFunctionPointer is expected (such as HsFetchOptions.postProcessFunction and transformFunction).

This is especially useful when you need to process results in a background task without blocking the main thread, or when passing post-processing functions to HTTP commands.

roLongInteger (BrightScript Long Integer)

Roku BrightScript distinguishes 32-bit integers from 64-bit long integers using a type designator on variables and literals. Long integers use the & suffix in BrightScript (e.g., x&).

Hosanna exposes a branded numeric type that lets the Hosanna Compiler emit the correct BrightScript long-integer form:

// sg-api
export type roLongInteger = number & { __brand: 'roLongInteger' };
  • When a value is typed or cast as roLongInteger, the Hosanna Compiler appends & in the generated BrightScript for that symbol and its assignments/uses (constants, object/class fields, params, arrays, and records).
  • Regular number values remain unchanged and do not get &.
const a: roLongInteger = 1234567890123456 as roLongInteger;
const list: roLongInteger[] = [50 as roLongInteger];
function double(x: roLongInteger): roLongInteger { return (x * 2) as roLongInteger }
Not Automatic

BrightScript requires the & suffix for long integers. TypeScript number does not carry that information, so Hosanna cannot infer it automatically. You must annotate or cast to roLongInteger where long integer semantics are required (typically for values > 2,147,483,647).