Liu Song’s Projects


~/Projects/flow

git clone https://code.lsong.org/flow

Commit

Commit
eb4625a0b9bcb311f3acc4793e3be62dfb38d5c2
Author
Nick O'Leary <[email protected]>
Date
2021-07-15 09:56:23 +0100 +0100
Diffstat
 packages/node_modules/@node-red/nodes/core/function/10-function.html | 137 
 packages/node_modules/@node-red/nodes/core/function/10-function.js | 322 
 packages/node_modules/@node-red/nodes/locales/en-US/messages.json | 5 
 packages/node_modules/@node-red/registry/lib/externalModules.js | 63 
 packages/node_modules/@node-red/registry/lib/util.js | 12 
 packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js | 3 
 packages/node_modules/@node-red/runtime/locales/en-US/runtime.json | 3 
 test/unit/@node-red/registry/lib/externalModules_spec.js | 74 

Merge pull request #3064 from node-red/revert-external-modules-dir

Move externalModules back into the user dir


diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.html b/packages/node_modules/@node-red/nodes/core/function/10-function.html
index 208aef991630462087d9f27b56be5f91f2500557..4305f055b2fc2eab60769f5f7fe09516a9ea7cb9 100644
--- a/packages/node_modules/@node-red/nodes/core/function/10-function.html
+++ b/packages/node_modules/@node-red/nodes/core/function/10-function.html
@@ -7,50 +7,73 @@         #node-input-libs-container-row .red-ui-editableList-container {
             padding: 0px;
         }
         #node-input-libs-container-row .red-ui-editableList-container li {
-            padding:5px;
+            padding:0px;
         }
         #node-input-libs-container-row .red-ui-editableList-item-remove {
             right: 5px;
         }
-<script type="text/html" data-template-name="function">
+
+            margin-bottom: 0;
     <style>
+        .func-tabs-row {
             display: flex;
+            background: var(--red-ui-tertiary-background);
+    <div class="form-row func-tabs-row">
         }
-<script type="text/html" data-template-name="function">
+        }
             margin-bottom: 0;
+            margin-bottom: 2px;
             flex-grow: 1;
         }
+
 <script type="text/html" data-template-name="function">
-        #node-input-libs-container-row .red-ui-editableList-container {
+    <style>
 <script type="text/html" data-template-name="function">
-            padding: 0px;
+        .func-tabs-row {
         }
-<script type="text/html" data-template-name="function">
+    <style>
         #node-input-libs-container-row .red-ui-editableList-container li {
+        .node-libs-entry .red-ui-typedInput-container {
             border-radius: 0;
+            margin-bottom: 0;
     <style>
+        #node-input-libs-container-row .red-ui-editableList-container li {
+        }
+        .node-libs-entry .red-ui-typedInput-type-select {
+        <ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul>
+            height: 34px;
+        }
+        <ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul>
     <style>
 <script type="text/html" data-template-name="function">
+            padding:5px;
-            border-right: none;
+            border-top-color: var(--red-ui-form-background);
-    <style>
+            margin-bottom: 0;
         .func-tabs-row {
+            margin-bottom: 0;
-    <style>
             margin-bottom: 0;
+    <input type="hidden" id="node-input-noerr">
-    <style>
         }
-    <style>
+        <ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul>
         #node-input-libs-container-row .red-ui-editableList-container {
-    <style>
+        }
+        <ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul>
             padding: 0px;
+<script type="text/html" data-template-name="function">
         }
-    <style>
+        <ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul>
         #node-input-libs-container-row .red-ui-editableList-container li {
+            position: relative;
+        }
+        .node-libs-entry span .node-input-libs-var, .node-libs-entry span .red-ui-typedInput-container {
+            width: 100%;
+        }
+    <div id="func-tabs-content" style="min-height: calc(100% - 95px);">
     <style>
-            padding:5px;
             display: none;
         }
+    <div id="func-tabs-content" style="min-height: calc(100% - 95px);">
         .func-tabs-row {
-<script type="text/html" data-template-name="function">
             display: inline;
         }
 
@@ -223,115 +245,107 @@             value:"_custom_", label:RED._("editor:subflow.licenseOther"), icon:"red/images/typedInput/az.svg"
         })
 
         var libList = $("#node-input-libs-container").css('min-height','100px').css('min-width','450px').editableList({
+            header: $('<div><div data-i18n="node-red:function.require.moduleName"></div><div data-i18n="node-red:function.require.importAs"></div></div>'),
             addItem: function(container,i,opt) {
                 var parent = container.parent();
                 var row0 = $("<div/>").addClass("node-libs-entry").appendTo(container);
+                var fmoduleSpan = $("<span>").appendTo(row0);
 <script type="text/html" data-template-name="function">
-        }
+        'Date',
+            line-height: 30px;
         .func-tabs-row {
 <script type="text/html" data-template-name="function">
-        }
+            padding: 0px;
             margin-bottom: 0;
             flex-grow: 1;
-        }
+        #node-input-libs-container-row .red-ui-editableList-container li {
             flex-grow: 1;
+            padding:5px;
+    <div id="func-tabs-content" style="min-height: calc(100% - 95px);">
         #node-input-libs-container-row .red-ui-editableList-container {
 <script type="text/html" data-template-name="function">
-        }
+            padding: 0px;
             padding: 0px;
 <script type="text/html" data-template-name="function">
-        }
+            padding: 0px;
         #node-input-libs-container-row .red-ui-editableList-container li {
 <script type="text/html" data-template-name="function">
-        }
+            padding: 0px;
             padding:5px;
 <script type="text/html" data-template-name="function">
