243 lines
9.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# tb-harmony-modern-ts
![](./img/toonboom.png) ![](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white) ![](https://img.shields.io/badge/Webpack-8DD6F9?style=for-the-badge&logo=Webpack&logoColor=white) ![](https://img.shields.io/badge/Babel-F9DC3E?style=for-the-badge&logo=babel&logoColor=white)
This repository is a project template that allows you to code in modern TypeScript (ES6) for ToonBoom Harmony.
## Example script
This script prints all the nodes recursively (following node groups) in the scene using JavaScript generators:
```ts
// src/lib/console.ts
const console = { log: MessageLog.trace };
```
```ts
// src/scripts/generator.ts
import console from "../lib/console";
function* recursiveIterNodes(n: string): Generator<string> {
yield n;
if (node.isGroup(n)) {
for (let i = 0; i < node.numberOfSubNodes(n); i++) {
yield* recursiveIterNodes(node.subNode(n, i));
}
}
}
function main() {
const nodes = Array.from(recursiveIterNodes(node.root()));
for (const node of nodes) {
console.log(node);
}
}
main();
```
## Why
You can script in two ways for ToonBoom Harmony: either with [Python](https://docs.toonboom.com/help/harmony-24/scripting/pythonmodule/index.html) or [QtScript](https://docs.toonboom.com/help/harmony-24/scripting/script/index.html) (JavaScript).
[QtScript](https://doc.qt.io/archives/qt-5.15/qtscript-index.html) (scripting engine part of the Qt framework) is an old JavaScript dialect that is based on the [ECMAScript 3.0 standard](https://www-archive.mozilla.org/js/language/e262-3.pdf), therefore it lacks features like modules, modern syntax, arrow functions, etc...
Today we have [TypeScript](https://www.typescriptlang.org/) which is a superset of JavaScript with syntax for types, ES modules, async/await. All those features provide better code organization, type checking and robustness.
## Usage
This template was built in a script-centric workflow. All the `.ts` scripts in the `src/scripts` directory are entrypoints and built into the `dist` directory as `.js` bundles.
1. Clone and install the dependencies:
```sh
git clone https://git.autourdeminuit.com/autour_de_minuit/tb-harmony-modern-ts
cd tb-harmony-modern-ts
npm install
```
2. Then create a new script in `src/scripts`:
```sh
touch src/scripts/arrow-function.ts
```
```ts
// src/scripts/arrow-function.ts
const hello = (name: string) => MessageLog.trace(`Hello, ${name}!`);
hello("Alice");
```
3. And build it:
```sh
npm run build # runs webpack
```
Then it's built as an [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) and transpiled into QtScript compatible syntax:
```js
// dist/arrow-function.js
/******/ (function () {
// webpackBootstrap
var hello = function hello(name) {
return MessageLog.trace("Hello, ".concat(name, "!"));
};
hello("Alice");
/******/
})();
```
Then you can run it by starting Harmony with the `TOONBOOM_GLOBAL_SCRIPT_LOCATION` env variable pointing at the `dist` folder where you cloned this repo.
For example you can also run this script from the command line in [batch mode](https://docs.toonboom.com/help/harmony-24/scripting/script/index.html#batch_mode) (in PowerShell):
```powershell
$env:TOONBOOM_GLOBAL_SCRIPT_LOCATION="tb-harmony-modern-ts/dist"
&"path\to\HarmonyPremium.exe" -scene "scene.xstage" -batch -compile "dist/arrow-function.js"
# ...
# Loading script: tb-harmony-modern-ts\dist\arrow-function.js
# Hello, Alice!
```
#### NPM scripts
```sh
npm run build # build all the scripts
npm run dev # build and watch files on save
npm run type-check # TypeScript type check code
npm run lint # lint code with ESLint
```
#### I'm building for Harmony version XX
The type definitions used in TypeScript are specified in the `tsconfig.json` file in the [`compilerOptions.types`](https://www.typescriptlang.org/tsconfig/#types) array.
To include the types for Harmony 24, do the following:
```json
// tsconfig.json
{
"compilerOptions": {
// ...
"types": ["tba-types/Harmony/24"]
}
}
```
`tba-types/Harmony/24` is referring to the path of the types in the [tba-types](https://github.com/bryab/tba-types) package.
> [!TIP]
> You could also use this template to code in StoryboardPro by changing the path to `tba-types/StoryboardPro/24`
### Guides
- [Install NPM packages like Moment.js](./guides/momentjs.md)
## How
Based on the above example, here is a diagram that shows the compilation/transpilation process (simplified):
![](./img/tb_ts_compilation_diagram.png)
### TypeScript and type definitions
We can code in TypeScript but in order to use Harmony's QtScript API, we need type definitions (`.d.ts` files describing the types).
Luckily for us, Bryan Fordney made scripts to generate them: [tba-types](https://github.com/bryab/tba-types) (Typescript definitions for Toon Boom Harmony and Storyboard Pro)
We can now type-check our code with the right classes and members accessible in the global QtScript scope. Those type definitions still have a few quirks but they can be improved through PRs!
### ES6 syntax and polyfill
The JavaScript language specification is moving fast. What if you want to use the newest features but your runtime isn't as recent? Here comes [Babel](https://babeljs.io/), it's a JavaScript compiler that allows us to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.
This is perfect because QtScript is like ES3 and we want to use ES6 features.
A key feature of Babel is that it integrates with [core-js](https://github.com/zloirock/core-js), providing polyfills for modern JavaScript features. For example QtScript don't have `async`/`await` but core-js can inject the [Regenerator runtime](https://www.npmjs.com/package/regenerator-runtime) to make it work.
### Modules and imports
In Harmony's QtScript you can import code from another file in two ways:
#### `include`
```js
include("module.js");
```
Which include the whole script into the current one, therefore polluting the current namespace.
#### `exports`
```js
// function.js
exports.test = function test() {
MessageLog.trace("test");
};
```
```js
// main.js
var module = require("./function.js");
module.test();
```
Which is a [CommonJS](https://nodejs.org/api/modules.html#modules-commonjs-modules) "like" custom implementation from ToonBoom without the same semantics. It's also used to define [ToonBoom packages](https://docs.toonboom.com/help/harmony-24/scripting/extended/index.html#create_package) by exporting a `configure` function from a `configure.js` file.
In today's modern JavaScript, the official standard package format is [ECMAScript modules](https://nodejs.org/api/esm.html#modules-ecmascript-modules) (or ESM/ES6 modules) with `import`/`export`.
### Module bundling
We use WebPack as the [module bundler](https://webpack.js.org/concepts/). This is a common technique to organize and combine many files into a single JavaScript file. It resolves imports and optimize the bundles.
Since ToonBoom Harmony's QtScript module system is not that advanced, it allows us to use ES modules (and CommonJS too) to split code into separate files without having to manually import files.
> [!TIP]
> It means that we can import NPM packages and run them inside Harmony! Not all packages are compatible thought since we don't have the same Node/browser environment and APIs. For simple packages like Moment.js, see [this guide](./guides/momentjs.md).
#### QtScript syntax fixes (custom Webpack plugin)
In QtScript, this syntax is not valid:
```js
var obj = { for: 0 }; // Unexpected keyword "for" for literal property name
obj.for = 1; // Same here as property name
```
```js
var re = /[^\s(/]/; // Syntax error, / is not escaped
// The correct syntax is /[^\s(\/]/
```
Most of the time, those expressions are injected from corejs polyfills. In order to patch this, I created a custom [`FindReplacePlugin`](./webpack.config.ts#L15) for Webpack that does simple regex find/replace to fix those syntax errors.
## Known limitations
- Some compiled JS bundle just crash in Harmony, I suspect it's a memory limit for the script context size ?
- You may need to add new rules in the `FindReplacePlugin` to handle special syntax.
- Very few NPM packages works within Harmony because most of the time they require a browser or node environment and the polyfills are not compatible.
## Contributing
This repo is just a template with an opinionated config. If you would like to contribute to the config itself, please open an issue!
If it's related to the TypeScript type definitions, [open an issue](https://github.com/bryab/tba-types/issues/new/choose) on the [tba-types](https://github.com/bryab/tba-types) repo.
## Acknowledgements
- [OpenHarmony](https://cfourney.github.io/OpenHarmony/) - The Toonboom Harmony Open Source DOM Library created by Mathieu Chaptel and Chris Fourney.
This library is like PyMEL but for ToonBoom Harmony and a huge source of knowledge about the details of scripting in Harmony. 🙏
- [tba-types](https://github.com/bryab/tba-types) - Typescript definitions for Toon Boom Harmony and Storyboard Pro created by Bryan Fordney.
These are generated types scrapped from the ToonBoom documentation itself.
- [core-js](https://core-js.io/) - Modular standard library for JavaScript. Includes polyfills for ECMAScript up to 2025, ECMAScript proposals and some cross-platform WHATWG / W3C features.
## License
[MIT](./LICENSE.md)
---
Made with ❤️ at [Autour de Minuit (ADV)](https://blog.autourdeminuit.com/) <img src="https://upload.wikimedia.org/wikipedia/commons/0/0c/Blender_logo_no_text.svg" alt="blender" width="20"/>