Repository: vshymanskyy/node-inline-cpp Branch: master Commit: 8e5d8da853a9 Files: 8 Total size: 11.7 KB Directory structure: gitextract_b4qtfggj/ ├── .npmignore ├── LICENSE ├── README.md ├── examples/ │ ├── 01_simple.js │ ├── 02_customCompilerOptions.js │ └── 03_customModuleInit.js ├── index.js └── package.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .npmignore ================================================ .git package-lock.json ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Volodymyr Shymanskyy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![NPM version](https://img.shields.io/npm/v/inline-cpp.svg)](https://www.npmjs.com/package/inline-cpp) [![NPM download](https://img.shields.io/npm/dm/inline-cpp.svg)](https://www.npmjs.com/package/inline-cpp) [![GitHub issues](https://img.shields.io/github/issues/vshymanskyy/node-inline-cpp.svg)](https://github.com/vshymanskyy/node-inline-cpp/issues) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/vshymanskyy/node-inline-cpp) # inline-cpp Inline C++ with Node.js **Works on:** Linux, Windows, MacOS **Purpose:** - Simplify native module prototyping. Enable native code in Node.js REPL. - Allow JS scripts to generate C++ code and run it dynamically. - Popularise NAPI usage and `node-addon-api`. - This is **NOT** intended to be used as native module replacement! If you want to publish a native module, please package it as required by `node-gyp`. ## Installation ```sh npm install --save inline-cpp ``` or install it globally (it works with Node.js REPL): ```sh npm install -g inline-cpp ``` ## Usage ```js // test.js const compile = require('inline-cpp'); const hello = compile ` String func(const CallbackInfo& info) { return String::New(info.Env(), "Hello world from C++!"); } ` console.log(hello()) ``` Now run it: ```sh ➜ node test.js Hello world from C++! ``` The first time you run the script, it takes longer to execute. For each inline block of code, a native module will be generated, compiled with `node-gyp` and loaded dynamically. If the module `Init` function is not defined, it is generated as well. The next time you run the script, it will reuse previously generated module, so it will run instantly (unless you change the inline C++ code). For more C++ code examples, see [node-addon-api](https://github.com/nodejs/node-addon-api#examples) For more `inline-cpp` API examples, see [examples on github](https://github.com/vshymanskyy/node-inline-cpp/tree/master/examples) ## API `inline-cpp` supports several invocation methods. Pass some code as string to build it with default options. ```js const InlineCPP = require('inline-cpp'); InlineCPP('code') ``` You can also pass code using [tagged template syntax](https://developers.google.com/web/updates/2015/01/ES6-Template-Strings#tagged_templates). ```js InlineCPP `code` ``` Pass an object to create a new compiler with custom options. Options will get passed to `node-gyp` target. ```js const customCompiler = InlineCPP({ ... }) ``` If the code block only contains a single function, the compiler returns the function. If it contains multiple functions or custom `Init`, the module itself is returned. ## Disclaimer This is just a prototype. I created this to check the general concept. You're welcome to contribute! Here are some ideas: - [x] Parse/Find all functions in the block of code, add them to exports - [ ] Use node-gyp directly, instead of invoking `node node-gyp.js` - [ ] Improve error handling/reporting - [ ] Create advanced usage examples - [ ] Cleanup unused modules from cache periodically - [ ] ... ## Debugging You can enable debug output by setting env. variable: `DEBUG=inline-cpp` ================================================ FILE: examples/01_simple.js ================================================ const InlineCPP = require('../'); // Tagged template with default options const hello = InlineCPP ` String func(const CallbackInfo& info) { return String::New(info.Env(), "Hello world!"); } ` console.log(hello()); // Hello world! ================================================ FILE: examples/02_customCompilerOptions.js ================================================ const InlineCPP = require('../'); // Create a compiler with some custom options to node-gyp const compile = InlineCPP({ "defines": [`SOME_MESSAGE="Bazinga!"`] }); const func = compile(` String func(const CallbackInfo& info) { return String::New(info.Env(), SOME_MESSAGE); } `) console.log(func()); // Bazinga! ================================================ FILE: examples/03_customModuleInit.js ================================================ const InlineCPP = require('../'); // Note: This is just an example. // You can actually remove the Init function here, // as the auto-generated one is the same. const mod = InlineCPP(` String alice(const CallbackInfo& info) { return String::New(info.Env(), "Alice"); } String bob(const CallbackInfo& info) { return String::New(info.Env(), "Bob"); } Object Init(Env env, Object exports) { exports.Set("alice", Function::New(env, alice)); exports.Set("bob", Function::New(env, bob)); return exports; } `); console.log(mod.alice(), 'and', mod.bob()); // Alice and Bob ================================================ FILE: index.js ================================================ const os = require('os'); const fs = require('fs-extra'); const path = require('path'); const crypto = require('crypto'); const { execSync } = require('child_process'); const paths = require('env-paths')('nodejs-inline-cpp', {suffix: ''}); const findParentDir = require('find-parent-dir'); const debug = require('debug')('inline-cpp'); const _ = require('lodash'); let nodeAddon, nodeGyp; function findBuildDeps() { if (nodeAddon && nodeGyp) return; nodeAddon = require.resolve('node-addon-api'); nodeAddon = findParentDir.sync(nodeAddon, 'package.json'); nodeGyp = require.resolve('node-gyp'); nodeGyp = findParentDir.sync(nodeGyp, 'package.json'); nodeGyp = path.join(nodeGyp, 'bin', 'node-gyp.js'); debug('Using node-gyp:', nodeGyp); debug('Using node-addon-api:', nodeAddon); // For some reason, windows needs path to be escaped if (os.platform() === 'win32') { nodeAddon = nodeAddon.replace(/[\\$'"]/g, "\\$&") } } function optsMerge(objValue, srcValue) { if (_.isArray(objValue)) { return objValue.concat(srcValue); } } function generateModule(code, opts) { opts = opts || {}; code = code.trim(); // The stripped code is only used for some auto-detection, it is not actually compiled! const strippedCode = ' ' + code .replace(/,/g, ' , ') .replace(/\(/g, ' ( ') .replace(/\)/g, ' ) ') .replace(/{/g, ' { ') .replace(/}/g, ' } ') .replace(/\s\s+/g, ' ') + ' '; // Find all function declarations let funcsRe = /(([a-zA-Z_][\w:]*)\s+([a-zA-Z_]\w*)\s*\(\s*((?:[a-zA-Z0-9_:&\*,\s])*)\s*\))\s*{/gm; let m; let funcs = []; let funcSingle, funcInit; while ((m = funcsRe.exec(strippedCode)) !== null) { // This is necessary to avoid infinite loops with zero-width matches if (m.index === funcsRe.lastIndex) { funcsRe.lastIndex++; } const func = { signature: m[1], name: m[3], returns: m[2], arguments: m[4] } debug('Function:', func.signature); if (func.name === 'Init') { funcInit = func; } else { funcs.push(func); } } if (funcs.length === 1) funcSingle = funcs[0]; let init = ''; // If init function is not provided, generate it if (!funcInit) { init = 'Object Init(Env env, Object exports) {\n'; for (let f of funcs) { init += ` exports.Set("${f.name}", Function::New(env, ${f.name}));\n`; } init += ' return exports;\n' init += '}\n'; } let body = ` #include using namespace Napi; ${code} ${init} NODE_API_MODULE(addon, Init) `; // Generate a hash using actual code and build options const modName = 'm_' + crypto.createHash('sha1').update(JSON.stringify(opts)).update(body).digest("hex"); const modPath = path.join(paths.cache, modName); const modNode = path.join(modPath, modName+'.node'); // If the same hash exists, try loading it if (fs.existsSync(modNode)) { debug('Loading cached', modPath); try { if (funcSingle && !funcInit) { return require(modNode)[funcSingle.name]; } else { return require(modNode); } } catch(e) {} } // Ok no luck, let's build it... findBuildDeps(); let gypTarget = { "target_name": modName, "sources": [ "module.cpp" ], "include_dirs": [ `