One, with the packaging template to achieve the simplest module packaging

Consider a module that follows the CommonJS specification:

  • tiny-bundler\index.js
console.log('hello, bundler');
module.exports = 'hello, world';
Copy the code

What does it need to look like when you pack it up?

As mentioned in the previous summary of several common modular specifications, Node.js handles CommonJS modules with a shell on the outside, including require, Module, exports and other methods that are not found in native JS. Here, we can do the same thing. Also, to get the scope of a module, we enclose it inside (function(){})().

1. (function() {
2.   var moduleList = [
3.     function (require.module.exports) {
4.       console.log('hello, bundler');
5.       module.exports = 'hello, world';
6.     }
7.   ]
8. 
9.   var module = {
10.     exports: {}
11.   };
12. 
13.   moduleList[0] (null.module.null);
14.}) ();Copy the code

When we observed that the content of the module was only in lines 4-5, it occurred to us that we could split the rest into templates. So, we set up the file:

  • tiny-bundler\src\index.bundle.boilerplate
(function() {
  var moduleList = [
    function (require.module.exports) {
      /* template */}]var module = {
    exports: {}}; moduleList[0] (null.module.null); }) ();Copy the code
  • tiny-bundler\src\bundle.js
const fs = require('fs');
const path = require('path');

const boilerplate = fs.readFileSync(path.resolve(__dirname, 'index.bundle.boilerplate'), 'utf-8');
const target = fs.readFileSync(path.resolve(__dirname, '.. /index.js'), 'utf-8');
const content = boilerplate.replace('/* template */', target);

fs.writeFileSync(path.resolve(__dirname, '.. /dist/index.bundle.js'), content, 'utf-8');
Copy the code

If you run node SRC /bundle.js in the tiny-Bundler directory, you can get the package:

  • tiny-bundler\dist\index.bundle.js
var moduleList = [ function (require, module, exports) { console.log('hello, world'); module.exports = 'hello, world';  } ] var module = { exports: {} }; moduleList[0](null, module, null); }) ();Copy the code

2. Implement require and complete the package of modules with require

Let’s create a new file:

  • tiny-bundler\moduleA.js
module.exports = new Date().getTime();
Copy the code

And change the contents of tiny-bundler\index.js to:

const moduleA = require('./moduleA');
console.log(moduleA);
Copy the code

At this point, how should packaging be implemented? We can still go backwards and write index.bundle.js by hand:

1. (function() {
2.   var moduleList = [
3.     // index.js
4.     function (require.module.exports) {
5.       const moduleA = require('./moduleA');
6.       console.log(moduleA);
7.     },
8.     // moduleA.js
9.     function (require.module.exports) {
10.       module.exports = new Date().getTime();
11.     }
12.   ]
13. 
14.   var moduleDepIdList = [
15.     {
16.       ModuleList [1] : // require('./moduleA') moduleList[1
17.       './moduleA': 1.18.     },
19.     {
20.     }
21.   ]
22. 
23.   // implement a require on your own
24.   function require(id, parentId) {
25.     varcurrentModuleId = parentId ! = =undefined ? moduleDepIdList[parentId][id] : id;
26.     var module = { exports: {}};27.     var moduleFunc = moduleList[currentModuleId];
28.     moduleFunc((id) = > require(id, currentModuleId), module.module.exports);
29.     return module.exports;
30.   }
31. 
32.   require(0);
33.}) ();Copy the code

After observation, we find that lines 3-11 and 15-20 in the above code are the parts that change when facing different module packaging, while the rest remain unchanged. Therefore, we can change tiny-bundler\ SRC \index.bundle.boilerplate to:

(function() {
  var moduleList = [
    /* template-module-list */
  ]

  var moduleDepIdList = [
    /* template-module-dep-id-list */
  ]

  function require(id, parentId) {
    varcurrentModuleId = parentId ! = =undefined ? moduleDepIdList[parentId][id] : id;
    var module = { exports: {}};var moduleFunc = moduleList[currentModuleId];
    moduleFunc((id) = > require(id, currentModuleId), module.module.exports);
    return module.exports;
  }

  require(0); }) ();Copy the code

Iii. Ensure to complete the packaging of chunk asynchronous loading

Or backwards, let’s first write out an implementation effect:

  • tiny-bundler\index.html
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta  name="viewport" content="width=device-width, ">< title>Document</title> </head> <body> <script SRC ="./index.bundle.js"></script> </body> </ HTML >Copy the code
  • tiny-bundler\index.bundle.js
(function() {
  var moduleList = [
    function (require.module.exports) {
      require
        .ensure('1')
        .then(res= > {
          console.log(res); })}]var moduleDepIdList = [];

  var cache = {};

  function require(id, parentId) {
    varcurrentModuleId = parentId ! = =undefined ? moduleDepIdList[parentId][id] : id;
    var module = { exports: {}};var moduleFunc = moduleList[currentModuleId];
    moduleFunc((id) = > require(id, currentModuleId), module.module.exports);
    return module.exports;
  }

  window.__JSONP = function (chunkId, moduleFunc) {
    var resolve = cache[chunkId][0];
    var module = {
      exports: {}};// Execute the chunk code
    moduleFunc(require.module.module.exports);
    // Then resolve to exit module code to get module.exports
    resolve(module.exports);
  }

  require.ensure = function (chunkId, parentId) {
    varcurrentModuleId = parentId ! = =undefined ? moduleDepIdList[parentId][chunkId] : chunkId;

    // If cache[currentModuleId] === undefined, the cache is loaded for the first time
    if (cache[currentModuleId] === undefined) {
      // Load this JS asynchronously through JSONP
      var $script = document.createElement('script');
      $script.src = '/chunk_' + chunkId + '.js';
      document.body.appendChild($script);

      // Attach several states to the global cache
      var promise = new Promise(function (resolve) {
        cache[currentModuleId] = [resolve];
        cache[currentModuleId].status = true; // This state is true to indicate that '/chunk_' + chunkId + '.js' is still being loaded
      });
      cache[currentModuleId].push(promise);

      return promise;
    } else if (cache[currentModuleId].status) {
      // Loading
      return cache[currentModuleId][1];
    }

    return cache[currentModuleId];
  }

  moduleList[0] (require.null.null); }) ();Copy the code
  • tiny-bundler\chunk_1.js
window.__JSONP('1'.function(require.module.exports) {
  module.exports = 'hello, world';
});
Copy the code

Run anywhere -p 80 in the tiny-Bundler directory to start the static server, visit http://localhost and you can see the following result in Chrome Devtools:

In the specific implementation, the template can be extracted and replaced as before.

By the way, hot updates. Since moduleList stores the code for all compiled modules, when we update a module, the WebSocket server will listen on fsevents. Watch and repackage will be triggered. After packaging, the client is notified through websocket. After receiving the notification, the client only needs to replace the code of the corresponding module in the moduleList. At this time, the content of the corresponding module in the moduleList can be changed directly in the memory to achieve the effect of hot update.