Skip to content

Commit 16fc9a5

Browse files
author
Sampson Gao
committed
Add migration tool for conversion from NAN to N-API
1 parent df3c99e commit 16fc9a5

File tree

2 files changed

+258
-0
lines changed

2 files changed

+258
-0
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,27 @@ To use N-API in a native module:
4848
At build time, the N-API back-compat library code will be used only when the
4949
targeted node version *does not* have N-API built-in.
5050

51+
## Conversion Tool
52+
To make the migration to node-api easier, we have provided a script to help
53+
complete the steps listed above. To use the conversion script:
54+
1. Go to your module directory
55+
```
56+
cd [module_path]
57+
```
58+
2. Install node-api module
59+
```
60+
npm install node-api
61+
```
62+
3. Run node-api conversion script
63+
```
64+
node ./node_modules/node-api/tools/conversion.js ./
65+
```
66+
4. While this script makes conversion easier, it still cannot fully convert
67+
the module. The next step is to try to build the module and complete the
68+
remaining conversions necessary to allow it to compile and pass all of the
69+
module's tests.
70+
71+
5172
<a name="collaborators"></a>
5273
### WG Members / Collaborators
5374
| Name | GitHub link |

tools/conversion.js

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
const fs = require('fs');
2+
3+
const args = process.argv.slice(2);
4+
const dir = args[0];
5+
if (!dir) {
6+
console.log('Usage: node ' + path.basename(__filename) + ' <target-dir>');
7+
process.exit(1);
8+
}
9+
10+
const NodeApiVersion = require('../package.json').version;
11+
12+
var ConfigFileOperations = {
13+
// ex. String::Utf8Value str(info[0]) to Napi::String str(env, info[0])
14+
'package.json': [
15+
[/"nan": *"[^"]+"/g, '"node-api": "' + NodeApiVersion + '"'],
16+
],
17+
// TODO: Add 'cflags!': [ '-fno-exceptions' ],
18+
// 'cflags_cc!': [ '-fno-exceptions' ]
19+
'binding.gyp': [
20+
[/node -e \\"require\('nan'\)\\"/g, 'node -p \\"require(\'node-api\').include\\"'],
21+
]
22+
};
23+
24+
var SourceFileOperations = [
25+
[/Local<FunctionTemplate>\s+(\w+)\s*=\s*Nan::New<FunctionTemplate>\([\w:]+\);(?:\w+->Reset\(\1\))?\s+\1->SetClassName\(Nan::New\("(\w+)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$2", {'],
26+
[/v8::Local<v8:FunctionTemplate>\s+(\w+)\s*=\s*Nan::New<v8::FunctionTemplate>\([\w:]+\);(?:\w+->Reset\(\1\))?\s+\1->SetClassName\(Nan::New\("(\w+)"\)\);/gm, 'Napi::Function $1 = DefineClass(env, "$2", {'],
27+
[/Nan::SetPrototypeMethod\(\w+, "(\w+)", (\w+)\);/g, ' InstanceMethod("$1", &$2),'],
28+
[/(?:\w+\.Reset\(\w+\);\s+)?\(target\)\.Set\("(\w+)",\s*Nan::GetFunction\((\w+)\)\);/gm,
29+
'});\n\n' +
30+
' constructor = Napi::Persistent($2);\n' +
31+
' constructor.SuppressDestruct();\n' +
32+
' target.Set("$1", $2);'],
33+
[/constructor_template/g, 'constructor'],
34+
35+
[/([\w:]+?)::Cast\((.+?)\)/g, '$2.As<$1>()'],
36+
37+
[/\*Nan::Utf8String\(([^)]+)\)/g, '$1->As<Napi::String>().Utf8Value().c_str()'],
38+
[/Nan::Utf8String +(\w+)\(([^)]+)\)/g, 'std::string $1 = $2.As<Napi::String>()'],
39+
[/Nan::Utf8String/g, 'std::string'],
40+
41+
[/v8::String::Utf8Value (.+?)\((.+?)\)/g, 'Napi::String $1(env, $2)'],
42+
[/String::Utf8Value (.+?)\((.+?)\)/g, 'Napi::String $1(env, $2)'],
43+
[/\.length\(\)/g, '.Length()'],
44+
45+
[/Nan::MakeCallback\(([^,]+),[\s\\]+([^,]+),/gm, '$2.MakeCallback($1,'],
46+
47+
[/class\s+(\w+)\s*:\s*public\s+Nan::ObjectWrap/g, 'class $1 : public Napi::ObjectWrap<$1>'],
48+
[/(\w+)\(([^\)]*)\)\s*:\s*Nan::ObjectWrap\(\)\s*(,)?/gm, '$1($2) : Napi::ObjectWrap<$1>()$3'],
49+
50+
// HandleOKCallback to OnOK
51+
[/HandleOKCallback/g, 'OnOK'],
52+
// HandleErrorCallback to OnError
53+
[/HandleErrorCallback/g, 'OnError'],
54+
55+
56+
[/Nan::Callback/g, 'Napi::Function'],
57+
[/Nan::Persistent<(v8::)*FunctionTemplate>/g, 'Napi::FunctionReference'],
58+
[/Nan::Persistent<(v8::)*Function>/g, 'Napi::FunctionReference'],
59+
[/Nan::Persistent<(v8::)*Object>/g, 'Napi::ObjectReference'],
60+
[/(v8::)*Persistent<(v8::)*FunctionTemplate>/g, 'Napi::FunctionReference'],
61+
[/(v8::)*Persistent<(v8::)*Function>/g, 'Napi::FunctionReference'],
62+
[/(v8::)*Persistent<(v8::)*Object>/g, 'Napi::FunctionReference'],
63+
[/Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target/g, 'Napi::Env& env, Napi::Object& target'],
64+
[/v8::FunctionTemplate/g, 'Napi::FunctionReference'],
65+
[/FunctionTemplate/g, 'Napi::FunctionReference'],
66+
67+
68+
// ex. Local<Value> to Napi::Value
69+
[/v8::Local<v8::(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>/g, 'Napi::$1'],
70+
[/Local<(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>/g, 'Napi::$1'],
71+
[/v8::(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)/g, 'Napi::$1'],
72+
// ex. .As<Function>() to .As<Napi::Object>()
73+
[/\.As<v8::(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>\(\)/g, '.As<Napi::$1>()'],
74+
[/\.As<(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>\(\)/g, '.As<Napi::$1>()'],
75+
76+
// ex. Nan::New<Number>(info[0]) to Napi::Number::New(info[0])
77+
[/Nan::New<Integer>\((.+?)\)/g, 'Napi::Number::New(env, $1)'],
78+
[/Nan::New\(([0-9\.]+)\)/g, 'Napi::Number::New(env, $1)'],
79+
[/Nan::New\("(.+?)"\)/g, 'Napi::String::New(env, "$1")'],
80+
[/Nan::New<(.+?)>\(\)/g, 'Napi::$1::New(env)'],
81+
[/Nan::New<(.+?)>\(/g, 'Napi::$1::New(env, '],
82+
[/Nan::NewBuffer\(/g, 'Napi::Buffer<char>::New(env, '],
83+
// TODO: Properly handle this
84+
[/Nan::New\(/g, 'Napi::New(env, '],
85+
86+
[/.IsInt32\(\)/g, '.IsNumber()'],
87+
88+
89+
// ex. Nan::To<bool>(info[0]) to info[0].Value()
90+
[/Nan::To<(Boolean|String|Number|Object|Array|Symbol|Function)>\((.+?)\)/g, '$2.To<Napi::$1>()'],
91+
[/Nan::To<v8::(Boolean|String|Number|Object|Array|Symbol|Function)>\((.+?)\)/g, '$2.To<Napi::$1>()'],
92+
// ex. Nan::To<bool>(info[0]) to info[0].Value()
93+
[/Nan::To<bool>\((.+?)\)/g, '$1.As<Napi::Boolean>().Value()'],
94+
// ex. Nan::To<int>(info[0]) to info[0].Int32Value()
95+
[/Nan::To<int>\((.+?)\)/g, '$1.As<Napi::Number>().Int32Value()'],
96+
// ex. Nan::To<int32_t>(info[0]) to info[0].Int32Value()
97+
[/Nan::To<int32_t>\((.+?)\)/g, '$1.As<Napi::Number>().Int32Value()'],
98+
// ex. Nan::To<uint32_t>(info[0]) to info[0].Uint32Value()
99+
[/Nan::To<uint32_t>\((.+?)\)/g, '$1.As<Napi::Number>().Uint32Value()'],
100+
// ex. Nan::To<int64_t>(info[0]) to info[0].Int64Value()
101+
[/Nan::To<int64_t>\((.+?)\)/g, '$1.As<Napi::Number>().Int64Value()'],
102+
// ex. Nan::To<float>(info[0]) to info[0].FloatValue()
103+
[/Nan::To<float>\((.+?)\)/g, '$1.As<Napi::Number>().FloatValue()'],
104+
// ex. Nan::To<double>(info[0]) to info[0].DoubleValue()
105+
[/Nan::To<double>\((.+?)\)/g, '$1.As<Napi::Number>().DoubleValue()'],
106+
107+
[/Nan::New\((\w+)\)->HasInstance\((\w+)\)/g, '$2.InstanceOf($1.Value())'],
108+
109+
[/Nan::Get\(([^)]+),\s*/gm, '($1).Get('],
110+
[/\.Get\([\s|\\]*Nan::New\(([^)]+)\)\)/gm, '.Get($1)'],
111+
112+
[/Nan::Set\(([^,]+),\s*/gm, '($1).Set('],
113+
[/\.Set\([\s|\\]*Nan::New\(([^)]+)\),/gm, '.Set($1,'],
114+
115+
116+
// ex. node::Buffer::HasInstance(info[0]) to info[0].IsBuffer()
117+
[/node::Buffer::HasInstance\((.+?)\)/g, '$1.IsBuffer()'],
118+
// ex. node::Buffer::Length(info[0]) to info[0].Length()
119+
[/node::Buffer::Length\((.+?)\)/g, '$1.As<Napi::Buffer<char>>().Length()'],
120+
// ex. node::Buffer::Data(info[0]) to info[0].Data()
121+
[/node::Buffer::Data\((.+?)\)/g, '$1.As<Napi::Buffer<char>>().Data()'],
122+
[/Nan::CopyBuffer\(/g, 'Napi::Buffer::Copy(env, '],
123+
124+
// Nan::AsyncQueueWorker(worker)
125+
[/Nan::AsyncQueueWorker\((.+)\);/g, '$1.Queue();'],
126+
[/Nan::(Undefined|Null|True|False)\(\)/g, 'env.$1()'],
127+
128+
// Nan::ThrowError(error) to Napi::Error::New(env, error).ThrowAsJavaScriptException()
129+
[/Nan::Throw(\w*?)Error\((.+?)\);/g, 'Napi::$1Error::New(env, $2).ThrowAsJavaScriptException();'],
130+
// Nan::RangeError(error) to Napi::RangeError::New(env, error)
131+
[/Nan::(\w*?)Error\((.+)\)/g, 'Napi::$1Error::New(env, $2)'],
132+
133+
[/Nan::Set\((.+?),\n* *(.+?),\n* *(.+?),\n* *(.+?)\)/g, '$1.Set($2, $3, $4)'],
134+
135+
[/info\[(\w+)\]->/g, 'info[$1].'],
136+
[/info\.This\(\)->/g, 'info.This().'],
137+
[/->Is(Object|String|Int32|Number)\(\)/g, '.Is$1()'],
138+
[/info\.GetReturnValue\(\)\.Set\(((\n|.)+?)\);/g, 'return $1;'],
139+
140+
[/Nan::(Escapable)?HandleScope\s+(\w+)\s*;/g, 'Napi::$1HandleScope $2(env);'],
141+
[/Nan::(Escapable)?HandleScope/g, 'Napi::$1HandleScope'],
142+
[/Nan::ForceSet\(([^,]+), ?/g, '$1->DefineProperty('],
143+
[/\.ForceSet\(Napi::String::New\(env, "(\w+)"\),\s*?/g, '.DefineProperty("$1", '],
144+
// [/Nan::GetPropertyNames\(([^,]+)\)/, '$1->GetPropertyNames()'],
145+
[/Nan::Equals\(([^,]+),/g, '$1.Equals('],
146+
147+
148+
149+
[/(\w+)\*\s+(\w+)\s*=\s*Nan::ObjectWrap::Unwrap<\w+>\(info\.This\(\)\);/g, '$1* $2 = this;'],
150+
[/Nan::ObjectWrap::Unwrap<(\w+)>\((.*)\);/g, '$2.Unwrap<$1>();'],
151+
152+
[/static\s*NAN_METHOD\s*\((\w+?)\)\s*{/g, 'Napi::Value $1(const Napi::CallbackInfo& info) {\n Napi::Env env = info.Env();'],
153+
[/NAN_METHOD\s*\((\w+?)\)\s*{/g, 'Napi::Value $1(const Napi::CallbackInfo& info) {\n Napi::Env env = info.Env();'],
154+
[/static\s*NAN_GETTER\s*\((\w+?)\)\s*{/g, 'Napi::Value $1(const Napi::CallbackInfo& info) {\n Napi::Env env = info.Env();'],
155+
[/NAN_GETTER\s*\((\w+?)\)\s*{/g, 'Napi::Value $1(const Napi::CallbackInfo& info) {\n Napi::Env env = info.Env();'],
156+
[/static\s*NAN_SETTER\s*\((\w+?)\)\s*{/g, 'Napi::Value $1(const Napi::CallbackInfo& info, const Napi::Value& value) {\n Napi::Env env = info.Env();'],
157+
[/NAN_SETTER\s*\((\w+?)\)\s*{/g, 'Napi::Value $1(const Napi::CallbackInfo& info, const Napi::Value& value) {\n Napi::Env env = info.Env();'],
158+
[/NAN_MODULE_INIT\s*\(([\w:]+?)\)/g, 'void $1(Napi::Env env, Napi::Object exports, Napi::Object module)'],
159+
[/Nan::NAN_METHOD_ARGS_TYPE/g, 'const Napi::CallbackInfo&'],
160+
[/(Nan::)*FunctionCallbackInfo<(.+?)*>&*/g, 'Napi::CallbackInfo&'],
161+
[/::(Init(?:ialize)?)\(target\)/g, '::$1(env, target, module)'],
162+
163+
// TODO: Other attribute combinations
164+
[/static_cast<PropertyAttribute>\(ReadOnly\s*\|\s*DontDelete\)/gm,
165+
'static_cast<napi_property_attributes>(napi_enumerable | napi_configurable)'],
166+
167+
// Declare an env in helper functions that take a Napi::Value
168+
[/(\w+)\(Napi::Value (\w+)(,\s*[^\()]+)?\)\s*{/g, '$1(Napi::Value $2$3) {\n Napi::Env env = $2.Env();'],
169+
170+
// delete #include <node.h> and/or <v8.h>
171+
[/#include +(<|")(?:node|nan).h("|>)/g, "#include $1napi.h$2\n#include $1uv.h$2"],
172+
// NODE_MODULE to NODE_API_MODULE
173+
[/NODE_MODULE/g, 'NODE_API_MODULE'],
174+
[/Nan::/g, 'Napi::'],
175+
[/nan.h/g, 'napi.h'],
176+
177+
// delete .FromJust()
178+
[/\.FromJust\(\)/g, ''],
179+
// delete .ToLocalCheck()
180+
[/\.ToLocalChecked\(\)/g, ''],
181+
[/^.*->SetInternalFieldCount\(.*$/gm, ''],
182+
183+
// replace using node; and/or using v8; to using Napi;
184+
[/using (node|v8);/g, 'using Napi;'],
185+
// delete using v8::XXX;
186+
[/using v8::Local;\n/g, ''],
187+
// delete using v8::XXX;
188+
[/using v8::([A-Za-z]+);/g, 'using Napi::$1;'],
189+
190+
];
191+
192+
var paths = listFiles(dir);
193+
paths.forEach(function(path) {
194+
var filename = path.split('\\').pop().split('/').pop();
195+
196+
// Check whether the file is a source file or a config file
197+
// then execute function accordingly
198+
var sourcePattern = /.+\.h|.+\.cc|.+\.cpp/;
199+
if (sourcePattern.test(filename)) {
200+
convertFile(path, SourceFileOperations);
201+
} else if (ConfigFileOperations[filename] != null) {
202+
convertFile(path, ConfigFileOperations[filename]);
203+
}
204+
});
205+
206+
function listFiles(dir, filelist) {
207+
var path = path || require('path');
208+
files = fs.readdirSync(dir);
209+
filelist = filelist || [];
210+
files.forEach(function(file) {
211+
if (fs.statSync(path.join(dir, file)).isDirectory()) {
212+
filelist = listFiles(path.join(dir, file), filelist);
213+
} else {
214+
filelist.push(path.join(dir, file));
215+
}
216+
});
217+
return filelist;
218+
}
219+
220+
function convert(content, operations) {
221+
operations.forEach(function(operation) {
222+
content = content.replace(operation[0], operation[1]);
223+
});
224+
return content;
225+
}
226+
227+
function convertFile(path, operations) {
228+
fs.readFile(path, "utf-8", function (err, file) {
229+
if (err) throw err;
230+
231+
file = convert(file, operations);
232+
233+
fs.writeFile(path, file, function(err){
234+
if (err) throw err;
235+
});
236+
});
237+
}

0 commit comments

Comments
 (0)