├── .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 | [](https://www.npmjs.com/package/inline-cpp)
2 | [](https://www.npmjs.com/package/inline-cpp)
3 | [](https://github.com/vshymanskyy/node-inline-cpp/issues)
4 | [](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 | `