使用Angular读取通过wasm-bindgen创建的包

使用Angular加载通过wasm-bindgen创建的包。

首先

随着wasm-bindgen等库工具的发展,用Rust编写的程序能在Web前端运行变得切实可行。然而,要充分利用它们在真正的Web前端中,需要与Angular等Web前端框架共存。
本文将以使用wasm-bindgen创建的mandelbrot包作为例子,介绍在使用angular-cli(版本7.0)创建的Angular项目中加载和运行WebAssembly代码的注意事项。

备办

$ ng new angular-example
$ cd angular-example
$ npm i path/to/mandelbrot/pkg

组件的实现

请将运行mandelbrot包所需的最小部分实现为组件。
根据需要进行模块化。

import { AfterViewInit, Component, ViewChild } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
  @ViewChild('display') display;

  ngAfterViewInit () {
    const mod = import('mandelbrot')
    const bg = import('mandelbrot/mandelbrot_bg')
    Promise.all([mod, bg]).then(([{ mandelbrot, Screen }, { memory }]) => {
      const canvas = this.display.nativeElement;
      const screen = new Screen(canvas.width, canvas.height);

      const bytes = new Uint8ClampedArray(memory.buffer, screen.pointer(), screen.size());
      const image = new ImageData(bytes, screen.width, screen.height);
      mandelbrot(screen, -3, -2, 0.01, 100);

      const ctx = canvas.getContext('2d');
      ctx.putImageData(image, 0, 0);
    })
  }
}
<canvas #display width="600" height="400"></canvas>

我认为在这个阶段进行构建时会出现以下错误。

ERROR in src/app/app.component.ts(12,17): error TS1323: Dynamic import is only supported when '--module' flag is 'commonjs' or 'esNext'.
src/app/app.component.ts(13,16): error TS1323: Dynamic import is only supported when '--module' flag is 'commonjs' or 'esNext'.
src/app/app.component.ts(13,23): error TS2307: Cannot find module 'mandelbrot/mandelbrot_bg'.

我們會解決這個問題。

启用动态导入。

首先,按照错误信息的提示,我们需要编辑 tsconfig.json 文件,启用 Dynamic import。

@@ -5,7 +5,7 @@
     "outDir": "./dist/out-tsc",
     "sourceMap": true,
     "declaration": false,
-    "module": "es2015",
+    "module": "esNext",
     "moduleResolution": "node",
     "emitDecoratorMetadata": true,
     "experimentalDecorators": true,

当进行再次构建时,可以确认错误已经减少如下。

ERROR in src/app/app.component.ts(13,23): error TS2307: Cannot find module 'mandelbrot/mandelbrot_bg'.

将bg模块进行类型标注

追加说明:由于在wasm-bindgen中进行了对mandelbrot_bg.d.ts的生成补丁,所以在将来的发布版本中应该不会再发生这种情况了。

由于缺少 mandelbrot/mandelbrot_bg 的类型定义文件(mandelbrot_bg.d.ts),导致剩余的错误发生。有几种避免方法可以考虑,但我认为最简单的方法是在 mandelbrot 包中预先创建该文件。

如果在wasm-bindgen上提出问题,它似乎会在wasm-bindgen方面进行生成的支持。

在Mandelbrot软件包中,将以下文件添加到代码中。

import 'webassembly-js-api';

export var memory : WebAssembly.Memory;

同时,我们也会添加额外的依赖包。

$ npm i @types/webassembly-js-api

返回Angular侧进行构建后,出现了以下错误的替代形式。

ERROR in ../mandelbrot/pkg/mandelbrot.js
Module not found: Error: Can't resolve './mandelbrot_bg' in '/Users/likr/src/ionic20181122demo/mandelbrot/pkg'
ERROR in ./src/app/app.component.ts
Module not found: Error: Can't resolve 'mandelbrot/mandelbrot_bg' in '/Users/likr/src/ionic20181122demo/angular-example/src/app'

导入.wasm文件

‘mandelbrot/mandelbrot_bg’的文件扩展名是.wasm,目前导入.wasm扩展名的文件会出现错误。
在普通的webpack环境中,可以导入.wasm扩展名的文件,但在angular-cli环境中却不能。

让我们使用@angular-builders/custom-webpack来扩展angular-cli标准的webpack设置。
首先,需要安装所需的包。

