The principle and realization of browser loading CommonJS module
Just this weekend, npm surpassed cpan and became the largest software module warehouse on the planet.
The modules of npm are all written in JavaScript language, but browsers can’t use them because they don’t support CommonJS format. In order for the browser to use these modules, the format must be converted.
This article introduces the principle of the browser loading CommonJS, and gives a very simple implementation .
1. Principle
The root cause of browser incompatibility with CommonJS lies in the lack of four Node.js environment variables.
- module
- exports
- require
- global
As long as these four variables can be provided, the browser can load the CommonJS module.
Here is a simple example.
var module = { exports: {} }; (function(module, exports) { exports.multiply = function (n) { return n * 1000 }; }(module, module.exports)) var f = module.exports.multiply; f(5) // 5000
The above code provides two external variables, module and exports, to an immediate function, and the module is placed in the immediate function. The output value of the module is placed in module.exports, so that the module can be loaded.
Second, the realization of Browserify
Knowing the principle, you can make a tool. Browserify is currently the most commonly used CommonJS format conversion tool.
Please see an example, the main.js module loads the foo.js module.
// foo.js module.exports = function(x) { console.log(x); }; // main.js var foo = require("./foo"); foo("Hi");
Use the following command to convert main.js into a format usable by the browser.
$ browserify main.js > compiled.js
What exactly does Browserify do? Install browser-unpack , you can see clearly.
$ npm install browser-unpack -g
Then, unpack the previously generated compile.js.
$ browser-unpack < compiled.js [ { "id":1, "source":"module.exports = function(x) {\n console.log(x);\n};", "deps":{} }, { "id":2, "source":"var foo = require(\"./foo\");\nfoo(\"Hi\");", "deps":{"./foo":1}, "entry":true } ]
As you can see, browerify puts all modules into an array. The id attribute is the number of the module, the source attribute is the source code of the module, and the deps attribute is the dependency of the module.
Because foo.js is loaded in main.js, the deps attribute specifies that ./foo corresponds to module 1. When executing, when the browser encounters a require(‘./foo’) statement, it will automatically execute the source attribute of module No. 1 and output the module.exports attribute value after execution.
Three, Tiny Browser Require
Although Browserify is very powerful, it cannot be operated in a browser, which is sometimes inconvenient.
Based on the internal implementation of mocha , I made a pure-browser CommonJS module loader tiny-browser-require . No command line is required at all, just put it directly into the browser, and all the code is only more than 30 lines.
Its logic is very simple, that is, read the module into an array, and the load path is the id of the module.
function require(p){ var path = require.resolve(p); var mod = require.modules[path]; if (!mod) throw new Error('failed to require "' + p + '"'); if (!mod.exports) { mod.exports = {}; mod.call(mod.exports, mod, mod.exports, require.relative(path)); } return mod.exports; } require.modules = {}; require.resolve = function (path){ var orig = path; var reg = path + '.js'; var index = path + '/index.js'; return require.modules[reg] && reg || require.modules[index] && index || orig; }; require.register = function (path, fn){ require.modules[path] = fn; }; require.relative = function (parent) { return function(p){ if ('.' != p.charAt(0)) return require(p); var path = parent.split('/'); var segs = p.split('/'); path.pop(); for (var i = 0; i < segs.length; i++) { var seg = segs[i]; if ('..' == seg) path.pop(); else if ('.' != seg) path.push(seg); } return require(path.join('/')); }; };
When using it, put the above code on the page first. Then, put the module in the following immediate execution function, you can call it.
<script src="require.js" /> <script> require.register("moduleId", function(module, exports, require){ // Module code goes here }); var result = require("moduleId"); </script>
Take the previous main.js loading foo.js as an example.
require.register("./foo.js", function(module, exports, require){ module.exports = function(x) { console.log(x); }; }); var foo = require("./foo.js"); foo("Hi");
Note that this library only simulates the three variables of require, module, and exports. If the module also uses global or other Node-specific variables (such as process), it can be provided through the immediate execution function.
(over)