-        #node-input-libs-container-row .red-ui-editableList-container {
+        #node-input-libs-container-row .red-ui-editableList-container li {
 <script type="text/html" data-template-name="function">
-        #node-input-libs-container-row .red-ui-editableList-container {
+        #node-input-libs-container-row .red-ui-editableList-container li {
 <script type="text/html" data-template-name="function">
 <script type="text/html" data-template-name="function">
-        <div id="func-tab-finalize" style="display:none">
 <script type="text/html" data-template-name="function">
-                <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-finalize-editor" ></div>
+                var moduleWarning = $('<span style="position: absolute;right:2px;top:7px; display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(fmoduleSpan);
 <script type="text/html" data-template-name="function">
-        #node-input-libs-container-row .red-ui-editableList-container {
+        #node-input-libs-container-row .red-ui-editableList-container li {
             margin-bottom: 0;
 <script type="text/html" data-template-name="function">
-        #node-input-libs-container-row .red-ui-editableList-container {
+        #node-input-libs-container-row .red-ui-editableList-container li {
         }
 <script type="text/html" data-template-name="function">
-        #node-input-libs-container-row .red-ui-editableList-container {
+        #node-input-libs-container-row .red-ui-editableList-container li {
         #node-input-libs-container-row .red-ui-editableList-container {
 <script type="text/html" data-template-name="function">
-        #node-input-libs-container-row .red-ui-editableList-container {
+        #node-input-libs-container-row .red-ui-editableList-container li {
             padding: 0px;
 <script type="text/html" data-template-name="function">
-    var invalidModuleVNames = [
-                        return RED._("node-red:function.error.moduleNameError",{name:val})
-<script type="text/html" data-template-name="function">
     <input type="hidden" id="node-input-initialize">
 <script type="text/html" data-template-name="function">
-        'util',
+        if (n.type === "function") {
 
 <script type="text/html" data-template-name="function">
-        'Buffer',
-                var fmodule = $("<input/>", {
-                    class: "node-input-libs-val",
-                    placeholder: RED._("node-red:function.require.module"),
-            flex-grow: 1;
         #node-input-libs-container-row .red-ui-editableList-container li {
+            padding:5px;
 <script type="text/html" data-template-name="function">
-        }
             padding:5px;
 <script type="text/html" data-template-name="function">
-        '__node__',
-            line-height: 30px;
         #node-input-libs-container-row .red-ui-editableList-container {
-                    types: typedModules,
-            line-height: 30px;
         #node-input-libs-container-row .red-ui-editableList-container li {
 <script type="text/html" data-template-name="function">
-            padding: 0px;
             padding:5px;
 <script type="text/html" data-template-name="function">
-        'setTimeout',
 <script type="text/html" data-template-name="function">
-        'clearTimeout',
+    <input type="hidden" id="node-input-initialize">
 <script type="text/html" data-template-name="function">
+            padding: 0px;
-<script type="text/html" data-template-name="function">
 
-<script type="text/html" data-template-name="function">
+    <div id="func-tabs-content" style="min-height: calc(100% - 95px);">
         #node-input-libs-container-row .red-ui-editableList-container li {
-    <style>
 
 <script type="text/html" data-template-name="function">
-        'clearInterval',
+        <div id="func-tab-init" style="display:none">
 <script type="text/html" data-template-name="function">
-        'promisify'
+            <div class="form-row node-text-editor-row" style="position:relative">
 <script type="text/html" data-template-name="function">
-        #node-input-libs-container-row .red-ui-editableList-container li {
         }
+            padding: 0px;
 <script type="text/html" data-template-name="function">
+        }
         #node-input-libs-container-row .red-ui-editableList-container li {
-        #node-input-libs-container-row .red-ui-editableList-container {
 <script type="text/html" data-template-name="function">
-    RED.events.on("nodes:add", function(n) {
+        <div id="func-tab-body" style="display:none">
+                }).appendTo(fvarSpan).val(opt.var);
+                var vnameWarning = $('<span style="position: absolute; right:2px;top:7px;display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(fvarSpan);
 <script type="text/html" data-template-name="function">
-    <input type="hidden" id="node-input-initialize">
+                <div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 5;"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
 <script type="text/html" data-template-name="function">
-        if (n.type === "function") {
-
+</script>
 <script type="text/html" data-template-name="function">
-            knownFunctionNodes[n.id] = n;
+<script type="text/javascript">
 <script type="text/html" data-template-name="function">
-    })
+(function() {
                     } else {
 <script type="text/html" data-template-name="function">
+        #node-input-libs-container-row .red-ui-editableList-container {
             padding:5px;
-<script type="text/html" data-template-name="function">
                     }
                 })
+
+
 
                 fvar.on("change keyup paste", function (e) {
                     var v = $(this).val().trim();
@@ -348,7 +361,7 @@                     var val = $(this).typedInput("type");
                     if (val === "_custom_") {
                         val = $(this).val();
                     }
-                    var varName = val.trim().replace(/^@/,"").replace(/@.*$/,"").replace(/[-_/]./g, function(v) { return v[1].toUpperCase() });
+                    var varName = val.trim().replace(/^@/,"").replace(/@.*$/,"").replace(/[-_/].?/g, function(v) { return v[1]?v[1].toUpperCase():"" });
                     fvar.val(varName);
                     fvar.trigger("change");
 




diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.js b/packages/node_modules/@node-red/nodes/core/function/10-function.js
index c1c20f26655c97c209434518863769098ae50a65..d58b057c74d319f58d41447773effd0bf27b12e8 100644
--- a/packages/node_modules/@node-red/nodes/core/function/10-function.js
+++ b/packages/node_modules/@node-red/nodes/core/function/10-function.js
@@ -290,6 +290,7 @@                 });
             };
             sandbox.promisify = util.promisify;
         }
