- 作者:约克
- 原文地址:https://yorkyu.cn/scratch-example-build-cra-blocks-vm-14f14897be6a.html
- 文章版权归作者所有,转载请注明出处!
一,前言
本文教你从0到1教你,如何使用 create-react-app 搭建scratch项目,实现简基础的积木编程与舞台渲染效果。Github项目usetools/scratch-example/v1.0.0。
1.1 技术栈
- web 框架
- react
- redux
- react-redux
- 脚手架
- create-react-app
- 积木编程技术
- scratch-blocks
- scratch-vm
- scratch-render
- scratch-storage
- 其他
- less
- ES6
- Typescript
1.2. 目录结构
代码语言:javascript复制.scratch-example
├── loader # scratch-render 自定义webpack loader(文件类型:vert/frag)
├── src
├── App.less
├── App.tsx # 应用入口
├── components
│ ├── Block # 积木编程区域
│ ├── Gui # 工作台
│ └── Stage # 舞台渲染区域
├── index.tsx # 项目入口
├── lib
│ ├── block-options.ts # 积木配置
│ ├── blocks.ts
│ ├── empty-project.json # 初始化项目
│ ├── make-toolbox-xml.js # 积木块
│ ├── sprites.json # 角色
│ ├── storage.js # 存储对象
│ └── vm-manager.js # 渲染虚拟机
├── logo.svg
├── react-app-env.d.ts
├── reportWebVitals.ts
├── setupTests.ts
└── store # redux store
├── index.js
├── src-source.js
└── vm.js
二,初始化React项目
2.1. 新建项目
使用create-react-app
创建 Typescript React 项目。
$ npx create-react-app scratch-example --template typescript
2.2. 定制CRA配置
react-app-rewired
可以在不 ‘eject’ 也不创建额外 react-scripts 的情况下修改 create-react-app
内置的 webpack 配置,然后你将拥有 create-react-app 的一切特性,且可以根据你的需要去配置 webpack 的 plugins, loaders 等。
2.2.1. react-app-rewired
安装 react-app-rewired
代码语言:javascript复制$ yarn add -D react-app-rewired customize-cra
2.2.2. config-overrides.js
在根目录中创建一个 config-overrides.js 文件
代码语言:javascript复制const {
override,
addWebpackPlugin,
} = require('customize-cra');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = override(
addWebpackPlugin(
new CopyWebpackPlugin({
patterns: [{
from: 'node_modules/scratch-blocks/media',
to: 'static/blocks-media'
}]
})
),
);
- 替换 package.json 中 scripts 执行部分
/* package.json */
"scripts": {
- "start": "react-scripts start",
"start": "react-app-rewired start",
- "build": "react-scripts build",
"build": "react-app-rewired build",
- "test": "react-scripts test --env=jsdom",
"test": "react-app-rewired test --env=jsdom",
"eject": "react-scripts eject"
}
三,Scratch工程配置
3.1. config-overrides.js
代码语言:javascript复制const {
override,
addWebpackAlias,
addWebpackPlugin,
babelInclude,
addExternalBabelPlugins,
addWebpackModuleRule,
addLessLoader,
fixBabelImports,
} = require('customize-cra');
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = override(
// webpack alias
addWebpackAlias({
'@src': path.resolve(__dirname, './src'),
}),
// 复制scratch-blocks中的媒体资源到当前项目
addWebpackPlugin(
new CopyWebpackPlugin({
patterns: [{
from: 'node_modules/scratch-blocks/media',
to: 'static/blocks-media'
}]
})
),
addExternalBabelPlugins(
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-transform-async-to-generator',
'@babel/plugin-proposal-object-rest-spread',
),
// 配置实时构建scratch工具
babelInclude([
path.resolve('src'),
/node_modules[\/]scratch-[^\/] [\/]src/,
/node_modules[\/]pify/,
/node_modules[\/]@vernier[\/]godirect/
]),
// 避免scratch-renderer 中加载sharder文件走到react-create-app默认配置的file-loader中,导致编译错误
addWebpackModuleRule({
test: /.(vert|frag)$/i,
use: {
loader: './loader/vert-frag-loader.js', // 关键loader配置,后续文章解释
},
}),
// antd 按需加载配置,babel-plugin-import 是一个用于按需加载组件代码和样式的 babel 插件
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
// 配置项目使用less
addLessLoader({
lessOptions: {
javascriptEnabled: true,
}
}),
);
3.2. Typescript 配置
3.2.1. tsconfig.json
create-react-app 脚手架默认生成的文件 tscofnig.json 新增下述配置。
代码语言:javascript复制{
"extends": "./tsconfig.scratch.json"
}
3.2.2. tsconfig.scratch.json
在项目根目录新建文件tsconfig.scratch.json
,给项目添加Typesscript扩展配置。
{
"compilerOptions": {
// scratch 不支持ts,固开启;在表达式和声明上有隐含的any类型。
"noImplicitAny": false,
// 与 webpack alias 对应
"paths": {
"@src/*": ["./src/*"],
},
}
}
3.3. 构建scratch工程
虽然通过npm包形式安装和使用scratch相关工程,但仍需配置webpack进行构建scratch。
3.3.1. 原因分析
比如 scratch-vm
包中,当在浏览器的工程中引用时,默认引入文件路径为源码:./src/index.js
3.3.2. 构建配置
在config-overrides.js
配置中,把scratch相关源码路径,添加到babel构建路径中。
addExternalBabelPlugins(
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-transform-async-to-generator',
'@babel/plugin-proposal-object-rest-spread',
),
// 配置实时构建scratch工具
babelInclude([
path.resolve('src'),
/node_modules[\/]scratch-[^\/] [\/]src/,
/node_modules[\/]pify/,
/node_modules[\/]@vernier[\/]godirect/
]),
3.4. 自定义loader
由于create-react-app
默认的webpack配置中,有规则会使scratch-render
加载vert/frag
文件时出现错误,经过分析对比,最终使用自定义vert-frag-loader.js
loader,进行适配处理。
3.4.1. 错误问题表象
启动项目后,在浏览器端会出现twgl.js
报错。
1: #define DRAW_MODE_default
2: export default __webpack_public_path__ "static/media/sprite.485d82de.vert";
*** Error compiling shader: ERROR: 0:2: 'export' : syntax error
twgl-full.js:2717 Uncaught TypeError: Cannot read properties of null (reading 'uniformSetters')
3.4.2. 原因分析
scratch-render/src/ShaderManager.js 工具中对shaders文件的引用使用了内联式的raw-loader
。点击查看webpack loader内联方式
const vsFullText = definesText require('raw-loader!./shaders/sprite.vert');
const fsFullText = definesText require('raw-loader!./shaders/sprite.frag');
而项目脚手架create-react-app
默认配置中的构建规则file-loader
与上述scratch-render
中的shaders资源引用产生冲突。默认走到了下述模块构建规则中。react-scripts/config/webpack.config.js
{
loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise be processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/.(js|mjs|jsx|ts|tsx)$/, /.html$/, /.json$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
3.4.3. 解决方法
通过自定义loader
,使.vert
.frag
文件构建时使用该定义loader。自定义loader如下:
module.exports = function vertFragLoader(source) {
// 替换require('raw-loader!./shaders/sprite.frag')中生成的module.exports,其中raw-loader使用0.5.1版本
const json = source.replace(/module.exports = \"/, '')
.replace(/\"/, '');
return json;
}
配置自定义loader到webpack配置中config-overrides.js
。
addWebpackModuleRule({
test: /.(vert|frag)$/i,
use: {
loader: './loader/vert-frag-loader.js', // 关键loader配置,后续文章解释
},
}),
四,Scratch项目
Scratch
项目搭建,关键步骤有:初始化积木块,初始化虚拟机,初始化动画渲染舞台,加载初始化项目
4.1. 初始化积木块
在文件scratch-example/src/components/Block/index.tsx中实现初始化工作。关键代码如下:
代码语言:javascript复制// 初始化积木块
const workspace = state.scratchBlocks.inject(divRef.current, state.options);
state.workspace.addChangeListener(vm.blockListener);
state.flyoutWorkspace.addChangeListener(vm.flyoutBlockListener);
4.2. 初始化虚拟机
在文件scratch-example/src/store/vm.js中实现初始化工作。关键代码如下:
代码语言:javascript复制const vm = new VM()
//在Scratch-render添加远程地址,使vm能够获取mit服务器上的资源文件
storage.addOfficialScratchWebStores()
vm.attachStorage(storage)
// 添加sb2支持
vm.attachV2SVGAdapter(new V2SVGAdapter())
vm.attachV2BitmapAdapter(new V2BitmapAdapter())
vm.setCompatibilityMode(true)
4.3. 初始化动画渲染舞台
在文件scratch-example/src/components/Stage/index.tsx中实现初始化工作。关键代码如下:
代码语言:javascript复制// 生成渲染对象
const renderer = new Renderer(canvasRef.current);
vm.attachRenderer(renderer);
vm.renderer.draw();
// 设置canvas画布大小
state.renderer.resize(offsetWidth, offsetHeight);
// 设置舞台
state.renderer.setStageSize(-offsetWidth / 2, offsetWidth / 2, -offsetHeight / 2, offsetHeight / 2);
4.4. 加载初始化项目
在文件scratch-example/src/components/Gui/index.tsx中实现初始化工作。关键代码如下:
代码语言:javascript复制// 加载默认项目
vm.loadProject(emptyProject);
// 启动渲染进程
vm.start();
参考
- [1] scratch-gui
- [2] scratch-demo