创建你的第一个Scratch3.0 Extension¶
我们近期刚写完Scratch3.0技术分析系列文章,接下来准备围绕Scratch3.0编辑器写一系列文章,这一系列的文章关注如何构建自己的Extension,如何接入codelab-adapter。
此外,我们的EIM的源码已开放。
对于你希望在自己的平台中接入codelab-adapter, 请阅读scratch3-adapter可以支持其他编程平台吗?。
说明¶
文章更新于: 2019.01.20
官方的源码仓库一直在变化,我们希望即时更新到最新状态。如果你发现有任何部分不能工作,及时通知我:)
项目的源码我们已经放到github: codelabclub/scratch3_hello_world
运行scratch-gui¶
我的开发环境为MacOS 10.13.5,确保你在本地按照了git和nodejs。
windows用户¶
有读者在邮件里反馈说,在Windows下遇到一些问题。我在Windows 10里做了测试,把注意事项补充进来。
安装nodejs(目前已测试版本 v10.15.0
、 v11.7.0
、 v14.16.0
(推荐)).
使用cmder,而不是cmd。
Tips
建议使用淘宝npm镜像,npm config set registry https://registry.npm.taobao.org
开始¶
node -v # v14.16.0 npm install -g webpack npm install -g webpack-dev-server mkdir Scratch3 # cd Scratch3 git clone https://github.com/LLK/scratch-gui cd scratch-gui npm install
现在你就得到了一个可在本地运行Scratch3.0编辑器。
运行webpack-dev-server --https
,打开:https://127.0.0.1:8601/
我们统一采用https来运行scratch-gui,因为以后接入codelab-adapter需要使用https。
运行scratch-vm¶
cd Scratch3 git clone https://github.com/LLK/scratch-vm cd scratch-vm npm install npm link # 新开一个shell cd scratch-gui npm link scratch-vm
完成npm link scratch-vm
之后,scratch-gui就会采用我们开发环境里的scratch-vm,而不是默认的scratch-vm. 这样一来我们就可以定制scratch-vm了。
scratch-vm是什么呢?
Virtual Machine used to represent, run, and maintain the state of programs for Scratch 3.0.
可见 Scratch 3.0由scratch-vm提供动力支持。你可以把它理解为运行scratch3.0积木代码的引擎/解释器。
Scratch3.0 Extension便是在scratch-vm中写的。
第一个Scratch3.0 Extension¶
先不解释过多的概念。
Learning by using, 开始创建我们的第一个Scratch3.0 Extension。
插件源码¶
在scratch-vm/src/extensions
目录创建scratch3_hello_world/index.js
直接把hello world插件的代码贴上来,对照插件的实际功能和源码,应该能很快理解每一部分,语义还是非常清晰的。
const ArgumentType = require('../../extension-support/argument-type'); const BlockType = require('../../extension-support/block-type'); const formatMessage = require('format-message'); // const MathUtil = require('../../util/math-util'); /** * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI. * @type {string} */ // eslint-disable-next-line max-len const blockIconURI = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+cGVuLWljb248L3RpdGxlPjxnIHN0cm9rZT0iIzU3NUU3NSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik04Ljc1MyAzNC42MDJsLTQuMjUgMS43OCAxLjc4My00LjIzN2MxLjIxOC0yLjg5MiAyLjkwNy01LjQyMyA1LjAzLTcuNTM4TDMxLjA2NiA0LjkzYy44NDYtLjg0MiAyLjY1LS40MSA0LjAzMi45NjcgMS4zOCAxLjM3NSAxLjgxNiAzLjE3My45NyA0LjAxNUwxNi4zMTggMjkuNTljLTIuMTIzIDIuMTE2LTQuNjY0IDMuOC03LjU2NSA1LjAxMiIgZmlsbD0iI0ZGRiIvPjxwYXRoIGQ9Ik0yOS40MSA2LjExcy00LjQ1LTIuMzc4LTguMjAyIDUuNzcyYy0xLjczNCAzLjc2Ni00LjM1IDEuNTQ2LTQuMzUgMS41NDYiLz48cGF0aCBkPSJNMzYuNDIgOC44MjVjMCAuNDYzLS4xNC44NzMtLjQzMiAxLjE2NGwtOS4zMzUgOS4zYy4yODItLjI5LjQxLS42NjguNDEtMS4xMiAwLS44NzQtLjUwNy0xLjk2My0xLjQwNi0yLjg2OC0xLjM2Mi0xLjM1OC0zLjE0Ny0xLjgtNC4wMDItLjk5TDMwLjk5IDUuMDFjLjg0NC0uODQgMi42NS0uNDEgNC4wMzUuOTYuODk4LjkwNCAxLjM5NiAxLjk4MiAxLjM5NiAyLjg1NU0xMC41MTUgMzMuNzc0Yy0uNTczLjMwMi0xLjE1Ny41Ny0xLjc2NC44M0w0LjUgMzYuMzgybDEuNzg2LTQuMjM1Yy4yNTgtLjYwNC41My0xLjE4Ni44MzMtMS43NTcuNjkuMTgzIDEuNDQ4LjYyNSAyLjEwOCAxLjI4Mi42Ni42NTggMS4xMDIgMS40MTIgMS4yODcgMi4xMDIiIGZpbGw9IiM0Qzk3RkYiLz48cGF0aCBkPSJNMzYuNDk4IDguNzQ4YzAgLjQ2NC0uMTQuODc0LS40MzMgMS4xNjVsLTE5Ljc0MiAxOS42OGMtMi4xMyAyLjExLTQuNjczIDMuNzkzLTcuNTcyIDUuMDFMNC41IDM2LjM4bC45NzQtMi4zMTYgMS45MjUtLjgwOGMyLjg5OC0xLjIxOCA1LjQ0LTIuOSA3LjU3LTUuMDFsMTkuNzQzLTE5LjY4Yy4yOTItLjI5Mi40MzItLjcwMi40MzItMS4xNjUgMC0uNjQ2LS4yNy0xLjQtLjc4LTIuMTIyLjI1LjE3Mi41LjM3Ny43MzcuNjE0Ljg5OC45MDUgMS4zOTYgMS45ODMgMS4zOTYgMi44NTYiIGZpbGw9IiM1NzVFNzUiIG9wYWNpdHk9Ii4xNSIvPjxwYXRoIGQ9Ik0xOC40NSAxMi44M2MwIC41LS40MDQuOTA1LS45MDQuOTA1cy0uOTA1LS40MDUtLjkwNS0uOTA0YzAtLjUuNDA3LS45MDMuOTA2LS45MDMuNSAwIC45MDQuNDA0LjkwNC45MDR6IiBmaWxsPSIjNTc1RTc1Ii8+PC9nPjwvc3ZnPg=='; const menuIconURI = blockIconURI; class Scratch3HelloBlocks { constructor (runtime) { /** * The runtime instantiating this block package. * @type {Runtime} */ this.runtime = runtime; } /** * The key to load & store a target's pen-related state. * @type {string} */ static get STATE_KEY () { return 'Scratch.helloWorld'; } /** * @returns {object} metadata for this extension and its blocks. */ getInfo () { return { id: 'helloWorld', name: formatMessage({ id: 'helloWorld.categoryName', default: 'hello World', description: 'Label for the hello world extension category' }), // menuIconURI: menuIconURI, blockIconURI: blockIconURI, // showStatusButton: true, blocks: [ { opcode: 'say', blockType: BlockType.COMMAND, text: formatMessage({ id: 'helloWorld.say', default: 'say [TEXT]', description: 'say something' }), arguments: { TEXT: { type: ArgumentType.STRING, defaultValue: formatMessage({ id: 'helloWorld.defaultTextToSay', default: 'hello world', description: 'default text to say.' }) } } } ], menus: {} }; } say (args, util) { const message = args.TEXT; console.log(message); this.runtime.emit('SAY', util.target, 'say', message); } } module.exports = Scratch3HelloBlocks;
配置¶
为了将插件挂载到scratch-gui的插件区,我们需要做一些配置工作。
编辑scratch-vm/src/extension-support/extension-manager.js
展示一下编辑前后的git diff
信息:
let builtinExtensions = { // ros: () => require('../extensions/scratch3_ros'), // This is an example that isn't loaded with the other core blocks, // but serves as a reference for loading core blocks as extensions. coreExample: () => require('../blocks/scratch3_core_example'), // These are the non-core built-in extensions. pen: () => require('../extensions/scratch3_pen'), wedo2: () => require('../extensions/scratch3_wedo2'), music: () => require('../extensions/scratch3_music'), microbit: () => require('../extensions/scratch3_microbit'), text2speech: () => require('../extensions/scratch3_text2speech'), translate: () => require('../extensions/scratch3_translate'), videoSensing: () => require('../extensions/scratch3_video_sensing'), ev3: () => require('../extensions/scratch3_ev3'), makeymakey: () => require('../extensions/scratch3_makeymakey'), boost: () => require('../extensions/scratch3_boost'), gdxfor: () => require('../extensions/scratch3_gdx_for'), // your extension helloWorld: () => require('../extensions/scratch3_hello_world'), } /**
接着我们将插件的封面图和小图标放到scratch-gui/src/lib/libraries/extensions/
目录里png图片尺寸推荐600x372
编辑scratch-gui/src/lib/libraries/extensions/index.jsx
往 export default
里添加:
import helloworldImage from './helloworld.png'; export default [ ... { name: 'Hello World', extensionId: 'helloWorld', iconURL: helloworldImage, description: 'hello world', featured: true } ];
完成¶
使用插件:
进阶¶
阅读已有的例子: scratch-vm/src/extensions