scratchblocks-plus
v2.2.0
Published
Make pictures of Scratch blocks from text.
Readme
Make pictures of Scratch blocks from text.
scratchblocks-plus is a fork of scratchblocks, and adds the following features:
- matrix support
- block highlight
- dropdown menu translate
- server-side rendering
- issue: scratchblocks#402
- PR: scratchblocks#589
- basic TypeScript support
- and more!
Compatibility with scratchblocks
scratchblocks-plus follows the core text syntax and common browser APIs of
scratchblocks 3.x, including parse, render, renderMatching, and
loadLanguages. Most browser integrations can migrate by changing the package
or script name.
It is not a complete package-level drop-in replacement:
- scratchblocks-plus is ESM-first and does not support CommonJS
require(); - the root package entry is browser-only because it uses
windowand the DOM; - Node.js rendering and syntax-only parsing use dedicated package subpaths;
- internal modules and exact generated SVG markup are not compatibility guarantees.
The classic script build still creates window.scratchblocks, so existing
browser code that uses the documented scratchblocks API generally remains
compatible.
scratchblocks-plus is used to write Scratch scripts:
It's MIT licensed, so you can use it in your projects.
For the full guide to the syntax, see the wiki.
Usage
ESM (recommended)
Install the package from npm:
npm install scratchblocks-plusThe root entry is intended for browser applications and bundlers. It default-exports the initialized scratchblocks API and automatically adds the required styles to the page:
import scratchblocks from "scratchblocks-plus"
scratchblocks.renderMatching("pre.blocks", {
style: "scratch3",
languages: ["en"],
// catHats: true,
// fontFamily: '"Noto Sans SC", sans-serif',
})Set catHats to true to render all Scratch 3 hat and custom block
definition hats as cat hats. It defaults to false and has no visual effect
with the scratch2 style.
Use fontFamily to override the font family for all block labels, input
values, and comments in that render. The value uses CSS font-family syntax;
load the font before rendering, for example by waiting for
document.fonts.ready. If the option is omitted or empty, each style keeps its
default fonts.
The ESM entry does not create window.scratchblocks. Import the default export
wherever it is needed.
To load every bundled locale in an ESM application:
import scratchblocks from "scratchblocks-plus"
import locales from "scratchblocks-plus/locales/all"
scratchblocks.loadLanguages(locales)The all-locales entry is large. Import individual locale JSON files when your bundler supports JSON modules and you only need a few languages.
Node.js rendering
Use the dedicated SSR entry instead of the browser root. Install a DOM and canvas implementation alongside scratchblocks-plus:
npm install scratchblocks-plus @xmldom/xmldom @napi-rs/canvasimport { renderToSVGString } from "scratchblocks-plus/node-ssr"
const svg = renderToSVGString("move (10) steps", {
style: "scratch3",
fontFamily: '"Noto Sans SC", sans-serif',
})For custom fonts in Node.js, register the font with the selected Canvas implementation before rendering so SVG layout measurement uses the same font.
Syntax-only parsing
Parsing and document analysis do not require a DOM or canvas implementation:
import { parse } from "scratchblocks-plus/syntax"
const document = parse("move (10) steps")
const block = document.scripts[0].blocks[0]
console.log(block.info.id) // "MOTION_MOVESTEPS"Custom extensions
Register a complete extension before parsing its blocks:
import scratchblocks from "scratchblocks-plus"
const extension = scratchblocks.registerExtension({
id: "demo",
name: "Demo",
category: {
styles: {
scratch3: {
primary: "#0fbd8c",
secondary: "#0da57a",
tertiary: "#0b8e69",
},
},
},
blocks: {
greet: {
spec: "hello %1",
aliases: ["greet %1"],
inputs: ["%s"],
shape: "stack",
},
},
translations: {
zh_cn: {
greet: { spec: "你好 %1", aliases: ["问候 %1"] },
},
},
})
console.log(extension.blockIds) // ["demo.greet"]
const doc = scratchblocks.parse("hello [world]")The extension ID is also its category name. Block keys are local opcodes;
the API generates extensionId.opcode IDs and registers each spec and its
aliases in English. IDs and opcodes accept letters, digits, underscores and
hyphens. Each spec or alias must contain every numbered input exactly once
(%1 through %9); translations can reorder them. Inputs use the existing
types, such as %s, %n, %b, %c, %m, and %d.note. Supported shapes are
stack, cap, hat, cat, reporter, boolean, ring, c-block, and
c-block cap; loops may also set hasLoopArrow: true.
Translations accept a spec string or { spec, aliases }. Language codes are
matched case-insensitively, treating _ and - alike. Load the normal language
pack with loadLanguages() before parsing in that language. Extension
translations are saved even if that pack is not yet loaded, and are reapplied
when a fresh pack replaces it. Define English on the blocks, not in
translations.en.
category.aliases adds names usable in :: category overrides. Category colors
must be three- or six-digit hex colors. Styles may include scratch2 (one color),
scratch3, scratch3HighContrast, and scratch3Outline (each a
{ primary, secondary, tertiary } palette). Omitted styles get usable defaults:
Scratch 2 uses the primary color, high contrast uses the Scratch 3 palette, and
outline uses white with its tertiary border color. Supply a separate high
contrast palette when needed.
Add an icons array using the existing RegisterIconOptions format, then
reference an icon's name in category.icon, or set inline: true and use
@iconName in a spec. Icon names contain letters only and are globally unique;
prefix them with your extension name to avoid collisions. If only Scratch 2 or
Scratch 3 artwork is supplied, it is reused for the other renderer. A high
contrast variant must use the same dimensions and vertical offset as its
Scratch 3 artwork.
// Spread these fields into your extension manifest.
const iconOptions = {
icons: [{
name: "demoIcon",
inline: true,
scratch3: {
width: 40,
height: 40,
source: { type: "image", data: "data:image/png;base64,..." },
},
}],
category: { icon: "demoIcon" },
}registerExtension() validates the entire manifest before registering any
definitions, rejects duplicate extension/category/block/icon IDs, and refreshes
installed page styles once. Existing SVGs must be rendered again by the caller.
Registries remain shared within one loaded library module; reinitializing the
renderer does not create an isolated registry. Unloading and replacement are
not supported. Existing registerIcon, registerCategory, registerBlock, and
registerBlockTranslation calls remain available.
Node.js uses the same manifest and returns the same registration result:
import {
registerExtension,
renderToSVGString,
} from "scratchblocks-plus/node-ssr"
registerExtension(myExtensionManifest)
const svg = renderToSVGString("hello [world]")This API accepts extension metadata only. If an application reads TurboWarp
extensions or another external format, it must obtain and convert that metadata
outside the library, then pass the resulting manifest to registerExtension().
The example site's existing TurboWarp loader does this conversion before calling
the API.
TypeScript model names are available as type-only exports from the browser entry:
import scratchblocks, {
type Block,
type Document,
} from "scratchblocks-plus"
const document: Document = scratchblocks.parse("move (10) steps")
const block: Block = document.scripts[0].blocks[0]block.info.id identifies the registered Scratch block definition. For an
application-defined identity, assign id directly to a Script, Block,
Input, or Comment before creating a view or rendering:
const document = scratchblocks.parse("move (10) steps")
const block = document.scripts[0].blocks[0]
block.id = "workspace-block-42"
const view = scratchblocks.newView(document, { style: "scratch3" })
view.render()
const element = view.getElementById("workspace-block-42")The ID is rendered as data-sb-id; it is not part of scratchblocks text and is
not included by stringify(). Duplicate IDs are allowed, and lookup returns the
first matching element in document order.
To hide a block without removing its layout space, set its optional hidden
property before creating the view:
const doc = scratchblocks.parse(`repeat (10)
move (10) steps
end`)
doc.getBlockByPath("1.1").hidden = true
const view = scratchblocks.newView(doc, { style: "scratch3" })
view.render()When hidden is true, the block, its nested blocks, and attached visual
annotations are invisible, while their original dimensions and positions are
preserved. Hidden blocks remain available through element lookup APIs. The
property is application-only: scratchblocks text cannot set it, and
stringify() does not include it. Create a new view after changing hidden on
an existing document.
React
Use the scratchblocks-plus-react package to render scratchblocks in React.
Classic HTML script
ESM is recommended for new projects. The classic IIFE build remains available
for pages that use a global window.scratchblocks object.
You'll need to include a copy of the scratchblocks-plus JS file on your webpage. There are a few ways of getting one:
- Download it from the https://github.com/LuYifei2011/scratchblocks-plus/releases page
- You could clone this repository and build it yourself using Node 16.14.0+ (
npm run build).
<script src="scratchblocks-plus.min.js"></script>The convention is to write scratchblocks inside pre tags with the class blocks:
<pre class="blocks">
when flag clicked
move (10) steps
</pre>You then need to call scratchblocks.renderMatching after the page has loaded.
Make sure this appears at the end of the page (just before the closing </body> tag):
<script>
scratchblocks.renderMatching("pre.blocks", {
style: "scratch3", // Optional, defaults to "scratch2".
languages: ["en", "de"], // Optional, defaults to ["en"].
scale: 1, // Optional, defaults to 1.
})
</script>The renderMatching() function takes a CSS-style selector for the elements that contain scratchblocks code: we use pre.blocks to target pre tags with the class blocks.
The style option controls how the blocks appear. Supported built-in styles are scratch2, scratch3, scratch3-high-contrast, and scratch3-outline.
Inline blocks
You might also want to use blocks "inline", inside a paragraph:
I'm rather fond of the <code class="b">stamp</code> block in Scratch.To allow this, make a second call to renderMatching using the inline argument.
<script>
scratchblocks.renderMatching("pre.blocks", ...)
scratchblocks.renderMatching("code.b", {
inline: true,
// Repeat `style` and `languages` options here.
})
</script>This time we use code.b to target code blocks with the class b.
Translations
If you want to use languages other than English, you'll need to include a second JS file that contains translations. The releases page includes two options; you can pick one:
translations.jsincludes a limited set of languages, as seen on the Scratch Forumstranslations-all.jsincludes every language that Scratch supports.
The translations files are hundreds of kilobytes in size, so to keep your page bundle size down you might like to build your own file with just the languages you need.
For example, a translations file that just loads the German language (ISO code de) would look something like this:
scratchblocks.loadLanguages({
de: <contents of locales/de.json>
})With ESM, import the locale JSON file when your bundler supports JSON modules:
import de from "scratchblocks-plus/locales/de.json"
scratchblocks.loadLanguages({
de,
})Languages
To update the translations:
npm upgrade scratch-l10n
npm run localesAdding a language
Each language requires some additional words which aren't in Scratch itself (mainly the words used for the flag and arrow images).
I'd be happy to accept pull requests for those! You'll need to rebuild the translations with npm run locales after editing the aliases.
Development
This should set you up and start a http-server for development:
npm install
npm startThen open http://localhost:8000/ :-)
For more details, see CONTRIBUTING.md.
Credits
Many, many thanks to the contributors!
- Maintained by LuYifei2011
- This is a fork of scratchblocks, so all the credit there still applies here.
- Original scratchblocks library by tjvr
- Original scratchblocks library maintained by tjvr and apple502j
- Icons derived from Scratch Blocks (Apache License 2.0)
- Scratch 2 SVG proof-of-concept, shapes & filters by as-com
- Anna helped with a formula, and pointed out that tjvr can't read graphs
- JSO designed the syntax and wrote the original Block Plugin
- Help with translation code from joooni
- Block translations from the scratch-l10n repository
- Ported to node by arve0
