Skip to content

Commit 886efaa

Browse files
committed
Add sync API
1 parent f07ce08 commit 886efaa

File tree

11 files changed

+122
-44
lines changed

11 files changed

+122
-44
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ node_modules/
88
debug/
99

1010
dist/
11+
/sync
1112
/wasm
1213
/worker
1314

CHANGELOG.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@
1111
- The library is able to reset its internal error state, which makes the
1212
[v2 wiki caveat](https://github.com/mdaines/viz.js/wiki/Caveats#rendering-graphs-with-user-input)
1313
unnecessary.
14-
- Rendering from main thread is no longer supported, you must use a worker
15-
(webworker or worker_thread).
14+
- Rendering from main thread is no longer supported on the default async API,
15+
you must use a worker (webworker or worker_thread).
1616
- The JS code is now transpiled from TypeScript, and typings are packed within
1717
the npm package. You can find the API documentation there!
18+
- There is a synchronous version available for legacy Node.js support.
1819

1920
##### Breaking changes and deprecations
2021

2122
- **BREAKING:** Bump required version of Node.js to v12 LTS (might work on v10
22-
LTS using CLI flags).
23+
LTS using CLI flags or the synchronous API).
2324
- **BREAKING:** Remove `Viz.prototype.renderSVGElement`. You can use
2425
`renderString` and `DOMParser` to achieve the same result.
2526
- **BREAKING:** Remove `Viz.prototype.renderImageElement`. You can use
@@ -52,7 +53,7 @@
5253
##### Added features
5354

