Skip to content

Commit faa5c80

Browse files
authored
Merge pull request #16 from piotr-oles/feature/make-fallback-opt-in
feat: make fallback opt-in
2 parents ed0d2a0 + 713d7a6 commit faa5c80

File tree

4 files changed

+131
-69
lines changed

4 files changed

+131
-69
lines changed

README.md

+124-58
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
<h1>as-loader</h1>
77
<p>AssemblyScript loader for webpack</p>
8-
<p>⚠️ In development ⚠️</p>
98

109
[![npm version](https://img.shields.io/npm/v/as-loader.svg)](https://www.npmjs.com/package/as-loader)
1110
[![build status](https://github.com/piotr-oles/as-loader/workflows/CI/CD/badge.svg?branch=main&event=push)](https://github.com/piotr-oles/as-loader/actions?query=branch%3Amain+event%3Apush)
@@ -55,15 +54,16 @@ module.exports = {
5554

5655
## Usage
5756

58-
By default, the loader emits `.wasm` file (+ `.wasm.map` if source maps are enabled) and
57+
By default, the loader emits a `.wasm` file (+ `.wasm.map` if source maps are enabled) and
5958
creates CommonJS module that exports URL to the emitted `.wasm` file.
6059

6160
If you enable `fallback` option, the loader will emit additional `.js` file (+ `.js.map` if source maps are enabled)
6261
and will expose async `fallback()` function which dynamically imports fallback module.
6362

64-
To simplify loading logic, you can use `as-loader/runtime` loader which uses
65-
`@assemblyscript/loader` under the hood.
66-
The loader provides correct types, checks for WebAssembly support, and uses fallback if available.
63+
To simplify loading logic, you can use `as-loader/runtime` loader which uses
64+
[@assemblyscript/loader](https://github.com/AssemblyScript/assemblyscript/tree/master/lib/loader), or
65+
`as-loader/runtime/bind` loader which uses [as-bind](https://github.com/torch2424/as-bind).
66+
These loaders provide correct types, checks for WebAssembly support, and uses fallback if available.
6767

6868
```typescript
6969
import * as myModule from "./assembly/myModule";
@@ -97,11 +97,13 @@ loadAndRun();
9797

9898
```
9999

100+
</details>
101+
100102
### API
101103
> For more details, check [src/runtime](src/runtime) directory
102104
103105
#### `as-loader/runtime`
104-
This runtime loader uses `@assemblyscript/loader` under the hood.
106+
This runtime loader uses [@assemblyscript/loader](https://github.com/AssemblyScript/assemblyscript/tree/master/lib/loader) under the hood.
105107
```typescript
106108
export interface WasmModuleInstance<TModule> {
107109
type: "wasm";
@@ -123,15 +125,51 @@ export function instantiate<TModule>(
123125
module: TModule,
124126
load: (url: string) => Promise<unknown>,
125127
imports?: object,
126-
fallback?: boolean,
128+
fallback: boolean = false,
127129
supports?: () => boolean
128130
): Promise<ModuleInstance<TModule>>
129131
```
130132

133+
<details>
134+
<summary><code>as-loader/runtime</code> binding code example:</summary>
135+
136+
```typescript
137+
// ./src/assembly/sayHello.ts
138+
export function sayHello(firstName: string, lastName: string): string {
139+
return `Hello ${firstName} ${lastName}!`;
140+
}
141+
142+
// ./src/sayHello.ts
143+
import * as sayHelloModule from "./assembly/sayHello";
144+
import { instantiate } from "as-loader/runtime";
145+
146+
export async function loadModule(): Promise<typeof sayHelloModule> {
147+
const { exports } = await instantiate(sayHelloModule, fetch);
148+
const { __pin, __unpin, __newString, __getString } = exports;
149+
150+
function sayHello(firstName: string, lastName: string): string {
151+
const firstNamePtr = __pin(__newString(firstName));
152+
const lastNamePtr = __pin(__newString(lastName));
153+
const result = __getString(
154+
exports.sayHello(firstNamePtr, lastNamePtr)
155+
);
156+
157+
__unpin(firstNamePtr);
158+
__unpin(lastNamePtr);
159+
160+
return result;
161+
}
162+
163+
return { sayHello };
164+
}
165+
```
166+
167+
</details>
168+
169+
131170
#### `as-loader/runtime/bind`
132-
This runtime loader uses `as-bind` under the hood, so you don't have to resolve pointers for
133-
types like `string`, `number[]`, `Int8Array`, etc. You still have to resolve pointers to objects.
134-
Requires `bind: true` option in the webpack loader configuration.
171+
This runtime loader uses [as-bind](https://github.com/torch2424/as-bind) under the hood.
172+
Requires `bind` option enabled in the webpack loader configuration.
135173
> Keep in mind that currently [it's recommended to manually set `Function.returnType`](https://github.com/torch2424/as-bind#production)
136174
```typescript
137175
export interface BoundWasmModuleInstance<TModule, TImports> {
@@ -162,30 +200,15 @@ export function instantiate<TModule, TImports>(
162200
module: TModule,
163201
load: (url: string) => Promise<unknown>,
164202
imports?: TImports,
165-
fallback?: boolean,
203+
fallback: boolean = false,
166204
supports?: () => boolean
167205
): Promise<BoundModuleInstance<TModule, TImports>>
168206
169207
export const RETURN_TYPES: AsBindReturnTypes;
170208
```
171209

172-
</details>
173-
174-
## Binding
175-
There are 2 aspects that you have to consider when interacting with WebAssembly module:
176-
1. WebAssembly doesn't support function arguments and returns others than `number` and `boolean` yet.
177-
Because of that, you have to [manually translate between WebAssembly pointers and JavaScript objects](https://www.assemblyscript.org/loader.html#usage).
178-
The alternative solution is to use [`as-bind`](https://github.com/torch2424/as-bind#readme) library which
179-
does this automatically for `string` and `array` types (doesn't support objects yet).
180-
2. WebAssembly doesn't provide GC yet ([proposal](https://github.com/WebAssembly/gc)) - to manage memory,
181-
AssemblyScript offers very lightweight GC implementation. If you use it (see `runtime` option - it's `"incremental"` by default),
182-
you have to manually `__pin` and `__unpin` pointers to instruct GC if given data can be collected or not.
183-
184-
The `as-loader/runtime` uses `@assemblyscript/loader` under the hood -
185-
[see the docs](https://www.assemblyscript.org/loader.html) for more information.
186-
187210
<details>
188-
<summary>`as-loader/runtime` binding code example:</summary>
211+
<summary><code>as-loader/runtime/bind</code> binding code example:</summary>
189212

190213
```typescript
191214
// ./src/assembly/sayHello.ts
@@ -195,40 +218,66 @@ export function sayHello(firstName: string, lastName: string): string {
195218
196219
// ./src/sayHello.ts
197220
import * as sayHelloModule from "./assembly/sayHello";
198-
import { instantiate } from "as-loader/runtime";
221+
import { instantiate, RETURN_TYPES } from "as-loader/runtime/bind";
199222
200223
export async function loadModule(): Promise<typeof sayHelloModule> {
201-
// this example doesn't use fallback so TypeScript knows that it's only WASM module
202-
const { exports } = await instantiate(sayHelloModule, fetch, undefined, false);
203-
const { __pin, __unpin, __newString, __getString } = exports;
204-
205-
function sayHello(firstName: string, lastName: string): string {
206-
const firstNamePtr = __pin(__newString(firstName));
207-
const lastNamePtr = __pin(__newString(lastName));
208-
const result = __getString(
209-
exports.sayHello(firstNamePtr, lastNamePtr)
210-
);
211-
212-
__unpin(firstNamePtr);
213-
__unpin(lastNamePtr);
214-
215-
return result;
216-
}
224+
const { exports } = await instantiate(sayHelloModule, fetch);
225+
const { sayHello } = exports;
226+
sayHello.returnType = RETURN_TYPES.STRING;
217227
218228
return { sayHello };
219229
}
220230
```
221231

222232
</details>
223233

224-
You can consider using `bind: true` option and `as-loader/runtime/bind` runtime loader which uses
225-
[`as-bind`](https://github.com/torch2424/as-bind) under the hood - this makes passing types like `string` and `array`
226-
much easier.
234+
## Binding
235+
There are 2 aspects that you have to consider when interacting with a WebAssembly module:
236+
1. WebAssembly doesn't support function arguments and returns others than `number | boolean | bigint` yet.
237+
Because of that, you have to [manually translate between WebAssembly pointers and JavaScript objects](https://www.assemblyscript.org/loader.html#usage).
238+
239+
The alternative is to enable the `bind` option and use `as-loader/runtime/bind` loader which uses an [as-bind](https://github.com/torch2424/as-bind) library.
240+
This simplifies passing types like strings and arrays.
241+
242+
2. WebAssembly doesn't provide Garbage Collector yet ([proposal](https://github.com/WebAssembly/gc)) - to manage memory,
243+
AssemblyScript offers very lightweight GC implementation. If you use it (see `runtime` option),
244+
you have to [manually `__pin` and `__unpin` pointers](https://www.assemblyscript.org/garbage-collection.html#incremental-runtime)
245+
to instruct GC if given data can be collected or not.
246+
247+
## Fallback
248+
If you need to support [older browsers](https://caniuse.com/wasm) like *Internet Explorer* or *Edge* < 16,
249+
you can use the `fallback` option. A fallback module is different from WebAssembly one because you don't have to bind it.
227250

228251

229252
<details>
230-
<summary>`as-loader/runtime/bind` binding code example:</summary>
253+
<summary>Fallback example:</summary>
231254

255+
```js
256+
// webpack.config.js
257+
module.exports = {
258+
entry: "src/index.ts",
259+
resolve: {
260+
extensions: [".ts", ".js"],
261+
},
262+
module: {
263+
rules: [
264+
{
265+
test: /\.ts$/,
266+
include: path.resolve(__dirname, "src/assembly"),
267+
loader: "as-loader",
268+
options: {
269+
fallback: true
270+
}
271+
},
272+
{
273+
test: /\.ts$/,
274+
exclude: path.resolve(__dirname, "src/assembly"),
275+
loader: "ts-loader",
276+
},
277+
],
278+
},
279+
};
280+
```
232281
```typescript
233282
// ./src/assembly/sayHello.ts
234283
export function sayHello(firstName: string, lastName: string): string {
@@ -237,15 +286,32 @@ export function sayHello(firstName: string, lastName: string): string {
237286
238287
// ./src/sayHello.ts
239288
import * as sayHelloModule from "./assembly/sayHello";
240-
import { instantiate, RETURN_TYPES } from "as-loader/runtime/bind";
289+
import { instantiate } from "as-loader/runtime";
241290
242291
export async function loadModule(): Promise<typeof sayHelloModule> {
243-
// this example doesn't use fallback so TypeScript knows that it's only WASM module
244-
const { exports } = await instantiate(sayHelloModule, fetch, undefined, false);
245-
const { sayHello } = exports;
246-
sayHello.returnType = RETURN_TYPES.STRING;
247-
248-
return { sayHello };
292+
// set fallback option to true (opt-in)
293+
const module = await instantiate(sayHelloModule, fetch, undefined, true);
294+
295+
if (module.type === 'wasm') {
296+
const { __pin, __unpin, __newString, __getString } = exports;
297+
298+
function sayHello(firstName: string, lastName: string): string {
299+
const firstNamePtr = __pin(__newString(firstName));
300+
const lastNamePtr = __pin(__newString(lastName));
301+
const result = __getString(
302+
exports.sayHello(firstNamePtr, lastNamePtr)
303+
);
304+
305+
__unpin(firstNamePtr);
306+
__unpin(lastNamePtr);
307+
308+
return result;
309+
}
310+
311+
return { sayHello };
312+
} else {
313+
return { sayHello: module.exports.sayHello }
314+
}
249315
}
250316
```
251317

@@ -257,9 +323,9 @@ export async function loadModule(): Promise<typeof sayHelloModule> {
257323
| Name | Type | Description |
258324
|------------|---------| ----------- |
259325
| `name` | string | Output asset name template, `[name].[contenthash].wasm` by default. |
260-
| `raw` | boolean | If true, returns binary instead of emitting file. Use for chaining with other loaders. |
326+
| `bind` | boolean | If true, adds [as-bind](https://github.com/torch2424/as-bind) library files to the compilation (required if you want to use `as-loader/runtime/bind`). |
261327
| `fallback` | boolean | If true, creates additional JavaScript file which can be used if WebAssembly is not supported. |
262-
| `bind` | boolean | If true, adds `as-bind` library files to the compilation (required if you want to use `as-loader/runtime/bind`). |
328+
| `raw` | boolean | If true, returns binary instead of emitting file. Use for chaining with other loaders. |
263329

264330
#### Compiler Options
265331

@@ -279,7 +345,7 @@ Options passed to the [AssemblyScript compiler](https://www.assemblyscript.org/c
279345
| `sharedMemory` | boolean | Declare memory as shared. Requires maximumMemory. |
280346
| `importTable` | boolean | Imports the function table provided as 'env.table'. |
281347
| `exportTable` | boolean | Exports the function table as 'table'. |
282-
| `runtime` | string | Specifies the runtime variant to include in the program. Available runtimes are: "incremental" (default), "minimal", "stub" |
348+
| `runtime` | string | Specifies the runtime variant to include in the program. Available runtime are: "incremental" (default), "minimal", "stub" |
283349
| `exportRuntime` | boolean | Exports the runtime helpers (__new, __collect etc.). Enabled by default. |
284350
| `explicitStart` | boolean | Exports an explicit '_start' function to call. |
285351
| `enable` | string[] | Enables WebAssembly features being disabled by default. Available features are: "sign-extension", "bulk-memory", "simd", "threads", "reference-types", "gc" |

src/runtime/index.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,22 @@ import { context } from "./context";
1313
async function instantiate<TModule>(
1414
module: TModule | string,
1515
load: (url: string) => Promise<unknown>,
16-
imports: Imports | undefined,
17-
fallback: false,
16+
imports?: Imports,
17+
fallback?: false,
1818
supports?: () => boolean
1919
): Promise<WasmModuleInstance<TModule>>;
2020
async function instantiate<TModule>(
2121
module: TModule | string,
2222
load: (url: string) => Promise<unknown>,
23-
imports?: Imports,
24-
fallback?: true,
23+
imports: Imports | undefined,
24+
fallback: true,
2525
supports?: () => boolean
2626
): Promise<ModuleInstance<TModule>>;
2727
async function instantiate<TModule>(
2828
module: TModule | string,
2929
load: (url: string) => Promise<unknown>,
3030
imports: Imports = {},
31-
fallback = true,
31+
fallback = false,
3232
supports = () => Boolean(context && context.WebAssembly)
3333
): Promise<ModuleInstance<TModule>> {
3434
const moduleURL: AsLoaderModule<TModule> = module as never;

test/e2e/fixtures/main/src/bind.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import * as assembly from "./assembly/correct/bind";
66
async function loadAndRun() {
77
const module = await instantiate(
88
assembly,
9-
fs.promises.readFile,
10-
undefined,
11-
false
9+
fs.promises.readFile
1210
);
1311

1412
const { hello } = module.exports;

test/e2e/fixtures/main/src/complex.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import * as assembly from "./assembly/correct/complex";
66
async function loadAndRun() {
77
const module = await instantiate(
88
assembly,
9-
fs.promises.readFile,
10-
undefined,
11-
false
9+
fs.promises.readFile
1210
);
1311

1412
const {

0 commit comments

Comments
 (0)