Skip to content

Commit bee15d8

Browse files
author
chenhebing
committed
feat: webassembly loader
0 parents  commit bee15d8

File tree

10 files changed

+4713
-0
lines changed

10 files changed

+4713
-0
lines changed

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# http://editorconfig.org
2+
root = true
3+
4+
[*]
5+
indent_style = space
6+
indent_size = 2
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true
10+
11+
[*.md]
12+
trim_trailing_whitespace = false

.eslintignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# 忽略目录
2+
lib/
3+
node_modules/

.eslintrc.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// eslint-disable-next-line
2+
const { deepmerge, tslint } = require('@ice/spec');
3+
4+
module.exports = deepmerge(tslint, {
5+
rules: {
6+
'array-bracket-spacing': [ 'error', 'always', { 'objectsInArrays': false, 'arraysInArrays': false, 'singleValue': true }],
7+
'object-curly-spacing': [ 'error', 'always' ],
8+
quotes: [ 'error', 'single', { avoidEscape: true }],
9+
'jsx-quotes': [ 'error', 'prefer-double' ],
10+
'no-useless-constructor': 'error',
11+
'spaced-comment': [ 'error', 'always', { 'markers': [ '/' ] }],
12+
'comma-spacing': [ 'error', { 'before': false, 'after': true }],
13+
'react-hooks/exhaustive-deps': 'error',
14+
'eol-last': [ 'error', 'always' ],
15+
'space-before-function-paren': [ 'error', {
16+
'anonymous': 'never',
17+
'named': 'never',
18+
'asyncArrow': 'always',
19+
}],
20+
'comma-dangle': [ 'error', 'always-multiline' ],
21+
'semi': [ 'error', 'always' ],
22+
'key-spacing': [ 'error', { afterColon: true }],
23+
'@typescript-eslint/no-useless-constructor': 'error',
24+
'react/jsx-filename-extension': [
25+
'error',
26+
{
27+
extensions: [ '.js', '.jsx', '.tsx' ],
28+
},
29+
],
30+
},
31+
});

.gitignore

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# dependencies
2+
/node_modules
3+
4+
# production
5+
/lib
6+
7+
# misc
8+
.idea/
9+
.happypack
10+
.DS_Store
11+
.vscode/
12+
13+
npm-debug.log*
14+
yarn-debug.log*
15+
yarn-error.log*
16+
17+
# ignore d.ts auto generated by css-modules-typescript-loader
18+
.eslintcache

README.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# `assemblyscript-wasm-loader`
2+
3+
> A webpack loader that compiles AssemblyScript to WebAssembly.
4+
5+
## Usage
6+
1. 安装依赖
7+
8+
```bash
9+
$ yarn add assemblyscript-wasm-loader
10+
```
11+
12+
2. webpack 配置
13+
14+
```javascript
15+
{
16+
test: /\.ts?$/,
17+
loader: 'assemblyscript-wasm-loader',
18+
include: /assembly/,
19+
options: {
20+
limit: 61440, // 编译的 wasm size 阈值(单位byte),size <= limit build in Bundle;size > limit 生成 wasm 文件
21+
optimize: '3z', // 编译优化,默认 2s
22+
// measure: true, // Prints measuring information on I/O and compile times
23+
}
24+
}
25+
```
26+
27+
优化项对应的配置
28+
29+
```json
30+
{
31+
"-Os": { "value": { "optimize": true, "shrinkLevel": 1 } },
32+
"-Oz": { "value": { "optimize": true, "shrinkLevel": 2 } },
33+
"-O0": { "value": { "optimizeLevel": 0, "shrinkLevel": 0 } },
34+
"-O1": { "value": { "optimizeLevel": 1, "shrinkLevel": 0 } },
35+
"-O2": { "value": { "optimizeLevel": 2, "shrinkLevel": 0 } },
36+
"-O3": { "value": { "optimizeLevel": 3, "shrinkLevel": 0 } },
37+
"-O0s": { "value": { "optimizeLevel": 0, "shrinkLevel": 1 } },
38+
"-O1s": { "value": { "optimizeLevel": 1, "shrinkLevel": 1 } },
39+
"-O2s": { "value": { "optimizeLevel": 2, "shrinkLevel": 1 } },
40+
"-O3s": { "value": { "optimizeLevel": 3, "shrinkLevel": 1 } },
41+
"-O0z": { "value": { "optimizeLevel": 0, "shrinkLevel": 2 } },
42+
"-O1z": { "value": { "optimizeLevel": 1, "shrinkLevel": 2 } },
43+
"-O2z": { "value": { "optimizeLevel": 2, "shrinkLevel": 2 } },
44+
"-O3z": { "value": { "optimizeLevel": 3, "shrinkLevel": 2 } }
45+
}
46+
```
47+
48+
3. 开发代码,(如下示例)
49+
50+
//assembly/index.ts
51+
52+
```js
53+
// the env from JS
54+
@external('env', 'log')
55+
export declare function logi(v: i32): void;
56+
@external('env', 'log')
57+
export declare function logs(a: string): void;
58+
@external('env', 'log')
59+
export declare function logss(a: string, b: string): void;
60+
@external('env', 'gValue')
61+
export declare const gValue:i32;
62+
63+
/**
64+
* @return
65+
* - a > b ,return 1
66+
* - a = b ,return 0
67+
* - a < b ,return -1
68+
*/
69+
export function compareVersion(va: string, vb: string, digit: i8 = 3): i8 {
70+
logi(gValue);
71+
logss(va, vb);
72+
73+
if(!va && !vb){
74+
return 0;
75+
}else if(!va){
76+
return -1;
77+
}else if(!vb){
78+
return 1;
79+
}
80+
81+
const aArr: string[] = va.split('.');
82+
const bArr: string[] = vb.split('.');
83+
84+
let i = -1;
85+
while(++i < digit){
86+
if(aArr[i] > bArr[i]){
87+
return 1;
88+
}else if(aArr[i] < bArr[i]){
89+
return -1;
90+
}
91+
}
92+
93+
return 0;
94+
}
95+
```
96+
97+
// demo/index.js
98+
99+
```js
100+
const imports = {
101+
env: {
102+
gValue: 666,
103+
log: console.log,
104+
abort: function abort(message, source, lineno, colno) {
105+
const memory = env.memory;
106+
throw Error(`abort: ${getString(memory, mesg)} at ${getString(memory, file)}:${lineno}:${colno}`);
107+
}
108+
}
109+
};
110+
111+
// module挂载API参照 [@assemblyscript/loader](https://web.npm.alibaba-inc.com/package/@assemblyscript/loader)
112+
const cback = utilMod => {
113+
let {
114+
compareVersion,
115+
__allocString,
116+
__retain,
117+
__release
118+
} = utilMod;
119+
120+
const a = '1.2.0';
121+
const b = '1.2.1';
122+
const va = __retain(__allocString(a));
123+
const vb = __retain(__allocString(b));
124+
125+
const r = compareVersion(va, vb);
126+
127+
console.log(
128+
`%c 版本 ${a} 比版本 ${b} ${{ 0: '相等', 1: '', '-1': '' }[r]}`,
129+
'color:#0f0;'
130+
);
131+
__release(va);
132+
__release(vb);
133+
};
134+
135+
import instantiate from '../assembly/index.ts';
136+
instantiate(utilImports).then(utilMod => {
137+
cback(utilMod);
138+
}).catch(e => console.error(e));
139+
```
140+
141+
## 其他
142+
- Promise.reject,根据失败的原因不同,当前有 3 类异常:
143+
- [WebAssembly.CompileError](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/CompileError)
144+
- [WebAssembly.LinkError](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/LinkError)
145+
- [WebAssembly.RuntimeError](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/RuntimeError)
146+
147+
- importsObject 配置,WebAssembly的实例化方法,可导入 4 种类型:
148+
- values
149+
- function
150+
- memory(JS与Wasm间内存共享)
151+
- tables(主要用于函数引用)
152+
153+
注:wasm模块中,若导入值,要与之匹配的属性对应,否则会抛出 [WebAssembly.LinkError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/LinkError)
154+
155+
Demo:
156+
157+
```javascript
158+
const utilImports = {
159+
env: {
160+
gValue: 666,
161+
log: console.log,
162+
//以wasm页(64K)为单位,初始640K,不必要配置最大限制
163+
memory: new WebAssembly.Memory({initial: 10}),
164+
//初始指定1个长度,不必要配置最大限制;存储对象的类型目前只支持函数
165+
table: new WebAssembly.Table({initial: 1, element: 'anyfunc'}),
166+
abort: function abort(message, source, lineno, colno) {
167+
const memory = env.memory;
168+
throw Error(`abort: ${getString(memory, mesg)} at ${getString(memory, file)}:${lineno}:${colno}`);
169+
}
170+
}
171+
};
172+
```

