Modules with CommonJS

Not all code is in one file. You might have a Python application that includes code from Flask. Or, you might split your large Python code into multiple Python modules, each in a separate file, and use Python imports to include them in other modules.

In this section we look at one popular way to modularize your frontend code, based on the Node’s CommonJS specification.

Overview

  • Modularize our JavaScript application into two files
  • Import code from one file, into the other
  • See how PyCharm helps on this

Background

Python has had modules for..well, if not forever, then a long time. The earliest available documentation, for Python 1.4 in 1996, explains modules and imports. Later came packages, eggs, wheels, and lots more.

JavaScript historically has had none of this. If you have a function incrementer in a file lib.js, and you wanted to use incrementer in a file app.js, you would do something like:

<script src="lib.js"></script>
<script src="app.js"></script>

That is, you’d tell your browser to load the first file, then the second file, and you’d hope it all worked out. The browser would jam everything into a global namespace, spawning a litany of obscure workarounds. Since everything is async, you can’t be sure lib.js is loaded before your app.js code is executed, spawning another litany of more obscure workarounds.

Server-side JavaScript via Node.js gained popularity, and Node added its own module export/import system to JavaScript. As explained in JavaScript Modules: A Beginner’s Guide, this “specification”, known as “CommonJS”, works great on a Node.js, which has a module loader, as well as fast I/O. Browsers have no module loader and wildly-unpredictable I/O, and thus require bundlers (the topic of the next article.)

Let’s get started writing modular JavaScript code.

Python Modules for Incrementer

Let’s say we want a function incrementer which can be re-used in multiple files. It takes a number and returns that number, incremented.

If this was Python, well, this really couldn’t be much easier. Our lib.py:

Modules lib.py
def incrementer(n):
    return n+1

This incrementer function is then imported into app.py:

Modules app.py
from lib import incrementer
print(incrementer(2))

Of course there could be some rough edges. If I import from a different directory, I’ll need a __init__.py file to make this directory a package. If I want to use relative imports, I can’t directly invoke app.py. But it’s clear that modules and imports are a first class part of Python.

Node.js Modules for Incrementer

Let’s take a look at this in JavaScript for Node. Our lib.js defines the function, and importantly, attaches it to the Node.js global module.exports:

Modules lib.js
function incrementer (i) {
    return i+1;
}

module.exports = incrementer;

In a nutshell, CommonJS “exports” definitions, available for import from other files, via a module.exports built-in variable. There are lots of semantics we are skipping over: named versus default exports, dynamic exports, and more. This example shows a default export.

Our application can now import this:

Modules app.js
var incrementer = require('./lib');
console.log(incrementer(3));

Our application imports our function from ./lib. Two important points:

  • The ./ in front tells Node to get this not from a pacakge in node_modules but instead, from a file in the local directory
  • We left .js off the end of the filename...Node support either

As we saw in NodeJS for Python and PyCharm Developers, we can run app.js from the command line or from PyCharm:

PyCharm and Modules

PyCharm understands Node.js import syntax, but you have to help it a little bit. In PyCharm Preferences, go to Languages & Frameworks -> Node.js and NPM. Under Code Insight, click the “Enable” button beside Node.js Core library is not enabled:

Screenshot node core modules

This will, for example, fix the warning about module being undefined. With the Node.js library enabled, not only is module defined, but it can be code-completed:

Screenshot module exports

Enable the library also let PyCharm help us on import mistakes. For example, with the Node.js core disabled, PyCharm doesn’t spot import failures from bad filenames. But with it enabled, a warning is correctly presented:

Wrapup

It’s odd in a way to say it, but this article is a big step in the process towards Polyglot Python with PyCharm. It comes with many caveats:

  • We just broke the browser. The code runs in Node.js but won’t work in browser-based JavaScript engines. We’ll fix that in Browser Bundling with Webpack.
  • Older module system. Some frontend developers are adopting ES2015 (aka ES6) modules. Teaching that, however, requires teaching the Babel transpiler, as Node.js doesn’t natively support ES2015 modules (because Chrome v8 doesn’t support it, because the spec is still in progress.)

With that said, this is still a big step towards developing Pythonically. We can work in isolation, with some of our code disassociated from a browser, and perhaps...dare we dream...start on test-driven development (TDD).

Comments

comments powered by Disqus