Home

Awesome

<div align="center"> <img width="100" height="100" src="media/assemblyscript-logo.svg" alt="AssemblyScript logo"> <img width="100" height="100" src="media/webpack-logo.svg" alt="webpack logo"> <h1>as-loader</h1> <p>AssemblyScript loader for webpack</p>

npm version build status

</div>

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();
<details> <summary>Alternatively, you can use exported URL directly:</summary>
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();

</details>

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>>
<details> <summary><code>as-loader/runtime</code> binding code example:</summary>
// ./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 };
}
</details>

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>>
<details> <summary><code>as-loader/runtime/bind</code> binding code example:</summary>
// ./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 };
}
</details>

Binding

There are 2 aspects that you have to consider when interacting with a WebAssembly module:

  1. 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 use as-loader/runtime/bind loader which uses an as-bind library. This simplifies passing types like strings and arrays.

  2. 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.

<details> <summary>Fallback example:</summary>
// 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 }
  }
}
</details>

Options

Loader Options

NameTypeDescription
namestringOutput asset name template, [name].[contenthash].wasm by default.
bindbooleanIf true, adds as-bind library files to the compilation (required if you want to use as-loader/runtime/bind).
fallbackbooleanIf true, creates additional JavaScript file which can be used if WebAssembly is not supported.
rawbooleanIf true, returns binary instead of emitting file. Use for chaining with other loaders.

Compiler Options

Options passed to the AssemblyScript compiler.

NameTypeDescription
debugbooleanEnables debug information in emitted binaries, enabled by default in webpack development mode.
optimizeLevelnumberHow much to focus on optimizing code, 3 by default. [0-3]
shrinkLevelnumberHow much to focus on shrinking code size, 1 by default. [0-2]
coveragebooleanRe-optimizes until no further improvements can be made.
noAssertbooleanReplaces assertions with just their value without trapping, enabled by default in webpack production mode.
importMemorybooleanImports the memory provided as 'env.memory'.
noExportMemorybooleanDoes not export the memory as 'memory'.
initialMemorynumberSets the initial memory size in pages.
maximumMemorynumberSets the maximum memory size in pages.
sharedMemorybooleanDeclare memory as shared. Requires maximumMemory.
importTablebooleanImports the function table provided as 'env.table'.
exportTablebooleanExports the function table as 'table'.
runtimestringSpecifies the runtime variant to include in the program. Available runtime are: "incremental" (default), "minimal", "stub"
exportRuntimebooleanExports the runtime helpers (__new, __collect etc.). Enabled by default.
explicitStartbooleanExports an explicit '_start' function to call.
enablestring[]Enables WebAssembly features being disabled by default. Available features are: "sign-extension", "bulk-memory", "simd", "threads", "reference-types", "gc"
disablestring[]Disables WebAssembly features being enabled by default. Available features are: "mutable-globals"
lowMemoryLimitbooleanEnforces very low (<64k) memory constraints.
memoryBasenumberSets the start offset of emitted memory segments.
tableBasenumberSets the start offset of emitted table elements.
trapModestringSets the trap mode to use. Available modes are: "allow", "clamp", "js"
noValidatebooleanSkips validating the module using Binaryen.

License

MIT