package.json

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"name": "assemblyscript-loader-loader",
3+
"version": "0.1.0",
4+
"description": "A webpack loader that compiles AssemblyScript to WebAssembly.",
5+
"keywords": [
6+
"webpack",
7+
"loader",
8+
"AssemblyScript",
9+
"WebAssembly"
10+
],
11+
"homepage": "https://github.com/chenhebing/assemblyscript-loader-loader",
12+
"license": "MIT",
13+
"module": "lib/index.js",
14+
"files": [
15+
"lib"
16+
],
17+
"engines": {
18+
"node": ">=8"
19+
},
20+
"repository": {
21+
"type": "git",
22+
"url": "git+ssh://[email protected]/chenhebing/assemblyscript-loader-loader.git"
23+
},
24+
"bugs": {
25+
"url": "https://github.com/chenhebing/assemblyscript-loader-loader/issues"
26+
},
27+
"dependencies": {
28+
"@assemblyscript/loader": "^0.10.1",
29+
"assemblyscript": "^0.12.3",
30+
"loader-utils": "^2.0.0",
31+
"memfs": "^3.2.0",
32+
"schema-utils": "^2.7.0"
33+
},
34+
"scripts": {
35+
"prepare": "tsc"
36+
},
37+
"lint-staged": {
38+
"*.{js,ts}": "eslint"
39+
},
40+
"husky": {
41+
"hooks": {
42+
"pre-commit": "npm run prepare && lint-staged"
43+
}
44+
},
45+
"devDependencies": {
46+
"@ice/spec": "^1.0.1",
47+
"@types/json-schema": "^7.0.5",
48+
"@types/loader-utils": "^2.0.0",
49+
"@types/webpack": "^4.41.17",
50+
"eslint": "~7.2.0",
51+
"husky": "^4.2.5",
52+
"lint-staged": "^10.2.11",
53+
"typescript": "^3.9.5"
54+
}
55+
}

src/index.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { loader } from 'webpack';
2+
import { fs } from 'memfs';
3+
import path from 'path';
4+
import asc from 'assemblyscript/cli/asc';
5+
import loaderUtils from 'loader-utils';
6+
import validate from 'schema-utils';
7+
8+
import { schemaOptions, transOptionToAscOption } from './option';
9+
10+
const loaderpkg = '@assemblyscript/loader';
11+
const configuration = {
12+
name: 'assemblyscript-loader-loader',
13+
};
14+
15+
const getInBundleWasmModule = (buf: Buffer): string => `
16+
import loader from '${loaderpkg}';
17+
export default async (imports) => {
18+
return await loader.instantiate(${buf.buffer}, imports || {});
19+
};`;
20+
21+
const getFileWasmModule = (fetchUrl: string): string => `
22+
import loader from '${loaderpkg}';
23+
export default async (imports) => {
24+
return await loader.instantiateStreaming(fetch(${fetchUrl}), imports || {});
25+
}`;
26+
27+
export default (content: loader.LoaderContext): void => {
28+
const options = loaderUtils.getOptions(content) || {};
29+
const callback = content.async() as loader.loaderCallback;
30+
// eslint-disable-next-line
31+
const buildPath = content._compiler.outputPath;
32+
const output = path.join(buildPath, `${path.parse(content.resourcePath).name}.wasm`);
33+
const ascArgv = [
34+
path.relative(process.cwd(), content.resourcePath),
35+
'-o', path.relative(process.cwd(), output),
36+
...transOptionToAscOption(options),
37+
];
38+
39+
validate(schemaOptions, options, configuration);
40+
content.addDependency(loaderpkg);
41+
42+
asc.ready.then(() => {
43+
asc.main(ascArgv, {
44+
readFile: (filename: string, baseDir: string): string => {
45+
const filePath = path.join(baseDir, filename);
46+
console.log('filePath-read', filePath);
47+
return fs.readFileSync(filePath).toString();
48+
},
49+
writeFile: (filename: string, contents: Uint8Array, baseDir: string): void => {
50+
const filePath = path.join(baseDir, filename);
51+
console.log('filePath-write', filePath);
52+
fs.writeFileSync(filePath, contents);
53+
},
54+
}, (err): number => {
55+
if (err) {
56+
callback(err);
57+
return 0;
58+
}
59+
const size = fs.statSync(output).size;
60+
const wasmFile = fs.readFileSync(output);
61+
62+
// 默认 1000
63+
const limit = Number(options.limit) || 1000;
64+
65+
if (size <= limit) {
66+
callback(null, getInBundleWasmModule(Buffer.from(wasmFile)));
67+
} else {
68+
const fileName = loaderUtils.interpolateName(content, typeof options.name === 'string' && options.name
69+
? options.name
70+
: '[name].[hash:7].wasm', {
71+
context: content.rootContext,
72+
content: wasmFile,
73+
});
74+
75+
content.emitFile(fileName, wasmFile, undefined);
76+
77+
let fetchUrl = `__webpack_public_path__ + ${JSON.stringify(fileName)}`;
78+
if (options.publicPath) {
79+
fetchUrl = JSON.stringify(`${options.publicPath}${fileName}`);
80+
} else if (options.useInWorker) {
81+
fetchUrl = `self.location.origin + '/${fileName}'`;
82+
}
83+
84+
callback(null, getFileWasmModule(fetchUrl));
85+
}
86+
return 0;
87+
},
88+
);
89+
});
90+
};

0 commit comments

Comments
 (0)