as-loader
v0.12.0
Published
AssemblyScript loader for webpack
Downloads
18
Maintainers
Readme
Installation
This loader requires AssemblyScript ~0.18, Node.js >= 12 and webpack 5
# with npm
npm install as-loader
npm install --save-dev assemblyscript
# with yarn
yarn add as-loader
yarn add --dev assemblyscript
The minimal webpack.config.js
:
module.exports = {
entry: "src/index.ts",
resolve: {
extensions: [".ts", ".js"],
},
module: {
rules: [
{
test: /\.ts$/,
include: path.resolve(__dirname, "src/assembly"),
loader: "as-loader",
options: {
// optional loader and compiler options
}
},
{
test: /\.ts$/,
exclude: path.resolve(__dirname, "src/assembly"),
loader: "ts-loader",
},
],
},
};
Usage
By default, the loader emits a .wasm
file (+ .wasm.map
if source maps are enabled) and
creates CommonJS module that exports URL to the emitted .wasm
file.
If you enable fallback
option, the loader will emit additional .js
file (+ .js.map
if source maps are enabled)
and will expose async fallback()
function which dynamically imports fallback module.
To simplify loading logic, you can use as-loader/runtime
loader which uses
@assemblyscript/loader, or
as-loader/runtime/bind
loader which uses as-bind.
These loaders provide correct types, checks for WebAssembly support, and uses fallback if available.
import * as myModule from "./assembly/myModule";
import { instantiate } from "as-loader/runtime";
async function loadAndRun() {
const { exports } = await instantiate(myModule);
exports.myFunction(100);
}
loadAndRun();
import * as myModule from "./assembly/myModule";
import { instantiate } from "@assemblyscript/loader";
async function loadAndRun() {
const { exports } = await instantiate<typeof myModule>(
// workaround for TypeScript
fetch((myModule as unknown) as string)
);
exports.myFunction(100);
}
loadAndRun();
API
For more details, check src/runtime directory
as-loader/runtime
This runtime loader uses @assemblyscript/loader under the hood.
export interface WasmModuleInstance<TModule> {
type: "wasm";
exports: AsLoaderRuntime & PointerCastObject<TModule>;
module: WebAssembly.Module;
instance: WebAssembly.Instance;
}
export interface JsModuleInstance<TModule> {
type: "js";
exports: TModule;
}
export type ModuleInstance<TModule> =
| WasmModuleInstance<TModule>
| JsModuleInstance<TModule>;
export function instantiate<TModule>(
module: TModule,
load: (url: string) => Promise<unknown>,
imports?: object,
fallback: boolean = false,
supports?: () => boolean
): Promise<ModuleInstance<TModule>>
// ./src/assembly/sayHello.ts
export function sayHello(firstName: string, lastName: string): string {
return `Hello ${firstName} ${lastName}!`;
}
// ./src/sayHello.ts
import * as sayHelloModule from "./assembly/sayHello";
import { instantiate } from "as-loader/runtime";
export async function loadModule(): Promise<typeof sayHelloModule> {
const { exports } = await instantiate(sayHelloModule, fetch);
const { __pin, __unpin, __newString, __getString } = exports;
function sayHello(firstName: string, lastName: string): string {
const firstNamePtr = __pin(__newString(firstName));
const lastNamePtr = __pin(__newString(lastName));
const result = __getString(
exports.sayHello(firstNamePtr, lastNamePtr)
);
__unpin(firstNamePtr);
__unpin(lastNamePtr);
return result;
}
return { sayHello };
}
as-loader/runtime/bind
This runtime loader uses as-bind under the hood.
Requires bind
option enabled in the webpack loader configuration.
Keep in mind that currently it's recommended to manually set
Function.returnType
export interface BoundWasmModuleInstance<TModule, TImports> {
type: "wasm-bound";
exports: AsLoaderRuntime & BoundExports<TModule>;
unboundExports: AsLoaderRuntime & PointerCastObject<TModule>;
importObject: TImports;
module: WebAssembly.Module;
instance: WebAssembly.Instance;
}
export interface JsModuleInstance<TModule> {
type: "js";
exports: TModule;
}
type BoundModuleInstance<TModule, TImports> =
| BoundWasmModuleInstance<TModule, TImports>
| JsModuleInstance<TModule>;
export function instantiate<TModule, TImports>(
module: TModule,
load: (url: string) => Promise<unknown>,
imports?: TImports,
fallback: boolean = false,
supports?: () => boolean
): Promise<BoundModuleInstance<TModule, TImports>>
// ./src/assembly/sayHello.ts
export function sayHello(firstName: string, lastName: string): string {
return `Hello ${firstName} ${lastName}!`;
}
// ./src/sayHello.ts
import * as sayHelloModule from "./assembly/sayHello";
import { instantiate } from "as-loader/runtime/bind";
export async function loadModule(): Promise<typeof sayHelloModule> {
const module = await instantiate(sayHelloModule, fetch);
return { sayHello: module.exports.sayHello };
}
Binding
There are 2 aspects that you have to consider when interacting with a WebAssembly module:
WebAssembly doesn't support function arguments and returns others than
number | boolean | bigint
yet. Because of that, you have to manually translate between WebAssembly pointers and JavaScript objects.The alternative is to enable the
bind
option and useas-loader/runtime/bind
loader which uses an as-bind library. This simplifies passing types like strings and arrays.WebAssembly doesn't provide Garbage Collector yet (proposal) - to manage memory, AssemblyScript offers very lightweight GC implementation. If you use it (see
runtime
option), you have to manually__pin
and__unpin
pointers to instruct GC if given data can be collected or not.
Fallback
If you need to support older browsers like Internet Explorer or Edge < 16,
you can use the fallback
option. A fallback module is different from WebAssembly one because you don't have to bind it.
// webpack.config.js
module.exports = {
entry: "src/index.ts",
resolve: {
extensions: [".ts", ".js"],
},
module: {
rules: [
{
test: /\.ts$/,
include: path.resolve(__dirname, "src/assembly"),
use: [
// fallback loader (must be before as-loader)
{
loader: "ts-loader",
options: {
transpileOnly: true
}
},
// as-loader, apart from building .wasm file,
// will forward assembly script files to the fallback loader above
// to build a .js file
{
loader: "as-loader",
options: {
fallback: true
}
}
]
},
{
test: /\.ts$/,
exclude: path.resolve(__dirname, "src/assembly"),
loader: "ts-loader",
},
],
},
};
// ./src/assembly/sayHello.ts
export function sayHello(firstName: string, lastName: string): string {
return `Hello ${firstName} ${lastName}!`;
}
// ./src/sayHello.ts
import * as sayHelloModule from "./assembly/sayHello";
import { instantiate } from "as-loader/runtime";
export async function loadModule(): Promise<typeof sayHelloModule> {
// set fallback option to true (opt-in)
const module = await instantiate(sayHelloModule, fetch, undefined, true);
if (module.type === 'wasm') {
const { __pin, __unpin, __newString, __getString } = exports;
function sayHello(firstName: string, lastName: string): string {
const firstNamePtr = __pin(__newString(firstName));
const lastNamePtr = __pin(__newString(lastName));
const result = __getString(
exports.sayHello(firstNamePtr, lastNamePtr)
);
__unpin(firstNamePtr);
__unpin(lastNamePtr);
return result;
}
return { sayHello };
} else {
return { sayHello: module.exports.sayHello }
}
}
Options
Loader Options
| Name | Type | Description |
|------------|---------| ----------- |
| name
| string | Output asset name template, [name].[contenthash].wasm
by default. |
| bind
| boolean | If true, adds as-bind library files to the compilation (required if you want to use as-loader/runtime/bind
). |
| fallback
| boolean | If true, creates additional JavaScript file which can be used if WebAssembly is not supported. |
| raw
| boolean | If true, returns binary instead of emitting file. Use for chaining with other loaders. |
Compiler Options
Options passed to the AssemblyScript compiler.
| Name | Type | Description |
|------------------|----------| ----------- |
| debug
| boolean | Enables debug information in emitted binaries, enabled by default in webpack development mode. |
| optimizeLevel
| number | How much to focus on optimizing code, 3 by default. [0-3] |
| shrinkLevel
| number | How much to focus on shrinking code size, 1 by default. [0-2] |
| coverage
| boolean | Re-optimizes until no further improvements can be made. |
| noAssert
| boolean | Replaces assertions with just their value without trapping, enabled by default in webpack production mode. |
| importMemory
| boolean | Imports the memory provided as 'env.memory'. |
| noExportMemory
| boolean | Does not export the memory as 'memory'. |
| initialMemory
| number | Sets the initial memory size in pages. |
| maximumMemory
| number | Sets the maximum memory size in pages. |
| sharedMemory
| boolean | Declare memory as shared. Requires maximumMemory. |
| importTable
| boolean | Imports the function table provided as 'env.table'. |
| exportTable
| boolean | Exports the function table as 'table'. |
| runtime
| string | Specifies the runtime variant to include in the program. Available runtime are: "incremental" (default), "minimal", "stub" |
| exportRuntime
| boolean | Exports the runtime helpers (__new, __collect etc.). Enabled by default. |
| explicitStart
| boolean | Exports an explicit '_start' function to call. |
| enable
| string[] | Enables WebAssembly features being disabled by default. Available features are: "sign-extension", "bulk-memory", "simd", "threads", "reference-types", "gc" |
| disable
| string[] | Disables WebAssembly features being enabled by default. Available features are: "mutable-globals" |
| lowMemoryLimit
| boolean | Enforces very low (<64k) memory constraints. |
| memoryBase
| number | Sets the start offset of emitted memory segments. |
| tableBase
| number | Sets the start offset of emitted table elements. |
| trapMode
| string | Sets the trap mode to use. Available modes are: "allow", "clamp", "js" |
| noValidate
| boolean | Skips validating the module using Binaryen. |
License
MIT