5455
- Add support for Node.js `worker_threads`.
55-
- Refactor JS files to Typescript.
56+
- Refactor JS files to TypeScript.
5657
- Refactor `viz.c` to C++ to use
5758
[Emscripten's Embind](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html).
5859
- Use `ALLOW_MEMORY_GROW` compiler option to avoid failing on large graphs.
@@ -61,6 +62,7 @@
6162
- Remove the need of creating new instances when render fails by resetting
6263
internal error state.
6364
- Switch to Mocha and Puppeteer for browser testing.
65+
- Add synchronous API using asm.js
6466
- Upgrade deps:
6567
- Upgrade Emscripten to 1.39.12
6668
- Upgrade Graphviz to 2.44.0

Makefile

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ all: \
5454
dist \
5555
dist/index.cjs dist/index.mjs dist/index.d.ts \
5656
dist/render.node.mjs dist/render.browser.js dist/render.wasm \
57+
dist/renderSync.cjs \
58+
sync \
5759
wasm \
5860
worker \
5961

@@ -100,16 +102,21 @@ deps: expat-full graphviz-full $(YARN_PATH)
100102
clean:
101103
@echo "\033[1;33mHint: use \033[1;32mmake clobber\033[1;33m to start from a clean slate\033[0m" >&2
102104
rm -rf build dist
103-
rm -f wasm worker
105+
rm -f sync wasm worker
104106
rm -f test/deno-files/render.wasm.uint8.js test/deno-files/index.d.ts
105107

106108
.PHONY: clobber
107109
clobber: | clean
108110
rm -rf build build-full $(PREFIX_FULL) $(PREFIX_LITE) $(YARN_DIR) node_modules
109111

112+
sync:
113+
echo "module.exports=require('./dist/renderSync.cjs')" > $@
110114
wasm worker:
111115
echo "throw new Error('The bundler you are using does not support package.json#exports.')" > $@
112116

117+
build/renderFunction.js: src/renderFunction.ts | build
118+
$(TSC) $(TS_FLAGS) --outDir build -m es6 --target esnext $<
119+
113120
build/worker.js: src/worker.ts | build
114121
$(TSC) $(TS_FLAGS) --outDir build -m es6 --target esnext $<
115122

@@ -176,6 +183,16 @@ build/render.js: src/viz.cpp | build
176183
$(CC) --version | grep $(EMSCRIPTEN_VERSION)
177184
$(CC) $(CC_FLAGS) -Oz -o $@ $< $(CC_INCLUDES)
178185

186+
build/asm.mjs: src/viz.cpp | build
187+
$(CC) --version | grep $(EMSCRIPTEN_VERSION)
188+
$(CC) $(CC_FLAGS) -s WASM=0 -s WASM_ASYNC_COMPILATION=0 --memory-init-file 0 -Oz -o $@ $< $(CC_INCLUDES)
189+
190+
build/renderSync.js: src/renderSync.ts | build
191+
$(TSC) $(TS_FLAGS) --outDir build -m es6 --target esnext $<
192+
193+
dist/renderSync.cjs: build/renderSync.js build/asm.mjs build/renderFunction.js
194+
$(ROLLUP) -f commonjs $< | $(TERSER) --toplevel > $@
195+
179196
test/deno-files/render.wasm.arraybuffer.js: dist/render.wasm
180197
echo "export default Uint16Array.from([" > $@ && \
181198
hexdump -v -x $< | awk '$$1=" "' OFS=",0x" >> $@ && \

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,24 @@ async function dot2svg(dot, options = {}) {
5252
}
5353
```
5454

55+
#### Synchronous API
56+
57+
There is a synchronous version of `renderString` method available:
58+
59+
```js
60+
const vizRenderStringSync = require("@aduh95/viz.js/sync");
61+
62+
console.log(vizRenderStringSync("digraph{1 -> 2 }"));
63+
```
64+
65+
Key differences with async API:
66+
67+
- It uses `asm.js` instead of `WebAssembly`, this should come with a performance
68+
hit and a bigger bundled file size.
69+
- It is a CommonJS module, while the rest of the API is written as standard
70+
ECMAScript modules. The upside is this syntax is supported on a wider Node.js
71+
version array.
72+
5573
### Browsers
5674

5775
You can either use the `worker` or the `workerURL` on the constructor. Note that

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"require": "./dist/index.cjs",
1212
"import": "./dist/index.mjs"
1313
},
14+
"./sync": "./dist/renderSync.cjs",
1415
"./wasm": "./dist/render.wasm",
1516
"./worker": {
1617
"import": "./dist/render.node.mjs",

src/asm.mjs.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import type { WebAssemblyModule } from "./render";
2+
3+
export default function (): WebAssemblyModule;

src/asm.mjs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Dummy file for tsc

src/renderFunction.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { RenderOptions } from "./types";
2+
import type { WebAssemblyModule } from "./render";
3+
4+
export default function render(
5+
Module: WebAssemblyModule,
6+
src: string,
7+
options: RenderOptions
8+
) {
9+
for (const { path, data } of options.files) {
10+
Module.vizCreateFile(path, data);
11+
}
12+
13+
Module.vizSetY_invert(options.yInvert ? 1 : 0);
14+
Module.vizSetNop(options.nop || 0);
15+
16+
var resultString = Module.vizRenderFromString(
17+
src,
18+
options.format,
19+
options.engine
20+
);
21+
22+
var errorMessageString = Module.vizLastErrorMessage();
23+
24+
if (errorMessageString !== "") {
25+
throw new Error(errorMessageString);
26+
}
27+
28+
return resultString;
29+
}

src/renderSync.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Module from "./asm.mjs";
2+
import render from "./renderFunction.js";
3+
4+
import type { RenderOptions } from "./types";
5+
6+
let asmModule;
7+
export default function renderStringSync(src: string, options: RenderOptions) {
8+
if (asmModule == null) {
9+
asmModule = Module();
10+
}
11+
return render(asmModule, src, {
12+
format: "svg",
13+
engine: "dot",
14+
files: [],
15+
images: [],
16+
yInvert: false,
17+
nop: 0,
18+
...options,
19+
});
20+
}

src/worker.ts

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
import type {
2-
RenderOptions,
3-
SerializedError,
4-
RenderResponse,
5-
RenderRequest,
6-
} from "./types";
1+
import type { SerializedError, RenderResponse, RenderRequest } from "./types";
72
import type { Worker } from "worker_threads";
83

94
import initializeWasm, {
105
WebAssemblyModule,
116
EMCCModuleOverrides,
127
} from "./render";
8+
import render from "./renderFunction.js";
139

1410
// Emscripten "magic" globals
1511
declare var ENVIRONMENT_IS_WORKER: boolean;
@@ -35,7 +31,7 @@ async function getModule() {
3531

3632
if (ENVIRONMENT_IS_WORKER) {
3733
let resolveModuleOverrides: Function;
38-
asyncModuleOverrides = new Promise(done => {
34+
asyncModuleOverrides = new Promise((done) => {
3935
resolveModuleOverrides = done;
4036
});
4137
exports = (moduleOverrides: EMCCModuleOverrides) => {
@@ -64,7 +60,7 @@ if (ENVIRONMENT_IS_WORKER) {
6460
);
6561
},
6662
} as Promise<never>;
67-
exports = moduleOverrides =>
63+
exports = (moduleOverrides) =>
6864
new Worker(__filename, {
6965
type: "module",
7066
workerData: { __filename, moduleOverrides },
@@ -77,15 +73,15 @@ if (ENVIRONMENT_IS_WORKER) {
7773
parentPort.on("message", (data: RenderResponse) =>
7874
onmessage({ data } as MessageEvent)
7975
);
80-
postMessage = function() {
76+
postMessage = function () {
8177
"use strict";
8278
return parentPort.postMessage.apply(parentPort, arguments);
8379
};
8480
} else {
8581
// Worker spawned by another module or script, exports a function that lets
8682
// user define a custom override objects.
8783
let resolveModuleOverrides: Function;
88-
asyncModuleOverrides = new Promise(done => {
84+
asyncModuleOverrides = new Promise((done) => {
8985
resolveModuleOverrides = done;
9086
});
9187
exports = (moduleOverrides: EMCCModuleOverrides) => {
@@ -101,42 +97,15 @@ if (ENVIRONMENT_IS_WORKER) {
10197
}
10298
}
10399

104-
function render(
105-
Module: WebAssemblyModule,
106-
src: string,
107-
options: RenderOptions
108-
) {
109-
for (const { path, data } of options.files) {
110-
Module.vizCreateFile(path, data);
111-
}
112-
113-
Module.vizSetY_invert(options.yInvert ? 1 : 0);
114-
Module.vizSetNop(options.nop || 0);
115-
116-
var resultString = Module.vizRenderFromString(
117-
src,
118-
options.format,
119-
options.engine
120-
);
121-
122-
var errorMessageString = Module.vizLastErrorMessage();
123-
124-
if (errorMessageString !== "") {
125-
throw new Error(errorMessageString);
126-
}
127-
128-
return resultString;
129-
}
130-
131100
export function onmessage(event: MessageEvent) {
132101
const { id, src, options } = event.data as RenderRequest;
133102

134103
return getModule()
135-
.then(Module => {
104+
.then((Module) => {
136105
const result = render(Module, src, options);
137106
postMessage({ id, result });
138107
})
139-
.catch(e => {
108+
.catch((e) => {
140109
const error: SerializedError =
141110
e instanceof Error
142111
? {

0 commit comments

Comments
 (0)