Creating Modules in JavaScript with ES7 and Babel
April 13th, 2016 | By Peter van der Zee | 6 min read
Last year, the new version of JavaScript gave us a lot of new goodies.
Amongst those was syntax for importing and exporting of modules which finally codified “the only way” to do modules in JavaScript. Or well, eventually. Another nice thing is that it’s specced in such a way that you can statically analyze the whole module dependency tree. Pretty awesome.
Let’s take a quick look at what they are:
import v from "mod";
import * as obj from "mod";
import {
x
}
from "mod";
import {
x as v
}
from "mod";
import "mod";
export var v;
export default function f() {};
export default function() {};
export default 42;
export {
x
};
export {
x as v
};
export {
x
}
from "mod";
export {
x as v
}
from "mod";
export * from "mod";
So basically you can import the main value of a module (the “default”), or a specific property from explicit exports, a combination of this, or everything.
In symmetry, you can export one value for the module as the default, or an object with multiple properties. You can also export these properties one by one. I’ll leave the preferred style to the style guides.
For ES7 there are some small additions proposed to extend this syntax.
export * as ns from "mod";
export v from "mod";
Nothing shocking, but when can we use this? Well. There’s no time like the present.
As with many syntactical features from ES6, you can use a tool called Babel to translate them back to ES5 as long as support for them doesn’t cover your runtime targets. Then once your targets do support them out of the box you can tell Babel not to translate them anymore.
Let’s take a look at the setup required for this. We’ll do this on Node.js and NPM. Let’s try to execute this file;
src/letter_keys.js
// you would have a constant for each key
// (I would normally uppercase all constants)
const a = 119;
const d = 100;
const s = 115;
const w = 119;
// you would export all keys here
// note: you can't say `w: 119` here. It just isn't valid.
// This destructures to `w: w, a: a, ...`
export {
w,
a,
d,
s,
}
src/arrow_keys.js
const UP = 38;
const RIGHT = 39;
const DOWN = 40;
const LEFT = 37;
export {
UP,
RIGHT,
DOWN,
LEFT,
}
src/move.js
export {
a, w, s, d
}
from './letter_keys';
export * as ARROWS from './arrow_keys';
The idea is that there is a main index.js file that exports stuff from internal modules. It assumes these keys are exported from the other files. The example is convoluted but that’s not very relevant.
src/index.js
import * as keys from './move';
console.log(keys);
This would be part of a project that depends on this module and it should print out the awsd keys as well as the arrows object. Let’s get crackin’ with npm first. Create the repo dir and initialize it.
~$ mkdir foo
~$ cd foo~/foo$ mkdir src#
put src files above in ~/foo/src~/foo$ npm init -yes~/foo$ npm install babel-cli babel-preset-es2015 babel-preset-stage-1 -D
This may take a minute. As you may have already guessed, babel-cli allows us to run Babel (6) from the command line and the babel-preset-stage-1 package contains the relevant ES7 module translation stuff (at the time of writing…).
The -yes flag will cause npm to create a default package.json without asking questions. The -D flag is short for --save-dev which adds the packages under the devDependency entry in package.json for you. We add the presets to the default babel configuration file:
.babelrc
{
"presets": ["es2015", "stage-1"]
}
If this works for you, that’s awesome, hello future! But these examples wouldn’t run in ES6, let alone Node.js at the time of writing. With these translation steps it can be executed anyway.
There should now also be a near-empty package.json file, which contains those three dev dependencies we added. Let’s add a script to that package.json to do our translation:
...
"scripts": {
"test": "echo "
Error: no test specified " && exit 1",
"translate": "node_modules/babel-cli/bin/babel-node.js src/index.js"
},
...
(Only added the “translate” line and the comma behind the “test” line).
The translated script is a build step. The final package.json file contents as used for this post (with fixed versions) can be found at the end of this article. Now all that is left is to call npm run translate to translate and run the code.
~/foo$ npm run translate --silent
{
A: [Getter],
W: [Getter],
S: [Getter],
D: [Getter],
ARROWS: {
UP: 38,
RIGHT: 39,
DOWN: 40,
LEFT: 37
}
}
Hurray! Now for bonus points, we can use Jscrambler to mangle that a little further. We can pass on the Babel-translated code, so why not?
~/foo$ npm install jscrambler -D
Our (final) package.json now looks like this:
package.json
{
"name": "foo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "
Error: no test specified " && exit 1",
"translate": "node_modules/babel-cli/bin/babel-node.js src/index.js"
},
"keywords": [],
"author": "Your Name <[email protected]> (http://localhost/)",
"license": "ISC",
"devDependencies": {
"babel-cli": "6.6.5",
"babel-preset-es2015": "6.6.0",
"babel-preset-stage-1": "6.5.0",
"jscrambler": "0.7.5"
}
}
Set up the config like you normally do (using Node.js requires a pro account), here’s the file I used (If you want to know more about how to set this file let this serve as an example and npm for further documentation:
.jscramblerrc
{
"keys": {
"accessKey": "See https://jscrambler.com/en/account/api_access",
"secretKey": "See https://jscrambler.com/en/account/api_access"
},
"params": {
"constant_folding": "%DEFAULT%",
"dead_code": "%DEFAULT%",
"dead_code_elimination": "%DEFAULT%",
"dictionary_compression": "%DEFAULT%",
"dot_notation_elimination": "%DEFAULT%",
"function_outlining": "%DEFAULT%",
"function-reorder": "%DEFAULT%",
"literal_duplicates": "%DEFAULT%",
"literal_hooking": "2;8",
"member_enumeration": "%DEFAULT%",
"mode": "nodejs",
"rename_local": "%DEFAULT%",
"string_splitting": "0.3",
"whitespace": "%DEFAULT%"
}
}
We’ll use a script to wrap it all together. This script will translate the original files with Babel, output them to /build folder, then have Jscrambler mangle them and put the result in /dist folder where we can run it as we normally would without using ES7 features.
run.sh
#!/bin/sh
echo "Babelifying src/*.js"
node_modules / babel - cli / bin / babel.js - d build src
/*.js
echo "Scrambling build/*.js"
node_modules/jscrambler/bin/jscrambler -o dist build/src/**
echo "Clean up artifacts"
mv dist/build/src/* dist/
rmdir dist/build/src
rmdir dist/build
echo "Done! See dist/scrambled.js"
echo "Running:"
node dist/index.js*/
Make it runnable:
chmod + x run.sh
And… run it!
~/foo$ ./run.sh
Babelifying src
/*.js
src/arrow_keys.js -> build/src/arrow_keys.js
src/index.js -> build/src/index.js
src/letter_keys.js -> build/src/letter_keys.js
src/move.js -> build/src/move.js
Scrambling build/*.js
Clean up artifacts
Done! See dist/ for your scrambled files
Running:
{ a: [Getter],
w: [Getter],
s: [Getter],
d: [Getter],
ARROWS: { UP: 38, RIGHT: 39, DOWN: 40, LEFT: 37 } }*/
Conclusion
You can investigate the results in the /dist folder yourself. It’ll be a far cry from the original source since it has been protected with Jscrambler but still runs.
And there you go, have fun working in ES7!
Jscrambler
The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.
View All ArticlesMust read next
Monkey Patching: An Analysis of Code Poisoning JavaScript
Monkey patching enables dynamically changing the behavior of JavaScript. While it can be used legitimately, it can also enable attacks like Magecart.
October 15, 2019 | By Jscrambler | 6 min read
12 Useful JavaScript Newsletters
With so much happening in the JS ecosystem, it's not easy to stay on top of things. Here are 12 newsletters to bring the best news straight to your inbox.
February 10, 2022 | By Jscrambler | 5 min read