Evaluating a JavaScript program may generate side-effects on the JavaScript global environment; therefore, each JavaScript program is evaluated in a separate v8 context. The overhead of creating such a context is significant, and in the recent v8 version that overhead has increased by roughly 40%.
To compensate for this overhead, it is suggested that you convert your JavaScript scripts to JavaScript modules.
In addition to the performance improvements, there are some other side benefits to using JavaScript modules, such as:
In addition to the performance improvements, there are some other side benefits to using JavaScript modules, such as:
export
) regular JavaScript code and can use any object or other functionality available to any other JavaScript program.export
are public.Evaluating a JavaScript program may generate side-effects on the JavaScript global environment; therefore, each JavaScript program is evaluated in a separate v8 context. The overhead of creating such a context is significant, and in the recent v8 version that overhead has increased by roughly 40%.
To compensate for this overhead, it is suggested that you convert your JavaScript scripts to JavaScript modules. A JavaScript module program is one that adheres to the following:
For further reading on the details of converting script programs into module programs, please see the chapter on modules in the ECMASCRIPT Language Specification.
Creating an ES6 module may be accomplished by adding an export
statement to any JavaScript script file: This will make the objects being exported available for all scripts to import.
Public variables, functions and classes are exposed using an export
statement. By default, all declared objects within an ES6 module are private. The module runs in strict mode with no need for the use strict
declaration. Here is a simple example:
// myMathLib.mjs export const PI = 3.14159265359; export const E = 2.718281828459; export const GAMMA = 0.577215; export const reducer = (accumulator, currentValue) => accumulator + currentValue; // The following objects are private const G = 0.915965594177; const x = 0.110001000000000000000001; export function adder(arguments) { console.log('Grand Total: ', arguments); return arguments.reduce(reducer); }
You may also dispense with all individual export
statements and use a single export
line
to define them all:
// myMathLib.mjs const PI = 3.14159265359; const E = 2.718281828459; const GAMMA = 0.577215; const reducer = (accumulator, currentValue) => accumulator + currentValue; // The following objects will remain private const G = 0.915965594177; const x = 0.110001000000000000000001; export function adder(arguments) { console.log('Grand Total: ', arguments); return arguments.reduce(reducer); } export { PI, E, GAMMA, reducer, adder, ... };
You then use an import
statement to pull items from a module into another script or module:
// myScript.js import { adder } from './myMathLib.mjs'; console.log( adder(1,2,3,4,5) ); => 15
From the import statement, we know that myMathLib.mjs resides in the same directory as myScript.js. In addition to the relative path shown above, you may also use:
Multiple items may be imported in the same export
line:
import { adder, aglomerator } from './myMathLib.mjs'; console.log( adder(1,2,3,4,5) ); // 15 console.log( aglomerator(1,2,3,4,5) ); // 12345
To resolve naming collisions, imported functions may be aliased as follows:
import { adder as sum, aglomerator as glob } from './myMathLib.mjs'; console.log( sum(1,2,3,4,5) ); // 15 console.log( glob(1,2,3,4,5) ); // 12345
Lastly, you are able to import all public items by providing a namespace:
import * as trans from './myMathLib.mjs'; console.log( trans.PI ); // 3.14159265359 console.log( trans.E ); // 2.718281828459 console.log( trans.GAMMA ); // 0.577215
As stated above: All import and export declarations must be made at the top level. So you cannot declare an export inside a function, or as part of a conditional clause. You also cannot export items programmatically by iterating through an array or on demand
import ... from someFunction(); // ERROR: may only import from "string"
The following statements will also not work:
if(some-condition) { import ...; // ERROR: can't import conditionally } { import ...; // ERROR: import is only allowed at the top level }
The net result is that, by only allowing import
at the top level, your code is much more easy to parse, analyze and to bundle. In the process, unused functions will be shakes out, potentially reducing the size of the executable. This is possible because of the strict rules under which modules operate.
On the web, you can tell browsers to treat a <script> element as a module by setting the type
attribute to module
.
<script type="module" src="optic.mjs"></script> <script nomodule src="optic.sjs"></script>
This works because modern browsers understand type="module"
and will also ignore scripts with a nomodule
attribute. This allows the programmer to serve a module-based payload to module-supporting browsers while providing a failover mode to older browsers. Only older browsers will get the nomodule
payload.
In order to support JavaScript module programs, a new server mimetype has been created. All module URIs must conform to the new mimetype:
You may view this new mimetype by navigating to the Admin UI and selecting the Mimetypes from the explorer pane.
The extension for a module URI in the import statement may be omitted. When the module URI in an import statement doesn't contain an extension, an extension mapping to any of the above MIME types must be added to resolve the specified module. For example:
import { square, diag } from 'lib/top'; // map to lib/top.js or lib/top.mjs
Based on these Performance Considerations, it is recommended that you import the following modules:
jsearch.mjs instead of jsearch.sjs.
optic.mjs instead of optic.sjs
Here is an example of importing optic.mjs:
'use strict'; import op from '/MarkLogic/optic.mjs'; op.fromLiterals([ {group:1, val:2}, {group:1, val:4}, {group:2, val:3}, {group:2, val:5}, {group:2, val:7} ]) .groupBy('group', op.avg('valAvg', 'val')) .result(); => {"group":1, "valAvg":3} {"group":2, "valAvg":5}
Here is an example of importing jsearch.mjs:
// Find all documents where the "author" JSON property value is "Mark Twain" import jsearch from '/MarkLogic/jsearch.mjs'; jsearch.documents() .where(jsearch.byExample({author: 'Mark Twain'})) .result() => { "results": ..., "estimate": 25 }
Sometimes you will need to evaluate a JavaScript snippet from within an XQuery program. Prior to ES6, you could simply pass the names of the required variables as a map of string and value pairs. For instance, given the following program:
'use strict'; function specialFunction( x, y ){ return "Hello " + x + " and " + y; }; module.exports.specialFunction = specialFunction;
You could invoke it via the following code:
xquery version 1.0; let $x := "'use strict'; var ext=require('/hello.sjs'); ext.specialFunction(x,y);" return xdmp:javascript-eval($x,map:map()=>map:with("x","Laurel")=>map:with("y","Hardy")); => Hello Laurel and Hardy
In ES6 modules, the same code will complain about variable x being undefined. This is because in ES6, variables are available as properties on the external
global object. So you will need to re-write your module in the following manner:
'use strict'; export function specialFunction(x,y) { return "Hello " + x + " and " + y; };
And your code that imports this module will look like this:
xquery version "1.0-ml"; let $x := "'use strict'; import {specialFunction} from '/hello.mjs'; specialFunction(external.x,external.y);" return xdmp:javascript-eval($x,map:map()=>map:with("x","Tom")=>map:with("y","Jerry")); => Hello Tom and Jerry
Note that use of external.x
and external.y
in the function call. This is the way to reference variables in the external
global object.