The Javascript Onion¶
Is this Javascript?
function(a) {
console.log(a);
}("hi")
var o = {}
var o["2"] = 2
var deepCopy = JSON.parse(JSON.stringify(o))
What about this?
const list = [1,2,3]
a = (l) => {
const [, num2, ] = l
console.log(num2)
}
const o = { "asdf": 2 }
const deepCopy = { ...o, b: 2 }
Both do seem to work when added as inline javascript to our local HTML file in the browser:
<html>
<body>
<script type="text/javascript">
const list = [1,2,3]
a = (l) => {
const [, num2, ] = l
console.log(num2)
}
const o = { "asdf": 2 }
const deepCopy = { ...o, b: 2 }
alert(JSON.stringify(deepCopy))
</script>
</body>
</html>
There’s no way that this could be valid Javascript:
[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]
+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]
]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[]
)[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]
+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!
+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![
]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+[]]+(![]+[])[!+
[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]
+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]
)[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[
]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]
]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(
![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+
(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+
!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[
]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!
![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+
[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+
!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]
)[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]
+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+
(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+
!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[
]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])
[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[
][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]](
(!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+
[])[+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[]
)[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[
])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!
+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(!
[]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+((![]+[])[+!+[]]+
(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[
]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[
]]+[!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]
+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+[]]+([![]
]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[+[]]+([![]]+[]
[[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]]+[+[]]+(!![]+[])[+[]
]+[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+
!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]+[+!+[]])[(
![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[]
)[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!
+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+
(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[
+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[
])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[
]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![
]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])
[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[
!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[
]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[]
)[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![
]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]
]]+(!![]+[])[+[]]]((!![]+[])[+[]])[([][(!![]+[])[!+[]+!+[]+!+[]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+(
!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()+[]
)[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+
[+[]]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]](([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[]
)[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!
+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[
]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([]
[(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]
]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])
[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]
+([][[]]+[])[+!+[]]+(![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[
!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[
])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]]+![]+(![]+[+[]])[([![]]+[][[]])
[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![
]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[
]+!+[]]]()[+!+[]+[+[]]])()[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])
[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[
+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+(
[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+
[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[
])[+!+[]]]((![]+[+[]])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]
]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+
[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+!+[]+[+[]]])+[])[+!+[]])+([]+[])[(![]+[])[+[]]+(!![
]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+
[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+
[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!
[]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+
[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]])())
You may have heard of both the MEAN/MERN stacks, and both use Express. Let’s try adding Express - it is a Javascript library, after all! Try adding the Express.js starter code for a basic hello world server as inline javascript:
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
When inspecting your browser debugger, you should immediately
encounter a bug of the form Uncaught ReferenceError: require is not defined
.
Yikes. This Javscript isn’t designed for the browser.
Finally, let’s try making our tiny HTML file a React app. When navigating to the
the Create React App tutorial,
we are immediately recommended to “use an integrated toolchain for the best user and developer experience.”
Thankfully, there’s a link to add React as a “plain” <script>
tag,
so let’s try that. Even in that section, there is a link to a complete HTML file
shown here for your convenience:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World</title>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
</script>
<!--
Note: this page is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Read this section for a production-ready setup with JSX:
https://reactjs.org/docs/add-react-to-a-website.html#add-jsx-to-a-project
In a larger project, you can use an integrated toolchain that includes JSX instead:
https://reactjs.org/docs/create-a-new-react-app.html
You can also use React without JSX, in which case you can remove Babel:
https://reactjs.org/docs/react-without-jsx.html
-->
</body>
</html>
Even after we parse through all the warnings that this isn’t suited
for production, we see that it “slowly compiles JSX with Babel.” There
also looks to be HTML within the javascript section of the app. The
<script>
tag also doesn’t pretend that it’s javascript anymore:
it is allegedly “text/babel.”
What on earth does this mean? Welcome to the Javascript Onion.
Vanilla Javascript¶
As the web grew in popularity, websites wanted to be able to run
code that would execute in the browser. Netscape,
an early web browser, designed a programming language called Javascript
that could be included in webpages and then executed in the browser.
This language caught on, and other browsers started to support Javascript.
Now, Javascript
is the single most popular programming language according to the latest
Stack Overflow Developer Survey.
As a result, Ecma International maintains
a set of public standards around Javascript, which the browsers use
to continuously update their own JS interepreters. These standards
get frequuently updated and often add new features that the
browsers eventually support. This ECMAScript-compliant JS should
be able to be run via inline <script>
tags out of the box
in the latest browsers.
A far more comprehensive introduction to Javascript as a whole can be found on the MDN Web Docs.
Node.js¶
Javascript (and web technologies more broadly) have become so popular that developers wanted to write Javascript code across all aspects their software stack. Instead of just having Javascript run just in the browser, why not be able to run Javascript as a process? Node.js allows you to write scripts and servers in Javascript.
Once you install node, you can node
to open up a Node interpreter.
Here, you can run Javascript that doesn’t involve browser interactions.
For example, alert('hi')
no longer makes sense here. console.log('hi')
,
however, does still work. Since Express is a framework to help run a
web server, and web servers run as processes, Express is meant to be
run in Node. Let’s make an empty project directory. From there, make a src
directory.
We can thus try putting the Express starter code given above
in a file src/server.js
and then running node src/server.js
in the
terminal:
$ node src/server.js
internal/modules/cjs/loader.js:883
throw err;
^
Error: Cannot find module 'express'
Interestingly, we no longer are experiencing the Uncaught ReferenceError
,
so we are getting closer. In fact, require
is not supported in
the ECMAScript specification, but is supported in Node (see the CommonJS Modules section).
Our remaining issue is that this third-party Express framework is a “module” that Node doesn’t understand.
Thankfully, Node.js comes with an excellent package manager
that is mysteriously named “npm”. You can check out their
homepage to see some possibilities regarding
the meaning of the acronym. Regardless, to manage third-party libraries,
we need to make this one little script an npm package, even if we
don’t plan on publishing this package publicly.
$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help init` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help init` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (guides)
version: (1.0.0)
description:
entry point: (server.js) src/server.js
test command:
git repository: (https://github.com/codethechange/guides)
keywords:
author:
license: (ISC)
About to write to ./package.json:
{
"name": "guides",
"version": "1.0.0",
"description": "=================================== Guides for Stanford Code the Change ===================================",
"main": "src/server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node src/server.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/codethechange/guides.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/codethechange/guides/issues"
},
"homepage": "https://github.com/codethechange/guides#readme"
}
Is this OK? (yes) yes
And voila! Inspecting our current directory reveals that a somewhat auto-generated
package.json
file was creaed for our convenience. The scripts
field of this JSON file even automatically created some default commands for us:
$ npm run start
> guides@1.0.0 start
> node src/server.js
internal/modules/cjs/loader.js:883
throw err;
^
Error: Cannot find module 'express'
As the handy npm output recommended for us, let’s install
packages via npm install express
.
Note
It is important to make sure that you verify the package name corresponds to the actual software you are interested in using. For example, the express package can be viewable from the npm package registry. Be sure to check out our guide on securing open source software for more information.
$ npm install express
> guides@1.0.0 start
> node src/server.js
internal/modules/cjs/loader.js:883
throw err;
^
Error: Cannot find module 'express'
We should now be able to run this server.js
file and be able to
visit http://localhost:3000:
$ npm run start
> guides@1.0.0 start
> node src/server.js
Example app listening at http://localhost:3000
CommonJS Modules¶
Although our express server now works thanks to Node and NPM, how do
does require
work under the hood? cd
into the newly created
node_modules
folder, which installs the npm
packages
specific to our current project as defined in package.json
. We can
then inspect the express
folder which corresponds to the express
package.
If you inspect node_modules/express/index.js
, you will see that there is a special
module.exports
assignment to another require
call:
/*!
* express
* Copyright(c) 2009-2013 TJ Holowaychuk
* Copyright(c) 2013 Roman Shtylman
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict';
module.exports = require('./lib/express');
This require()
has a relative path included, which tells Node to look locally for the
module file as opposed to installing a third-party NPM dependency.
If we inspect node_modules/express/lib/express.js
, we will see a much
more intricate file with many module.exports
assignments (but assigned to
a exports
convenience variable to not have to prefix with module
each time).
Back in node_modules/express/index.js
, let’s add an additional export field:
/*!
* express
* Copyright(c) 2009-2013 TJ Holowaychuk
* Copyright(c) 2013 Roman Shtylman
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict';
module.exports = require('./lib/express');
module.exports.hi = "hello there";
Did we just edit the Express package? Let’s see. Go back to src/server.js
,
add a simple console.log(express.hi)
line, and then run node server.js
.
You should see “hello there” in the output in addition to your server!
In the same way that the express
package used relative imports, you do not have to only
import and export module information from within node_modules
. To import your own source
files in the project, merely add relative paths in the require()
argument.
Warning
Editing an imported NPM package locally should not be done in practice –
this exercise was for merely instructive purposes. A successive npm install
could overwrite all your work.
Warning
NPM does support installing local packages as NPM packages. Some organizations, have faced security issues where someone registers their project’s local package name as a public package on NPM, causing a name collision. Here is a Medium post introducing this supply chain attack vector. GitHub published a blog post regarding how to install local packages via NPM safely.
ES6, Babel, and Transpilation¶
As we go back to the browser, you may have noticed an issue with continuously updating standards: browsers have to send updates to all their users so that all client-side browsers can execute the newest features of Javascript. Inevitably, there will be users with outdated browsers that are incapable of running modern versions of Javascript.
One especially big version change was ECMAScript 2015, or ES6.
The let
keyword and arrow function notation from the
second code snippet are part of ES6. The spread operator in
the deepCopy
line is from ES9.
Thankfully, these language additions do not change
the expressivity of the language and can be viewed as
syntactic sugar. Babel is
a JS framework that helps convert your modern Javascript
code into Javascript that is compliant with the old
ECMAScript standards so that it can be run out of the box. We will will see this in action on our modern server.js
file that uses arrow functions.
Note
In fact, Babel can perform arbitrary transpilation, which is why React uses Babel to transform their JSX into pure, native javascript. We will want to explicitly tell Babel to transpile this modern javascript a pre-ECMA2015 world.
Next, we will install babel, babel’s cli, and the default presets, which include replacing arrow functions. Then, we will use Babel to transpile our server.js file:
$ npm install --save-dev @babel/core @babel/cli @babel/preset-env
$ ./node_modules/.bin/babel src --out-dir lib --presets=@babel/env
Successfully compiled 1 file with Babel (431ms).
$ cat lib/server.js
"use strict";
var express = require('express');
var app = express();
var port = 3000;
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.listen(port, function () {
console.log("Example app listening at http://localhost:".concat(port));
});
Et Voila! Our arrow functions have been replaced with traditional functions. Be sure to continue reading here for more information on how to use Babel.
ES6 + CommonJS: Import & Export¶
When ES6 came about, Ecma International decided to put module imports and exports in to the JS standard. Crazily enough, the browsers support ES6 modules as well, although I have never tried using ES6 modules directly via inline JS. Node, by extension, also supports managing packages this way as well. One particularly cool aspect is that Node makes ES6 module syntax interoperable with other modules that use CommonJS syntax.
Try editing our server.js file to use ES6 import syntax:
import express from 'express'
console.log(express.hi)
var app = express();
var port = 3000;
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.listen(port, function () {
console.log("Example app listening at http://localhost:" + port);
});
If we try running this, we get the following error message:
$ node server.js
(node:82672) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
./src/server.js:1
import express from 'express'
^^^^^^
Interestingly, Node doesn’t support interchanging CommonJS and ES6 syntax
within the same module. Thankfully, this is an easy fix. Add the "type": "module"
(“type” defaults to “commonjs” when the field isn’t specified), which tells Node.js
to interpret this module in ES6. Even though express was written with CommonJS
modules, we can still import Express with ES6:
$ node server.js
hello there
Example app listening at http://localhost:3000
Note
We ran ES6 code via a normal node
command. What gives?
The reality is that your new Node installation certainly supports ES6
out of the box without transpilation in “experimental mode”, and most browsers already support ES6
anyways. As time goes on, there will be less and less of a need to transpile
to pre-2015 ECMAScript-compliant JS. Still, the underlying principles behind
transpilation will likely persist – there will always be a gap between the newest
versions of Javascript and the browser versions of billions of users all trying
to run your javascrip on their local machines. You don’t need to transpile, however,
if your JS is being solely on a Node.js process whose version you control.
Indeed, our tweaked Express package in node_modules is still unchanged (“hello there” is still being printed) and thus must still be written via CommonJS module syntax.
ES6 import/export statements also leverage the object destructuring syntax
added in the standard. Here is another example. Add other_file.js
to your
src
directory:
const hi = 'hi from other file'
const say_bye = () => { console.log("good bye from other file") }
export {hi, say_bye}
Then, update our increasingly messy server.js
to use this functionality
from other_file
:
import express from 'express'
import { hi, say_bye } from './other_file.js'
console.log(hi)
say_bye()
console.log(express.hi)
var app = express();
var port = 3000;
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.listen(port, function () {
console.log("Example app listening at http://localhost:" + port);
});
Node.js, despite having experimental support with importing modules via ES6 syntax,
doesn’t work too well with exports out of the box. Thankfully, Babel will convert our ES6
syntax into CommonJS syntax via transpilation. Remove the "type": "module"
line from our package.json
to tell node
that we’re reverting into CommonJS
syntax. Let’s now compile our ES6 JS into CommonJS syntax via the same Babel command we used
before:
$ ./node_modules/.bin/babel src --out-dir lib --presets=@babel/env
$ node lib/server.js
hi from other file
good bye from other file
hello there
Example app listening at http://localhost:3000
Typescript¶
Imagine you have the following function to check if someone will be of legal drinking age in the US exactly 5 years from now given their current age:
function willBeOfAge(age) {
if (age + 5 >= 21) {
return true
} else {
return false
}
}
What will happen if age is 10? What about “10”?
$ node
Welcome to Node.js v14.13.1.
Type ".help" for more information.
> 10 + 5 >= 21
false
> "10" + 5 >= 21
true
> "10" + 5
'105'
Javascript can support weird operations between different types, and this can make it difficult to catch bugs where the wrong types are being used. For example, imagine that, in the age example, the age value was derived from some string regex. A developer could have forgotten to cast the string into a number (there are no int types in JS). In fact, many bugs can be viewed as type bugs – incorrect function signatures, handling null values, and performing comparisons with the wrong implicit types all could be prevened with TypeScript.
Javascript does not support static types out of the box, so TypeScript
was introduced to add static types to Javascript. A cool feature of TypeScript
is that one can have incremental adoption of TypeScript – one can use different
settings to enforce static types on only some Javascript files, or one could disable
checks or null
or undefined
.
We’ll try to use TypeScript to prevent a developer from accidentally passing in a
string to this willBeOfAge function. For our convenience, we’ll make a new
file age_checker.ts
and include the following function:
const willBeOfAge = (age) => {
if (age + 5 >= 21) {
return true
}
return false
}
console.log(willBeOfAge(10))
console.log(willBeOfAge("10"))
Here, we’re also leveraging arrow functions for a more modern JS feel.
Run the file via node age_checker.ts
to see how passing
in the number as a string will not throw an exception but possibly
give the wrong answer.
We can selectively add types to whatever variables we would like:
we could add a type constraint to the argument age
.
const willBeOfAge = (age : number) : boolean => {
if (age + 5 >= 21) {
return true
}
return false
}
console.log(willBeOfAge(10))
console.log(willBeOfAge("10"))
Let’s now try to compile this with TypeScript and check for type correctness. First, install TypeScript globally on your machine:
$ npm install -g typescript
Next, try compiling your new .ts
file:
$ tsc age_checker.ts
age_checker.ts:8:25 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
8 console.log(willBeOfAge("10"))
Found 1 error.
Here, TypeScript catches incorrect argument types before we deploy
our code. To be nice, Typescript will still compile the .ts
into
normal Javascript. If you inspect the newly created age_checker.js
,
you may notice that TypeScript performs a dual function – it converted our fancy
arrow functions into older ES3 function definitions! Thus, TypeScript can already transpile
most of what Babel also supports in addition to their static type checking.
To learn more about TypeScript, check out their documentation.