From 607037f4d3bcd38331d05fd89c5e6574914a9363 Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Sat, 10 Jul 2021 16:35:49 -0500 Subject: [PATCH] feat: add node n-api bindings --- dub.sdl | 21 +++++++++++++++++++ dub.selections.json | 1 + package.json | 7 ++++++- pnpm-lock.yaml | 8 ++++++++ src/native/lib.d | 7 ++++--- src/native/libc.d | 4 +--- src/node/build.js | 31 ++++++++++++++++++++-------- src/node/cli.ts | 29 +++++++++++++++++++++++++- src/node/lib.ts | 50 +++++---------------------------------------- src/node/node.d | 9 ++++++++ 10 files changed, 105 insertions(+), 62 deletions(-) create mode 100644 src/node/node.d diff --git a/dub.sdl b/dub.sdl index ae0980f..baa5f97 100644 --- a/dub.sdl +++ b/dub.sdl @@ -17,10 +17,31 @@ configuration "executable" { } configuration "library" { + mainSourceFile "./src/native/lib.d" + excludedSourceFiles "./src/native/cli.d" targetType "library" targetName "minijson" } +configuration "node-executable" { + targetType "executable" + mainSourceFile "./src/native/cli.d" + + postGenerateCommands "node ./src/node/build.js" +} + +configuration "node-lib" { + targetType "dynamicLibrary" + targetName "minijson.node" + + mainSourceFile "./src/node/node.d" + excludedSourceFiles "./src/native/cli.d" "./src/native/libc.d" + + dependency "node_dlang" version="0.4.11" + + postGenerateCommands "node ./src/node/build.js" +} + # -------- Build Options and configurations -------- // enables all disp except =nosharedaccess diff --git a/dub.selections.json b/dub.selections.json index 3f9d51f..a73b4c9 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -2,6 +2,7 @@ "fileVersion": 1, "versions": { "automem": "0.6.6", + "node_dlang": "0.4.11", "test_allocator": "0.3.3", "unit-threaded": "2.0.0" } diff --git a/package.json b/package.json index e1a36da..89a5f5c 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "build.native.release": "pnpm build.native -- --build release-nobounds --compiler=ldc2", "build.native.profile": "pnpm build.native -- --build profile --compiler=ldc2 && node ./src/node/build.js", "start.profile": "npm run start.benchmark && profdump.exe --dot trace.log trace.dot && dot -Tsvg trace.dot -o trace.svg && ./trace.svg", - "build.node": "npm run build.native.release && node ./src/node/build.js && (tsc -p ./src/node/tsconfig.json || echo done)", + "build.node.exe": "dub build --config=node-executable --build=release-nobounds && tsc -p ./src/node/tsconfig.json", + "build.node.lib": "dub build --config=node-lib --build=release-nobounds && tsc -p ./src/node/tsconfig.json", + "build.node": "npm run build.node.exe && npm run build.node.lib", "build.wasm": "ldc2 ./src/wasm/wasm.d ./src/native/lib.d --od ./dist --O3 --mtriple=wasm32-unknown-unknown-wasm", "build.browser": "npm run build.wasm && parcel build --target browser ./src/browser/index.html", "start.browser": "servor ./dist/ --browse --reload", @@ -32,6 +34,9 @@ "zip": "zip -9 -j ./dist/minijson-windows-x64.zip ./dist/win32-x64/minijson.exe && zip -9 -j ./dist/minijson-macos-x64.zip ./dist/darwin-x64/minijson && zip -9 -j ./dist/minijson-linux-x64.zip ./dist/linux-x64/minijson", "prepublishOnly": "shx rm -rf ./dist/tsconfig.tsbuildinfo ./dist/build.*" }, + "dependencies": { + "node-addon-api": "^4.0.0" + }, "devDependencies": { "@types/jasmine": "^3.7.7", "@types/node": "16.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4ce890..4a42f3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,16 @@ specifiers: jasmine-spec-reporter: ^7.0.0 jsonminify: ^0.4.1 mjs-dirname: ^1.0.0 + node-addon-api: ^4.0.0 parcel: ^2.0.0-beta.3.1 prettier-config-atomic: ^2.0.5 servor: ^4.0.2 shx: 0.3.3 strip-json-comments: ^3.1.1 +dependencies: + node-addon-api: 4.0.0 + devDependencies: '@types/jasmine': 3.7.7 '@types/node': 16.0.0 @@ -4427,6 +4431,10 @@ packages: resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} dev: true + /node-addon-api/4.0.0: + resolution: {integrity: sha512-ALmRVBFzfwldBfk3SbKfl6+PVMXiCPKZBEfsJqB/EjXAMAI+MfFrEHR+GMRBuI162DihZ1QjEZ8ieYKuRCJ8Hg==} + dev: false + /node-forge/0.10.0: resolution: {integrity: sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==} engines: {node: '>= 6.0.0'} diff --git a/src/native/lib.d b/src/native/lib.d index fdee184..69d2803 100644 --- a/src/native/lib.d +++ b/src/native/lib.d @@ -17,7 +17,7 @@ const spaceOrBreakRegex = ctRegex!(`\s`); Return: the minified json string */ -string minifyString(in string jsonString, in bool hasComment = false) @trusted +extern (C) string minifyString(string jsonString, bool hasComment = false) @trusted { auto in_string = false; auto in_multiline_comment = false; @@ -51,7 +51,8 @@ string minifyString(in string jsonString, in bool hasComment = false) @trusted { leftContextSubstr = leftContextSubstr.replaceAll(spaceOrBreakRegex, ""); } - if (!noLeftContext) { + if (!noLeftContext) + { result ~= leftContextSubstr; } @@ -133,7 +134,7 @@ private bool notSlashAndNoSpaceOrBreak(in string matchFrontHit) @safe files = the paths to the files. hasComment = a boolean to support comments in json. Default: `false`. */ -void minifyFiles(in string[] files, in bool hasComment = false) +extern (C) void minifyFiles(string[] files, bool hasComment = false) { import std.parallelism : parallel; import std.file : readText, write; diff --git a/src/native/libc.d b/src/native/libc.d index 59ee634..dc2e1e8 100644 --- a/src/native/libc.d +++ b/src/native/libc.d @@ -2,8 +2,6 @@ module minijson.libc; import minijson.lib : minifyString; -extern (C): - /** Minify the given JSON string using C ABI. @@ -14,7 +12,7 @@ extern (C): Return: the minified json string */ -auto minifyString(in char* jsonCString, in bool hasComment = false) +extern (C) auto c_minifyString(in char* jsonCString, in bool hasComment = false) { import std : fromStringz, toStringz; diff --git a/src/node/build.js b/src/node/build.js index efe5d79..d4c39f4 100644 --- a/src/node/build.js +++ b/src/node/build.js @@ -1,15 +1,9 @@ const { join, dirname } = require("path") -const { renameSync, mkdirSync } = require("fs") +const { renameSync, mkdirSync, existsSync } = require("fs") const { execFileSync } = require("child_process") -const exeExtention = process.platform === "win32" ? ".exe" : "" -const binName = `minijson${exeExtention}` - const distFolder = join(dirname(dirname(__dirname)), "dist") -const minijsonSource = join(distFolder, binName) - const distPlatformFolder = join(distFolder, `${process.platform}-${process.arch}`) -mkdirSync(distPlatformFolder, { recursive: true }) function stripBin(file) { if (process.platform === "win32") { @@ -17,8 +11,27 @@ function stripBin(file) { } return execFileSync(process.env.STRIP || "strip", [file, process.platform === "darwin" ? "-Sx" : "--strip-all"]) } -stripBin(minijsonSource) +mkdirSync(distPlatformFolder, { recursive: true }) + +// exe + +const exeExtention = process.platform === "win32" ? ".exe" : "" +const binName = `minijson${exeExtention}` +const minijsonSource = join(distFolder, binName) const minijsonDist = join(distPlatformFolder, binName) -renameSync(minijsonSource, minijsonDist) +if (existsSync(minijsonSource)) { + stripBin(minijsonSource) + renameSync(minijsonSource, minijsonDist) +} + +// lib + +const libExtension = process.platform === "win32" ? ".dll" : ".so" +const minijsonLibSource = join(distFolder, `minijson.node${libExtension}`) +const minijsonLibDist = join(distPlatformFolder, `minijson.node`) + +if (existsSync(minijsonLibSource)) { + renameSync(minijsonLibSource, minijsonLibDist) +} diff --git a/src/node/cli.ts b/src/node/cli.ts index 458d3b6..57a59fc 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -1,4 +1,31 @@ -import { spawnMinijson } from "./lib" +import { execFile } from "child_process" +import { join } from "path" + +const exeExtention = process.platform === "win32" ? ".exe" : "" +const binName = `minijson${exeExtention}` + +const minijsonBin = join(__dirname, `${process.platform}-${process.arch}`, binName) + +/** + * Spawn minijson with the given arguments + * + * @param args An array of arguments + * @returns {Promise} Returns a promise that resolves to stdout output string when the operation finishes + * @throws {Promise} The promise is rejected with the reason for failure + */ +export function spawnMinijson(args: string[]): Promise { + return new Promise((resolve, reject) => { + execFile(minijsonBin, args, (err, stdout, stderr) => { + if (err) { + reject(err) + } + if (stderr !== "") { + reject(stderr) + } + resolve(stdout) + }) + }) +} async function main() { await spawnMinijson(process.argv) diff --git a/src/node/lib.ts b/src/node/lib.ts index c252b24..edb4028 100644 --- a/src/node/lib.ts +++ b/src/node/lib.ts @@ -1,5 +1,7 @@ -import { execFile } from "child_process" import { join } from "path" +const minijsonLib = join(__dirname, `${process.platform}-${process.arch}`, "minijson.node") + +const nativeLib = require(minijsonLib) // eslint-disable-line @typescript-eslint/no-var-requires /** * Minify all the given JSON files in place. It minifies the files in parallel. @@ -14,19 +16,7 @@ export async function minifyFiles(files: string[], hasComment = false): Promise< if (filesNum === 0) { return Promise.resolve() } - - const args = [...files] - const spliceUpper = 2 * filesNum - 2 - - for (let iSplice = 0; iSplice <= spliceUpper; iSplice += 2) { - args.splice(iSplice, 0, "--file") - } - - if (hasComment) { - args.push("--comment") - } - - await spawnMinijson(args) + nativeLib.minifyFiles(files, hasComment) } /** @@ -38,36 +28,6 @@ export async function minifyFiles(files: string[], hasComment = false): Promise< * @throws {Promise} The promise is rejected with the reason for failure */ export async function minifyString(jsonString: string, hasComment = false): Promise { - const args = ["--string", jsonString] - if (hasComment) { - args.push("--comment") - } // trim is needed due to using stdout for interop - return (await spawnMinijson(args)).trim() -} - -const exeExtention = process.platform === "win32" ? ".exe" : "" -const binName = `minijson${exeExtention}` - -const minijsonBin = join(__dirname, `${process.platform}-${process.arch}`, binName) - -/** - * Spawn minijson with the given arguments - * - * @param args An array of arguments - * @returns {Promise} Returns a promise that resolves to stdout output string when the operation finishes - * @throws {Promise} The promise is rejected with the reason for failure - */ -export function spawnMinijson(args: string[]): Promise { - return new Promise((resolve, reject) => { - execFile(minijsonBin, args, (err, stdout, stderr) => { - if (err) { - reject(err) - } - if (stderr !== "") { - reject(stderr) - } - resolve(stdout) - }) - }) + return nativeLib.minifyFiles(jsonString, hasComment) } diff --git a/src/node/node.d b/src/node/node.d new file mode 100644 index 0000000..3d83d49 --- /dev/null +++ b/src/node/node.d @@ -0,0 +1,9 @@ +module minijson.node; + +import node_dlang; + +import minijson.lib : minifyString; + +private extern (C) void atStart (napi_env env) {} + +mixin exportToJs!(minifyString, MainFunction!atStart);