njs icon indicating copy to clipboard operation
njs copied to clipboard

export default function name() {...}

Open drsm opened this issue 6 years ago • 12 comments

now is parsed as named function expression, so the code below does not work:

export default function tropical() {
}
tropical.prototype.sum = function(a, b) {
    return a < b ? a : b;
};
tropical.prototype.prod = function(a, b) {
    return a + b;
};
tropical.prototype.ZERO = Infinity;
tropical.prototype.ONE = 0;

drsm avatar Mar 28 '19 20:03 drsm

@xeioex @drsm

I'm still not sure about vm->options.module. Do you mean the script will behave like a normal module if running by -t module?

  1. If yes, can a module be executed by CLI?
console.log('start module');
function foo() {}
export default foo;
  1. export is required in module, so it's strange that if the main script behaves like a module, right?

  2. It seems we introduced an issue after supporting blocked scope function.

diff -r 31232e755143 njs/njs_parser.c
--- a/njs/njs_parser.c	Sun Apr 21 18:11:58 2019 +0800
+++ b/njs/njs_parser.c	Tue Apr 23 01:24:31 2019 +0800
@@ -203,7 +203,7 @@ njs_parser_scope_begin(njs_vm_t *vm, njs
     } else {
         if (type == NJS_SCOPE_GLOBAL) {
             type += NJS_INDEX_GLOBAL_OFFSET;
-            scope->module = vm->options.module;
+            scope->module = vm->options.module; // wrong???
         }

hongzhidao avatar Apr 22 '19 17:04 hongzhidao

@hongzhidao

ES5 has one execution mode of a file. ES6 has two:

  • script mode (like in ES5)
  • module mode (new).

The second one was introduced to support import/export stuff. Imports and exports, by design (not in our simplified implementation) are early bound at compilation phase. So actually: export name means "here's some variable (as memory region) named name, it is accessible", like non-static var in C. import name from mod means "create a lexical binding named name in the current module namespace to the memory region of the module mod named *default*" and so on... The mesh of links between module scopes is created.

@xeioex @drsm

I'm still not sure about vm->options.module. Do you mean the script will behave like a normal module if running by -t module?

  1. If yes, can a module be executed by CLI?
console.log('start module');
function foo() {}
export default foo;
  1. export is required in module, so it'll be curious that if the main script behaves like a module, right?

no, export is not required. in REPL it will do nothing, as nobody can import it, there is no file. but files are executed OK:

root@node:~# head one.mjs two.mjs 
==> one.mjs <==
export const one = 1;
import { two } from './two.mjs'
// to leave TDZ
setImmediate(() => console.log('two from one', two));

==> two.mjs <==
export const two = 2;
import { one } from './one.mjs'
// to leave TDZ
setImmediate(() => console.log('one from two', one));
root@node:~# node --experimental-modules one.mjs 
(node:8490) ExperimentalWarning: The ESM module loader is experimental.
one from two 1
two from one 2
root@node:~# node --experimental-modules two.mjs 
(node:8501) ExperimentalWarning: The ESM module loader is experimental.
two from one 2
one from two 1

drsm avatar Apr 22 '19 18:04 drsm

@drsm

no, export is not required.

  1. In the case of -t module, the script is not a module, as we set export statement is required in module file. right?

export name means "here's some variable (as memory region) named name, it is accessible", like non-static var in C.

  1. export statement can exist in both script and module files?

  2. We need one more condition in adding variable?

The patch is what I want to submit. @xeioex take a look.

diff -r 52983554e61f njs/njs_parser.c
--- a/njs/njs_parser.c	Mon Apr 22 19:53:41 2019 +0300
+++ b/njs/njs_parser.c	Tue Apr 23 02:46:18 2019 +0800
@@ -203,7 +203,6 @@ njs_parser_scope_begin(njs_vm_t *vm, njs
     } else {
         if (type == NJS_SCOPE_GLOBAL) {
             type += NJS_INDEX_GLOBAL_OFFSET;
-            scope->module = vm->options.module;
         }

         scope->next_index[0] = type;
diff -r 52983554e61f njs/njs_variable.c
--- a/njs/njs_variable.c	Mon Apr 22 19:53:41 2019 +0300
+++ b/njs/njs_variable.c	Tue Apr 23 02:46:18 2019 +0800
@@ -91,17 +91,17 @@ njs_variable_scope_add(njs_vm_t *vm, njs
     if (nxt_lvlhsh_find(&scope->variables, lhq) == NXT_OK) {
         var = lhq->value;

-        if (!scope->module && scope->type != NJS_SCOPE_BLOCK) {
-            return var;
-        }
-
-        if (type == NJS_VARIABLE_FUNCTION
-            || var->type == NJS_VARIABLE_FUNCTION)
+        if (scope->module || scope->type == NJS_SCOPE_BLOCK
+            || (scope->type == NJS_SCOPE_GLOBAL && vm->options.module))
         {
-            njs_parser_syntax_error(vm, vm->parser,
-                                    "\"%V\" has already been declared",
-                                    &lhq->key);
-            return NULL;
+            if (type == NJS_VARIABLE_FUNCTION
+                || var->type == NJS_VARIABLE_FUNCTION)
+            {
+                njs_parser_syntax_error(vm, vm->parser,
+                                        "\"%V\" has already been declared",
+                                        &lhq->key);
+                return NULL;
+            }
         }

         return var;

hongzhidao avatar Apr 22 '19 18:04 hongzhidao

@hongzhidao

Didn't get it.

$ head main.js one.js two.js 
==> main.js <==
import one from 'one.js';
import two from 'two.js';
one();
two();

==> one.js <==
export default function() {
    console.log('one');
}

==> two.js <==
export default function() {
    console.log('two');
}
$ build/njs -t module main.js 
one
two
$ build/njs -t module one.js 
SyntaxError: Illegal export statement in one.js:1

Everything is ok. We can't export from main file for now, as there no early binding.

-t script should be fixed to disallow import/export at all, i think.

drsm avatar Apr 22 '19 19:04 drsm

Caused coredump now.

==> test.js <==
var x;
export default x;

$ build/njs -t module test.js

Continue it tomorrow :)

hongzhidao avatar Apr 22 '19 19:04 hongzhidao

@hongzhidao @xeioex

Some thoughts:

  • import/export stuff should be disabled when -t script. Otherwise there is no chance to evolve it to support all the features. Not sure, but we'll have to do it at some point.

  • export from the main file/REPL (entry point) should be restricted for now. As it won't work properly anyway in the current implementation.

drsm avatar Apr 23 '19 07:04 drsm

there is a regression in 0.6+:

$ cat idx.js 
import tropical from './mod.js';
$ cat mod.js 
export default function tropical() { }

console.log(typeof tropical); // will cause SEGV
$ njs idx.js 
Segmentation fault (core dumped)

edit: looks like another variant of #300

drsm avatar Jun 24 '21 08:06 drsm

Can someone confirm that, in the latest version of nginx/njs, the "export default FUNCTION_NAME" at the bottom or the ".js" (or part of the function definition) is NOT needed anymore? Adding the export caused issues. Removing it makes it work. I'm guessing the "export" is not needed anymore. Thanks for the clarification!

pierregangloff avatar Nov 13 '21 14:11 pierregangloff

@pierregangloff

The only supported variant of exporting is as follows:

export default {fun1, fun2,...};

xeioex avatar Nov 13 '21 15:11 xeioex

Thanks @xeioex !!

The only supported variant of exporting is as follows: export default {fun1, fun2,...};

But is it REQUIRED to provide the export? Right now, I don't have any export in my validate_api_key.js and it works just fine, I can use "js_content validate_api_key;" just fine from my nginx.conf (no export needed). I'm confused. Maybe this is not needed anymore? Or are there special circumstances where it IS needed? Thanks again!

pierregangloff avatar Nov 15 '21 14:11 pierregangloff

@pierregangloff

Do you use js_include or js_import directive? I guess you are using js_include.

js_include just loads a provided file into the VM runtime. js_content looks for a validate_api_key function in the global scope. only one js_include may be used. This directive is deprecated.

Whereas js_import foo.js imports an object exported using directive export default from foo.js. In the global scope this object is available as foo.

When js_content is provided with foo.bar it queries object named foo in the global scope for the function named bar.

xeioex avatar Nov 15 '21 14:11 xeioex

Thank so much for the clarification @xeioex !! Indeed I was using the js_include (I now updated my code to use js_import to follow best practice). That makes a LOT of sense :)

pierregangloff avatar Nov 16 '21 13:11 pierregangloff