Code.Movie
Changelog
v 0.0.32
Improvement: decoration coordinates are now compared to the document (the documents length and/or number of lines) and rejected if too large
Improvement: improve animation heuristics for ECMAScript
Improvement: fully support @scope in CSS
v 0.0.31
Feature: add minCols option to animateHTML() and toAnimationHTML(), which sets a minimum width for animations
Improvement: support range syntax in CSS media queries
Improvement: improve general animation heuristics
Bugfix: do not throw away trailing newlines
v 0.0.30
Feature: optional comments in JSON! Set the option comments to true when instantiating the JSON language module to unlock support for C-style line and block comments:
import { animateHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
// "comments" is optional and defaults to "false"
let rfc8259 = json();
let normalJSON = animateHTML([{ code: "[42]" }], {
tabSize: 2,
language: rfc8259,
});
// unlocks line and block comments
let jsonWithComments = json({ comments: true });
let withComments = animateHTML([{ code: "[42 /* Hello */ ] // World" }], {
tabSize: 2,
language: jsonWithComments,
});123456789101112131415161718import { animateHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
// "comments" is optional and defaults to "false"
let rfc8259 = json();
let normalJSON = animateHTML([{ code: "[42]" }], {
tabSize: 2,
language: rfc8259,
});
// unlocks line and block comments
let jsonWithComments = json({ comments: true });
let withComments = animateHTML([{ code: "[42 /* Hello */ ] // World" }], {
tabSize: 2,
language: jsonWithComments,
});Bugfix: protect against problems that arose when certain over-enthusiastic build tools optimized (arguably redundant) SVG attributes away, leading to some CSS rules not longer matching where they should.
Bugfix: prevent word wrapping in selectable text under more circumstances.
v 0.0.29
Feature: conditional themes. This feature enables Code.Movie to generate CSS from themes with an attached condition, such as a media query or a parent selector. To use this feature, pass an array of themes with conditions to animateHTML() or toAnimationHTML() in place of a single theme object:
import {
toAnimationHTML,
monokaiLight,
monokaiDark,
} from "@codemovie/code-movie";
// Example: use dark or light theme dependent on a media query
const html1 = toAnimationHTML(sceneData, {
theme: [
{ theme: monokaiLight, condition: "@media (prefers-color-scheme: light)" },
{ theme: monokaiDark, condition: "@media (prefers-color-scheme: dark)" },
],
});
// Example: use dark or light theme dependent on a parent class
const html2 = toAnimationHTML(sceneData, {
theme: [
{ theme: monokaiLight, condition: ":root.isLight" },
{ theme: monokaiDark, condition: ":root.isDark" },
],
});123456789101112131415161718192021import {
toAnimationHTML,
monokaiLight,
monokaiDark,
} from "@codemovie/code-movie";
// Example: use dark or light theme dependent on a media query
const html1 = toAnimationHTML(sceneData, {
theme: [
{ theme: monokaiLight, condition: "@media (prefers-color-scheme: light)" },
{ theme: monokaiDark, condition: "@media (prefers-color-scheme: dark)" },
],
});
// Example: use dark or light theme dependent on a parent class
const html2 = toAnimationHTML(sceneData, {
theme: [
{ theme: monokaiLight, condition: ":root.isLight" },
{ theme: monokaiDark, condition: ":root.isDark" },
],
});Improvement: better animation heuristics, especially for CSS and several ECMAScript features.
Bugfix: properly type the instanceof keyword in ECMAScript as well as async method definitions and setters in ECMAScript object literals.
Bugfix: fix --ui-status-bar-enabled not working in Monokai Dark theme
v 0.0.28
Bugfix: repair event more CSS regressions. Specifically, exclude svg elements from the css reset: This should make decoration underlines work again.
v 0.0.27
Bugfix: repair a few more CSS regressions like in 0.0.26.
v 0.0.26
Bugfix: repair a few CSS regressions that occurred due to the introduction of the style isolation improvements in 0.0.25. Specifically, the CSS reset had unintended side effects when global styles did not use certain defaults like text-align: center.
v 0.0.25
Feature: custom style isolation. All generated CSS is scoped to to animation element it was generated for, but this does not stop third-party styles from messing with the generated CSS. This is, in part at least, by design - users should be able to override any and all styles that Code.Movie generates! But the default approach to style isolation, which consisted of nothing more than prefixing all selectors with an auto-generated class, turned out to be insufficient for real-world use cases. This can be dealt with thanks to the new output option wrapCSS, which enables custom style isolation. The default approach is still to wrap everything in a class, but you can modify this by supplying custom wrapper strings:
import { animateHTML } from "@codemovie/code-movie";
import { json } from "@codemovie/code-movie/language/json";
const html = animateHTML(arrayOfFrames, {
language: json(),
tabSize: 2,
// Use the element's id in an actual ID selector to beef up the specificity.
// You could also make use of @layer, @scope, or simple make the selector even
// more specific to fit your needs!
wrapCSS: (id, content) => `#${id}{${content}}`,
});1234567891011import { animateHTML } from "@codemovie/code-movie";
import { json } from "@codemovie/code-movie/language/json";
const html = animateHTML(arrayOfFrames, {
language: json(),
tabSize: 2,
// Use the element's id in an actual ID selector to beef up the specificity.
// You could also make use of @layer, @scope, or simple make the selector even
// more specific to fit your needs!
wrapCSS: (id, content) => `#${id}{${content}}`,
});The auto-generated CSS now also contains a very basic CSS reset that applies all: unset to all elements inside the animation. This, together with the new wrapCSS option makes sure that you can precisely pick the right amount of CSS isolation for your animations.
v 0.0.24
Bugfix: text content in <textarea> elements in HTML now gets the same treatment as other text content received in 0.0.23. Entity and character references are now also categorized as distinct types, as they should always have been.
v 0.0.23
Bugfix: text content in HTML should always have been chopped up into word-level tokens, but this was apparently never implemented. This is fixed in 0.0.23.
v 0.0.22
Feature: Monokai Dark and Monokai Light themes
This release ships with two additional built-in themes. Both Monokai Dark and Monokai Light are quite different from the default theme (which is still the default) in that they use several new features to render quite a bit more than just text:

The themes themselves can also be customized with CSS variables - read the docs for more details!
Feature: styling customization upgrades
The two new themes would not be possible without a massive upgrade to the options for styling animations. If you wanted to render anything interesting in your animations before, you had to use the CSS pseudo classes ::before and ::after on the root element. This not only required you to be a true CSS wizard, but also limited you to at most two elements to play with. As of version 0.0.22, the HTML output of every animation gets 10 additional <div> elements named cm-box0, cm-box1 ... cm-box9 that exist purely as hooks for style customization. You (or your friendly CSS wizard neighbor) can use each box to draw a new element, such as a editor window with borders and drop shadows, and decorative elements for the editor window (like title and status bars, or a different background and/or border for the gutter). The new themes are good examples of what's possible.
Also there's several new CSS hooks: --total-frames gets you the number of frames in the animation, while --current-frame reflects the current frame number (starting at 1). In case you intend to render the animation state via the string-only content property, --total-frames-string and --current-frame-string are also available. --has-gutter-decorations indicates whether the animation contains any gutter decorations, which may warrant resizing the line number column. Similarly, --has-line-numbers indicated whether the animation's style settings result in line numbers being enabled. All this, taken together, makes the two new themes in this release possible.
And yes, the implementation of this "feature" consists of little more than adding 10 divs with hard-coded class names to the output HTML. This actually gives you 32 boxes to play with, because you get to use the divs themselves, their ::before and ::after pseudo elements and ::before and ::after on the animation element itself. And 32 boxes ought to be enough for anybody.
Bugfixes and other minor changes in 0.0.22
- New CSS variable
--cm-animate-heightcan control whether the animation's container element animates its actual height - Measure flag and heart emoji correctly to fix some potential layout issues. Fixing this while keeping performance in check turned out to be non-trivial (with emoji flags themselves being non-trivial, as is the heart emoji). Emoji flags and hearts should be handled correctly in 0.0.22 with improved, rather than degraded performance.
- Make start and end of strings as well as component tags in JSX syntax map to the correct theme colors.
- Make the dimensions calculated for metadata properly take wide characters into account
- Fix a very specific off-by-one error that lead to invalid sizing for text decorations that ran to the end of the second-to-last line, making the affected decorations invisible.
- Ensure that decorations and ranges always get cloned on input, making them reusable.
- Improve support enums, const assertions and type guard signatures in TypeScript, add support for the
implementskeyword and const generics.
v 0.0.21
Feature: Decorations
Decorations are finally available! You can add highlights, gutter icons and underlines (and much more once you get creative with CSS) to your animations.
Breaking: auto-incrementing root class seed
This library auto-generates class names for the animations based on a seed value. This seed value can be changed via the option rootClassSeed when calling animateHTML() or toAnimationHTML(), but this was (and remains) optional:
import { animateHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
let html = animateHTML(
[
/* keyframes */
],
{
tabSize: 2,
language: json(),
rootClassSeed: 42, // optional
},
);12345678910111213import { animateHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
let html = animateHTML(
[
/* keyframes */
],
{
tabSize: 2,
language: json(),
rootClassSeed: 42, // optional
},
);This seed value used to default to 0, which meant that (unless a custom seed value was provided) two animations in a single page would probably suffer from class collisions. This default value arguably did more harm than good, so 0.0.21 changes it to an auto-incrementing number, starting with a random large integer. If you rely on reproducing the same class names each time you call animateHTML() or toAnimationHTML(), be sure to pass the same number as the rootClassSeed option.
Bugfixes in 0.0.21
- General
- make
fromStringsToSceneandanimateHTMLhandle empty frame arrays without throwing cryptic errors - prevent unintended line breaks in selectable text due to rounding errors in browsers
- make
- ECMAScript
- add better heuristics when dealing with empty blocks and function expressions
- fix a few bugs around TypeScript interfaces, support
usingin TypeScript
v 0.0.20
Feature: animateHTML(input, options)
This new function combines fromStringsToScene() and toAnimationHTML() into one slightly more convenient function. Where you previously had to wrangle two functions and two options objects...
import { fromStringsToScene, toAnimationHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
let keyframes = [
/* keyframes go here */
];
let html = toAnimationHTML(
fromStringsToScene(keyframes, {
tabSize: 2,
language: json(),
}),
{
rootClassSeed: 42,
},
);12345678910111213141516import { fromStringsToScene, toAnimationHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
let keyframes = [
/* keyframes go here */
];
let html = toAnimationHTML(
fromStringsToScene(keyframes, {
tabSize: 2,
language: json(),
}),
{
rootClassSeed: 42,
},
);You can now just import and call a single function to get the same effect:
import { animateHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
let keyframes = [
/* keyframes go here */
];
let html = animateHTML(keyframes, {
tabSize: 2,
language: json(),
rootClassSeed: 42,
});123456789101112import { animateHTML } from "@codemovie/code-movie";
import json from "@codemovie/code-movie/languages/json";
let keyframes = [
/* keyframes go here */
];
let html = animateHTML(keyframes, {
tabSize: 2,
language: json(),
rootClassSeed: 42,
});The options object combines the options available to fromStringsToScene() and toAnimationHTML() into a single object. The old functions fromStringsToScene() and toAnimationHTML() are still available and will remain supported for the forseeable future.
Bugfixes in 0.0.20
- ECMAScript: better highlighting support for methods, getters and getters in object literals
- ECMAScript: fix highlighting support for
whileloops andsuper
v 0.0.19
Bugfixes in 0.0.19
- General: reduce download size of the npm module
- CSS: add proper highlighting support for @-rules
v 0.0.18
Breaking: theme object changes
The theme object has always contained some unused data for decorations (code underlines, gutter icons an similar). In preparation for the upcoming release of actual decoration support, the schema for this data has changed. If you have created your own theme object previously, you will need to perform the following modifications:
- set
versionto1 - replace
layers.decorationswith the following:
Show the new content of layers.decorations
let myTheme = {
version: 1,
/* ... other things... */
layers: {
/* ... other things... */
decorations: {
gutter: {
kind: "DECORATION_GUTTER",
offset: [0, 0],
},
line: {
kind: "DECORATION_LINE",
background: {
endLeft: "scene",
endRight: "scene",
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#FFF2BC",
},
},
},
foreground: {
endLeft: "scene",
endRight: "scene",
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#00000000",
},
},
},
},
text: {
kind: "DECORATION_TEXT",
background: {
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#FFFF00",
},
},
underline: {
kind: "UNDERLINE",
offsetY: 0,
style: "none",
color: {
kind: "COLOR",
value: "#000000",
},
scale: 1,
width: 1,
},
},
foreground: {
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#00000000",
},
},
underline: {
kind: "UNDERLINE",
offsetY: 0,
style: "none",
color: {
kind: "COLOR",
value: "#000000",
},
scale: 1,
width: 1,
},
},
},
},
/* ... other things... */
},
};12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182let myTheme = {
version: 1,
/* ... other things... */
layers: {
/* ... other things... */
decorations: {
gutter: {
kind: "DECORATION_GUTTER",
offset: [0, 0],
},
line: {
kind: "DECORATION_LINE",
background: {
endLeft: "scene",
endRight: "scene",
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#FFF2BC",
},
},
},
foreground: {
endLeft: "scene",
endRight: "scene",
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#00000000",
},
},
},
},
text: {
kind: "DECORATION_TEXT",
background: {
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#FFFF00",
},
},
underline: {
kind: "UNDERLINE",
offsetY: 0,
style: "none",
color: {
kind: "COLOR",
value: "#000000",
},
scale: 1,
width: 1,
},
},
foreground: {
backdrop: {
kind: "BACKDROP",
value: {
kind: "COLOR",
value: "#00000000",
},
},
underline: {
kind: "UNDERLINE",
offsetY: 0,
style: "none",
color: {
kind: "COLOR",
value: "#000000",
},
scale: 1,
width: 1,
},
},
},
},
/* ... other things... */
},
};Other improvements in 0.0.18
- A new styling hook
--line-numbers-widthcontains the width of the line number column (if line numbers are enabled) - Fix a whole class of bugs related to coordinate computation when astral symbols, emoji and CJK characters are involved
- Document typographic edge cases
Bugfixes in 0.0.18
- ECMAScript: add proper highlighting support for generator functions and associated features
- ECMAScript: add proper highlighting support for the
typeofoperator, the?.operator theaskeyword (in both TS and JS contexts) - ECMAScript: fix several omissions around operators and literals like
Infinity
v 0.0.17
New language: Rust
Support for Rust has landed! Simply import the language module @codemovie code-movie/languages/rust and go from there. Rust is a complex language that I personally know next to nothing about, so you can expect this first version to maybe miss some syntax and definitely have some sub-par animation heuristics. You can also expect this to improve over time.
Improvement: Code Splitting
Languages modules now benefit from code splitting, which removes a lot of previously duplicated code. This only applies to ESM modules but should greatly reduce the overall bundle size for all projects. All language modules that leverage the common parsing infrastructure or that make use of other languages modules (like JS and CSS being used in HTML to support <style> and <script>) will benefit from this change.
Bugfixes in 0.0.17
- ECMAScript: add support the ancient and deprecated
withstatement
Other changes in 0.0.17
- Playground: add a way to change the tab size
- Elixir: improve diffing heuristics
v 0.0.16
Bugfixes in 0.0.16
- General: most language parsers crashed when encountering certain non-terminated string-like language constructs
- CSS: the value set by
--cm-content-margin-leftwas erroneously always multiplied by two - TypeScript: add support for the
declarekeyword and associated syntax, like namespaces - ECMAScript: add proper support for regular expressions and the
accessorkeyword - Docs: fixed various typos and missing words, add missing docs for Elixir
- Work around chrome's inability to deal with long selector lists
v 0.0.15
Breaking: language modules
Adding support for Elixir in 0.0.13 increased the bundle size considerably, which made modularizing the entire project necessary. Languages are now modules that need to be imported and instantiated explicitly:
// Main module imports like before
import { fromStringsToScene, toAnimationHTML } from "@codemovie/code-movie";
// NEW: import the language module you want to use
import json from "@codemovie/code-movie/languages/json";
// NEW: instantiate the language. Some languages can be configured to support
// certain features by passing options to the language function (eg. enable JSX
// support in ECMAScript by passing `{ jsx: true }`)
const language = json();
let sceneData = fromStringsToScene(
[
/* ...keyframes... */
],
{
tabSize: 2,
language, // NEW: now the language object instead of a string
},
);
let html = toAnimationHTML(sceneData);12345678910111213141516171819202122// Main module imports like before
import { fromStringsToScene, toAnimationHTML } from "@codemovie/code-movie";
// NEW: import the language module you want to use
import json from "@codemovie/code-movie/languages/json";
// NEW: instantiate the language. Some languages can be configured to support
// certain features by passing options to the language function (eg. enable JSX
// support in ECMAScript by passing `{ jsx: true }`)
const language = json();
let sceneData = fromStringsToScene(
[
/* ...keyframes... */
],
{
tabSize: 2,
language, // NEW: now the language object instead of a string
},
);
let html = toAnimationHTML(sceneData);This change breaks the following APIs:
- The
InputOptionspassed tofromStringsToScene()as its second argument must now be an object returned from a language function instead of a string referring to the language name fromStringsToScene()now returns an object containing theSceneobject and the language object that was originally passed in theInputOptions- The first argument to
toAnimationHTML()must now be an object containing thesceneobject and the language object used to create the scene (in other words, it still takes the output offromStringsToScene()as its first argument, but the actual object is slightly different) - The main module exports
SUPPORTED_LANGUAGES(set of language names) andLanguage(TypeScript union type of language names) do no longer exist
The following language modules are currently available:
@codemovie/code-movie/languages/css@codemovie/code-movie/languages/ecmascript(for JavaScript and TypeScript)@codemovie/code-movie/languages/elixir@codemovie/code-movie/languages/html@codemovie/code-movie/languages/json@codemovie/code-movie/languages/plaintext
Improvement: JSX support for JavaScript and TypeScript
The language module @codemovie/code-movie/languages/ecmascript enables support for JSX in both TypeScript and vanilla JavaScript:
import ecmascript from "@codemovie/code-movie/languages/ecmascript";
let regularJavaScript = ecmascript();
let typeScript = ecmascript({ ts: true });
let javaScriptWithJSX = ecmascript({ jsx: true });
let typeScriptWithJSX = ecmascript({ ts: true, jsx: true });123456import ecmascript from "@codemovie/code-movie/languages/ecmascript";
let regularJavaScript = ecmascript();
let typeScript = ecmascript({ ts: true });
let javaScriptWithJSX = ecmascript({ jsx: true });
let typeScriptWithJSX = ecmascript({ ts: true, jsx: true });Improvement: optional ranges and decorations
The fields ranges and decorations on InputFrames (the first argument to fromStringsToScene()) are now optional and default to empty arrays. They will be empty for most frames anyway and filling in the blanks is trivial. Plus it makes the tutorial shorter!
Bugfixes in 0.0.15
The bugfixes all revolve around plugging small to medium-sized holes in language support:
- Elixir:
- an empty string as input was causing exceptions
- the diff algorithm had too little meta information on
doblocks to reliably produce non-confusing results
- TypeScript:
- template literal types with interpolations inside were missing their non-interpolation bits (the actual string parts of a template literal type)
readonlyin index signatures and theinferkeyword were not properly detected
- ECMAScript:
- curly braces for object literals and
switchbodies were not properly detected - the
breakandcontinuekeywords were not properly detected
- curly braces for object literals and
Other changes in in 0.0.15
Adjusted some magic numbers in the token matchup functions. This should provide an overall improved diffing behavior.
v 0.0.14
Improve stability and add support for Elixir.
v 0.0.13
Initial public release.