Necromancing.
IMHO, the existing answers leave much to be desired.
At first, it's very confusing.
You have a (nowhere defined) function "require", which is used to get modules.
And in said (CommonJS) modules, you can use require, exports and module
,
WITHOUT THEM EVER BEING DEFINED.
Not that it would be new that you could use undefined variables in JS, but you couldn't use an undefined function.
So it looks a little like magic at first.
But all magic is based on deception.
When you dig a little deeper, it turns out it is really quite simple:
Require is simply a (non-standard) function defined at global scope.
(or rather at pseudo-global scope; global scope = window-object in browser, global-object in NodeJS).
Note that by default, the "require function" is only implemented in NodeJS, not in the browser.
Also, note that to add to the confusion, for the browser, there is RequireJS, which, despite the name containing the characters "require", RequireJS absolutely does NOT implement require/CommonJS - instead RequireJS implements AMD, which is something similar, but not the same (aka incompatible).
That last one is just one important thing you have to realize on your way to understanding require.
Now, as such, to answer the question "what is require", we "simply" need to know what this function does.
This is perhaps best explained with code.
Here's a simple implementation by Michele Nasti, the code you can find on his github page.
Let's call our minimalisc implementation of the require function "myRequire":
function myRequire(name)
{
console.log(`Evaluating file ${name}`);
if (!(name in myRequire.cache)) {
console.log(`${name} is not in cache; reading from disk`);
let code = fs.readFileSync(name, 'utf8');
let module = { exports: {} };
myRequire.cache[name] = module;
let wrapper = Function("require, exports, module", code);
wrapper(myRequire, module.exports, module);
}
console.log(`${name} is in cache. Returning it...`);
return myRequire.cache[name].exports;
}
myRequire.cache = Object.create(null);
window.require = myRequire;
const stuff = window.require('./main.js');
console.log(stuff);
Now you notice, the object "fs" is used here.
For simplicity's sake, Michele just imported the NodeJS fs module:
const fs = require('fs');
Which wouldn't be necessary.
So in the browser, you could make a simple implementation of require with a SYNCHRONOUS XmlHttpRequest:
const fs = {
file: `
// module.exports = \"Hello World\";
module.exports = function(){ return 5*3;};
`
, getFile(fileName: string, encoding: string): string
{
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests
let client = new XMLHttpRequest();
// client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
// open(method, url, async)
client.open("GET", fileName, false);
client.send();
if (client.status === 200)
return client.responseText;
return null;
}
, readFileSync: function (fileName: string, encoding: string): string
{
// this.getFile(fileName, encoding);
return this.file; // Example, getFile would fetch this file
}
};
Basically, what require thus does, is it downloads a JavaScript-file, evals it in an anonymous namespace (aka Function), with the parameters "require", "exports" and "module", and returns the exports, meaning an object's public functions and properties.
Note that this evaluation is recursive: you require files, which themselfs can require files.
This way, all "global" variables used in your module are variables in the require-wrapper-function namespace, and don't pollute the global scope with unwanted variables.
Also, this way, you can reuse code without depending on namespaces, so you get "modularity" in JavaScript. "modularity" in quotes, because this is not exactly true, though, because you can still write window.bla/global.bla, and hence still pollute the global scope... Also, this establishes a separation between private and public functions, the public functions being the exports.
Now instead of saying
module.exports = function(){ return 5*3;};
You can also say:
function privateSomething()
{
return 42:
}
function privateSomething2()
{
return 21:
}
module.exports = {
getRandomNumber: privateSomething
,getHalfRandomNumber: privateSomething2
};
and return an object.
Also, because your modules get evaluated in a function with parameters
"require", "exports" and "module", your modules can use the undeclared variables "require", "exports" and "module", which might be startling at first. The require parameter there is of course a pointer to the require function saved into a variable.
Cool, right ?
Seen this way, require looses its magic, and becomes simple.
Now, the real require-function will do a few more checks and quirks, of course, but this is the essence of what that boils down to.
Also, in 2020+, you should use the ECMA implementations instead of require:
import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { export1 , export2 } from "module-name";
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export1 [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
And if you need a dynamic non-static import (e.g. load a polyfill based on browser-type), there is the ECMA-import function/keyword:
var promise = import("module-name");
note that import is not synchronous like require.
Instead, import is a promise, so
var something = require("something");
becomes
var something = await import("something");
because import returns a promise (asynchronous).
So basically, unlike require, import replaces fs.readFileSync with fs.readFileAsync.
async readFileAsync(fileName, encoding)
{
const textDecoder = new TextDecoder(encoding);
// textDecoder.ignoreBOM = true;
const response = await fetch(fileName);
console.log(response.ok);
console.log(response.status);
console.log(response.statusText);
// let json = await response.json();
// let txt = await response.text();
// let blo:Blob = response.blob();
// let ab:ArrayBuffer = await response.arrayBuffer();
// let fd = await response.formData()
// Read file almost by line
// https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read#Example_2_-_handling_text_line_by_line
let buffer = await response.arrayBuffer();
let file = textDecoder.decode(buffer);
return file;
} // End Function readFileAsync
This of course requires the import-function to be async as well.
"use strict";
async function myRequireAsync(name) {
console.log(`Evaluating file ${name}`);
if (!(name in myRequireAsync.cache)) {
console.log(`${name} is not in cache; reading from disk`);
let code = await fs.readFileAsync(name, 'utf8');
let module = { exports: {} };
myRequireAsync.cache[name] = module;
let wrapper = Function("asyncRequire, exports, module", code);
await wrapper(myRequireAsync, module.exports, module);
}
console.log(`${name} is in cache. Returning it...`);
return myRequireAsync.cache[name].exports;
}
myRequireAsync.cache = Object.create(null);
window.asyncRequire = myRequireAsync;
async () => {
const asyncStuff = await window.asyncRequire('./main.js');
console.log(asyncStuff);
};
Even better, right ?
Well yea, except that there is no ECMA-way to dynamically import synchronously (without promise).
Now, to understand the repercussions, you absolutely might want to read up on promises/async-await here, if you don't know what that is.
But very simply put, if a function returns a promise, it can be "awaited":
"use strict";
function sleep(interval)
{
return new Promise(
function (resolve, reject)
{
let wait = setTimeout(function () {
clearTimeout(wait);
//reject(new Error(`Promise timed out ! (timeout = ${timeout})`));
resolve();
}, interval);
});
}
The promise would then normally be used like this:
function testSleep()
{
sleep(3000).then(function ()
{
console.log("Waited for 3 seconds");
});
}
But when you return a promise, you can also use await, which means we get rid of the callback (sort of - actually, it is being replaced with a state-machine in the compiler/interpreter).
This way, we make asynchronous code feel like synchronous, so we now can use try-catch for error-handling.
Note that if you want to use await in a function, that function must be declared async (hence async-await).
async function testSleep()
{
await sleep(5000);
console.log("i waited 5 seconds");
}
And also please note that in JavaScript, there is no way to call an async function (blockingly) from a synchronous one (the ones you know). So if you want to use await (aka ECMA-import), all your code needs to be async, which most likely is a problem, if everything isn't already async...
An example of where this simplified implementation of require fails, is when you require a file that is not valid JavaScript, e.g. when you require css, html, txt, svg and images or other binary files.
And it's easy to see why:
If you e.g. put HTML into a JavaScript function body, you of course rightfully get
SyntaxError: Unexpected token '<'
because of Function("bla", "<doctype...")
Now, if you wanted to extend this to for example include non-modules, you could just check the downloaded file-contents for code.indexOf("module.exports") == -1
(or the xml-request-mime-type), and then e.g. add jQuery as script tag (eval is not the same) instead of Func (which works fine as long as you're in the browser). Since downloads with Fetch/XmlHttpRequests are subject to the same-origin-policy, and integrity is ensured by SSL/TLS, the use of eval here is rather harmless, provided you checked the JS files before you added them to your site, but that much should be standard-operating-procedure.
Note that there are several implementations of require-like functionality:
the CommonJS (CJS) format, used in Node.js, uses a require function and module.exports to define dependencies and modules. The npm ecosystem is built upon this format. (this is what is implemented above)
the Asynchronous Module Definition (AMD) format, used in browsers, uses a define function to define modules. (basically, this is overcomplicated archaic crap that you wouldn't ever want to use). Also, AMD is the format that is implemented by RequireJS (note that despite the name containing the characters "require", AMD absolutely is NOT CommonJS).
the ES Module (ESM) format. As of ES6 (ES2015), JavaScript supports a native module format. It uses an export keyword to export a module’s public API and an import keyword to import it. This is the one you should use if you don't give a flying f*ck about archaic browsers, such as Safari and IE/EdgeHTML.
the System.register format, designed to support ES6 modules within ES5. (the one you should use, if you need support for older browsers (Safari & IE & old versions of Chrome on mobile phones/tablets), because it can load all formats [for some, plugins are required], can handle cyclic-dependencies, and CSS and HTML - don't define your modules as system.register, though - the format is rather complicated, and remember, it can read the other easier formats)
the Universal Module Definition (UMD) format, compatible to all the above mentioned formats (except ECMA), used both in the browser and in Node.js. It’s especially useful if you write modules that can be used in both NodeJS and the browser. It's somewhat flawed, as it doesn't support the latest ECMA modules, though (maybe this will get fixed) - use System.register instead.
Important sidenote on the function argument "exports":
JavaScript uses call-by-value-sharing - meaning objects are passed as a pointer, but the pointer-value itselfs is passed BY VALUE, not by reference. So you can't override exports by assigning it a new object. Instead, if you want to override exports, you need to assign the new object to module.exports - because hey, module is the pointer passed by value, but exports in module.exports is the reference to the original exports pointer.
Important sidenote on module-Scope:
Modules are evaluated ONCE, and then cached by require.
That means all your modules have a Singleton scope.
If you want a non-singleton scope, you have to do something like:
var x = require("foo.js").createInstance();
or simply
var x = require("foo.js")();
with appropriate code returned by your module.
If you need CommonJS-support for the browser (IE5+, Chrome, Firefox),
check out my code in my comment on Michele Nasti's project