How to run ES6 and npm deps in Rhino - javascript

Situation: You have a JS module that you wrote yourself or 3rd party. It may have npm dependencies. It may be written with ES6 syntax and may use new ES6 (or 7, etc) features. You want to use it on Rhino.
The problem: Rhino supports require()ing files, but it does not have OOTB support for npm. It only supports JavaScript 5 syntax and features.
How can I run my pretty new JS stuff on Rhino?

You can bundle your JS into a single file and convert it to JS 5.
Solution: Babel supports JS syntax conversion. core-js polyfills fill in missing ES6 features. Browserify supports bundling up a dependency tree (including npm dependencies) into a single standalone file.
The Details: Easier said than done. Read on for a step-by-step guide.
Create a "bundler" directory. Later on, we will put mymodule.js in it, but let's hold off on that.
cd into your bundler directory and run npm init to create a package.json file.
Add your build command to the "scripts" section of package.json
:
"build": "browserify mymodule.js -t babelify --standalone mymodule -o bundle.js"
browserify mymodule.js means that browserify is going to bundle up mymodule.js and all its dependencies. -t babelify means that babel will transpile the code, converting ES6 things to 5. --standalone mymodule means that the things your module exports via module.exports will be preserved. -o bundle.js means that the output file will be bundle.js. You'll want to rename this file back to your module's name when you add it to Rhino.
Create the babel config file babel.config.js
:
module.exports = function (api) {
api.cache(true);
const presets = [
[
"#babel/preset-env",
{
useBuiltIns: "entry",
corejs: 3,
}
],
];
const plugins = [];
return {
presets,
plugins
};
};
The newest version of corejs is currently 3, but use a newer version if there is one. The useBuiltins can be "usage" instead, which may be better depending on your situation.
Install things
:
npm install --global browserify
npm install --save-dev babelify #babel/core #babel/preset-env
npm install core-js
This completes the setup of your "bundler", per se. You can save what you have so far for future use. The following steps now show how to use this bundler on one of your modules...
Put your module file (let's call it mymodule.js) and all of its dependencies into the bundler directory. This includes running npm install ... to install all of mymodule.js's npm dependencies.
Add the line
:
import "core-js/stable";
to the top of mymodule.js. This imports the needed polyfills. To reduce the filesize, you can import the specific JS features that your module (and its dependencies) actually use. For example, import "core-js/stable/number/is-nan"; to import the isNaN method on the Number class. It may be difficult though to know what you specifically use. That's why the "usage" option in tandem with browserslist may be the best way to slim. See core-js for more.
For example, your file may look like:
import "core-js/stable";
import mylib from "my-3rd-party-lib";
module.exports = mylib;
if it is nothing more than a 3rd party library that you want to use in Rhino. If it's a module you wrote yourself, then there will be a bunch of code between the core-js import at the top and the module.exports at the bottom.
Finally, run the build command you wrote in step 4
:
npm run build
You should now have a file called bundle.js. You should be able to copy that file into the place where the Rhino engine will find it, and it should work.
cp bundle.js /path/to/where/to/place/dist/file/mymodule.js
Happy coding.

Related

Running js tests - getting "Cannot use import statement outside of a module"

I added the type: module but that didn't help.
I am trying to run mocha or jest tests that use import and export for the source files.
The existing questions about this have specifics that are different from mine and I also find them confusing to follow for someone with my specific situation, especially since I have developed a specific answer with details not relevant to the existing questions, but relevant to other people in my situation.
In the past, you could not use ES modules (i.e. import/export) in Node without transpiling your code using Babel. However, support for ES modules in Node is now a reality, and both Jest and Mocha have also recently added support for using ES modules natively.
It takes more than just adding "type": "module" to your package.json, however.
Steps for using native ES Modules in Jest
As already mentioned, add "type": "module" to your package.json
Install either jest-environment-node or jest-environment-jsdom-sixteen to your development dependencies. For example:
$ npm i -D jest-environment-node
Update the Jest configuration in package.json and add the testEnvironment setting. For example:
"jest": {
"testEnvironment": "jest-environment-jsdom-node"
}
If you are using a version of Node earlier than 13.2, then you will need to add two additional flags when running Node: --experimental-modules and experimental-vm-modules. I use npx to execute the commands, although it's a little verbose:
$ npx --node-arg=--experimental-modules --node-arg=--experimental-vm-modules jest
This will run all your Jest tests using the appropriate Node flags. I'd recommend making this your test script in package.json if this is the way you go.
Now you should be able to use import/export without Babel!
One last important point: when using native ES modules in Node, you have to use the entire import path to your local modules, including the file extensions. For example:
import lib from "./my/lib.js"
Here is the Node documentation on native ES modules, if you want to read about this in more detail: https://nodejs.org/dist/latest-v14.x/docs/api/esm.html
I'd also recommend reading through this Github issue for more details on the Jest implementation of native ES modules: https://github.com/facebook/jest/issues/9430
Per the Jest documentation, if you make sure babel-jest is installed and supply your Babel configuration per the Babel documentation, in a config file or package.json e.g.:
.babelrc.json
{
"presets": [
"#babel/env"
]
}
Then babel-jest will pick it up automatically and you don't need to explicitly pre-build the files to test them. This also means you don't have to set flags on the command line when calling Babel.
Note that if you do want to explicitly pre-build, I'd recommend:
Using a pre<script> script rather than having multiple steps in one line; and
Re-using the build script so you don't have to make changes in two places.
In your case:
"scripts": {
"build": "babel src/ --out-dir lib",
"pretest": "npm run build",
"test": "jest lib/*.test.js"
}
You need to use a compiler and then use the compiled files when running tests.
Many of the references say to add type: module but don't say much more.
To be clear, the basic message:
SyntaxError: Cannot use import statement outside a module
is because you are using import/export and you are trying to run the files directly without compilation.
The mindshift here is getting used to editing the files in one directory and running the tests in another. Alternatively, some solutions offer "in'flight" compilation so this detail is essentially hidden and only the source files are used.
There are a few different approaches to doing this depending on specific needs. Here is one of the simplest approach I've found so far, using Babel for the compilation step:
Install babel npm install babel --save-dev
Add babel commands in package.json scripts, for example:
"scripts": {
"test": "babel src/ --out-dir lib --presets=#babel/env; jest lib/*.test.js",
"build": "babel src/ --out-dir lib --presets=#babel/env"
}, // this was for jest but you can use mocha, etc as needed
// Note that using preset this way eliminates the need for a specific .babel.config.json file
Now, if you run:
jest .
you get SyntaxError: Cannot use import statement outside a module, but if you run npm t you get
Successfully compiled 2 files with Babel.
PASS lib/app.test.js
All tests
✓ Canary test (2ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Going forward, just remember to:
Edit files in src/
Run tests in lib/

How to create a local module in TypeScript

I have created in folder src/modules/my-module/ which has package.json and defined the main file which exports everything we need.
I can import from it now import {A} from '../../modules/my-module'
I want to change the syntax into import {A} from 'my-module' and I have a few reasons for it:
When I move the module to another folder, I do not want to change all the code calling this module.
Later, I would like to have the possibility to move the module to a separate repository as the npm package and reuse it in multiple projects. I do not want to change all calling code later.
I have managed to compile it by adding to tsconfig.json
"paths": {
"my-module": ["src/modules/my-module"]
}
But I can't run the result via node.js as the node can't find the module. Is there any way to use non-realtive module reference in such scenario.
TS doesn't convert that "my-module" when transpiling your ts files to js.
Using module-alias package might solve your problem.
Add this configuration below into package.json:
"_moduleAliases": {
"my-module": "<your_build_folder>/modules/my-module"
},
And this code on first line of your main file (server.ts/index.ts)
import 'module-alias/register';
By the sounds of it, what you're wanting to do is package up your local my-module so that it can be used in the same way you'd install and use a package from the npm registry.
When you're doing local development, its easy to configure a dependency to reference to your module as a file path - though you need to have your typescript transpiled for it to work in your case.
Here's the method I'm using for local development, in an environment where we have many utility modules for a microservices architecture. I package the module into an archive and install it using npm install:
Use npm pack to package the module into a .tgz. Our package.json defines the target directory to be packaged, and the build script performs the transpile into the target (obviously adjust for your needs):
...
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "npx babel src --out-dir dist --extensions .ts,.tsx,.js --ignore **/*.test.ts,**/*.test.tsx",
...
Run npm pack and install the generated package in your application
/my-module/> npm pack
/my-module/> cd ../my-app
/my-app/> npm install --save ../my-module/my-module-0.0.1.tgz
Or as an all-in-one (builds tgz in my-app dir):
/my-app/> && npm pack ../my-module && npm i -s my-module-0.0.1.tgz
Once you're done with development, you'll probably want to publish your module in a way that its available to your project(s) on deployment.
Your options are along the lines of:
Publish to your local system using npm link
Publish to a private registry
Publish to the npm registry (as either a public or private module)
Here's a good resource for these options: https://medium.com/#debshish.pal/publish-a-npm-package-locally-for-testing-9a00015eb9fd
Add local module as dependency to package.json (in the root of your project):
"dependencies": {
"my-module": "file:src/modules/my-module",
...
}
Configure your typescript settings like here #tsconfig/recommended
Run npm install my-module in your root folder
Then you can do:
import {A} from 'my-module'
You can transpile your external local project (reference project) since Typescript 3 in July 2018.
See: How to share code between TypeScript projects?

package npm project written in flow

I have to publish a npm package that is written with Flow and compiled using babel.
What I did was I compiled all my source files. Then I copied compiled files from dist/ and put them into some other directory. I also put package.json there and edited it and then I published the package on npm.
I can then normally install project and require it my project. However when I run my project, it throws error that I need to require babel-core and babel-polyfills (install them as dev-dependencies). The problem is since my new project only requires my own package and does not use babel or something like that, so I see no point in requiring babel dependencies in my new project.
My question is, how can I package my library that is written with Flow and compiled by babel, so that I can then use this package in other places without requiring babel.
Did you add main and files into your package.json?
https://docs.npmjs.com/files/package.json#main
https://docs.npmjs.com/files/package.json#files
Your files should probably be
"files": [
"dist/**"
],
Also to prevent Users who install your package from needing to install the transpilers (i.e babel) add them into your devDependencies
Directly from the https://docs.npmjs.com/files/package.json#dependencies
Please do not put test harnesses or transpilers in your dependencies object. See devDependencies, below.
Also to add flow to your dist add https://github.com/AgentME/flow-copy-source
flow-copy-source -v src dist
The problem was in using incorrectly defined preset
In my babelrc I only had defined
preset: "env" without specifying that it needs to run on node 8 and higher, since the code used async.
Therefore i defined preset as:
preset: ["env", {
targets: {
"node": "8.9.1"
}
}
And the asnyc functions compiles correctly.
Also, thanks to #Kenneth I used flow-copy-source to add type to my library, so it can be seen in intelisense.

Is there source map support for typescript in node / nodemon?

I have a node project written in typescript#2.
My tsconfig has sourceMap set to true and the *.map.js files are generated. When I execute my transpiled *.js JavaScript files via node or nodemon, I only see the error messages relative to the js file and not to the mapped typescript files; I assume it's completely ignored.
Is sourceMap support only intended for browser-support? Or can I use it together with node or nodemon? If the latter, how would I enable it?
I want to see runtime errors detected from an executed javascript file relative to the original typescript file.
🚩 for Node versions since v12.12, there is an easier and better solution.
I recently got this working in my express app. Steps as follows:
Install the required library:
npm install --save-dev source-map-support
In your entry point (eg app.ts):
require('source-map-support').install();
In your app.ts, you may also require better logging for errors within promises:
process.on('unhandledRejection', console.log);
In your tsconfig, under compilerOptions:
"inlineSourceMap": true
The answers here are correct for Node versions before v12.12.0, which added the (experimental) --enable-source-maps flag. With that enabled, source maps are applied to stack traces without an additional dependency. As demonstrated in this article, it has the slightly different and possibly beneficial behavior of including both the generated .js file location and the source file location. For example:
Error: not found
at Object.<anonymous> (/Users/bencoe/oss/source-map-testing/test.js:29:7)
-> /Users/bencoe/oss/source-map-testing/test.ts:13:7
Install source map support:
npm install source-map-support
(I run in in production as well, as it immensely helps finding bugs from the logs of when an error an occurs. I did not experience a large performance impact, yet your experience may be different.)
Add to your tsconfig.json:
{
"compilerOptions": {
"sourceMap": true
}
}
When running your JavaScript file, add the require parameter:
nodemon -r source-map-support/register dist/pathToJson.js
node -r source-map-support/register dist/pathToJson.js
Alternatively, you add in your entry call:
require('source-map-support').install()
yet I find this tedious is projects with multiple entry points.
Sidenote: mocha also supports the --require / -r option, so to have the sourcemap support in mocha you can also call your tests with it, e.g. similar to:
NODE_ENV=test npx mocha --forbid-only --require source-map-support/register --exit --recursive ./path/to/your/tests/
I found this npm module which seems to do the trick:
https://github.com/evanw/node-source-map-support
run npm install source-map-support --save at the root of your node project and add import 'source-map-support/register' to your main.ts or index.ts file.
That's it.
Source map support works perfectly fine with node
All you need to do is add
"source-map-support": "0.4.11",
to dependencies or dev-dependencies in package.json by running
npm install --save source-map-support
And in your entry point ts file, simply add at the top
require('source-map-support').install()
(note: this is calling nodeJS require - there is no need for source-map-support definition files)
For Node versions from v12.12.0 use the --enable-source-maps flag when you run node.
Example: node --enable-source-maps main.js
Do not install "source-map-support" for Node versions from v12.12.0

Mocha + Babel + React Modules

How can I configure Mocha/Babel to also transform a module in my node_modules folder when required?
I am developing an application in ES6 with react.
I include an external react component which I installed through npm install.
When I run the test (mocha --recursive --compilers js:babel/register) the test fails with an Unexpected token '<' error from the external module.
The reason is that the external react module needs to be transformed when being loaded. This is specified in the modules package.json like so:
"browserify": {
"transform": [
"reactify"
]
}
It runs fine for the browser. The source is compiled with browserify -t babelify. The browserify information from the package.json is used and the module is transformed properly when it is loaded.
How can I configure Mocha/Babel to take this also into account?
Or how can I configure Mocha/Babel to also compile the modules in the node_modules folder?
Or any other way to solve this?
Babel will, by default, not transpile the node_modules directory. If you wish to disable this, you can set the ignore option to false.
On the command line, you can pass --ignore false to either babel or babel-node to allow Babel to transpile the node_modules directory.
For your uses, you can pass Mocha the --compilers js:babel/register option to transpile the files Mocha accesses. In order to allow Babel's register hook to transpile the node_modules directory, you can configure Babel with a .babelrc file. The file should contain the following: { "ignore": false }.
Either of these options should allow you to use this external module (since Babel will transpile it).

Categories

Resources