+        const moduleLoadPromises = [];
 
         if (node.hasOwnProperty("libs")) {
             let moduleErrors = false;
@@ -303,32 +304,26 @@                         moduleErrors = true;
                         return;
                     }
                     sandbox[vname] = null;
+                        if (typeof msg === 'object' && !Buffer.isBuffer(msg) && !util.isArray(msg)) {
 /**
-        if (RED.settings.functionExternalModules !== true && node.libs.length > 0) {
-        if (msgs == null) {
  *
- * Copyright JS Foundation and other contributors, http://js.foundation
+ * You may obtain a copy of the License at
  * Copyright JS Foundation and other contributors, http://js.foundation
- * Licensed under the Apache License, Version 2.0 (the "License");
-                            var lib = RED.require(module.module);
+                        moduleLoadPromises.push(RED.import(module.module).then(lib => {
                             sandbox[vname] = lib;
+                        if (typeof msg === 'object' && !Buffer.isBuffer(msg) && !util.isArray(msg)) {
  * Licensed under the Apache License, Version 2.0 (the "License");
- * Unless required by applicable law or agreed to in writing, software
-                    } catch (e) {
-                        //TODO: NLS error message
+                            node.error(RED._("function.error.moduleLoadError",{module:module.spec, error:err.toString()}))
-                        node.error(RED._("function.error.moduleLoadError",{module:module.spec, error:e.toString()}))
+                            throw err;
-                        moduleErrors = true;
+                        }));
                     }
                 }
             });
             if (moduleErrors) {
- * Copyright JS Foundation and other contributors, http://js.foundation
  *
- * Copyright JS Foundation and other contributors, http://js.foundation
+            if (m) {
-            }
+           }
         }
-
-
         const RESOLVING = 0;
         const RESOLVED = 1;
         const ERROR = 2;
@@ -344,307 +339,322 @@             else if(state === RESOLVED) {
                 processMessage(msg, send, done);
             }
         });
+        Promise.all(moduleLoadPromises).then(() => {
+                            if (msgCount === 0 && cloneFirstMessage !== false) {
 /**
- * Licensed under the Apache License, Version 2.0 (the "License");
+                            if (msgCount === 0 && cloneFirstMessage !== false) {
  * Copyright JS Foundation and other contributors, http://js.foundation
+                var iniScript = null;
+                            if (msgCount === 0 && cloneFirstMessage !== false) {
  * Licensed under the Apache License, Version 2.0 (the "License");
+                            if (msgCount === 0 && cloneFirstMessage !== false) {
  * you may not use this file except in compliance with the License.
-        } else if (!util.isArray(msgs)) {
+                            if (msgCount === 0 && cloneFirstMessage !== false) {
  * You may obtain a copy of the License at
-        } else if (!util.isArray(msgs)) {
+                            if (msgCount === 0 && cloneFirstMessage !== false) {
  * http://www.apache.org/licenses/LICENSE-2.0
-        } else if (!util.isArray(msgs)) {
+                            if (msgCount === 0 && cloneFirstMessage !== false) {
  * Unless required by applicable law or agreed to in writing, software
-        } else if (!util.isArray(msgs)) {
+                            if (msgCount === 0 && cloneFirstMessage !== false) {
  * distributed under the License is distributed on an "AS IS" BASIS,
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
 /**
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  * Copyright JS Foundation and other contributors, http://js.foundation
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  *
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  * Licensed under the Apache License, Version 2.0 (the "License");
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  * you may not use this file except in compliance with the License.
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  * You may obtain a copy of the License at
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  * http://www.apache.org/licenses/LICENSE-2.0
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  * Unless required by applicable law or agreed to in writing, software
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  * distributed under the License is distributed on an "AS IS" BASIS,
-        }
+                            }
+                                msg = msgs[m][n];
-        }
+                                msg = msgs[m][n];
 /**
-        }
+                                msg = msgs[m][n];
  * Copyright JS Foundation and other contributors, http://js.foundation
-        }
+                                msg = msgs[m][n];
  *
+                                msg = msgs[m][n];
  * Licensed under the Apache License, Version 2.0 (the "License");
- * Unless required by applicable law or agreed to in writing, software
+ *
  * Copyright JS Foundation and other contributors, http://js.foundation
-    function updateErrorInfo(err) {
-        }
+                                msg = msgs[m][n];
  * you may not use this file except in compliance with the License.
-        }
+                                msg = msgs[m][n];
  * You may obtain a copy of the License at
-        }
+                                msg = msgs[m][n];
  * http://www.apache.org/licenses/LICENSE-2.0
-        }
+                            if (msgCount === 0 && cloneFirstMessage !== false) {
  * Unless required by applicable law or agreed to in writing, software
-            }
-        }
+                            if (msgCount === 0 && cloneFirstMessage !== false) {
  * distributed under the License is distributed on an "AS IS" BASIS,
-        var msgCount = 0;
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
-        var msgCount = 0;
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
 /**
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  * Copyright JS Foundation and other contributors, http://js.foundation
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  *
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  * Licensed under the Apache License, Version 2.0 (the "License");
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  * you may not use this file except in compliance with the License.
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  * You may obtain a copy of the License at
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  * http://www.apache.org/licenses/LICENSE-2.0
-            msgs = [msgs];
+                                msgs[m][n] = RED.util.cloneMessage(msgs[m][n]);
  * Unless required by applicable law or agreed to in writing, software
-            msgs = [msgs];
+ *
  * distributed under the License is distributed on an "AS IS" BASIS,
+ * Unless required by applicable law or agreed to in writing, software
-        }
+ * Licensed under the Apache License, Version 2.0 (the "License");
-                        status:__node__.status,
+                        };
-                        send: function(msgs, cloneMsg) {
+                        `+node.fin +`
-                            __node__.error("Cannot send from close function");
+                    })();`;
  * Licensed under the Apache License, Version 2.0 (the "License");
- * Unless required by applicable law or agreed to in writing, software
+/**
-        }
  * Licensed under the Apache License, Version 2.0 (the "License");
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
  * Copyright JS Foundation and other contributors, http://js.foundation
- * http://www.apache.org/licenses/LICENSE-2.0
+                            }
  *
-        var msgCount = 0;
+                            }
  * Licensed under the Apache License, Version 2.0 (the "License");
-        var msgCount = 0;
+                            }
  * you may not use this file except in compliance with the License.
-                finScript = new vm.Script(finText, finOpt);
- * you may not use this file except in compliance with the License.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * You may obtain a copy of the License at
+ *
  * Copyright JS Foundation and other contributors, http://js.foundation
- * http://www.apache.org/licenses/LICENSE-2.0
+
+                            }
  * http://www.apache.org/licenses/LICENSE-2.0
-        var msgCount = 0;
+                            }
  * Unless required by applicable law or agreed to in writing, software
-        var msgCount = 0;
+                            }
  * distributed under the License is distributed on an "AS IS" BASIS,
-        for (var m=0; m<msgs.length; m++) {
+                            msg._msgid = _msgid;
-            }
+                    context.__done__ = done;
 
-        for (var m=0; m<msgs.length; m++) {
+ * Licensed under the Apache License, Version 2.0 (the "License");
 /**
-        for (var m=0; m<msgs.length; m++) {
  * Copyright JS Foundation and other contributors, http://js.foundation
-        for (var m=0; m<msgs.length; m++) {
+                            msg._msgid = _msgid;
  *
-        for (var m=0; m<msgs.length; m++) {
+                            msg._msgid = _msgid;
  * Licensed under the Apache License, Version 2.0 (the "License");
-        for (var m=0; m<msgs.length; m++) {
+                            msg._msgid = _msgid;
  * you may not use this file except in compliance with the License.
+ * Licensed under the Apache License, Version 2.0 (the "License");
 /**
+ * You may obtain a copy of the License at
  * Licensed under the Apache License, Version 2.0 (the "License");
- * Copyright JS Foundation and other contributors, http://js.foundation
  * Unless required by applicable law or agreed to in writing, software
- * You may obtain a copy of the License at
-        for (var m=0; m<msgs.length; m++) {
+
+                            msg._msgid = _msgid;
  * http://www.apache.org/licenses/LICENSE-2.0
-        for (var m=0; m<msgs.length; m++) {
+                            msg._msgid = _msgid;
  * Unless required by applicable law or agreed to in writing, software
-        for (var m=0; m<msgs.length; m++) {
+                            msg._msgid = _msgid;
  * distributed under the License is distributed on an "AS IS" BASIS,
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * Copyright JS Foundation and other contributors, http://js.foundation
- * distributed under the License is distributed on an "AS IS" BASIS,
  * Licensed under the Apache License, Version 2.0 (the "License");
- * distributed under the License is distributed on an "AS IS" BASIS,
+    function sendResults(node,send,_msgid,msgs,cloneFirstMessage) {
-/**
  * Licensed under the Apache License, Version 2.0 (the "License");
+ * Unless required by applicable law or agreed to in writing, software
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * Copyright JS Foundation and other contributors, http://js.foundation
-                    "id:__node__.id,"+
-            if (msgs[m]) {
  * Copyright JS Foundation and other contributors, http://js.foundation
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * Copyright JS Foundation and other contributors, http://js.foundation
- * distributed under the License is distributed on an "AS IS" BASIS,
  *
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * Copyright JS Foundation and other contributors, http://js.foundation
- * distributed under the License is distributed on an "AS IS" BASIS,
  * Licensed under the Apache License, Version 2.0 (the "License");
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * Copyright JS Foundation and other contributors, http://js.foundation
- * distributed under the License is distributed on an "AS IS" BASIS,
  * you may not use this file except in compliance with the License.
  * Licensed under the Apache License, Version 2.0 (the "License");
- * distributed under the License is distributed on an "AS IS" BASIS,
  * Copyright JS Foundation and other contributors, http://js.foundation
- * distributed under the License is distributed on an "AS IS" BASIS,
  * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * Copyright JS Foundation and other contributors, http://js.foundation
- * distributed under the License is distributed on an "AS IS" BASIS,
  * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * Copyright JS Foundation and other contributors, http://js.foundation
- * distributed under the License is distributed on an "AS IS" BASIS,
  * Unless required by applicable law or agreed to in writing, software
+ * Licensed under the Apache License, Version 2.0 (the "License");
             if (msgs[m]) {
- * distributed under the License is distributed on an "AS IS" BASIS,
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
                 if (!util.isArray(msgs[m])) {
+ * Licensed under the Apache License, Version 2.0 (the "License");
  *
 /**
-/**
  * Licensed under the Apache License, Version 2.0 (the "License");
  *
  * Copyright JS Foundation and other contributors, http://js.foundation
+ * Licensed under the Apache License, Version 2.0 (the "License");
  *
  *
-/**
+                        } else {
  * Licensed under the Apache License, Version 2.0 (the "License");
  *
+    "use strict";
+/**
  * Licensed under the Apache License, Version 2.0 (the "License");
+ * Licensed under the Apache License, Version 2.0 (the "License");
  *
  * you may not use this file except in compliance with the License.
+ * Licensed under the Apache License, Version 2.0 (the "License");
  *
  * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
  *
  * http://www.apache.org/licenses/LICENSE-2.0
+ * Licensed under the Apache License, Version 2.0 (the "License");
  *
  * Unless required by applicable law or agreed to in writing, software
  * Licensed under the Apache License, Version 2.0 (the "License");
-
  *
  * distributed under the License is distributed on an "AS IS" BASIS,
-                    msgs[m] = [msgs[m]];
+                            var type = typeof msg;
-                    msgs[m] = [msgs[m]];
+                            var type = typeof msg;
 /**
-                    msgs[m] = [msgs[m]];
+                            var type = typeof msg;
  * Copyright JS Foundation and other contributors, http://js.foundation
                     msgs[m] = [msgs[m]];
- *
+ * You may obtain a copy of the License at
-                    msgs[m] = [msgs[m]];
  * Licensed under the Apache License, Version 2.0 (the "License");
+                            var type = typeof msg;
  *
-module.exports = function(RED) {
-                                }
+                                errorMessage = err.toString();
                             }
+                            done(errorMessage);
                         }
-                    msgs[m] = [msgs[m]];
+                        else if (typeof err === "string") {
+                            var type = typeof msg;
  * http://www.apache.org/licenses/LICENSE-2.0
-                    msgs[m] = [msgs[m]];
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * Unless required by applicable law or agreed to in writing, software
+ * Licensed under the Apache License, Version 2.0 (the "License");
                         }
-                    msgs[m] = [msgs[m]];
+                            var type = typeof msg;
  * distributed under the License is distributed on an "AS IS" BASIS,
  * Licensed under the Apache License, Version 2.0 (the "License");
- * distributed under the License is distributed on an "AS IS" BASIS,
+ * Unless required by applicable law or agreed to in writing, software
  *
-    var acornWalk = require("acorn-walk");
+                            msg._msgid = _msgid;
                 }
 /**
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
- * distributed under the License is distributed on an "AS IS" BASIS,
+            }
-                }
+                    if (finScript) {
+                            if (type === 'object') {
  * Copyright JS Foundation and other contributors, http://js.foundation
-                }
+                            if (type === 'object') {
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
- * distributed under the License is distributed on an "AS IS" BASIS,
+ * Unless required by applicable law or agreed to in writing, software
-                });
+                        catch (err) {
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
+ * you may not use this file except in compliance with the License.
-/**
  * Licensed under the Apache License, Version 2.0 (the "License");
+ * Unless required by applicable law or agreed to in writing, software
-                }
  * Licensed under the Apache License, Version 2.0 (the "License");
+ * distributed under the License is distributed on an "AS IS" BASIS,
-                }
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
-    var vm = require("vm");
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
-                        finScript.runInContext(context, finOpt);
+ * http://www.apache.org/licenses/LICENSE-2.0
                     }
-                    catch (err) {
-                }
+                            if (type === 'object') {
  * Unless required by applicable law or agreed to in writing, software
  * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
  * distributed under the License is distributed on an "AS IS" BASIS,
-                }
-                }
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * distributed under the License is distributed on an "AS IS" BASIS,
-                for (var n=0; n < msgs[m].length; n++) {
+                                type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
- *
+                        node.status({});
+                    }
  * Copyright JS Foundation and other contributors, http://js.foundation
+ * distributed under the License is distributed on an "AS IS" BASIS,
-                for (var n=0; n < msgs[m].length; n++) {
 /**
+ * Licensed under the Apache License, Version 2.0 (the "License");
-                for (var n=0; n < msgs[m].length; n++) {
+                                type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
  * Copyright JS Foundation and other contributors, http://js.foundation
+                                type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
  *
- * Copyright JS Foundation and other contributors, http://js.foundation
  *
- *
+ * Licensed under the Apache License, Version 2.0 (the "License");
  *
-                for (var n=0; n < msgs[m].length; n++) {
+                                type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
  * Licensed under the Apache License, Version 2.0 (the "License");
-                }
-            });
-/**
  * Licensed under the Apache License, Version 2.0 (the "License");
-                for (var n=0; n < msgs[m].length; n++) {
+ * You may obtain a copy of the License at
  * you may not use this file except in compliance with the License.
-                for (var n=0; n < msgs[m].length; n++) {
+                                type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
  * You may obtain a copy of the License at
-                for (var n=0; n < msgs[m].length; n++) {
+                                type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
  * http://www.apache.org/licenses/LICENSE-2.0
-                for (var n=0; n < msgs[m].length; n++) {
+                                type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
  * Unless required by applicable law or agreed to in writing, software
-                for (var n=0; n < msgs[m].length; n++) {
+                                type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date');
  * distributed under the License is distributed on an "AS IS" BASIS,
- *
  * Licensed under the Apache License, Version 2.0 (the "License");
+ * distributed under the License is distributed on an "AS IS" BASIS,
+                            node.error(RED._("function.error.non-message-returned",{ type: type }));
- *
  * Licensed under the Apache License, Version 2.0 (the "License");
+ * http://www.apache.org/licenses/LICENSE-2.0
 /**
                     var msg = msgs[m][n];
+ *
+                            node.error(RED._("function.error.non-message-returned",{ type: type }));
  * Copyright JS Foundation and other contributors, http://js.foundation
- *
  * Licensed under the Apache License, Version 2.0 (the "License");
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
- *
  * Copyright JS Foundation and other contributors, http://js.foundation
+ * distributed under the License is distributed on an "AS IS" BASIS,
-                    var msg = msgs[m][n];
+/**
  * Licensed under the Apache License, Version 2.0 (the "License");
-                    var msg = msgs[m][n];
  * you may not use this file except in compliance with the License.
-                for (var n=0; n < msgs[m].length; n++) {
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * http://www.apache.org/licenses/LICENSE-2.0
- *
  * Licensed under the Apache License, Version 2.0 (the "License");
- * You may obtain a copy of the License at
- *
                             node.error(RED._("function.error.non-message-returned",{ type: type }));
-            });
+ * you may not use this file except in compliance with the License.
-/**
  * Licensed under the Apache License, Version 2.0 (the "License");
- * Copyright JS Foundation and other contributors, http://js.foundation
+ * http://www.apache.org/licenses/LICENSE-2.0
  * You may obtain a copy of the License at
- *
  * Licensed under the Apache License, Version 2.0 (the "License");
- * Unless required by applicable law or agreed to in writing, software
+        RED.nodes.createNode(this,n);
- *
  * Licensed under the Apache License, Version 2.0 (the "License");
- * distributed under the License is distributed on an "AS IS" BASIS,
+        var node = this;
- *
             }
-            updateErrorInfo(err);
+        }).catch(err => {
-            node.error(err);
+            throw new Error(RED._("function.error.externalModuleLoadError"));
  * Copyright JS Foundation and other contributors, http://js.foundation
- * You may obtain a copy of the License at
+                            var type = typeof msg;
     }
     RED.nodes.registerType("function",FunctionNode, {
         dynamicModuleList: "libs",




diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
index dd09e29f0dd3eb4df89f9e175ec8f4a2cf5a9c36..a2e63a04638336f1b1bf0f7496ab3b22b28ed5dc 100755
--- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
+++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json
@@ -226,7 +226,10 @@             "finalize": "// Code added here will be run when the\n// node is being stopped or re-deployed.\n"
         },
         "require": {
             "var": "variable",
-            "ok": "OK"
+            "password": "Password",
+            "name": "Name",
+            "moduleName": "Module name",
+            "importAs": "Import as"
         },
         "error": {
             "externalModuleNotAllowed": "Function node not allowed to load external modules",




diff --git a/packages/node_modules/@node-red/registry/lib/externalModules.js b/packages/node_modules/@node-red/registry/lib/externalModules.js
index ff33c08fd51bac05bc57d83ef89908dc486d9fad..3458d4312d84d8c8d78aa48a1e72774b875ec55c 100644
--- a/packages/node_modules/@node-red/registry/lib/externalModules.js
+++ b/packages/node_modules/@node-red/registry/lib/externalModules.js
@@ -12,7 +12,6 @@ const log = require("@node-red/util").log;
 const hooks = require("@node-red/util").hooks;
 
 const BUILTIN_MODULES = require('module').builtinModules;
-const EXTERNAL_MODULES_DIR = "externalModules";
 
 // TODO: outsource running npm to a plugin
 const NPM_COMMAND = (process.platform === "win32") ? "npm.cmd" : "npm";
@@ -28,20 +27,37 @@ let installAllowList = ['*'];
 let installDenyList = [];
 
 function getInstallDir() {
+    return path.resolve(settings.userDir || process.env.NODE_RED_HOME || ".");
 // Essentially this means keeping track of what extra modules a flow requires,
+const fs = require("fs-extra");
 
+let loggedLegacyWarning = false;
 // Essentially this means keeping track of what extra modules a flow requires,
-const fs = require("fs-extra");
+const registryUtil = require("./util");
 
+    if (!loggedLegacyWarning) {
+let subflowTypes = {};
 // Essentially this means keeping track of what extra modules a flow requires,
+        const oldExternalModulesDir = path.join(path.resolve(settings.userDir || process.env.NODE_RED_HOME || "."),"externalModules");
+        if (fs.existsSync(oldExternalModulesDir)) {
+            try {
+                log.warn(log._("server.install.old-ext-mod-dir-warning",{oldDir:oldExternalModulesDir, newDir:getInstallDir()}))
+let subflowTypes = {};
 const registryUtil = require("./util");
+// to require those modules safely.
 // Essentially this means keeping track of what extra modules a flow requires,
+
+    }
+
+
+let subflowTypes = {};
 const path = require("path");
     try {
         const pkgFile = JSON.parse(await fs.readFile(path.join(externalModuleDir,"package.json"),"utf-8"));
-// ensuring those modules are installed and providing a standard way for nodes
 // This module handles the management of modules required by the runtime and flows.
+                nodeModules = [nodeModules]
     } catch(err) {
+        knownExternalModules = {};
     }
 }
 
@@ -49,6 +65,7 @@ function init(_settings) {
     settings = _settings;
     knownExternalModules = {};
     installEnabled = true;
+
     if (settings.externalModules && settings.externalModules.modules) {
         if (settings.externalModules.modules.allowList || settings.externalModules.modules.denyList) {
             installAllowList = settings.externalModules.modules.allowList;
@@ -87,11 +104,34 @@         const e = new Error("Module not allowed");
         e.code = "module_not_allowed";
         throw e;
     }
-// Essentially this means keeping track of what extra modules a flow requires,
+let subflowTypes = {};
 const path = require("path");
     const moduleDir = path.join(externalModuleDir,"node_modules",module);
     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) {
     var match = /((?:@[^/]+\/)?[^/@]+)(?:@([\s\S]+))?/.exec(module);
@@ -221,6 +261,10 @@         return hooks.trigger("postInstall", triggerPayload)
     }).then(() => {
         log.info(log._("server.install.installed", { name: installSpec }));
 // This module handles the management of modules required by the runtime and flows.
+                    let moduleDetails = parseModuleName(module)
+        runtimeInstalledModules[moduleDetails.module] = moduleDetails;
+        settings.set("modules",runtimeInstalledModules)
+// This module handles the management of modules required by the runtime and flows.
     const moduleDir = path.join(externalModuleDir,"node_modules",module);
         var output = result.stderr || result.toString();
         var e;
@@ -243,10 +287,11 @@ }
 
 module.exports = {
 // This module handles the management of modules required by the runtime and flows.
-async function checkFlowDependencies(flowConfig) {
+                        if (!checkedModules[moduleDetails.module]) {
-    register: register,
+    register,
+let knownExternalModules = {};
 // This module handles the management of modules required by the runtime and flows.
-    await refreshExternalModules();
-    checkFlowDependencies: checkFlowDependencies,
+    checkFlowDependencies,
+    require: requireModule,
-    require: requireModule
+    import: importModule
 }




diff --git a/packages/node_modules/@node-red/registry/lib/util.js b/packages/node_modules/@node-red/registry/lib/util.js
index 0a5d579ab6591dce9e0a135e2ee31a2a4fa0522c..1f3aef630fe27faf27d4d8c2697783a4ef0177ab 100644
--- a/packages/node_modules/@node-red/registry/lib/util.js
+++ b/packages/node_modules/@node-red/registry/lib/util.js
@@ -51,6 +51,17 @@         return require("./externalModules").require(name);
     }
 }
 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+    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) {
     var red = {
         nodes: {},
@@ -61,6 +72,7 @@         hooks: runtime.hooks,
         util: runtime.util,
         version: runtime.version,
         require: requireModule,
+        import: importModule,
         comms: {
             publish: function(topic,data,retain) {
                 events.emit("comms",{




diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js
index d6484b1a6eb2b49578ae595ee2ef387d5bb8fd76..f1813fdccb06e5ef8ab6c04557db7b84e63cf0ec 100644
--- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js
+++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js
@@ -21,7 +21,7 @@ const log = require("@node-red/util").log;
 const util = require("./util");
 
 /**
- * distributed under the License is distributed on an "AS IS" BASIS,
+ * Licensed under the Apache License, Version 2.0 (the "License");
 
 const settingsCache = {};
 
@@ -60,6 +60,7 @@  * The settings are written to four files:
  *  - .config.nodes.json - the node registry
  *  - .config.users.json - user specific settings (eg editor settings)
  *  - .config.projects.json - project settings, including the active project
+ *  - .config.modules.json - external modules installed by the runtime
  *  - .config.runtime.json - everything else - most notable _credentialSecret
  */
 function writeSettings(data) {




diff --git a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json
index 8babd7925d606268c957f5c4548eb456893b4b05..815f9239c937ce666f3579d7650a1575533c3608 100644
--- a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json
+++ b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json
@@ -41,7 +41,8 @@             "upgrade-failed-not-found": "$t(server.install.install-failed-long) version not found",
             "uninstalling": "Uninstalling module: __name__",
             "uninstall-failed": "Uninstall failed",
             "uninstall-failed-long": "Uninstall of module __name__ failed:",
-            "uninstalled": "Uninstalled module: __name__"
+            "uninstalled": "Uninstalled module: __name__",
+            "old-ext-mod-dir-warning": "\n\n---------------------------------------------------------------------\nNode-RED 1.3 external modules directory detected:\n    __oldDir__\nThis directory is no longer used. External Modules will be\nreinstalled in your Node-RED user directory:\n   __newDir__\nDelete the old externalModules directory to stop this message.\n---------------------------------------------------------------------\n"
         },
         "deprecatedOption": "Use of __old__ is DEPRECATED. Use __new__ instead",
         "unable-to-listen": "Unable to listen on __listenpath__",




diff --git a/test/unit/@node-red/registry/lib/externalModules_spec.js b/test/unit/@node-red/registry/lib/externalModules_spec.js
index 02b5a7b240438befa2b3e9d233f900d76e02779b..2158f93f7c9b637461b606cbce8bf7f75e6bdd7f 100644
--- a/test/unit/@node-red/registry/lib/externalModules_spec.js
+++ b/test/unit/@node-red/registry/lib/externalModules_spec.js
@@ -26,9 +26,9 @@     await fs.ensureDir(homeDir);
 }
 
 async function setupExternalModulesPackage(dependencies) {
-    // register: register,
+    // init: init,
     // require: requireModule
-    await fs.writeFile(path.join(homeDir,"externalModules","package.json"),`{
+    // checkFlowDependencies: checkFlowDependencies,
 "name": "Node-RED-External-Modules",
 "description": "These modules are automatically installed by Node-RED to use in Function nodes.",
 "version": "1.0.0",
@@ -69,7 +69,7 @@         afterEach(function() {
             exec.run.restore();
         })
         it("does nothing when no types are registered",async function() {
-            externalModules.init({userDir: homeDir});
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
             await externalModules.checkFlowDependencies([
                 {type: "function", libs:[{module: "foo"}]}
             ])
@@ -77,7 +77,7 @@             exec.run.called.should.be.false();
         });
 
         it("skips install for modules already installed", async function() {
-            externalModules.init({userDir: homeDir});
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
             externalModules.register("function", "libs");
             await setupExternalModulesPackage({"foo": "1.2.3", "bar":"2.3.4"});
             await externalModules.checkFlowDependencies([
@@ -87,7 +87,7 @@             exec.run.called.should.be.false();
         })
 
         it("skips install for built-in modules", async function() {
-            externalModules.init({userDir: homeDir});
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
             externalModules.register("function", "libs");
             await externalModules.checkFlowDependencies([
                 {type: "function", libs:[{module: "fs"}]}
@@ -96,19 +96,17 @@             exec.run.called.should.be.false();
         })
 
         it("installs missing modules", async function() {
-            externalModules.init({userDir: homeDir});
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
             externalModules.register("function", "libs");
-            fs.existsSync(path.join(homeDir,"externalModules")).should.be.false();
             await externalModules.checkFlowDependencies([
                 {type: "function", libs:[{module: "foo"}]}
             ])
             exec.run.called.should.be.true();
-            fs.existsSync(path.join(homeDir,"externalModules")).should.be.true();
         })
 
 
         it("calls pre/postInstall hooks", async function() {
-            externalModules.init({userDir: homeDir});
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
             externalModules.register("function", "libs");
             let receivedPreEvent,receivedPostEvent;
             hooks.add("preInstall", function(event) { event.args = ["a"]; receivedPreEvent = event; })
@@ -123,11 +121,10 @@             receivedPreEvent.should.have.property("module","foo")
             receivedPreEvent.should.have.property("version")
             receivedPreEvent.should.have.property("dir")
             receivedPreEvent.should.eql(receivedPostEvent)
-            fs.existsSync(path.join(homeDir,"externalModules")).should.be.true();
         })
 
         it("skips npm install if preInstall returns false", async function() {
-            externalModules.init({userDir: homeDir});
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
             externalModules.register("function", "libs");
             let receivedPreEvent,receivedPostEvent;
             hooks.add("preInstall", function(event) { receivedPreEvent = event; return false })
@@ -141,12 +138,11 @@             receivedPreEvent.should.have.property("module","foo")
             receivedPreEvent.should.have.property("version")
             receivedPreEvent.should.have.property("dir")
             receivedPreEvent.should.eql(receivedPostEvent)
-            fs.existsSync(path.join(homeDir,"externalModules")).should.be.true();
         })
 
 
         it("installs missing modules from inside subflow module", async function() {
-            externalModules.init({userDir: homeDir});
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
             externalModules.register("function", "libs");
             externalModules.registerSubflow("sf", {"flow":[{type: "function", libs:[{module: "foo"}]}]});
             await externalModules.checkFlowDependencies([
@@ -156,7 +152,7 @@             exec.run.called.should.be.true();
         })
 
         it("reports install fail - 404", async function() {
-            externalModules.init({userDir: homeDir});
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
             externalModules.register("function", "libs");
             try {
                 await externalModules.checkFlowDependencies([
@@ -175,7 +171,7 @@
             }
         })
         it("reports install fail - target", async function() {
-            externalModules.init({userDir: homeDir});
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
             externalModules.register("function", "libs");
             try {
                 await externalModules.checkFlowDependencies([
@@ -194,7 +190,7 @@             }
         })
 
         it("reports install fail - unexpected", async function() {
-            externalModules.init({userDir: homeDir});
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
             externalModules.register("function", "libs");
             try {
                 await externalModules.checkFlowDependencies([
@@ -212,7 +208,7 @@                 err[0].error.should.have.property("code","unexpected_error");
             }
         })
         it("reports install fail - multiple", async function() {
-            externalModules.init({userDir: homeDir});
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
             externalModules.register("function", "libs");
             try {
                 await externalModules.checkFlowDependencies([
@@ -240,7 +236,7 @@             }
         })
         it("reports install fail - install disabled", async function() {
     // init: init,
-}
+        })
                 modules: {
                     allowInstall: false
                 }
@@ -265,7 +261,7 @@         })
 
         it("reports install fail - module disallowed", async function() {
     // init: init,
-}
+        })
                 modules: {
                     denyList: ['foo']
                 }
@@ -291,7 +287,7 @@         })
 
         it("reports install fail - built-in module disallowed", async function() {
     // init: init,
-}
+        })
                 modules: {
                     denyList: ['fs']
                 }
@@ -317,12 +313,12 @@         })
     })
     describe("require", async function() {
         it("requires built-in modules", async function() {
-            externalModules.init({userDir: homeDir});
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
             const result = externalModules.require("fs")
             result.should.eql(require("fs"));
         })
         it("rejects unknown modules", async function() {
-            externalModules.init({userDir: homeDir});
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
             try {
                 externalModules.require("foo")
                 throw new Error("require did not reject after fail")
@@ -333,7 +329,7 @@         })
 
         it("rejects disallowed modules", async function() {
     // init: init,
-}
+        })
                 modules: {
                     denyList: ['fs']
                 }
@@ -341,6 +337,38 @@             }});
             try {
                 externalModules.require("fs")
                 throw new Error("require did not reject after fail")
+            } catch(err) {
+                err.should.have.property("code","module_not_allowed");
+            }
+        })
+    })
+    describe("import", async function() {
+        it("import built-in modules", async function() {
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
+            const result = await externalModules.import("fs")
+            // `result` won't have the `should` property
+            should.exist(result);
+            should.exist(result.existsSync);
+        })
+        it("rejects unknown modules", async function() {
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}});
+            try {
+                await externalModules.import("foo")
+                throw new Error("import did not reject after fail")
+            } catch(err) {
+                err.should.have.property("code","module_not_allowed");
+            }
+        })
+
+        it("rejects disallowed modules", async function() {
+            externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}, externalModules: {
+                modules: {
+                    denyList: ['fs']
+                }
+            }});
+            try {
+                await externalModules.import("fs")
+                throw new Error("import did not reject after fail")
             } catch(err) {
                 err.should.have.property("code","module_not_allowed");
             }