├── .npmignore ├── LICENSE ├── README.md ├── examples ├── 01_simple.js ├── 02_customCompilerOptions.js └── 03_customModuleInit.js ├── index.js └── package.json /.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Volodymyr Shymanskyy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM version](https://img.shields.io/npm/v/inline-cpp.svg)](https://www.npmjs.com/package/inline-cpp) 2 | [![NPM download](https://img.shields.io/npm/dm/inline-cpp.svg)](https://www.npmjs.com/package/inline-cpp) 3 | [![GitHub issues](https://img.shields.io/github/issues/vshymanskyy/node-inline-cpp.svg)](https://github.com/vshymanskyy/node-inline-cpp/issues) 4 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/vshymanskyy/node-inline-cpp) 5 | 6 | # inline-cpp 7 | Inline C++ with Node.js 8 | 9 | **Works on:** 10 | Linux, 11 | Windows, 12 | MacOS 13 | 14 | **Purpose:** 15 | - Simplify native module prototyping. Enable native code in Node.js REPL. 16 | - Allow JS scripts to generate C++ code and run it dynamically. 17 | - Popularise NAPI usage and `node-addon-api`. 18 | - This is **NOT** intended to be used as native module replacement! 19 | If you want to publish a native module, please package it as required by `node-gyp`. 20 | 21 | ## Installation 22 | 23 | ```sh 24 | npm install --save inline-cpp 25 | ``` 26 | or install it globally (it works with Node.js REPL): 27 | ```sh 28 | npm install -g inline-cpp 29 | ``` 30 | 31 | ## Usage 32 | 33 | ```js 34 | // test.js 35 | const compile = require('inline-cpp'); 36 | 37 | const hello = compile ` 38 | String func(const CallbackInfo& info) { 39 | return String::New(info.Env(), "Hello world from C++!"); 40 | } 41 | ` 42 | 43 | console.log(hello()) 44 | ``` 45 | Now run it: 46 | ```sh 47 | ➜ node test.js 48 | Hello world from C++! 49 | ``` 50 | 51 | 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. 52 | 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). 53 | 54 | For more C++ code examples, see [node-addon-api](https://github.com/nodejs/node-addon-api#examples) 55 | For more `inline-cpp` API examples, see [examples on github](https://github.com/vshymanskyy/node-inline-cpp/tree/master/examples) 56 | 57 | ## API 58 | 59 | `inline-cpp` supports several invocation methods. 60 | 61 | Pass some code as string to build it with default options. 62 | ```js 63 | const InlineCPP = require('inline-cpp'); 64 | InlineCPP('code') 65 | ``` 66 | 67 | You can also pass code using [tagged template syntax](https://developers.google.com/web/updates/2015/01/ES6-Template-Strings#tagged_templates). 68 | ```js 69 | InlineCPP `code` 70 | ``` 71 | 72 | Pass an object to create a new compiler with custom options. 73 | Options will get passed to `node-gyp` target. 74 | ```js 75 | const customCompiler = InlineCPP({ ... }) 76 | ``` 77 | 78 | If the code block only contains a single function, the compiler returns the function. 79 | If it contains multiple functions or custom `Init`, the module itself is returned. 80 | 81 | ## Disclaimer 82 | 83 | This is just a prototype. I created this to check the general concept. 84 | You're welcome to contribute! Here are some ideas: 85 | 86 | - [x] Parse/Find all functions in the block of code, add them to exports 87 | - [ ] Use node-gyp directly, instead of invoking `node node-gyp.js` 88 | - [ ] Improve error handling/reporting 89 | - [ ] Create advanced usage examples 90 | - [ ] Cleanup unused modules from cache periodically 91 | - [ ] ... 92 | 93 | ## Debugging 94 | 95 | You can enable debug output by setting env. variable: `DEBUG=inline-cpp` 96 | -------------------------------------------------------------------------------- /examples/01_simple.js: -------------------------------------------------------------------------------- 1 | const InlineCPP = require('../'); 2 | 3 | // Tagged template with default options 4 | const hello = InlineCPP ` 5 | String func(const CallbackInfo& info) { 6 | return String::New(info.Env(), "Hello world!"); 7 | } 8 | ` 9 | 10 | console.log(hello()); // Hello world! 11 | -------------------------------------------------------------------------------- /examples/02_customCompilerOptions.js: -------------------------------------------------------------------------------- 1 | const InlineCPP = require('../'); 2 | 3 | // Create a compiler with some custom options to node-gyp 4 | const compile = InlineCPP({ "defines": [`SOME_MESSAGE="Bazinga!"`] }); 5 | 6 | const func = compile(` 7 | String func(const CallbackInfo& info) { 8 | return String::New(info.Env(), SOME_MESSAGE); 9 | } 10 | `) 11 | 12 | console.log(func()); // Bazinga! 13 | -------------------------------------------------------------------------------- /examples/03_customModuleInit.js: -------------------------------------------------------------------------------- 1 | const InlineCPP = require('../'); 2 | 3 | // Note: This is just an example. 4 | // You can actually remove the Init function here, 5 | // as the auto-generated one is the same. 6 | 7 | const mod = InlineCPP(` 8 | String alice(const CallbackInfo& info) { 9 | return String::New(info.Env(), "Alice"); 10 | } 11 | 12 | String bob(const CallbackInfo& info) { 13 | return String::New(info.Env(), "Bob"); 14 | } 15 | 16 | Object Init(Env env, Object exports) { 17 | exports.Set("alice", Function::New(env, alice)); 18 | exports.Set("bob", Function::New(env, bob)); 19 | 20 | return exports; 21 | } 22 | `); 23 | 24 | console.log(mod.alice(), 'and', mod.bob()); // Alice and Bob 25 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const fs = require('fs-extra'); 3 | const path = require('path'); 4 | const crypto = require('crypto'); 5 | const { execSync } = require('child_process'); 6 | const paths = require('env-paths')('nodejs-inline-cpp', {suffix: ''}); 7 | const findParentDir = require('find-parent-dir'); 8 | const debug = require('debug')('inline-cpp'); 9 | const _ = require('lodash'); 10 | 11 | let nodeAddon, nodeGyp; 12 | 13 | function findBuildDeps() { 14 | if (nodeAddon && nodeGyp) return; 15 | 16 | nodeAddon = require.resolve('node-addon-api'); 17 | nodeAddon = findParentDir.sync(nodeAddon, 'package.json'); 18 | 19 | nodeGyp = require.resolve('node-gyp'); 20 | nodeGyp = findParentDir.sync(nodeGyp, 'package.json'); 21 | nodeGyp = path.join(nodeGyp, 'bin', 'node-gyp.js'); 22 | 23 | debug('Using node-gyp:', nodeGyp); 24 | debug('Using node-addon-api:', nodeAddon); 25 | 26 | // For some reason, windows needs path to be escaped 27 | if (os.platform() === 'win32') { 28 | nodeAddon = nodeAddon.replace(/[\\$'"]/g, "\\$&") 29 | } 30 | } 31 | 32 | function optsMerge(objValue, srcValue) { 33 | if (_.isArray(objValue)) { 34 | return objValue.concat(srcValue); 35 | } 36 | } 37 | 38 | function generateModule(code, opts) { 39 | 40 | opts = opts || {}; 41 | 42 | code = code.trim(); 43 | 44 | // The stripped code is only used for some auto-detection, it is not actually compiled! 45 | const strippedCode = ' ' + code 46 | .replace(/,/g, ' , ') 47 | .replace(/\(/g, ' ( ') 48 | .replace(/\)/g, ' ) ') 49 | .replace(/{/g, ' { ') 50 | .replace(/}/g, ' } ') 51 | .replace(/\s\s+/g, ' ') + ' '; 52 | 53 | // Find all function declarations 54 | let funcsRe = /(([a-zA-Z_][\w:]*)\s+([a-zA-Z_]\w*)\s*\(\s*((?:[a-zA-Z0-9_:&\*,\s])*)\s*\))\s*{/gm; 55 | let m; 56 | let funcs = []; 57 | let funcSingle, funcInit; 58 | 59 | while ((m = funcsRe.exec(strippedCode)) !== null) { 60 | // This is necessary to avoid infinite loops with zero-width matches 61 | if (m.index === funcsRe.lastIndex) { 62 | funcsRe.lastIndex++; 63 | } 64 | const func = { 65 | signature: m[1], 66 | name: m[3], 67 | returns: m[2], 68 | arguments: m[4] 69 | } 70 | debug('Function:', func.signature); 71 | 72 | if (func.name === 'Init') { 73 | funcInit = func; 74 | } else { 75 | funcs.push(func); 76 | } 77 | } 78 | 79 | if (funcs.length === 1) funcSingle = funcs[0]; 80 | 81 | let init = ''; 82 | 83 | // If init function is not provided, generate it 84 | if (!funcInit) { 85 | init = 'Object Init(Env env, Object exports) {\n'; 86 | 87 | for (let f of funcs) { 88 | init += ` exports.Set("${f.name}", Function::New(env, ${f.name}));\n`; 89 | } 90 | 91 | init += ' return exports;\n' 92 | init += '}\n'; 93 | } 94 | 95 | let body = 96 | ` 97 | #include 98 | using namespace Napi; 99 | 100 | ${code} 101 | ${init} 102 | 103 | NODE_API_MODULE(addon, Init) 104 | `; 105 | 106 | // Generate a hash using actual code and build options 107 | const modName = 'm_' + crypto.createHash('sha1').update(JSON.stringify(opts)).update(body).digest("hex"); 108 | 109 | const modPath = path.join(paths.cache, modName); 110 | const modNode = path.join(modPath, modName+'.node'); 111 | 112 | // If the same hash exists, try loading it 113 | if (fs.existsSync(modNode)) { 114 | debug('Loading cached', modPath); 115 | try { 116 | if (funcSingle && !funcInit) { 117 | return require(modNode)[funcSingle.name]; 118 | } else { 119 | return require(modNode); 120 | } 121 | } catch(e) {} 122 | } 123 | 124 | // Ok no luck, let's build it... 125 | 126 | findBuildDeps(); 127 | 128 | let gypTarget = { 129 | "target_name": modName, 130 | "sources": [ 131 | "module.cpp" 132 | ], 133 | "include_dirs": [ 134 | `