【Scratch入门到精通】使用CRA搭建项目

  • 作者:约克
  • 原文地址:
  • 文章版权归作者所有,转载请注明出处!

blog/20211010115741_0c378ae33fc1b58f64e2b6259c77c61b.png

一,前言

本文教你从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. 目录结构

.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

$ yarn add -D react-app-rewired customize-cra

2.2.2. config-overrides.js

在根目录中创建一个 config-overrides.js 文件

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

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 新增下述配置。

{
+   "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
blog/20211010164033_36bb2eb2b510662aa9e4ef23cc59fce8.png

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')

blog/20211010162352_b27f859b1d91fa5442f72a373b0c31cd.png

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中实现初始化工作。关键代码如下:

// 初始化积木块
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中实现初始化工作。关键代码如下:

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中实现初始化工作。关键代码如下:

// 生成渲染对象
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中实现初始化工作。关键代码如下:

// 加载默认项目
vm.loadProject(emptyProject);
// 启动渲染进程
vm.start();

参考