Skip to content

Commit

Permalink
Merge pull request #2 from aduh95/sync-api
Browse files Browse the repository at this point in the history
Add sync API
  • Loading branch information
aduh95 authored Apr 24, 2020
2 parents 33c70eb + ce6e7cc commit cee9196
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 40 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ node_modules/
debug/

dist/
/sync/
/wasm
/worker

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

##### Breaking changes and deprecations

- **BREAKING:** Bump required version of Node.js to v12 LTS (might work on v10
LTS using CLI flags).
LTS using CLI flags or the synchronous API).
- **BREAKING:** Remove `Viz.prototype.renderSVGElement`. You can use
`renderString` and `DOMParser` to achieve the same result.
- **BREAKING:** Remove `Viz.prototype.renderImageElement`. You can use
Expand All @@ -40,7 +41,9 @@
`package.json`#`exports`, you can use the specifier `@aduh95/viz.js/worker`.
- **BREAKING:** Compiles to WebAssembly, which cannot be bundled in the
`render.js` file like asm.js used to. Depending on your bundling tool, you may
need some extra config to make everything work.
need some extra config to make everything work. You might also use the
synchronous API, which bundles the asm.js code, although its usage should be
strictly limited to Node.js or webworker use.
- **BREAKING:** Remove ES5 and CJS dist files, all modern browsers now support
ES2015 modules. If you want to support an older browser, you would need to
transpile it yourself or use an older version.
Expand All @@ -52,7 +55,7 @@
##### Added features

