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.