jam3-lesson ยป module-basics
Modules for Frontend JavaScript
This is a brief intro to modules for modern frontend development. It will introduce Node, npm, and browserify. After this guide, you should be equipped to create your first npm module.
# sections
# concepts
modules
In this context, "modular" JavaScript refers to a piece of code that operates independently of the applications which surround it. A "module" โ sometimes just a single function โ is written, tested, and published in isolation, and often a good candidate for re-use across projects.
The goal is to build small programs that do one thing, do it well, and compose easily with other programs (the Unix Philosophy).
Some examples:
- xhr - an XMLHttpRequest abstraction
- seed-random - deterministic seeded pseudo-random number generator
- object-assign - a polyfill for
Object.assign
- gl-matrix - vector & matrix math, ideal for 2D and 3D applications
There are a few different formats for authoring and publishing modules, such as ES6, AMD, and CommonJS. This article will focus on CommonJS (used by Node).
dependencies
Each module can have a formalized set of "dependencies" โ that is, other modules it needs to work. Dependencies can help reduce repetition and share code across many modules. The tooling will figure out how to download and consume these dependencies under the hood. In turn, those modules can have their own dependencies, and so forth.
For example, the earlier mentioned xhr module has a dependency graph that looks a little like this, where each is a separate module:
xhr
โโโ once # run a function once
โโโฌ parse-headers # parse http headers
โโโฌ for-each # forEach() but works on objects
โ โโโ is-function # test if value is a function
โโโ trim # string trim utility
semantic versioning
As you re-use the same piece of code across many projects, bugs and limitations with the original module begin to emerge. The code evolves and changes to better suit the new use cases, and sometimes it leads to a breaking API (i.e. renaming a public function).
This is where semantic versioning can help us. A version is broken down into major.minor.patch
numbers, separated by decimals:
major
denotes a breaking change in the documented API (e.g. renaming or removing a public function)minor
denotes a new feature was added in a backwards-compatible mannerpatch
denotes a bug was fixed in a backwards-compatible manner
Modules typically start at version 1.0.0
. When you bump one number, you must reset the numbers to the right of it back to zero. For example:
- making a bug fix to
1.0.0
->1.0.1
(patch change) - adding a new feature to
1.0.1
->1.1.0
(minor change) - changing a method name in
1.1.0
->2.0.0
(major change)
When we install a module, we are also opting in for all of its patch
and minor
versions within the same major
version. This way, we get the latest bug fixes, but our code won't break if a dependency decides to change its API. Later, we will explore how to configure this option and "lock down" our application's dependencies.
# tools
To bring these concepts together, we will primarily be using these three open source tools:
- node - a platform built on Chrome's JavaScript engine (V8)
- npm - a package manager for JavaScript modules
- browserify - a tool which bundles Node modules into something browsers understand
node
The first step is to install Node; you can download it from nodejs.org. Among other things, it provides a command-line interface for executing JavaScript source.
Once it's installed, open Terminal and try entering the following:
node -e "console.log(10 * 2)"
If all goes well, the above should print 20
(where -e
is for eval).
(if that doesn't work, you may need to re-install Node via homebrew)
Now you can run JavaScript files with node path/to/file.js
, or start a REPL (read-eval-print-loop) with node
.
Tip: You can quit the REPL with Control + C
This is a great way to quickly test some generic JavaScript code without booting up a browser.
Since we are running in Node, we also have access to a standard library for things like File I/O and backend programming.
For example, let's use Node's built-in url module to parse the URL string "http://google.com/"
Here we are requiring a built-in module called url
. Require statements are part of Node, and look like this:
const url = require('url');
//... use url.parse()
npm
The next tool we need is npm. If you installed Node through the site above, you should already have npm set up! We can test that with the following command:
npm -v
(if that doesn't work, you may need to re-install Node and npm via homebrew)
Node bundles with a very outdated version of npm, so it's best to update it to get the latest features and security fixes:
sudo npm install npm -g
Let's try installing our first module: http-server. This helps us get a static site up quickly without a bloated GUI tool like MAMP.
npm install http-server -g
Note: The -g
flag tells npm to install the module globally.
If you get an EACCESS
error, you need to fix your permissions:
Fixing npm permissions
More Details (Stackoverflow)
Once it installs, you should be able to run it like so:
http-server -o
This should open the browser on http://localhost:8080/
.
Awesome! You just used your first module. This one is a command line tool, which is why we installed it globally with -g
. When we install code dependencies (like xhr), we typically install them locally.
browserify
To bridge the gap between Node/npm and the browser, we will use a tool called browserify. It transforms Node require()
statements into something the browser can execute, and bundles different modules into a single script. This allows us to use built-in Node modules like url, as well as modules on npm.
Close the earlier process (Control + C
), and then stub out a folder where we can write some demos. In Linux/OSX shell:
#go to your projects folder
cd ~/Documents
#make a new test folder
mkdir test-browserify
#move into that folder
cd test-browserify
#make an empty JavaScript file
touch index.js
On Windows, it might look like this:
cd `%HOMEPATH%/Documents`
mkdir test-browserify
cd test-browserify
type NUL > index.js
basics
Open the empty index.js
file we created above in your favourite editor (like SublimeText). Add the following and save it:
const url = require('url');
const parts = url.parse(window.location.href);
console.log(parts);
The above code won't work yet in the browser, since it uses Node APIs. For our demo, we will use a tool called budล to transform our Node require()
statements into something the browser can understand. Install the tool like so:
npm install budo -g
Now start budo
on our source file, like so:
budo index.js --live
This will start a browserify development server on port 9966
. When index.js
is requested, it will get "browserified" and transformed into something the browser can run. It will also serve you a basic index.html
if you didn't write one, with a <script>
tag for our index.js
. The --live
flag will ensure the browser reloads when your JavaScript code changes.
Now, when you open up http://localhost:9966
in your browser and check the DevTools console, you'll see the Node code working as expected!
local modules
The last thing we'll cover here is using a third-party module in our code. Let's install a SVG helper module, svg-create-element.
npm install svg-create-element
Note: We aren't installing this globally with -g
. In later lessons we'll formalize this as a dependency with a package.json
.
This will add the module into a folder called node_modules
in our current directory. Now let's replace our index.js
code with the following:
//require the SVG helper module
const create = require('svg-create-element');
//create a SVG element
const svg = create('svg');
//add a polyline
const line = create('polyline', {
stroke: 'orange',
strokeWidth: 4,
fill: 'transparent',
points: [
[ 50, 100 ],
[ 100, 50 ],
[ 150, 100 ],
[ 200, 50 ]
].join(' ')
});
svg.appendChild(line);
//add contnet to DOM
document.body.appendChild(svg);
And now, when we refresh our http://localhost:9966/
page, we will see a sweet 2D line!
# other tools
In this guide, we use Browserify and require()
statements. This is perhaps one of the easiest and most "bare bones" way of using Node.js and npm modules within your frontend code. However, there are many other tools like Browserify that include more features, including supporting ES6 import
and export
, such as:
- Parcel - a zero-config bundler and development server
- Webpack - a powerful bundler for modern JavaScript applications
# further reading
This article only covers the basics of Node, npm, and getting modules working in the browser. A future lesson will go into more depth on the require()
statement, and how we can build and publish our own modules. Until then, check out some of these links: