WebAssembly学习(三):AssemblyScript - TypeScript到WebAssembly的编译
虽然说只要高级语言能转换成 LLVM IR,就能被编译成 WebAssembly 字节码,官方也推荐c/c++的方式,但是让一个前端工程师去熟练使用c/c++显然是有点困难,那么TypeScript 的方式便是前端编写 WebAssembly 最佳选择。
要将TypeScript 编译为WebAssembly,就要用到
1 cnpm install --save-dev AssemblyScript/assemblyscript
执行过程如下,安装完成之后只有一个node_modules文件夹
1 E:\Code\assembly>cnpm install --save-dev AssemblyScript/assemblyscript 2 - [@AssemblyScript/assemblyscript] install from git github:AssemblyScript/assemblyscript, may be very slow, please keep patience 3 √ Installed 1 packages 4 √ Linked 15 latest versions 5 √ Run 0 scripts 6 Recently updated (since 2019-02-17): 1 packages (detail see file E:\Code\assembly\node_modules\.recently_updates.txt) 7 √ All packages installed (14 packages installed from npm registry, 1 packages installed from git, used 1m(network 1m), speed 10.27kB/s, json 14(31.43kB), tarball 707.23kB)
1 npx asinit .
就像使用express初始化项目一样,执行过程如下
1 E:\Code\assembly>npx asinit . 2 Version: 0.6.0 3 4 This command will make sure that the following files exist in the project 5 directory 'E:\Code\assembly': 6 7 ./assembly 8 Directory holding the AssemblyScript sources being compiled to WebAssembly. 9 10 ./assembly/tsconfig.json 11 TypeScript configuration inheriting recommended AssemblyScript settings. 12 13 ./assembly/index.ts 14 Exemplary entry file being compiled to WebAssembly to get you started. 15 16 ./build 17 Build artifact directory where compiled WebAssembly files are stored. 18 19 ./build/.gitignore 20 Git configuration that excludes compiled binaries from source control. 21 22 ./index.js 23 Main file loading the WebAssembly module and exporting its exports. 24 25 ./package.json 26 Package info containing the necessary commands to compile to WebAssembly. 27 28 The command will try to update existing files to match the correct settings 29 for this instance of the compiler in 'E:\Code\assembly\node_modules\_assemblyscript@0.6.0@assemblyscript'. 30 31 Do you want to proceed? [Y/n] y 32 33 - Making sure that the project directory exists... 34 Exists: E:\Code\assembly 35 36 - Making sure that the 'assembly' directory exists... 37 Created: E:\Code\assembly\assembly 38 39 - Making sure that 'assembly/tsconfig.json' is set up... 40 Created: E:\Code\assembly\assembly\tsconfig.json 41 42 - Making sure that 'assembly/index.ts' exists... 43 Created: E:\Code\assembly\assembly\index.ts 44 45 - Making sure that the 'build' directory exists... 46 Created: E:\Code\assembly\build 47 48 - Making sure that 'build/.gitignore' is set up... 49 Created: E:\Code\assembly\build\.gitignore 50 51 - Making sure that 'package.json' contains the build commands... 52 Updated: E:\Code\assembly\package.json 53 54 - Making sure that 'index.js' exists... 55 Created: E:\Code\assembly\index.js 56 57 Done! 58 59 To edit the entry file, open 'assembly/index.ts' in your editor of choice. 60 Create as many additional files as necessary and use them as imports. 61 62 To build the entry file to WebAssembly when you are ready, run: 63 64 npm run asbuild 65 66 Running the command above creates the following binaries incl. their respective 67 text format representations and source maps: 68 69 ./build/untouched.wasm 70 ./build/untouched.wasm.map 71 ./build/untouched.wat 72 73 ^ The untouched WebAssembly module as generated by the compiler. 74 This one matches your sources exactly, without any optimizations. 75 76 ./build/optimized.wasm 77 ./build/optimized.wasm.map 78 ./build/optimized.wat 79 80 ^ The optimized WebAssembly module using default optimization settings (-O2s). 81 You can change the optimization settings in 'package.json'. 82 83 Additional documentation is available at the AssemblyScript wiki: 84 85 https://github.com/AssemblyScript/assemblyscript/wiki 86 87 Have a nice day! 88 89 E:\Code\assembly>
完成的目录结构是这样的:
目录结构含义:
assembly / index.ts上的示例性条目文件
index.js中的通用索引文件,用于加载已编译的二进制文件和必要的配置文件,如package.json和tsconfig.json
初始化后,只需在编码时使用现有的TypeScript工具,并使用编译器手动构建到WebAssembly,或者使用生成的构建任务:按照提示,运行以下命令:
1 npm run asbuild
build文件夹初始化只有一个.gitignore,该命令会在build文件夹中创建.wat与.wasm文件
以上只是构建一个示例项目,如果需要适合开发的安装,可以通过克隆GitHub存储库来实现:
1 $> git clone https://github.com/AssemblyScript/assemblyscript.git 2 $> cd assemblyscript 3 $> npm install 4 $> npm link
请注意,编译器的新克隆将使用dist /中的分发文件,但它也可以在npm运行清理后直接通过ts节点运行源,这在开发中很有用。 也可以通过运行asc -v来检查这种情况(如果它声明-dev则运行源)。
3.使用编译器
类似于TypeScript的tsc编译为JavaScript,AssemblyScript的asc编译为WebAssembly:
例如,用 TypeScript 实现斐波那契序列计算的模块 f.ts 如下:
1 export function f(x: i32): i32 { 2 if (x === 1 || x === 2) { 3 return 1; 4 } 5 return f(x - 1) + f(x - 2) 6 }
执行以下asc命令,就能把以上代码编译成可运行的 WebAssembly 模块,比起c/c++的编译方式,这种人性化多了。
1 asc f.ts -o f.wasm
以上代码中出现了一个新的内置类型 i32,这是 AssemblyScript 在 TypeScript 的基础上内置的类型。 AssemblyScript 和 TypeScript 有细微区别,AssemblyScript 是 TypeScript 的子集,为了方便编译成 WebAssembly 在 TypeScript 的基础上加了更严格的类型限制, 区别如下:
- 比 TypeScript 多了很多更细致的内置类型,以优化性能和内存占用,详情文档;
- 不能使用 any 和 undefined 类型,以及枚举类型;
- 可空类型的变量必须是引用类型,而不能是基本数据类型如 string、number、boolean;
- 函数中的可选参数必须提供默认值,函数必须有返回类型,无返回值的函数返回类型需要是 void;
- 不能使用 JS 环境中的内置函数,只能使用 AssemblyScript 提供的内置函数。
总体来说 AssemblyScript 比 TypeScript 又多了很多限制,编写起来会觉得局限性很大; 用 AssemblyScript 来写 WebAssembly 经常会出现 tsc 编译通过但运行 WebAssembly 时出错的情况,这很可能就是你没有遵守以上限制导致的;但 AssemblyScript 通过修改 TypeScript 编译器默认配置能在编译阶段找出大多错误。
AssemblyScript 的实现原理其实也借助了 LLVM,它通过 TypeScript 编译器把 TS 源码解析成 AST,再把 AST 翻译成 IR,再通过 LLVM 编译成 WebAssembly 字节码实现; 上面提到的各种限制都是为了方便把 AST 转换成 LLVM IR。
asc编译器具体API如下
1 SYNTAX 2 asc [entryFile ...] [options] 3 4 EXAMPLES 5 asc hello.ts 6 asc hello.ts -b hello.wasm -t hello.wat 7 asc hello1.ts hello2.ts -b -O > hello.wasm 8 9 OPTIONS 10 --version, -v Prints just the compiler's version and exits. 11 --help, -h Prints this message and exits. 12 --optimize, -O Optimizes the module. Also has the usual shorthands: 13 14 -O Uses defaults. Equivalent to -O2s 15 -O0 Equivalent to --optimizeLevel 0 16 -O1 Equivalent to --optimizeLevel 1 17 -O2 Equivalent to --optimizeLevel 2 18 -O3 Equivalent to --optimizeLevel 3 19 -Oz Equivalent to -O but with --shrinkLevel 2 20 -O3s Equivalent to -O3 with --shrinkLevel 1 etc. 21 22 --optimizeLevel How much to focus on optimizing code. [0-3] 23 --shrinkLevel How much to focus on shrinking code size. [0-2, s=1, z=2] 24 --validate, -c Validates the module using Binaryen. Exits if invalid. 25 --baseDir Specifies the base directory of input and output files. 26 --outFile, -o Specifies the output file. File extension indicates format. 27 --binaryFile, -b Specifies the binary output file (.wasm). 28 --textFile, -t Specifies the text output file (.wat). 29 --asmjsFile, -a Specifies the asm.js output file (.js). 30 --idlFile, -i Specifies the WebIDL output file (.webidl). 31 --tsdFile, -d Specifies the TypeScript definition output file (.d.ts). 32 --sourceMap Enables source map generation. Optionally takes the URL 33 used to reference the source map from the binary file. 34 --debug Enables debug information in emitted binaries. 35 --noAssert Replaces assertions with just their value without trapping. 36 --noEmit Performs compilation as usual but does not emit code. 37 --importMemory Imports the memory instance provided by the embedder. 38 --sharedMemory Declare memory as shared by settings the max shared memory. 39 --memoryBase Sets the start offset of compiler-generated static memory. 40 --importTable Imports the function table instance provided by the embedder. 41 --noLib Does not include the shipped standard library. 42 --lib Adds one or multiple paths to custom library components and 43 uses exports of all top-level files at this path as globals. 44 --use, -u Aliases a global object under another name, e.g., to switch 45 the default 'Math' implementation used: --use Math=JSMath 46 --trapMode Sets the trap mode to use. 47 48 allow Allow trapping operations. This is the default. 49 clamp Replace trapping operations with clamping semantics. 50 js Replace trapping operations with JS semantics. 51 52 --runPasses Specifies additional Binaryen passes to run after other 53 optimizations, if any. See: Binaryen/src/passes/pass.cpp 54 --enable Enables additional (experimental) WebAssembly features. 55 56 sign-extension Enables sign-extension operations 57 mutable-global Enables mutable global imports and exports 58 bulk-memory Enables bulk memory operations 59 simd Enables SIMD types and operations. 60 threads Enables threading and atomic operations. 61 62 --transform Specifies the path to a custom transform to 'require'. 63 --measure Prints measuring information on I/O and compile times. 64 --noColors Disables terminal colors.
编译器API也可以以编程方式使用。 它接受与CLI相同的选项,但也允许您覆盖stdout和stderr和/或提供回调:
1 const asc = require("assemblyscript/cli/asc"); 2 asc.main([ 3 "myModule.ts", 4 "--binaryFile", "myModule.wasm", 5 "--optimize", 6 "--sourceMap", 7 "--measure" 8 ], { 9 stdout: process.stdout, 10 stderr: process.stderr 11 }, function(err) { 12 if (err) 13 throw err; 14 ... 15 });
可以通过编程方式获取可用的命令行选项:
1 const options = require("assemblyscript/cli/asc.json"); 2 ...
您也可以直接编译源字符串,例如在浏览器环境中:
1 const { binary, text, stdout, stderr } = asc.compileString(`...`, { optimize: 2 }); 2 ...
4.使用编译的.wasm模块
index.js示例代码如下:
1 const fs = require("fs"); 2 const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/optimized.wasm")); 3 const imports = {}; 4 Object.defineProperty(module, "exports", { 5 get: () => new WebAssembly.Instance(compiled, imports).exports 6 });
使用上面编译的f.wasm
1 fetch('f.wasm') // 网络加载 f.wasm 文件 2 .then(res => res.arrayBuffer()) // 转成 ArrayBuffer 3 .then(WebAssembly.instantiate) // 编译为当前 CPU 架构的机器码 + 实例化 4 .then(mod => { // 调用模块实例上的 f 函数计算 5 console.log(mod.instance.f(50)); 6 });
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探