From db106459f2ea5c70c4457600e641694051c14c30 Mon Sep 17 00:00:00 2001 From: lizschwab Date: Tue, 4 Aug 2026 13:19:37 -0700 Subject: [PATCH 1/6] chore: moved sample-app into monorepo --- packages/plugins/sample-app/.gitignore | 22 + packages/plugins/sample-app/README.md | 50 ++ packages/plugins/sample-app/package.json | 29 + .../plugins/sample-app/src/blocks/text.js | 35 + .../sample-app/src/generators/javascript.js | 30 + packages/plugins/sample-app/src/index.css | 40 ++ packages/plugins/sample-app/src/index.html | 16 + packages/plugins/sample-app/src/index.js | 68 ++ .../plugins/sample-app/src/serialization.js | 32 + packages/plugins/sample-app/src/toolbox.js | 629 ++++++++++++++++++ packages/plugins/sample-app/webpack.config.js | 59 ++ 11 files changed, 1010 insertions(+) create mode 100644 packages/plugins/sample-app/.gitignore create mode 100644 packages/plugins/sample-app/README.md create mode 100644 packages/plugins/sample-app/package.json create mode 100644 packages/plugins/sample-app/src/blocks/text.js create mode 100644 packages/plugins/sample-app/src/generators/javascript.js create mode 100644 packages/plugins/sample-app/src/index.css create mode 100644 packages/plugins/sample-app/src/index.html create mode 100644 packages/plugins/sample-app/src/index.js create mode 100644 packages/plugins/sample-app/src/serialization.js create mode 100644 packages/plugins/sample-app/src/toolbox.js create mode 100644 packages/plugins/sample-app/webpack.config.js diff --git a/packages/plugins/sample-app/.gitignore b/packages/plugins/sample-app/.gitignore new file mode 100644 index 00000000000..4c77fc2f8cb --- /dev/null +++ b/packages/plugins/sample-app/.gitignore @@ -0,0 +1,22 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Dependency directories +node_modules/ + +# Generated files +dist/ +build/ +.DS_Store + +# Optional npm cache directory +.npm + +# IDEs and editors +.idea +*.sublime-workspace +.vscode/* diff --git a/packages/plugins/sample-app/README.md b/packages/plugins/sample-app/README.md new file mode 100644 index 00000000000..1b3c36d82b8 --- /dev/null +++ b/packages/plugins/sample-app/README.md @@ -0,0 +1,50 @@ +# Blockly Sample App + +## Purpose + +This app illustrates how to use Blockly together with common programming tools like node/npm, webpack, typescript, eslint, and others. You can use it as the starting point for your own application and modify it as much as you'd like. It contains basic infrastructure for running, building, testing, etc. that you can use even if you don't understand how to configure the related tool yet. When your needs outgrow the functionality provided here, you can replace the provided configuration or tool with your own. + +## Quick Start + +1. [Install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) npm if you haven't before. +2. Run [`npx @blockly/create-package app `](https://www.npmjs.com/package/@blockly/create-package) to clone this application to your own machine. +3. Run `npm install` to install the required dependencies. +4. Run `npm run start` to run the development server and see the app in action. +5. If you make any changes to the source code, just refresh the browser while the server is running to see them. + +## Tooling + +The application uses many of the same tools that the Blockly team uses to develop Blockly itself. Following is a brief overview, and you can read more about them on our [developer site](https://developers.google.com/blockly/guides/contribute/get-started/development_tools). + +- Structure: The application is built as an npm package. You can use npm to manage the dependencies of the application. +- Modules: ES6 modules to handle imports to/exports from other files. +- Building/bundling: Webpack to build the source code and bundle it into one file for serving. +- Development server: webpack-dev-server to run locally while in development. +- Testing: Mocha to run unit tests. +- Linting: Eslint to lint the code and ensure it conforms with a standard style. +- UI Framework: Does not use a framework. For more complex applications, you may wish to integrate a UI framework like React or Angular. + +You can disable, reconfigure, or replace any of these tools at any time, but they are preconfigured to get you started developing your Blockly application quickly. + +## Structure + +- `package.json` contains basic information about the app. This is where the scripts to run, build, etc. are listed. +- `package-lock.json` is used by npm to manage dependencies +- `webpack.config.js` is the configuration for webpack. This handles bundling the application and running our development server. +- `src/` contains the rest of the source code. +- `dist/` contains the packaged output (that you could host on a server, for example). This is ignored by git and will only appear after you run `npm run build` or `npm run start`. + +### Source Code + +- `index.html` contains the skeleton HTML for the page. This file is modified during the build to import the bundled source code output by webpack. +- `index.js` is the entry point of the app. It configures Blockly and sets up the page to show the blocks, the generated code, and the output of running the code in JavaScript. +- `serialization.js` has code to save and load the workspace using the browser's local storage. This is how your workspace is saved even after refreshing or leaving the page. You could replace this with code that saves the user's data to a cloud database instead. +- `toolbox.js` contains the toolbox definition for the app. The current toolbox contains nearly every block that Blockly provides out of the box. You probably want to replace this definition with your own toolbox that uses your custom blocks and only includes the default blocks that are relevant to your application. +- `blocks/text.js` has code for a custom text block, just as an example of creating your own blocks. You probably want to delete this block, and add your own blocks in this directory. +- `generators/javascript.js` contains the JavaScript generator for the custom text block. You'll need to include block generators for any custom blocks you create, in whatever programming language(s) your application will use. + +## Serving + +To run your app locally, run `npm run start` to run the development server. This mode generates source maps and ingests the source maps created by Blockly, so that you can debug using unminified code. + +To deploy your app so that others can use it, run `npm run build` to run a production build. This will bundle your code and minify it to reduce its size. You can then host the contents of the `dist` directory on a web server of your choosing. If you're just getting started, try using [GitHub Pages](https://pages.github.com/). diff --git a/packages/plugins/sample-app/package.json b/packages/plugins/sample-app/package.json new file mode 100644 index 00000000000..2342e4a59e6 --- /dev/null +++ b/packages/plugins/sample-app/package.json @@ -0,0 +1,29 @@ +{ + "name": "sample-app", + "version": "1.0.0", + "description": "A sample app using Blockly", + "main": "index.js", + "private": true, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "webpack --mode production", + "start": "webpack serve --open --mode development" + }, + "keywords": [ + "blockly" + ], + "author": "Blockly team", + "license": "Apache-2.0", + "devDependencies": { + "css-loader": "^7.1.4", + "html-webpack-plugin": "^5.6.7", + "source-map-loader": "^5.0.0", + "style-loader": "^4.0.0", + "webpack": "^5.107.2", + "webpack-cli": "^7.0.3", + "webpack-dev-server": "^5.2.5" + }, + "dependencies": { + "blockly": "^13.2.0" + } +} diff --git a/packages/plugins/sample-app/src/blocks/text.js b/packages/plugins/sample-app/src/blocks/text.js new file mode 100644 index 00000000000..a84680bc6b2 --- /dev/null +++ b/packages/plugins/sample-app/src/blocks/text.js @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly/core'; + +// Create a custom block called 'add_text' that adds +// text to the output div on the sample app. +// This is just an example and you should replace this with your +// own custom blocks. +const addText = { + type: 'add_text', + message0: 'Add text %1', + args0: [ + { + type: 'input_value', + name: 'TEXT', + check: 'String', + }, + ], + previousStatement: null, + nextStatement: null, + colour: 160, + tooltip: '', + helpUrl: '', +}; + +// Create the block definitions for the JSON-only blocks. +// This does not register their definitions with Blockly. +// This file has no side effects! +export const blocks = Blockly.common.createBlockDefinitionsFromJsonArray([ + addText, +]); diff --git a/packages/plugins/sample-app/src/generators/javascript.js b/packages/plugins/sample-app/src/generators/javascript.js new file mode 100644 index 00000000000..0315d9504d6 --- /dev/null +++ b/packages/plugins/sample-app/src/generators/javascript.js @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {Order} from 'blockly/javascript'; + +// Export all the code generators for our custom blocks, +// but don't register them with Blockly yet. +// This file has no side effects! +export const forBlock = Object.create(null); + +forBlock['add_text'] = function (block, generator) { + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; + const addText = generator.provideFunction_( + 'addText', + `function ${generator.FUNCTION_NAME_PLACEHOLDER_}(text) { + + // Add text to the output area. + const outputDiv = document.getElementById('output'); + const textEl = document.createElement('p'); + textEl.innerText = text; + outputDiv.appendChild(textEl); +}`, + ); + // Generate the function call for this block. + const code = `${addText}(${text});\n`; + return code; +}; diff --git a/packages/plugins/sample-app/src/index.css b/packages/plugins/sample-app/src/index.css new file mode 100644 index 00000000000..f282700701f --- /dev/null +++ b/packages/plugins/sample-app/src/index.css @@ -0,0 +1,40 @@ +body { + margin: 0; + max-width: 100vw; +} + +pre, +code { + overflow: auto; +} + +#pageContainer { + display: flex; + width: 100%; + max-width: 100vw; + height: 100vh; +} + +#blocklyDiv { + flex-basis: 100%; + height: 100%; + min-width: 600px; +} + +#outputPane { + display: flex; + flex-direction: column; + width: 400px; + flex: 0 0 400px; + overflow: auto; + margin: 1rem; +} + +#generatedCode { + height: 50%; + background-color: rgb(247, 240, 228); +} + +#output { + height: 50%; +} diff --git a/packages/plugins/sample-app/src/index.html b/packages/plugins/sample-app/src/index.html new file mode 100644 index 00000000000..36d8eeacd99 --- /dev/null +++ b/packages/plugins/sample-app/src/index.html @@ -0,0 +1,16 @@ + + + + + Blockly Sample App + + +
+
+
+
+
+
+
+ + diff --git a/packages/plugins/sample-app/src/index.js b/packages/plugins/sample-app/src/index.js new file mode 100644 index 00000000000..0bb19d3da9d --- /dev/null +++ b/packages/plugins/sample-app/src/index.js @@ -0,0 +1,68 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly'; +import {blocks} from './blocks/text'; +import {forBlock} from './generators/javascript'; +import {javascriptGenerator} from 'blockly/javascript'; +import {save, load} from './serialization'; +import {toolbox} from './toolbox'; +import './index.css'; + +// Register the blocks and generator with Blockly +Blockly.common.defineBlocks(blocks); +Object.assign(javascriptGenerator.forBlock, forBlock); + +// Set up UI elements and inject Blockly +const codeDiv = document.getElementById('generatedCode').firstChild; +const outputDiv = document.getElementById('output'); +const blocklyDiv = document.getElementById('blocklyDiv'); +const ws = Blockly.inject(blocklyDiv, {toolbox}); + +// This function resets the code and output divs, shows the +// generated code from the workspace, and evals the code. +// In a real application, you probably shouldn't use `eval`. +const runCode = () => { + const code = javascriptGenerator.workspaceToCode(ws); + codeDiv.innerText = code; + + outputDiv.innerHTML = ''; + + // Wrap `eval` in a `try/catch` so that any runtime errors are + // logged to the console, instead of failing quietly. + try { + eval(code); + } catch (error) { + console.log(error); + } +}; + +// Load the initial state from storage and run the code. +load(ws); +runCode(); + +// Every time the workspace changes state, save the changes to storage. +ws.addChangeListener((e) => { + // UI events are things like scrolling, zooming, etc. + // No need to save after one of these. + if (e.isUiEvent) return; + save(ws); +}); + +// Whenever the workspace changes meaningfully, run the code again. +ws.addChangeListener((e) => { + // Don't run the code when the workspace finishes loading; we're + // already running it once when the application starts. + // Don't run the code during drags; we might have invalid state. + if ( + e.isUiEvent || + e.type == Blockly.Events.FINISHED_LOADING || + ws.isDragging() + ) { + return; + } + runCode(); +}); diff --git a/packages/plugins/sample-app/src/serialization.js b/packages/plugins/sample-app/src/serialization.js new file mode 100644 index 00000000000..685eaf2fd71 --- /dev/null +++ b/packages/plugins/sample-app/src/serialization.js @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly/core'; + +const storageKey = 'mainWorkspace'; + +/** + * Saves the state of the workspace to browser's local storage. + * @param {Blockly.Workspace} workspace Blockly workspace to save. + */ +export const save = function (workspace) { + const data = Blockly.serialization.workspaces.save(workspace); + window.localStorage?.setItem(storageKey, JSON.stringify(data)); +}; + +/** + * Loads saved state from local storage into the given workspace. + * @param {Blockly.Workspace} workspace Blockly workspace to load into. + */ +export const load = function (workspace) { + const data = window.localStorage?.getItem(storageKey); + if (!data) return; + + // Don't emit events during loading. + Blockly.Events.disable(); + Blockly.serialization.workspaces.load(JSON.parse(data), workspace, false); + Blockly.Events.enable(); +}; diff --git a/packages/plugins/sample-app/src/toolbox.js b/packages/plugins/sample-app/src/toolbox.js new file mode 100644 index 00000000000..1e1f8e6c931 --- /dev/null +++ b/packages/plugins/sample-app/src/toolbox.js @@ -0,0 +1,629 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/* +This toolbox contains nearly every single built-in block that Blockly offers, +in addition to the custom block 'add_text' this sample app adds. +You probably don't need every single block, and should consider either rewriting +your toolbox from scratch, or carefully choosing whether you need each block +listed here. +*/ + +export const toolbox = { + kind: 'categoryToolbox', + contents: [ + { + kind: 'category', + name: 'Logic', + categorystyle: 'logic_category', + contents: [ + { + kind: 'block', + type: 'controls_if', + }, + { + kind: 'block', + type: 'logic_compare', + }, + { + kind: 'block', + type: 'logic_operation', + }, + { + kind: 'block', + type: 'logic_negate', + }, + { + kind: 'block', + type: 'logic_boolean', + }, + { + kind: 'block', + type: 'logic_null', + }, + { + kind: 'block', + type: 'logic_ternary', + }, + ], + }, + { + kind: 'category', + name: 'Loops', + categorystyle: 'loop_category', + contents: [ + { + kind: 'block', + type: 'controls_repeat_ext', + inputs: { + TIMES: { + shadow: { + type: 'math_number', + fields: { + NUM: 10, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'controls_whileUntil', + }, + { + kind: 'block', + type: 'controls_for', + inputs: { + FROM: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + TO: { + shadow: { + type: 'math_number', + fields: { + NUM: 10, + }, + }, + }, + BY: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'controls_forEach', + }, + { + kind: 'block', + type: 'controls_flow_statements', + }, + ], + }, + { + kind: 'category', + name: 'Math', + categorystyle: 'math_category', + contents: [ + { + kind: 'block', + type: 'math_number', + fields: { + NUM: 123, + }, + }, + { + kind: 'block', + type: 'math_arithmetic', + inputs: { + A: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + B: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_single', + inputs: { + NUM: { + shadow: { + type: 'math_number', + fields: { + NUM: 9, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_trig', + inputs: { + NUM: { + shadow: { + type: 'math_number', + fields: { + NUM: 45, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_constant', + }, + { + kind: 'block', + type: 'math_number_property', + inputs: { + NUMBER_TO_CHECK: { + shadow: { + type: 'math_number', + fields: { + NUM: 0, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_round', + fields: { + OP: 'ROUND', + }, + inputs: { + NUM: { + shadow: { + type: 'math_number', + fields: { + NUM: 3.1, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_on_list', + fields: { + OP: 'SUM', + }, + }, + { + kind: 'block', + type: 'math_modulo', + inputs: { + DIVIDEND: { + shadow: { + type: 'math_number', + fields: { + NUM: 64, + }, + }, + }, + DIVISOR: { + shadow: { + type: 'math_number', + fields: { + NUM: 10, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_constrain', + inputs: { + VALUE: { + shadow: { + type: 'math_number', + fields: { + NUM: 50, + }, + }, + }, + LOW: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + HIGH: { + shadow: { + type: 'math_number', + fields: { + NUM: 100, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_random_int', + inputs: { + FROM: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + TO: { + shadow: { + type: 'math_number', + fields: { + NUM: 100, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_random_float', + }, + { + kind: 'block', + type: 'math_atan2', + inputs: { + X: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + Y: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + }, + }, + ], + }, + { + kind: 'category', + name: 'Text', + categorystyle: 'text_category', + contents: [ + { + kind: 'block', + type: 'text', + }, + { + kind: 'block', + type: 'text_join', + }, + { + kind: 'block', + type: 'text_append', + inputs: { + TEXT: { + shadow: { + type: 'text', + fields: { + TEXT: '', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_length', + inputs: { + VALUE: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_isEmpty', + inputs: { + VALUE: { + shadow: { + type: 'text', + fields: { + TEXT: '', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_indexOf', + inputs: { + VALUE: { + block: { + type: 'variables_get', + }, + }, + FIND: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_charAt', + inputs: { + VALUE: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'text_getSubstring', + inputs: { + STRING: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'text_changeCase', + inputs: { + TEXT: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_trim', + inputs: { + TEXT: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_count', + inputs: { + SUB: { + shadow: { + type: 'text', + }, + }, + TEXT: { + shadow: { + type: 'text', + }, + }, + }, + }, + { + kind: 'block', + type: 'text_replace', + inputs: { + FROM: { + shadow: { + type: 'text', + }, + }, + TO: { + shadow: { + type: 'text', + }, + }, + TEXT: { + shadow: { + type: 'text', + }, + }, + }, + }, + { + kind: 'block', + type: 'text_reverse', + inputs: { + TEXT: { + shadow: { + type: 'text', + }, + }, + }, + }, + { + kind: 'block', + type: 'add_text', + inputs: { + TEXT: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + ], + }, + { + kind: 'category', + name: 'Lists', + categorystyle: 'list_category', + contents: [ + { + kind: 'block', + type: 'lists_create_with', + }, + { + kind: 'block', + type: 'lists_create_with', + }, + { + kind: 'block', + type: 'lists_repeat', + inputs: { + NUM: { + shadow: { + type: 'math_number', + fields: { + NUM: 5, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_length', + }, + { + kind: 'block', + type: 'lists_isEmpty', + }, + { + kind: 'block', + type: 'lists_indexOf', + inputs: { + VALUE: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_getIndex', + inputs: { + VALUE: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_setIndex', + inputs: { + LIST: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_getSublist', + inputs: { + LIST: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_split', + inputs: { + DELIM: { + shadow: { + type: 'text', + fields: { + TEXT: ',', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_sort', + }, + { + kind: 'block', + type: 'lists_reverse', + }, + ], + }, + { + kind: 'sep', + }, + { + kind: 'category', + name: 'Variables', + categorystyle: 'variable_category', + custom: 'VARIABLE', + }, + { + kind: 'category', + name: 'Functions', + categorystyle: 'procedure_category', + custom: 'PROCEDURE', + }, + ], +}; diff --git a/packages/plugins/sample-app/webpack.config.js b/packages/plugins/sample-app/webpack.config.js new file mode 100644 index 00000000000..1a095648fd7 --- /dev/null +++ b/packages/plugins/sample-app/webpack.config.js @@ -0,0 +1,59 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +// Base config that applies to either development or production mode. +const config = { + entry: './src/index.js', + output: { + // Compile the source files into a bundle. + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist'), + clean: true, + }, + // Enable webpack-dev-server to get hot refresh of the app. + devServer: { + static: './build', + }, + module: { + rules: [ + { + // Load CSS files. They can be imported into JS files. + test: /\.css$/i, + use: ['style-loader', 'css-loader'], + }, + ], + }, + plugins: [ + // Generate the HTML index page based on our template. + // This will output the same index page with the bundle we + // created above added in a script tag. + new HtmlWebpackPlugin({ + template: 'src/index.html', + }), + ], +}; + +module.exports = (env, argv) => { + if (argv.mode === 'development') { + // Set the output path to the `build` directory + // so we don't clobber production builds. + config.output.path = path.resolve(__dirname, 'build'); + + // Generate source maps for our code for easier debugging. + // Not suitable for production builds. If you want source maps in + // production, choose a different one from https://webpack.js.org/configuration/devtool + config.devtool = 'eval-cheap-module-source-map'; + + // Include the source maps for Blockly for easier debugging Blockly code. + config.module.rules.push({ + test: /(blockly[/\\].*\.js)$/, + use: [require.resolve('source-map-loader')], + enforce: 'pre', + }); + + // Ignore spurious warnings from source-map-loader + // It can't find source maps for some Closure modules and that is expected + config.ignoreWarnings = [/Failed to parse source map.*blockly/]; + } + return config; +}; From acdc515fffd13a4db8b4439a5fa17533650f942c Mon Sep 17 00:00:00 2001 From: lizschwab Date: Tue, 4 Aug 2026 13:22:30 -0700 Subject: [PATCH 2/6] chore: moved sample-app-ts into monorepo --- packages/plugins/sample-app-ts/.gitignore | 22 + packages/plugins/sample-app-ts/package.json | 31 + .../plugins/sample-app-ts/src/blocks/text.ts | 35 + .../src/generators/javascript.ts | 34 + packages/plugins/sample-app-ts/src/index.css | 40 ++ packages/plugins/sample-app-ts/src/index.html | 16 + packages/plugins/sample-app-ts/src/index.ts | 74 +++ .../sample-app-ts/src/serialization.ts | 32 + packages/plugins/sample-app-ts/src/toolbox.ts | 629 ++++++++++++++++++ packages/plugins/sample-app-ts/tsconfig.json | 12 + .../plugins/sample-app-ts/webpack.config.js | 67 ++ 11 files changed, 992 insertions(+) create mode 100644 packages/plugins/sample-app-ts/.gitignore create mode 100644 packages/plugins/sample-app-ts/package.json create mode 100644 packages/plugins/sample-app-ts/src/blocks/text.ts create mode 100644 packages/plugins/sample-app-ts/src/generators/javascript.ts create mode 100644 packages/plugins/sample-app-ts/src/index.css create mode 100644 packages/plugins/sample-app-ts/src/index.html create mode 100644 packages/plugins/sample-app-ts/src/index.ts create mode 100644 packages/plugins/sample-app-ts/src/serialization.ts create mode 100644 packages/plugins/sample-app-ts/src/toolbox.ts create mode 100644 packages/plugins/sample-app-ts/tsconfig.json create mode 100644 packages/plugins/sample-app-ts/webpack.config.js diff --git a/packages/plugins/sample-app-ts/.gitignore b/packages/plugins/sample-app-ts/.gitignore new file mode 100644 index 00000000000..4c77fc2f8cb --- /dev/null +++ b/packages/plugins/sample-app-ts/.gitignore @@ -0,0 +1,22 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Dependency directories +node_modules/ + +# Generated files +dist/ +build/ +.DS_Store + +# Optional npm cache directory +.npm + +# IDEs and editors +.idea +*.sublime-workspace +.vscode/* diff --git a/packages/plugins/sample-app-ts/package.json b/packages/plugins/sample-app-ts/package.json new file mode 100644 index 00000000000..813834373a6 --- /dev/null +++ b/packages/plugins/sample-app-ts/package.json @@ -0,0 +1,31 @@ +{ + "name": "sample-app-typescript", + "version": "1.0.0", + "description": "A sample app using Blockly", + "main": "index.js", + "private": true, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "webpack --mode production", + "start": "webpack serve --open --mode development" + }, + "keywords": [ + "blockly" + ], + "author": "Blockly team", + "license": "Apache-2.0", + "devDependencies": { + "css-loader": "^7.1.4", + "html-webpack-plugin": "^5.6.7", + "source-map-loader": "^5.0.0", + "style-loader": "^4.0.0", + "ts-loader": "^9.6.1", + "typescript": "^6.0.3", + "webpack": "^5.107.2", + "webpack-cli": "^7.0.3", + "webpack-dev-server": "^5.2.5" + }, + "dependencies": { + "blockly": "^13.2.0" + } +} diff --git a/packages/plugins/sample-app-ts/src/blocks/text.ts b/packages/plugins/sample-app-ts/src/blocks/text.ts new file mode 100644 index 00000000000..a84680bc6b2 --- /dev/null +++ b/packages/plugins/sample-app-ts/src/blocks/text.ts @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly/core'; + +// Create a custom block called 'add_text' that adds +// text to the output div on the sample app. +// This is just an example and you should replace this with your +// own custom blocks. +const addText = { + type: 'add_text', + message0: 'Add text %1', + args0: [ + { + type: 'input_value', + name: 'TEXT', + check: 'String', + }, + ], + previousStatement: null, + nextStatement: null, + colour: 160, + tooltip: '', + helpUrl: '', +}; + +// Create the block definitions for the JSON-only blocks. +// This does not register their definitions with Blockly. +// This file has no side effects! +export const blocks = Blockly.common.createBlockDefinitionsFromJsonArray([ + addText, +]); diff --git a/packages/plugins/sample-app-ts/src/generators/javascript.ts b/packages/plugins/sample-app-ts/src/generators/javascript.ts new file mode 100644 index 00000000000..4c2580a54d8 --- /dev/null +++ b/packages/plugins/sample-app-ts/src/generators/javascript.ts @@ -0,0 +1,34 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {Order} from 'blockly/javascript'; +import * as Blockly from 'blockly/core'; + +// Export all the code generators for our custom blocks, +// but don't register them with Blockly yet. +// This file has no side effects! +export const forBlock = Object.create(null); + +forBlock['add_text'] = function ( + block: Blockly.Block, + generator: Blockly.CodeGenerator, +) { + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; + const addText = generator.provideFunction_( + 'addText', + `function ${generator.FUNCTION_NAME_PLACEHOLDER_}(text) { + + // Add text to the output area. + const outputDiv = document.getElementById('output'); + const textEl = document.createElement('p'); + textEl.innerText = text; + outputDiv.appendChild(textEl); +}`, + ); + // Generate the function call for this block. + const code = `${addText}(${text});\n`; + return code; +}; diff --git a/packages/plugins/sample-app-ts/src/index.css b/packages/plugins/sample-app-ts/src/index.css new file mode 100644 index 00000000000..f282700701f --- /dev/null +++ b/packages/plugins/sample-app-ts/src/index.css @@ -0,0 +1,40 @@ +body { + margin: 0; + max-width: 100vw; +} + +pre, +code { + overflow: auto; +} + +#pageContainer { + display: flex; + width: 100%; + max-width: 100vw; + height: 100vh; +} + +#blocklyDiv { + flex-basis: 100%; + height: 100%; + min-width: 600px; +} + +#outputPane { + display: flex; + flex-direction: column; + width: 400px; + flex: 0 0 400px; + overflow: auto; + margin: 1rem; +} + +#generatedCode { + height: 50%; + background-color: rgb(247, 240, 228); +} + +#output { + height: 50%; +} diff --git a/packages/plugins/sample-app-ts/src/index.html b/packages/plugins/sample-app-ts/src/index.html new file mode 100644 index 00000000000..36d8eeacd99 --- /dev/null +++ b/packages/plugins/sample-app-ts/src/index.html @@ -0,0 +1,16 @@ + + + + + Blockly Sample App + + +
+
+
+
+
+
+
+ + diff --git a/packages/plugins/sample-app-ts/src/index.ts b/packages/plugins/sample-app-ts/src/index.ts new file mode 100644 index 00000000000..4c6fe756f6b --- /dev/null +++ b/packages/plugins/sample-app-ts/src/index.ts @@ -0,0 +1,74 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly'; +import {blocks} from './blocks/text'; +import {forBlock} from './generators/javascript'; +import {javascriptGenerator} from 'blockly/javascript'; +import {save, load} from './serialization'; +import {toolbox} from './toolbox'; +import './index.css'; + +// Register the blocks and generator with Blockly +Blockly.common.defineBlocks(blocks); +Object.assign(javascriptGenerator.forBlock, forBlock); + +// Set up UI elements and inject Blockly +const codeDiv = document.getElementById('generatedCode')?.firstChild; +const outputDiv = document.getElementById('output'); +const blocklyDiv = document.getElementById('blocklyDiv'); + +if (!blocklyDiv) { + throw new Error(`div with id 'blocklyDiv' not found`); +} +const ws = Blockly.inject(blocklyDiv, {toolbox}); + +// This function resets the code and output divs, shows the +// generated code from the workspace, and evals the code. +// In a real application, you probably shouldn't use `eval`. +const runCode = () => { + const code = javascriptGenerator.workspaceToCode(ws as Blockly.Workspace); + if (codeDiv) codeDiv.textContent = code; + + if (outputDiv) outputDiv.innerHTML = ''; + + // Wrap `eval` in a `try/catch` so that any runtime errors are + // logged to the console, instead of failing quietly. + try { + eval(code); + } catch (error) { + console.log(error); + } +}; + +if (ws) { + // Load the initial state from storage and run the code. + load(ws); + runCode(); + + // Every time the workspace changes state, save the changes to storage. + ws.addChangeListener((e: Blockly.Events.Abstract) => { + // UI events are things like scrolling, zooming, etc. + // No need to save after one of these. + if (e.isUiEvent) return; + save(ws); + }); + + // Whenever the workspace changes meaningfully, run the code again. + ws.addChangeListener((e: Blockly.Events.Abstract) => { + // Don't run the code when the workspace finishes loading; we're + // already running it once when the application starts. + // Don't run the code during drags; we might have invalid state. + if ( + e.isUiEvent || + e.type == Blockly.Events.FINISHED_LOADING || + ws.isDragging() + ) { + return; + } + runCode(); + }); +} diff --git a/packages/plugins/sample-app-ts/src/serialization.ts b/packages/plugins/sample-app-ts/src/serialization.ts new file mode 100644 index 00000000000..5278e82825c --- /dev/null +++ b/packages/plugins/sample-app-ts/src/serialization.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly/core'; + +const storageKey = 'mainWorkspace'; + +/** + * Saves the state of the workspace to browser's local storage. + * @param workspace Blockly workspace to save. + */ +export const save = function (workspace: Blockly.Workspace) { + const data = Blockly.serialization.workspaces.save(workspace); + window.localStorage?.setItem(storageKey, JSON.stringify(data)); +}; + +/** + * Loads saved state from local storage into the given workspace. + * @param workspace Blockly workspace to load into. + */ +export const load = function (workspace: Blockly.Workspace) { + const data = window.localStorage?.getItem(storageKey); + if (!data) return; + + // Don't emit events during loading. + Blockly.Events.disable(); + Blockly.serialization.workspaces.load(JSON.parse(data), workspace, undefined); + Blockly.Events.enable(); +}; diff --git a/packages/plugins/sample-app-ts/src/toolbox.ts b/packages/plugins/sample-app-ts/src/toolbox.ts new file mode 100644 index 00000000000..1e1f8e6c931 --- /dev/null +++ b/packages/plugins/sample-app-ts/src/toolbox.ts @@ -0,0 +1,629 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/* +This toolbox contains nearly every single built-in block that Blockly offers, +in addition to the custom block 'add_text' this sample app adds. +You probably don't need every single block, and should consider either rewriting +your toolbox from scratch, or carefully choosing whether you need each block +listed here. +*/ + +export const toolbox = { + kind: 'categoryToolbox', + contents: [ + { + kind: 'category', + name: 'Logic', + categorystyle: 'logic_category', + contents: [ + { + kind: 'block', + type: 'controls_if', + }, + { + kind: 'block', + type: 'logic_compare', + }, + { + kind: 'block', + type: 'logic_operation', + }, + { + kind: 'block', + type: 'logic_negate', + }, + { + kind: 'block', + type: 'logic_boolean', + }, + { + kind: 'block', + type: 'logic_null', + }, + { + kind: 'block', + type: 'logic_ternary', + }, + ], + }, + { + kind: 'category', + name: 'Loops', + categorystyle: 'loop_category', + contents: [ + { + kind: 'block', + type: 'controls_repeat_ext', + inputs: { + TIMES: { + shadow: { + type: 'math_number', + fields: { + NUM: 10, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'controls_whileUntil', + }, + { + kind: 'block', + type: 'controls_for', + inputs: { + FROM: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + TO: { + shadow: { + type: 'math_number', + fields: { + NUM: 10, + }, + }, + }, + BY: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'controls_forEach', + }, + { + kind: 'block', + type: 'controls_flow_statements', + }, + ], + }, + { + kind: 'category', + name: 'Math', + categorystyle: 'math_category', + contents: [ + { + kind: 'block', + type: 'math_number', + fields: { + NUM: 123, + }, + }, + { + kind: 'block', + type: 'math_arithmetic', + inputs: { + A: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + B: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_single', + inputs: { + NUM: { + shadow: { + type: 'math_number', + fields: { + NUM: 9, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_trig', + inputs: { + NUM: { + shadow: { + type: 'math_number', + fields: { + NUM: 45, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_constant', + }, + { + kind: 'block', + type: 'math_number_property', + inputs: { + NUMBER_TO_CHECK: { + shadow: { + type: 'math_number', + fields: { + NUM: 0, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_round', + fields: { + OP: 'ROUND', + }, + inputs: { + NUM: { + shadow: { + type: 'math_number', + fields: { + NUM: 3.1, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_on_list', + fields: { + OP: 'SUM', + }, + }, + { + kind: 'block', + type: 'math_modulo', + inputs: { + DIVIDEND: { + shadow: { + type: 'math_number', + fields: { + NUM: 64, + }, + }, + }, + DIVISOR: { + shadow: { + type: 'math_number', + fields: { + NUM: 10, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_constrain', + inputs: { + VALUE: { + shadow: { + type: 'math_number', + fields: { + NUM: 50, + }, + }, + }, + LOW: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + HIGH: { + shadow: { + type: 'math_number', + fields: { + NUM: 100, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_random_int', + inputs: { + FROM: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + TO: { + shadow: { + type: 'math_number', + fields: { + NUM: 100, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_random_float', + }, + { + kind: 'block', + type: 'math_atan2', + inputs: { + X: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + Y: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + }, + }, + ], + }, + { + kind: 'category', + name: 'Text', + categorystyle: 'text_category', + contents: [ + { + kind: 'block', + type: 'text', + }, + { + kind: 'block', + type: 'text_join', + }, + { + kind: 'block', + type: 'text_append', + inputs: { + TEXT: { + shadow: { + type: 'text', + fields: { + TEXT: '', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_length', + inputs: { + VALUE: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_isEmpty', + inputs: { + VALUE: { + shadow: { + type: 'text', + fields: { + TEXT: '', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_indexOf', + inputs: { + VALUE: { + block: { + type: 'variables_get', + }, + }, + FIND: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_charAt', + inputs: { + VALUE: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'text_getSubstring', + inputs: { + STRING: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'text_changeCase', + inputs: { + TEXT: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_trim', + inputs: { + TEXT: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_count', + inputs: { + SUB: { + shadow: { + type: 'text', + }, + }, + TEXT: { + shadow: { + type: 'text', + }, + }, + }, + }, + { + kind: 'block', + type: 'text_replace', + inputs: { + FROM: { + shadow: { + type: 'text', + }, + }, + TO: { + shadow: { + type: 'text', + }, + }, + TEXT: { + shadow: { + type: 'text', + }, + }, + }, + }, + { + kind: 'block', + type: 'text_reverse', + inputs: { + TEXT: { + shadow: { + type: 'text', + }, + }, + }, + }, + { + kind: 'block', + type: 'add_text', + inputs: { + TEXT: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + ], + }, + { + kind: 'category', + name: 'Lists', + categorystyle: 'list_category', + contents: [ + { + kind: 'block', + type: 'lists_create_with', + }, + { + kind: 'block', + type: 'lists_create_with', + }, + { + kind: 'block', + type: 'lists_repeat', + inputs: { + NUM: { + shadow: { + type: 'math_number', + fields: { + NUM: 5, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_length', + }, + { + kind: 'block', + type: 'lists_isEmpty', + }, + { + kind: 'block', + type: 'lists_indexOf', + inputs: { + VALUE: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_getIndex', + inputs: { + VALUE: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_setIndex', + inputs: { + LIST: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_getSublist', + inputs: { + LIST: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_split', + inputs: { + DELIM: { + shadow: { + type: 'text', + fields: { + TEXT: ',', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_sort', + }, + { + kind: 'block', + type: 'lists_reverse', + }, + ], + }, + { + kind: 'sep', + }, + { + kind: 'category', + name: 'Variables', + categorystyle: 'variable_category', + custom: 'VARIABLE', + }, + { + kind: 'category', + name: 'Functions', + categorystyle: 'procedure_category', + custom: 'PROCEDURE', + }, + ], +}; diff --git a/packages/plugins/sample-app-ts/tsconfig.json b/packages/plugins/sample-app-ts/tsconfig.json new file mode 100644 index 00000000000..f486ef22737 --- /dev/null +++ b/packages/plugins/sample-app-ts/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "rootDir": "./src", + "outDir": "dist", + "declaration": true, + "declarationMap": true, + "module": "ES2015", + "moduleResolution": "node", + "target": "ES2015", + "strict": true + } +} diff --git a/packages/plugins/sample-app-ts/webpack.config.js b/packages/plugins/sample-app-ts/webpack.config.js new file mode 100644 index 00000000000..da23023d805 --- /dev/null +++ b/packages/plugins/sample-app-ts/webpack.config.js @@ -0,0 +1,67 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +// Base config that applies to either development or production mode. +const config = { + entry: './src/index.ts', + output: { + // Compile the source files into a bundle. + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist'), + clean: true, + }, + // Enable webpack-dev-server to get hot refresh of the app. + devServer: { + static: './build', + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + { + // Load CSS files. They can be imported into JS files. + test: /\.css$/i, + use: ['style-loader', 'css-loader'], + }, + ], + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + }, + plugins: [ + // Generate the HTML index page based on our template. + // This will output the same index page with the bundle we + // created above added in a script tag. + new HtmlWebpackPlugin({ + template: 'src/index.html', + }), + ], +}; + +module.exports = (env, argv) => { + if (argv.mode === 'development') { + // Set the output path to the `build` directory + // so we don't clobber production builds. + config.output.path = path.resolve(__dirname, 'build'); + + // Generate source maps for our code for easier debugging. + // Not suitable for production builds. If you want source maps in + // production, choose a different one from https://webpack.js.org/configuration/devtool + config.devtool = 'eval-cheap-module-source-map'; + + // Include the source maps for Blockly for easier debugging Blockly code. + config.module.rules.push({ + test: /(blockly[/\\].*\.js)$/, + use: [require.resolve('source-map-loader')], + enforce: 'pre', + }); + + // Ignore spurious warnings from source-map-loader + // It can't find source maps for some Closure modules and that is expected + config.ignoreWarnings = [/Failed to parse source map.*blockly/]; + } + return config; +}; From d2a1220b5909a2082aa6a9c52b0a5f02ceec7f53 Mon Sep 17 00:00:00 2001 From: lizschwab Date: Tue, 4 Aug 2026 14:45:10 -0700 Subject: [PATCH 3/6] chore: update dev-create path for sample-app, add sample-app to workspaces --- package.json | 2 ++ packages/plugins/dev-create/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 088f2e2eecf..a7b8098b371 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "packages/plugins/theme-tritanopia", "packages/plugins/block-test", "packages/plugins/dev-tools", + "packages/plugins/sample-app", + "packages/plugins/sample-app-ts", "packages/plugins/field-grid-dropdown", "packages/plugins/block-dynamic-connection", "packages/plugins/block-plus-minus", diff --git a/packages/plugins/dev-create/package.json b/packages/plugins/dev-create/package.json index ae304505b3f..2a955a2437b 100644 --- a/packages/plugins/dev-create/package.json +++ b/packages/plugins/dev-create/package.json @@ -18,7 +18,7 @@ "templates/sample-app-ts/*" ], "scripts": { - "prepack": "cp -r ../../examples/sample-app ./templates && cp -r ../../examples/sample-app-ts ./templates" + "prepack": "cp -r ../sample-app ./templates && cp -r ../sample-app-ts ./templates" }, "homepage": "https://github.com/RaspberryPiFoundation/blockly/tree/main/packages/plugins/dev-create#readme", "bugs": { From 042149b32c3663ee5bd8c1c520010b813365dedd Mon Sep 17 00:00:00 2001 From: lizschwab Date: Tue, 4 Aug 2026 15:01:09 -0700 Subject: [PATCH 4/6] chore: updated package-lock --- package-lock.json | 134 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/package-lock.json b/package-lock.json index 079e0fe0ba3..fe9f1d46dd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,8 @@ "packages/plugins/theme-tritanopia", "packages/plugins/block-test", "packages/plugins/dev-tools", + "packages/plugins/sample-app", + "packages/plugins/sample-app-ts", "packages/plugins/field-grid-dropdown", "packages/plugins/block-dynamic-connection", "packages/plugins/block-plus-minus", @@ -28121,6 +28123,14 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sample-app": { + "resolved": "packages/plugins/sample-app", + "link": true + }, + "node_modules/sample-app-typescript": { + "resolved": "packages/plugins/sample-app-ts", + "link": true + }, "node_modules/sax": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", @@ -29314,6 +29324,23 @@ "anynum": "^1.0.1" } }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -33623,6 +33650,113 @@ "url": "https://opencollective.com/sinon" } }, + "packages/plugins/sample-app": { + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "blockly": "^13.2.0" + }, + "devDependencies": { + "css-loader": "^7.1.4", + "html-webpack-plugin": "^5.6.7", + "source-map-loader": "^5.0.0", + "style-loader": "^4.0.0", + "webpack": "^5.107.2", + "webpack-cli": "^7.0.3", + "webpack-dev-server": "^5.2.5" + } + }, + "packages/plugins/sample-app-ts": { + "name": "sample-app-typescript", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "blockly": "^13.2.0" + }, + "devDependencies": { + "css-loader": "^7.1.4", + "html-webpack-plugin": "^5.6.7", + "source-map-loader": "^5.0.0", + "style-loader": "^4.0.0", + "ts-loader": "^9.6.1", + "typescript": "^6.0.3", + "webpack": "^5.107.2", + "webpack-cli": "^7.0.3", + "webpack-dev-server": "^5.2.5" + } + }, + "packages/plugins/sample-app-ts/node_modules/css-loader": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.4.tgz", + "integrity": "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.40", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.6.3" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "packages/plugins/sample-app/node_modules/css-loader": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.4.tgz", + "integrity": "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.40", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.6.3" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, "packages/plugins/scroll-options": { "name": "@blockly/plugin-scroll-options", "version": "13.1.0", From 1e78e3ce73d2fb640e89373603fc3d7a99124850 Mon Sep 17 00:00:00 2001 From: lizschwab Date: Tue, 4 Aug 2026 15:40:00 -0700 Subject: [PATCH 5/6] chore: updated sample app tsconfig for compatibility --- packages/plugins/sample-app-ts/src/declarations.d.ts | 1 + packages/plugins/sample-app-ts/tsconfig.json | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 packages/plugins/sample-app-ts/src/declarations.d.ts diff --git a/packages/plugins/sample-app-ts/src/declarations.d.ts b/packages/plugins/sample-app-ts/src/declarations.d.ts new file mode 100644 index 00000000000..35306c6fc9a --- /dev/null +++ b/packages/plugins/sample-app-ts/src/declarations.d.ts @@ -0,0 +1 @@ +declare module '*.css'; diff --git a/packages/plugins/sample-app-ts/tsconfig.json b/packages/plugins/sample-app-ts/tsconfig.json index f486ef22737..d8b8ebea7a3 100644 --- a/packages/plugins/sample-app-ts/tsconfig.json +++ b/packages/plugins/sample-app-ts/tsconfig.json @@ -4,9 +4,11 @@ "outDir": "dist", "declaration": true, "declarationMap": true, + "sourceMap": true, "module": "ES2015", - "moduleResolution": "node", - "target": "ES2015", + "moduleResolution": "bundler", + "target": "es6", "strict": true - } + }, + "include": ["src"] } From 7e4fd549a2a205a0c187322e7c4082c4fc5789f0 Mon Sep 17 00:00:00 2001 From: lizschwab Date: Tue, 4 Aug 2026 15:57:09 -0700 Subject: [PATCH 6/6] chore: downgraded no test specified from error to warning --- packages/plugins/sample-app-ts/package.json | 2 +- packages/plugins/sample-app/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugins/sample-app-ts/package.json b/packages/plugins/sample-app-ts/package.json index 813834373a6..c0da73bfaa7 100644 --- a/packages/plugins/sample-app-ts/package.json +++ b/packages/plugins/sample-app-ts/package.json @@ -5,7 +5,7 @@ "main": "index.js", "private": true, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "echo \"Warning: no test specified\" && exit 0", "build": "webpack --mode production", "start": "webpack serve --open --mode development" }, diff --git a/packages/plugins/sample-app/package.json b/packages/plugins/sample-app/package.json index 2342e4a59e6..28015098a61 100644 --- a/packages/plugins/sample-app/package.json +++ b/packages/plugins/sample-app/package.json @@ -5,7 +5,7 @@ "main": "index.js", "private": true, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "echo \"Warning: no test specified\" && exit 0", "build": "webpack --mode production", "start": "webpack serve --open --mode development" },