使用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包。