- Add support for Node.js `worker_threads`.
- Refactor JS files to Typescript.
- Refactor JS files to TypeScript.
- Refactor `viz.c` to C++ to use
[Emscripten's Embind](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html).
- Use `ALLOW_MEMORY_GROW` compiler option to avoid failing on large graphs.
Expand All @@ -61,6 +64,7 @@
- Remove the need of creating new instances when render fails by resetting
internal error state.
- Switch to Mocha and Puppeteer for browser testing.
- Add synchronous API using asm.js.
- Upgrade deps:
- Upgrade Emscripten to 1.39.12
- Upgrade Graphviz to 2.44.0
Expand Down
27 changes: 25 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ all: \
dist \
dist/index.cjs dist/index.mjs dist/index.d.ts dist/types.d.ts \
dist/render.node.mjs dist/render.browser.js dist/render.wasm \
dist/renderSync.js \
sync \
sync/index.js sync/index.d.ts \
wasm \
worker \

Expand Down Expand Up @@ -142,17 +145,27 @@ deps: expat-full graphviz-full $(YARN_PATH)
.NOTPARALLEL: clean
clean:
@echo "\033[1;33mHint: use \033[1;32mmake clobber\033[1;33m to start from a clean slate\033[0m" >&2
rm -rf build dist
rm -rf build dist sync
rm -f wasm worker
rm -f test/deno-files/render.wasm.uint8.js test/deno-files/index.d.ts

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

sync/index.js: | sync
echo "module.exports=require('../dist/renderSync.js')" > $@
sync/index.d.ts: dist/renderSync.d.ts | sync
echo 'export {default} from "../dist/renderSync"' > $@
dist/renderSync.d.ts: src/renderSync.ts | dist
$(TSC) $(TS_FLAGS) --outDir $(DIST_FOLDER) -d --emitDeclarationOnly $<

wasm worker:
echo "throw new Error('The bundler you are using does not support package.json#exports.')" > $@

build/renderFunction.js: src/renderFunction.ts | build
$(TSC) $(TS_FLAGS) --outDir build -m es6 --target esnext $<

build/worker.js: src/worker.ts | build
$(TSC) $(TS_FLAGS) --outDir build -m es6 --target esnext $<

Expand Down Expand Up @@ -222,12 +235,22 @@ build/render.js: src/viz.cpp | build
$(CC) --version | grep $(EMSCRIPTEN_VERSION)
$(CC) $(CC_FLAGS) -Oz -o $@ $< $(CC_INCLUDES)

build/asm.mjs: src/viz.cpp | build
$(CC) --version | grep $(EMSCRIPTEN_VERSION)
$(CC) $(CC_FLAGS) -s WASM=0 -s WASM_ASYNC_COMPILATION=0 --memory-init-file 0 -Oz -o $@ $< $(CC_INCLUDES)

build/renderSync.js: src/renderSync.ts | build
$(TSC) $(TS_FLAGS) --outDir build -m es6 --target esnext $<

dist/renderSync.js: build/renderSync.js build/asm.mjs build/renderFunction.js
$(ROLLUP) -f commonjs $< | $(TERSER) --toplevel > $@

test/deno-files/render.wasm.arraybuffer.js: dist/render.wasm
echo "export default Uint16Array.from([" > $@ && \
hexdump -v -x $< | awk '$$1=" "' OFS=",0x" >> $@ && \
echo "]).buffer.slice(2$(shell stat -f%z $< | awk '{if (int($$1) % 2) print ",-1"}'))" >> $@

$(PREFIX_FULL) build dist sources $(YARN_DIR):
$(PREFIX_FULL) build dist sources sync $(YARN_DIR):
mkdir -p $@

.PHONY: expat–full
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,29 @@ async function dot2svg(dot, options = {}) {
}
```

#### Synchronous API

There is a synchronous version of `renderString` method available:

```js
const vizRenderStringSync = require("@aduh95/viz.js/sync");

console.log(vizRenderStringSync("digraph{1 -> 2 }"));
```

Key differences with async API:

- It compiles Graphviz to JavaScript instead of `WebAssembly`, this should come
with a performance hit and a bigger bundled file size (brotli size is 27%
bigger).
- It is a CommonJS module, while the rest of the API is written as standard
ECMAScript modules. The upside is this syntax is supported on a wider Node.js
version array.

> Note: Using the sync API on the browser main thread is not recommended, it
> might degrade the overall user experience of the web page. It is strongly
> recommended to use web workers – with the sync or the async API.
### Browsers

You can either use the `worker` or the `workerURL` on the constructor. Note that
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"require": "./dist/index.cjs",
"import": "./dist/index.mjs"
},
"./sync": "./dist/renderSync.js",
"./wasm": "./dist/render.wasm",
"./worker": {
"import": "./dist/render.node.mjs",
Expand All @@ -31,6 +32,7 @@
],
"files": [
"dist/",
"sync/",
"wasm",
"worker"
],
Expand Down
3 changes: 3 additions & 0 deletions src/asm.mjs.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { WebAssemblyModule } from "./render";

export default function (): WebAssemblyModule;
1 change: 1 addition & 0 deletions src/asm.mjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Dummy file for tsc
29 changes: 29 additions & 0 deletions src/renderFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { RenderOptions } from "./types";
import type { WebAssemblyModule } from "./render";

export default function render(
Module: WebAssemblyModule,
src: string,
options: RenderOptions
): string {
for (const { path, data } of options.files) {
Module.vizCreateFile(path, data);
}

Module.vizSetY_invert(options.yInvert ? 1 : 0);
Module.vizSetNop(options.nop || 0);

const resultString = Module.vizRenderFromString(
src,
options.format,
options.engine
);

const errorMessageString = Module.vizLastErrorMessage();

if (errorMessageString !== "") {
throw new Error(errorMessageString);
}

return resultString;
}
23 changes: 23 additions & 0 deletions src/renderSync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Module from "./asm.mjs";
import render from "./renderFunction.js";

import type { RenderOptions } from "./types";

let asmModule;
export default function renderStringSync(
src: string,
options?: RenderOptions
): string {
if (asmModule == null) {
asmModule = Module();
}
return render(asmModule, src, {
format: "svg",
engine: "dot",
files: [],
images: [],
yInvert: false,
nop: 0,
...(options || {}),
});
}
35 changes: 2 additions & 33 deletions src/worker.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import type {
RenderOptions,
SerializedError,
RenderResponse,
RenderRequest,
} from "./types";
import type { SerializedError, RenderResponse, RenderRequest } from "./types";
import type { Worker } from "worker_threads";

import initializeWasm, {
WebAssemblyModule,
EMCCModuleOverrides,
} from "./render";
import render from "./renderFunction.js";

/* eslint-disable no-var */
//
Expand Down Expand Up @@ -38,33 +34,6 @@ async function getModule(): Promise<WebAssemblyModule> {
return Module;
}

function render(
Module: WebAssemblyModule,
src: string,
options: RenderOptions
): string {
for (const { path, data } of options.files) {
Module.vizCreateFile(path, data);
}

Module.vizSetY_invert(options.yInvert ? 1 : 0);
Module.vizSetNop(options.nop || 0);

const resultString = Module.vizRenderFromString(
src,
options.format,
options.engine
);

const errorMessageString = Module.vizLastErrorMessage();

if (errorMessageString !== "") {
throw new Error(errorMessageString);
}

return resultString;
}

export function onmessage(event: MessageEvent): Promise<void> {
const { id, src, options } = event.data as RenderRequest;

Expand Down
15 changes: 15 additions & 0 deletions test/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import Viz from "@aduh95/viz.js";
import vizRenderStringSync from "@aduh95/viz.js/sync";

// @ts-expect-error
Viz({ workerURL: "string" });
Expand Down Expand Up @@ -50,3 +51,17 @@ viz.terminateWorker();

// @ts-expect-error
viz.terminateWorker("argument");

// @ts-expect-error
vizRenderStringSync();

vizRenderStringSync("string");
// @ts-expect-error
vizRenderStringSync("string").then(() => {});
vizRenderStringSync("string").replace("string", "");
vizRenderStringSync("string", {});
vizRenderStringSync("string", { format: "dot" });
// @ts-expect-error
vizRenderStringSync("string", { format: "unknown" });
// @ts-expect-error
vizRenderStringSync("string", { unknown: "unknown" });
17 changes: 17 additions & 0 deletions test/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,21 @@ describe("Test graph rendering using Node.js", function () {
.then((result) => assert.ok(result))
.finally(() => viz.terminateWorker());
});

it("should render a graph using sync version", function () {
const renderStringSync = require("@aduh95/viz.js/sync");

assert.ok(renderStringSync("digraph { a -> b; }"));
});

it("should render same graph using async and sync versions", async function () {
const viz = await getViz();
const renderStringSync = require("@aduh95/viz.js/sync");

const resultSync = renderStringSync("digraph { a -> b; }");
return viz
.renderString("digraph { a -> b; }")
.then((result) => assert.strictEqual(result, resultSync))
.finally(() => viz.terminateWorker());
});
});

0 comments on commit cee9196

Please sign in to comment.