$ npm i -D @angular-builders/custom-webpack @angular-builders/dev-server

为了描述扩展设置,请按照以下方式创建extra-webpack.config.js文件。

module.exports = {
  resolve: {
    extensions: ['.wasm']
  }
}

编辑angular.json以使用@angular-builders/custom-webpack。

@@ -11,8 +11,12 @@
       "schematics": {},
       "architect": {
         "build": {
-          "builder": "@angular-devkit/build-angular:browser",
+          "builder": "@angular-builders/custom-webpack:browser",
           "options": {
+            "customWebpackConfig": {
+              "path": "./extra-webpack.config.js"
+            },
+            "showCircularDependencies": false,
             "outputPath": "dist/angular-example",
             "index": "src/index.html",
             "main": "src/main.ts",
@@ -55,7 +59,7 @@
           }
         },
         "serve": {
-          "builder": "@angular-devkit/build-angular:dev-server",
+          "builder": "@angular-builders/dev-server:generic",
           "options": {
             "browserTarget": "angular-example:build"
           },
@@ -72,8 +76,11 @@
           }
         },
         "test": {
-          "builder": "@angular-devkit/build-angular:karma",
+          "builder": "@angular-builders/custom-webpack:karma",
           "options": {
+            "customWebpackConfig": {
+              "path": "./extra-webpack.config.js"
+            },
             "main": "src/test.ts",
             "polyfills": "src/polyfills.ts",
             "tsConfig": "src/tsconfig.spec.json",

现在,ng build和ng serve可以顺利运行,没有任何错误。

生产版本

当执行 ng build –prod 的时候,仍然会出现错误。

 70% finish module graph WasmFinalizeExportsPlugin/Users/likr/src/ionic20181122demo/angular-example/node_modules/webpack/lib/wasm/WasmFinalizeExportsPlugin.js:33
                                    const importedNames = ref.importedNames;
                                                              ^

TypeError: Cannot read property 'importedNames' of null
    at compilation.hooks.finishModules.tap.modules (/Users/likr/src/ionic20181122demo/angular-example/node_modules/webpack/lib/wasm/WasmFinalizeExportsPlugin.js:33:36)
    at SyncHook.eval [as call] (eval at create (/Users/likr/src/ionic20181122demo/angular-example/node_modules/tapable/lib/HookCodeFactory.js:19:10), <anonymous>:16:1)
    at SyncHook.lazyCompileHook (/Users/likr/src/ionic20181122demo/angular-example/node_modules/tapable/lib/Hook.js:154:20)
    at Compilation.finish (/Users/likr/src/ionic20181122demo/angular-example/node_modules/webpack/lib/Compilation.js:1133:28)
    at hooks.make.callAsync.err (/Users/likr/src/ionic20181122demo/angular-example/node_modules/webpack/lib/Compiler.js:545:17)
    at _done (eval at create (/Users/likr/src/ionic20181122demo/angular-example/node_modules/tapable/lib/HookCodeFactory.js:32:10), <anonymous>:9:1)
    at _err1 (eval at create (/Users/likr/src/ionic20181122demo/angular-example/node_modules/tapable/lib/HookCodeFactory.js:32:10), <anonymous>:36:22)
    at _addModuleChain (/Users/likr/src/ionic20181122demo/angular-example/node_modules/webpack/lib/Compilation.js:1065:12)
    at processModuleDependencies.err (/Users/likr/src/ionic20181122demo/angular-example/node_modules/webpack/lib/Compilation.js:981:9)
    at process._tickCallback (internal/process/next_tick.js:61:11)

目前由angular-cli安装的@angular-devkit/build-angular@0.10所依赖的webpack@4.19.1,在生产构建时读取.wasm文件会导致错误。

在下一个版本的@angular-devkit/build-angular@0.11中,由于webpack已经修复,因此可以通过以下命令更新 @angular-devkit/build-angular。

$ npm i @angular-devkit/build-angular@next

这个问题,时间会为我们解决的。

最后

经历了一些曲折,但终于成功地在Angular项目中通过wasm-bindgen生成的包进行了加载。请注意,需要更改angular-cli的标准设置的一部分。顺便提一下,在Ionic v4中也可以通过类似的步骤加载WebAssembly包。

广告
将在 10 秒后关闭
bannerAds