mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Add RED.import to support importing ES6 modules
This commit is contained in:
parent
65081767bf
commit
7fd17b4ec0
@ -290,6 +290,7 @@ module.exports = function(RED) {
|
|||||||
};
|
};
|
||||||
sandbox.promisify = util.promisify;
|
sandbox.promisify = util.promisify;
|
||||||
}
|
}
|
||||||
|
const moduleLoadPromises = [];
|
||||||
|
|
||||||
if (node.hasOwnProperty("libs")) {
|
if (node.hasOwnProperty("libs")) {
|
||||||
let moduleErrors = false;
|
let moduleErrors = false;
|
||||||
@ -303,199 +304,197 @@ module.exports = function(RED) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sandbox[vname] = null;
|
sandbox[vname] = null;
|
||||||
try {
|
var spec = module.module;
|
||||||
var spec = module.module;
|
if (spec && (spec !== "")) {
|
||||||
if (spec && (spec !== "")) {
|
RED.import(module.module).then(lib => {
|
||||||
var lib = RED.require(module.module);
|
|
||||||
sandbox[vname] = lib;
|
sandbox[vname] = lib;
|
||||||
}
|
}).catch(err => {
|
||||||
} catch (e) {
|
node.error(RED._("function.error.moduleLoadError",{module:module.spec, error:err.toString()}))
|
||||||
//TODO: NLS error message
|
moduleErrors = true;
|
||||||
node.error(RED._("function.error.moduleLoadError",{module:module.spec, error:e.toString()}))
|
throw err;
|
||||||
moduleErrors = true;
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (moduleErrors) {
|
|
||||||
throw new Error(RED._("function.error.externalModuleLoadError"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Promise.all(moduleLoadPromises).then(() => {
|
||||||
|
const RESOLVING = 0;
|
||||||
|
const RESOLVED = 1;
|
||||||
|
const ERROR = 2;
|
||||||
|
var state = RESOLVING;
|
||||||
|
var messages = [];
|
||||||
|
var processMessage = (() => {});
|
||||||
|
|
||||||
|
node.on("input", function(msg,send,done) {
|
||||||
|
if(state === RESOLVING) {
|
||||||
|
messages.push({msg:msg, send:send, done:done});
|
||||||
|
}
|
||||||
|
else if(state === RESOLVED) {
|
||||||
|
processMessage(msg, send, done);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const RESOLVING = 0;
|
var context = vm.createContext(sandbox);
|
||||||
const RESOLVED = 1;
|
try {
|
||||||
const ERROR = 2;
|
var iniScript = null;
|
||||||
var state = RESOLVING;
|
var iniOpt = null;
|
||||||
var messages = [];
|
if (node.ini && (node.ini !== "")) {
|
||||||
var processMessage = (() => {});
|
var iniText = `
|
||||||
|
(async function(__send__) {
|
||||||
node.on("input", function(msg,send,done) {
|
var node = {
|
||||||
if(state === RESOLVING) {
|
id:__node__.id,
|
||||||
messages.push({msg:msg, send:send, done:done});
|
name:__node__.name,
|
||||||
}
|
outputCount:__node__.outputCount,
|
||||||
else if(state === RESOLVED) {
|
log:__node__.log,
|
||||||
processMessage(msg, send, done);
|
error:__node__.error,
|
||||||
}
|
warn:__node__.warn,
|
||||||
});
|
debug:__node__.debug,
|
||||||
|
trace:__node__.trace,
|
||||||
var context = vm.createContext(sandbox);
|
status:__node__.status,
|
||||||
try {
|
send: function(msgs, cloneMsg) {
|
||||||
var iniScript = null;
|
__node__.send(__send__, RED.util.generateId(), msgs, cloneMsg);
|
||||||
var iniOpt = null;
|
|
||||||
if (node.ini && (node.ini !== "")) {
|
|
||||||
var iniText = `
|
|
||||||
(async function(__send__) {
|
|
||||||
var node = {
|
|
||||||
id:__node__.id,
|
|
||||||
name:__node__.name,
|
|
||||||
outputCount:__node__.outputCount,
|
|
||||||
log:__node__.log,
|
|
||||||
error:__node__.error,
|
|
||||||
warn:__node__.warn,
|
|
||||||
debug:__node__.debug,
|
|
||||||
trace:__node__.trace,
|
|
||||||
status:__node__.status,
|
|
||||||
send: function(msgs, cloneMsg) {
|
|
||||||
__node__.send(__send__, RED.util.generateId(), msgs, cloneMsg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
`+ node.ini +`
|
|
||||||
})(__initSend__);`;
|
|
||||||
iniOpt = createVMOpt(node, " setup");
|
|
||||||
iniScript = new vm.Script(iniText, iniOpt);
|
|
||||||
}
|
|
||||||
node.script = vm.createScript(functionText, createVMOpt(node, ""));
|
|
||||||
if (node.fin && (node.fin !== "")) {
|
|
||||||
var finText = `(function () {
|
|
||||||
var node = {
|
|
||||||
id:__node__.id,
|
|
||||||
name:__node__.name,
|
|
||||||
outputCount:__node__.outputCount,
|
|
||||||
log:__node__.log,
|
|
||||||
error:__node__.error,
|
|
||||||
warn:__node__.warn,
|
|
||||||
debug:__node__.debug,
|
|
||||||
trace:__node__.trace,
|
|
||||||
status:__node__.status,
|
|
||||||
send: function(msgs, cloneMsg) {
|
|
||||||
__node__.error("Cannot send from close function");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
`+node.fin +`
|
|
||||||
})();`;
|
|
||||||
finOpt = createVMOpt(node, " cleanup");
|
|
||||||
finScript = new vm.Script(finText, finOpt);
|
|
||||||
}
|
|
||||||
var promise = Promise.resolve();
|
|
||||||
if (iniScript) {
|
|
||||||
context.__initSend__ = function(msgs) { node.send(msgs); };
|
|
||||||
promise = iniScript.runInContext(context, iniOpt);
|
|
||||||
}
|
|
||||||
|
|
||||||
processMessage = function (msg, send, done) {
|
|
||||||
var start = process.hrtime();
|
|
||||||
context.msg = msg;
|
|
||||||
context.__send__ = send;
|
|
||||||
context.__done__ = done;
|
|
||||||
|
|
||||||
node.script.runInContext(context);
|
|
||||||
context.results.then(function(results) {
|
|
||||||
sendResults(node,send,msg._msgid,results,false);
|
|
||||||
if (handleNodeDoneCall) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
|
|
||||||
var duration = process.hrtime(start);
|
|
||||||
var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100;
|
|
||||||
node.metric("duration", msg, converted);
|
|
||||||
if (process.env.NODE_RED_FUNCTION_TIME) {
|
|
||||||
node.status({fill:"yellow",shape:"dot",text:""+converted});
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
if ((typeof err === "object") && err.hasOwnProperty("stack")) {
|
|
||||||
//remove unwanted part
|
|
||||||
var index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/);
|
|
||||||
err.stack = err.stack.slice(0, index).split('\n').slice(0,-1).join('\n');
|
|
||||||
var stack = err.stack.split(/\r?\n/);
|
|
||||||
|
|
||||||
//store the error in msg to be used in flows
|
|
||||||
msg.error = err;
|
|
||||||
|
|
||||||
var line = 0;
|
|
||||||
var errorMessage;
|
|
||||||
if (stack.length > 0) {
|
|
||||||
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
|
|
||||||
line++;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
`+ node.ini +`
|
||||||
|
})(__initSend__);`;
|
||||||
|
iniOpt = createVMOpt(node, " setup");
|
||||||
|
iniScript = new vm.Script(iniText, iniOpt);
|
||||||
|
}
|
||||||
|
node.script = vm.createScript(functionText, createVMOpt(node, ""));
|
||||||
|
if (node.fin && (node.fin !== "")) {
|
||||||
|
var finText = `(function () {
|
||||||
|
var node = {
|
||||||
|
id:__node__.id,
|
||||||
|
name:__node__.name,
|
||||||
|
outputCount:__node__.outputCount,
|
||||||
|
log:__node__.log,
|
||||||
|
error:__node__.error,
|
||||||
|
warn:__node__.warn,
|
||||||
|
debug:__node__.debug,
|
||||||
|
trace:__node__.trace,
|
||||||
|
status:__node__.status,
|
||||||
|
send: function(msgs, cloneMsg) {
|
||||||
|
__node__.error("Cannot send from close function");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
`+node.fin +`
|
||||||
|
})();`;
|
||||||
|
finOpt = createVMOpt(node, " cleanup");
|
||||||
|
finScript = new vm.Script(finText, finOpt);
|
||||||
|
}
|
||||||
|
var promise = Promise.resolve();
|
||||||
|
if (iniScript) {
|
||||||
|
context.__initSend__ = function(msgs) { node.send(msgs); };
|
||||||
|
promise = iniScript.runInContext(context, iniOpt);
|
||||||
|
}
|
||||||
|
|
||||||
if (line < stack.length) {
|
processMessage = function (msg, send, done) {
|
||||||
errorMessage = stack[line];
|
var start = process.hrtime();
|
||||||
var m = /:(\d+):(\d+)$/.exec(stack[line+1]);
|
context.msg = msg;
|
||||||
if (m) {
|
context.__send__ = send;
|
||||||
var lineno = Number(m[1])-1;
|
context.__done__ = done;
|
||||||
var cha = m[2];
|
|
||||||
errorMessage += " (line "+lineno+", col "+cha+")";
|
node.script.runInContext(context);
|
||||||
|
context.results.then(function(results) {
|
||||||
|
sendResults(node,send,msg._msgid,results,false);
|
||||||
|
if (handleNodeDoneCall) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
var duration = process.hrtime(start);
|
||||||
|
var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100;
|
||||||
|
node.metric("duration", msg, converted);
|
||||||
|
if (process.env.NODE_RED_FUNCTION_TIME) {
|
||||||
|
node.status({fill:"yellow",shape:"dot",text:""+converted});
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
if ((typeof err === "object") && err.hasOwnProperty("stack")) {
|
||||||
|
//remove unwanted part
|
||||||
|
var index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/);
|
||||||
|
err.stack = err.stack.slice(0, index).split('\n').slice(0,-1).join('\n');
|
||||||
|
var stack = err.stack.split(/\r?\n/);
|
||||||
|
|
||||||
|
//store the error in msg to be used in flows
|
||||||
|
msg.error = err;
|
||||||
|
|
||||||
|
var line = 0;
|
||||||
|
var errorMessage;
|
||||||
|
if (stack.length > 0) {
|
||||||
|
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line < stack.length) {
|
||||||
|
errorMessage = stack[line];
|
||||||
|
var m = /:(\d+):(\d+)$/.exec(stack[line+1]);
|
||||||
|
if (m) {
|
||||||
|
var lineno = Number(m[1])-1;
|
||||||
|
var cha = m[2];
|
||||||
|
errorMessage += " (line "+lineno+", col "+cha+")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!errorMessage) {
|
||||||
|
errorMessage = err.toString();
|
||||||
|
}
|
||||||
|
done(errorMessage);
|
||||||
}
|
}
|
||||||
if (!errorMessage) {
|
else if (typeof err === "string") {
|
||||||
errorMessage = err.toString();
|
done(err);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
done(JSON.stringify(err));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
node.on("close", function() {
|
||||||
|
if (finScript) {
|
||||||
|
try {
|
||||||
|
finScript.runInContext(context, finOpt);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
node.error(err);
|
||||||
}
|
}
|
||||||
done(errorMessage);
|
|
||||||
}
|
}
|
||||||
else if (typeof err === "string") {
|
while (node.outstandingTimers.length > 0) {
|
||||||
done(err);
|
clearTimeout(node.outstandingTimers.pop());
|
||||||
}
|
}
|
||||||
else {
|
while (node.outstandingIntervals.length > 0) {
|
||||||
done(JSON.stringify(err));
|
clearInterval(node.outstandingIntervals.pop());
|
||||||
|
}
|
||||||
|
if (node.clearStatus) {
|
||||||
|
node.status({});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
node.on("close", function() {
|
promise.then(function (v) {
|
||||||
if (finScript) {
|
var msgs = messages;
|
||||||
try {
|
|
||||||
finScript.runInContext(context, finOpt);
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
node.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (node.outstandingTimers.length > 0) {
|
|
||||||
clearTimeout(node.outstandingTimers.pop());
|
|
||||||
}
|
|
||||||
while (node.outstandingIntervals.length > 0) {
|
|
||||||
clearInterval(node.outstandingIntervals.pop());
|
|
||||||
}
|
|
||||||
if (node.clearStatus) {
|
|
||||||
node.status({});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.then(function (v) {
|
|
||||||
var msgs = messages;
|
|
||||||
messages = [];
|
|
||||||
while (msgs.length > 0) {
|
|
||||||
msgs.forEach(function (s) {
|
|
||||||
processMessage(s.msg, s.send, s.done);
|
|
||||||
});
|
|
||||||
msgs = messages;
|
|
||||||
messages = [];
|
messages = [];
|
||||||
}
|
while (msgs.length > 0) {
|
||||||
state = RESOLVED;
|
msgs.forEach(function (s) {
|
||||||
}).catch((error) => {
|
processMessage(s.msg, s.send, s.done);
|
||||||
messages = [];
|
});
|
||||||
state = ERROR;
|
msgs = messages;
|
||||||
node.error(error);
|
messages = [];
|
||||||
});
|
}
|
||||||
|
state = RESOLVED;
|
||||||
|
}).catch((error) => {
|
||||||
|
messages = [];
|
||||||
|
state = ERROR;
|
||||||
|
node.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
catch(err) {
|
catch(err) {
|
||||||
// eg SyntaxError - which v8 doesn't include line number information
|
// eg SyntaxError - which v8 doesn't include line number information
|
||||||
// so we can't do better than this
|
// so we can't do better than this
|
||||||
updateErrorInfo(err);
|
updateErrorInfo(err);
|
||||||
node.error(err);
|
node.error(err);
|
||||||
}
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
throw new Error(RED._("function.error.externalModuleLoadError"));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
RED.nodes.registerType("function",FunctionNode, {
|
RED.nodes.registerType("function",FunctionNode, {
|
||||||
dynamicModuleList: "libs",
|
dynamicModuleList: "libs",
|
||||||
|
@ -48,7 +48,7 @@ async function refreshExternalModules() {
|
|||||||
const externalModuleDir = getInstallDir();
|
const externalModuleDir = getInstallDir();
|
||||||
try {
|
try {
|
||||||
const pkgFile = JSON.parse(await fs.readFile(path.join(externalModuleDir,"package.json"),"utf-8"));
|
const pkgFile = JSON.parse(await fs.readFile(path.join(externalModuleDir,"package.json"),"utf-8"));
|
||||||
knownExternalModules = pkgFile.dependencies;
|
knownExternalModules = pkgFile.dependencies || {};
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
knownExternalModules = {};
|
knownExternalModules = {};
|
||||||
}
|
}
|
||||||
@ -101,6 +101,31 @@ function requireModule(module) {
|
|||||||
const moduleDir = path.join(externalModuleDir,"node_modules",module);
|
const moduleDir = path.join(externalModuleDir,"node_modules",module);
|
||||||
return require(moduleDir);
|
return require(moduleDir);
|
||||||
}
|
}
|
||||||
|
function importModule(module) {
|
||||||
|
if (!registryUtil.checkModuleAllowed( module, null,installAllowList,installDenyList)) {
|
||||||
|
const e = new Error("Module not allowed");
|
||||||
|
e.code = "module_not_allowed";
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedModule = parseModuleName(module);
|
||||||
|
|
||||||
|
if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) {
|
||||||
|
return import(parsedModule.module);
|
||||||
|
}
|
||||||
|
if (!knownExternalModules[parsedModule.module]) {
|
||||||
|
const e = new Error("Module not allowed");
|
||||||
|
e.code = "module_not_allowed";
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
const externalModuleDir = getInstallDir();
|
||||||
|
const moduleDir = path.join(externalModuleDir,"node_modules",module);
|
||||||
|
// Import needs the full path to the module's main .js file
|
||||||
|
const moduleFile = require.resolve(moduleDir);
|
||||||
|
return import(moduleFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function parseModuleName(module) {
|
function parseModuleName(module) {
|
||||||
var match = /((?:@[^/]+\/)?[^/@]+)(?:@([\s\S]+))?/.exec(module);
|
var match = /((?:@[^/]+\/)?[^/@]+)(?:@([\s\S]+))?/.exec(module);
|
||||||
@ -254,5 +279,6 @@ module.exports = {
|
|||||||
register: register,
|
register: register,
|
||||||
registerSubflow: registerSubflow,
|
registerSubflow: registerSubflow,
|
||||||
checkFlowDependencies: checkFlowDependencies,
|
checkFlowDependencies: checkFlowDependencies,
|
||||||
require: requireModule
|
require: requireModule,
|
||||||
|
import: importModule
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,16 @@ function requireModule(name) {
|
|||||||
return require("./externalModules").require(name);
|
return require("./externalModules").require(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function importModule(name) {
|
||||||
|
var moduleInfo = require("./index").getModuleInfo(name);
|
||||||
|
if (moduleInfo && moduleInfo.path) {
|
||||||
|
var relPath = path.relative(__dirname, moduleInfo.path);
|
||||||
|
return import(relPath);
|
||||||
|
} else {
|
||||||
|
// Require it here to avoid the circular dependency
|
||||||
|
return require("./externalModules").import(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createNodeApi(node) {
|
function createNodeApi(node) {
|
||||||
var red = {
|
var red = {
|
||||||
@ -61,6 +71,7 @@ function createNodeApi(node) {
|
|||||||
util: runtime.util,
|
util: runtime.util,
|
||||||
version: runtime.version,
|
version: runtime.version,
|
||||||
require: requireModule,
|
require: requireModule,
|
||||||
|
import: importModule,
|
||||||
comms: {
|
comms: {
|
||||||
publish: function(topic,data,retain) {
|
publish: function(topic,data,retain) {
|
||||||
events.emit("comms",{
|
events.emit("comms",{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user