Skip to content

piotr-oles/as-loader

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

85 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AssemblyScript logo webpack logo

as-loader

AssemblyScript loader for webpack

npm version build status

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();
Alternatively, you can use exported URL directly:
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>>
as-loader/runtime binding code example:
// ./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>>
as-loader/runtime/bind binding code example:
// ./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:

  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.

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