First, write first

In the daily development process, we use webpack more and more construction tools, but for its use, we are more stuck in some simple configuration, such as loader,plugin configuration. We rarely build a project from scratch using WebPack (more often using the CLI) and even less understand how packaging works inside it. Why is it able to convert files into one module, and why is it able to package all modules into one file? What does the packaged file look like (probably most people rarely look at the packaged build file)? We didn’t know much about any of this, but when our build speed slowed down, we wanted to optimize it because we didn’t know enough about WebPack. And the more interviews ask about the underlying principles of Webpack, the more webpack gets in the way. But for many people, to see the source of Webpack, may be a lot of people will have a big head, do not know how to start. If you look at other people’s source code, you will get caught up in all kinds of concepts, such as the event mechanism, internal hooks, Tapable plugin architecture and hook design. It’s hard to understand. Instead, I think if we look at the results, see what webPack looks like when it’s finally packaged, and then implement a simple, identical wrapper, we can bypass a lot of the sophistication and understand how it works. At this time we go to see the source code or other people’s articles may get twice the result with half the effort.

Module packer

2.1 What is a module packer

Webpack is a static module bundler for modern JavaScript applications. When WebPack works with an application, it recursively builds a Dependency graph containing every module the application needs, and then packages all of those modules into one or more bundles. More commonly understood is: each file is a module, a file will introduce the content of other files, we finally want to implement a file as the entrance: all its dependent files are finally packaged into a file, this is the module packer.

2.2 Using WebPack packed files

We know that the module packer packs multiple files into a single file, so we need to know what the packed file looks like before we can implement it, so let’s look at the following webPack package. Example: Suppose we have the following files in the same folder: file: index.js

let action = require("./action.js").action;   / / introduce aciton. Js
let name = require("./name.js").name;         / / introduction of the name. Js
let message = `${name} is ${action}`;
console.log(message);
Copy the code

The index.js file introduces action.js and name.js. File: action. Js

let action = "making webpack";
exports.action = action;
Copy the code

File: name. Js

let familyName = require("./family-name.js").name;
exports.name = `${familyName}Albert ';
Copy the code

The name.js file introduces the family-name.js file. File: family – name. Js

exports.name = "haiyingsitan";
Copy the code

Next we use WebPack to package and remove the packaged comments, resulting in the following code:

 (() = > {

   var __webpack_modules__ = ({
     "./action.js": ((__unused_webpack_module, exports) = > {
       let action = "making webpack";
       exports.action = action;
     }),
     "./family-name.js": ((__unused_webpack_module, exports) = > {
       exports.name = "haiyingsitan";
     }),
     "./name.js": ((__unused_webpack_module, exports, __webpack_require__) = > {
       let familyName = __webpack_require__( / *! ./family-name.js */ "./family-name.js").name;
       exports.name = `${familyName}Albert '; })});var __webpack_module_cache__ = {};
   function __webpack_require__(moduleId) {
     if (__webpack_module_cache__[moduleId]) {
       return __webpack_module_cache__[moduleId].exports;
     }
     var module = __webpack_module_cache__[moduleId] = {
       exports: {}}; __webpack_modules__[moduleId](module.module.exports, __webpack_require__);
     return module.exports;
   }

   (() = > {
     let action = __webpack_require__(  "./action.js").action;
     let name = __webpack_require__(  "./name.js").name;
     let message = `${name} is ${action}`;
     console.log(message); }) (); }) ();Copy the code

The above code still looks a bit complicated, so let’s simplify it further:

(() = > {
    // Get all dependencies
  var modules = {
    "./action.js": (module.exports) = > {
      let action = "making webpack";
      exports.action = action;
    },
     / /... Other code
  };

  // require the execution of the corresponding module function
  function __webpack_require__(moduleId) {
    // Other implementations
    return module.exports;
  }

  // The entry function executes immediately
  let entryFn = () = > {
    let action = __webpack_require__("./action.js").action;
    let name = __webpack_require__("./name.js").name;
    let message = `${name} is ${action}`;
    console.log(message); }; entryFn(); }) ();Copy the code

As you can see, the file is an immediate function when it is finally packaged. This function consists of three parts:

  1. Module collection This module collection is a collection of all modules, with the path as the key and the module content as the value. When we need to use a module, we can get it directly from the module collection. Why do I need this collection of modules? Imagine if we metrequire("./action.js")So thisaction.jsWhich module does it correspond to? Therefore, we must be able to retrieve all modules and distinguish them (by module ID or module name) directly from the module collection by module ID or module name.
  var modules = {
    "./action.js": (module.exports) = > {
      let action = "making webpack";
      exports.action = action; }};Copy the code
  1. Module functions perform each module corresponding to a function when encounteredrequire(xxx)Is actually to execute the module function introduced.
   var __webpack_module_cache__ = {};
   function __webpack_require__(moduleId) {
     if (__webpack_module_cache__[moduleId]) {
       return __webpack_module_cache__[moduleId].exports;
     }
     var module = __webpack_module_cache__[moduleId] = {
       exports: {}}; __webpack_modules__[moduleId](module.module.exports, __webpack_require__);
     return module.exports;
   }
Copy the code
  1. We all know that to package a module, there must be an entry file, and that this file must be executed immediately to retrieve all dependencies. In fact, the entry file is also a module, immediately execute the corresponding function of this module can be.
  let entryFn = () = > {
    let action = __webpack_require__("./action.js").action;
    let name = __webpack_require__("./name.js").name;
    let message = `${name} is ${action}`;
    console.log(message);
  };
  entryFn();
Copy the code

So far, we have a pretty good idea of what the webPack module looks like when packaged. If we want to implement the same functionality, we only need to implement it simultaneously: module collection, module execution, and entry function execution immediately. One of the most critical is to implement module collection and module execution.

Third, concrete implementation

From the above analysis, we can know that what we want to achieve mainly includes two parts:

  1. Generate all files in a project into a large collection of modules
  2. Modules execute functions. When an imported module is encountered, the corresponding function is executed.

Let’s implement these two parts separately.

3.1 Implementation module set

3.1.1 Shell file Contents

We can take a look at the details of each module when WebPack is packaged:

  var modules = {
    "./action.js": (module.exports) = > {
        // File contents
      let action = "making webpack";
      exports.action = action; }};Copy the code

We can see that each module is actually a function shell around the outer layer. The reason for putting the contents of a file into a function is that we all know that one of the most important aspects of modularity is the isolation of the environment. Imagine if the contents of a file were packaged together rather than isolated, then variables defined between modules would surely affect each other in the same scope. Functions are often used to form a separate scope to isolate variables. Therefore, we shell all the files first. Index. Js module

function(require.exports){
    let action = require("./action.js").action;
    let name = require("./name.js").name;
    let message = `${name} is ${action}`;
    console.log(message);
}
Copy the code

Action. Js module

function(require.exports){
    let action = "making webpack";
    exports.action = action;
}
Copy the code

Name. Js module

function(require.exports){
    let familyName = require("./family-name.js").name;
    exports.name = `${familyName}Albert ';
}
Copy the code

Family – name. Js module.

function(require.exports){
    exports.name = "haiyingsitan";
}
Copy the code

Then, in order to distinguish or obtain these modules, we need to give each module a module ID or module name. Here we directly use the file path as the ID of each module. Finally, these modules are grouped into a collection. As follows:

const modules = {
  "./index.js":function(require.exports){
    let action = require("./action.js").action;
    let name = require("./name.js").name;
    let message = `${name} is ${action}`;
    console.log(message);
  },
  "./action.js": function (require.exports) {
    let action = "making webpack";
    exports.action = action;
  },
  "./name.js": function (require.exports) {
    let familyName = require("./family-name.js").name;
    exports.name = `${familyName}Albert ';
  },
  "./family-name.js": function (require.exports) {
    exports.name = "haiyingsitan"; }};Copy the code

In other words, what we’re ultimately going to implement is a set like this.

So far, we have implemented the following functions:

  1. Shell each file content
  2. Each module has a path as its module ID
  3. Put all the modules together to form a set

Let’s look at the implementation as follows:

const fs = require("fs");
let modules = {};
const fileToModule = function (path) {
  const fileContent = fs.readFileSync(path).toString();
  return {
    id: path,                             // Use path as module id
    code: 'function(require,exports){// exports${fileContent.toString()}; } `}; };let result = fileToModule("./index.js");
modules[result["id"]] = result.code;
console.log("modules=",modules);
Copy the code

The output is:

modules= {
    './index.js':'function(require,exports){\n let action = require("./action.js").action; \r\nlet name = require("./name.js").name; \r\nlet message = `${name} is ${action}`; \r\nconsole.log(message); \r\n; \n }' 
}
Copy the code

From the above we can see that we successfully converted the entry file shell into a module, named it, and added it to the module object. However, we found that our files also relied on. /action.js and./name.js, but we could not get their module contents. Therefore, we need to deal with modules introduced by require. This means finding all the dependencies in the current module and then parsing them into the module collection.

3.1.2 Get all dependencies of the current module

The next step is to find all the dependencies in a module.

// const action = require("./action.js")
function getDependencies(fileContent) {
  let reg = /require\(['"](.+?) ['"]\)/g;
  let result = null;
  let dependencies = [];
  while ((result = reg.exec(fileContent))) {
    dependencies.push(result[1]);
  }
  return dependencies;
}
Copy the code

Here we use the regex judgment, and anything in the format require(“”) or require(“”) will be treated as a module import. We then put all the imports into an array to get all the dependencies of the current module. We use this function to check the dependency of the entry file:

const fileContent = fs.readFileSync(path).toString();
let result = getDependencies(fileContent);
console.log(result)  // ["./action.js","./name.js"]
Copy the code

We can get all the dependencies of the import file smoothly, and then we need to further resolve the dependencies of the import file. Therefore, when we convert files into modules, it is better to display all dependency information of modules for easy processing. So, let’s change the fileToModule function.

const fileToModule = function (path) {
  const fileContent = fs.readFileSync(path).toString();
  return {
      id:path,
      dependencies:getDependencies(fileContent),   // Add module information
      code:`(require,exports) => {
            ${fileContent.toString()}; } `}};Copy the code

Ok, so far we’ve been able to get the dependencies for each module, and we’ve been able to convert each dependency into an object, so the next step is to put all the objects into one big object to get a collection of all the modules in the project.

3.1.3 Combine all modules into a set

function createGraph(filename) {
  let module = fileToModule(filename);
  let queue = [module];
  
  for (let module of queue) {
    const dirname = path.dirname(moduleId);
    module.dependencies.forEach((relativePath) = > {
      const absolutePath = path.join(dirname, relativePath);
      const child = fileToModule(absolutePath);
      queue.push(child);
    });
  }
    // This is an array. Convert to object
  let modules = {}
  queue.forEach((item) = > {
    modules[item.id] = item.code;
  })
  return modules;
}
console.log(createGraph("./index.js"));
Copy the code

CreateGraph takes all the dependencies from the entry file, gets them in turn, and adds each one to the queue array, and since we iterate with let of, let of continues to iterate over the new element, without needing to do anything like the for loop does. Let’s take a look at the resulting collection of modules from the entry file:

{
    './index.js': 'function(require,exports){ let action = require("./action.js").action; \r\nlet name = require("./name.js").name; \r\nlet message = `${name} is ${action}`; \r\nconsole.log(message); \r\n; \n}'.'action.js': 'function(require,exports){let action = "making webpack"; \r\nexports.action = action;; \n }'.'name.js': 'function(require,exports){let familyName = require("./family-name.js").name; \r\nexports.name = '${familyName} Albert'; \n}'.'family-name.js': 'function(require,exports){exports.name = "haiyingsitan";; \n }'
}
Copy the code

3.2 Executing module functions

After obtaining all module information in the module object above, we execute the function corresponding to the entry fileexec.From the figure above we can see that when we execute the function corresponding to the entry fileexec(index.js), it found that:

  • Is dependent on./action.js, so callexec("./action.js"). There are no other dependencies, so return the value directly. This line ends.
  • Is dependent on./name.js, so callexec("./name.js"). Again found dependence./family-name.js, so callexec("./family-name.js"). There are no other dependencies at this point, return value. This line ends.

We can see that this is a recursive process, looking for dependencies and then executing the corresponding function. Therefore, we can write the following function roughly:

const exec = function(moduleId){
  const fn = modules[moduleId];  // Get the function for each id
  let exports = {};
  const require = function(filename){
     const dirname = path.dirname(moduleId);
     const absolutePath = path.join(dirname, filename);
      return exec(absolutePath);
  }
  fn(require.exports);
  return exports
}
Copy the code

Note: Modules [moduleId] above is actually a string if we get it from our previous data structure, but we need it to be executed as a function. So let’s modify the direct file transfer module code a little bit here for your convenience.

const fileToModule = function (path) {
  console.log("path:",path)
  const fileContent = fs.readFileSync(path).toString();
  return {
    id: path,
    dependencies: getDependencies(fileContent),
    code: function(require.exports) {
      eval(fileContent.toString())   // See what's inside here. Use eval. It's a function declaration, not a string.}}; };Copy the code

It is not convenient to execute a string, so we consider declaring code as a function that contains the contents of the module and executes it through eval. But we don’t need to do that when we’re writing to a file, just for convenience.

3.3 Write the packaged file to the specified file

Ok, so far we have implemented the two most important parts of a module packer: the collection of modules, and the module’s execution functions. The final complete code is as follows:

const fs = require("fs");
const path = require("path");

// Convert files to module objects
const fileToModule = function (path) {
  const fileContent = fs.readFileSync(path).toString();
  return {
    id: path,
    dependencies: getDependencies(fileContent),
    code: function (require.exports) {
      eval(fileContent.toString()); }}; };// Get all dependencies for the module
function getDependencies(fileContent) {
  let reg = /require\(['"](.+?) ['"]\)/g;
  let result = null;
  let dependencies = [];
  while ((result = reg.exec(fileContent))) {
    dependencies.push(result[1]);
  }
  return dependencies;
}

// Convert all modules and their dependencies into module objects
function createGraph(filename) {
  let module = fileToModule(filename);
  let queue = [module];

  for (let module of queue) {
    const dirname = path.dirname(moduleId);
    module.dependencies.forEach((relativePath) = > {
      const absolutePath = path.join(dirname, relativePath);
      const child = fileToModule(absolutePath);
      queue.push(child);
    });
  }
  let modules = {};
  queue.forEach((item) = > {
    modules[item.id] = item.code;
  });
  return modules;
}

let modules = createGraph("./index.js");

// Execute the module function
const exec = function (moduleId) {
  const fn = modules[moduleId];
  let exports = {};
  const require = function (filename) {
    const dirname = path.dirname(moduleId);
    const absolutePath = path.join(dirname, filename);
    return exec(absolutePath);
  };
  fn(require.exports);
  return exports;
};
Copy the code

Let’s start packing from the entry file and see if we can get the same results as WebPack.

exec("./index.js");  // Output: Haiyingsitan Albert is making webpack
Copy the code

We can see that we have successfully achieved the same results as WebPack and successfully implemented module packaging. The next thing we need to do is package our module and generate it into a file.

function createBundle(modules){
  let __modules = "";
  for (let attr in modules) {
    __modules += `"${attr}":${modules[attr]}, `;
  }
  const result = `(function(){
    const modules = {${__modules}}; const exec = function (moduleId) { const fn = modules[moduleId]; let exports = {}; const require = function (filename) { const dirname = path.dirname(moduleId); const absolutePath = path.join(dirname, filename); return exec(absolutePath); }; fn(require, exports); return exports; }; exec("./index.js"); `}) ();
  fs.writeFileSync("./dist/bundle3.js", result);
}
Copy the code

The createBundle function is used to write the packaged file to a separate file. We can take a look at the generated file after packaging:

(function () {
  // Set of modules
  const modules = {
    "./index.js": function (require.exports) {
      let action = require("./action.js").action;
      let name = require("./name.js").name;
      let message = `${name} is ${action}`;
      console.log(message);;
    },
    "action.js": function (require.exports) {
      let action = "making webpack";
      exports.action = action;;
    },
    "name.js": function (require.exports) {
      let familyName = require("./family-name.js").name;
      exports.name = `${familyName}Albert ';;
    },
    "family-name.js": function (require.exports) {
      exports.name = "haiyingsitan";; }};const exec = function (moduleId) {
    const fn = modules[moduleId];
    let exports = {};
    const require = function (filename) {
      const dirname = path.dirname(moduleId);
      const absolutePath = path.join(dirname, filename);
      return exec(absolutePath);
    };
    fn(require.exports);
    return exports;
  };
  // the entry function executes
  exec("./index.js"); }) ()Copy the code

We can see that the packaged file is basically the same as the webPack packaged file. (Note: currently, only custom modules can be imported, and the built-in path cannot be imported. Therefore, if you want to test the normal execution of the packaged file, please manually add the import of path at the top of the file.)

Fourth, further optimization

4.1 Problems existing in using regex to match require

So far, we have been able to implement packaged generation of modules, but there are still some problems, as I mentioned earlier in the 2.2.1 implementation of getting all dependencies of the current module. We use /require\([‘”](.+?). [‘”]\)/g matches the introduction of require. However, if there is something in the file that matches this re but is not used for import. Such as:

const str = 'require(' write casually ')';
const str = /require\(['"](.+?) ['"]\)/g;
console.log(re.exec(str))// This also matches correctly
Copy the code

We found that the string above also matches our re correctly, but this string is not a require introduction. However, it will be treated as an import, resulting in an error. It could be argued that we could write a better regex to distinguish more cases, but the best regex won’t be compatible with all cases, so is there any way to completely correctly distinguish require used for introduction from other require? We can refer to Webpack to see how it can identify correctly. The key is to use Babel.

4.2 introduced the Babel

aboutbabelThe principle of what we can look for other articles. Just to keep in mind that the heart of Babel isResolution (parse).Translation (transform).Generate (generate)These three steps are shown in the figure below.

By parsing the code into an abstract syntax tree (AST), we can then operate on the nodes we want, convert to a new AST, and then regenerate into new code. This may sound complicated, but we don’t cover the underlying principles of Babel. We just apply its transformation capabilities, so we don’t need to dig too deeply. You can see how to convert code to AST in AST Explore. Take this code as an example:

let action = require("./action.js");
Copy the code

We can find that after Babel converts the above code into AST, we can accurately obtain the type of require node as CallExpression, the name of the node is require, and the parameter value is./action.js. In this way, require, which is used as a reference, can be correctly distinguished from require, which is used as a value or variable. Therefore, we need to modify the implementation of the function that gets the dependency:

Modify before:

function getDependencies(fileContent) {
  let reg = /require\(['"](.+?) ['"]\)/g;
  let result = null;
  let dependencies = [];
  while ((result = reg.exec(fileContent))) {
    dependencies.push(result[1]);
  }
  return dependencies;
}
Copy the code

Revised:

function getDependencies(filePath) {
  let result = null;
  let dependencies = [];
  const fileContent = fs.readFileSync(filePath).toString();
  // parse
  const ast = parse(fileContent, { sourceType: "CommonJs" });
  // transform
  traverse(ast, {
    enter: (item) = > {
      if (
        item.node.type === "CallExpression" &&
        item.node.callee.name === "require"
      ) {
        const dirname = path.dirname(filePath);
        dependencies.push(path.join(dirname, item.node.arguments[0].value));
        console.log("dependencies", dependencies); }}});return dependencies;
}
Copy the code

After obtaining ast through Babel’s parse, we find whether the type of each node is CallExpression and whether the name of the node is require. If both are satisfied, it means that the require is a function used to introduce modules. Then we can store its parameters in an array as dependencies.

4.3 Solve the problem of interdependence between modules

We know that modules can refer to each other, for example the name.js module introduced the family-name.js module. In the family.js module, name.js module is introduced. As shown below:

Name. Js module

let familyName = require("./family-name.js").name;    // The family-name.js module is introduced
exports.name = `${familyName}Albert ';
Copy the code

Family – name. Js module

const name1 = require("./name.js");    // The family-name.js module is introduced
exports.name = "haiyingsitan";
Copy the code

This can cause problems. Since we generated module objects in 2.1.3 putting all modules into a collection. If there is a dependency, convert it to a module and add it to the module collection. Because of interdependence, the module family.js is first added to the module, and then name.js is added to the module object. Then name.js relies on family.js and needs to add duplicate family.js modules, which will cause the module collection to loop indefinitely.

function createGraph(filename) {
  let module = fileToModule(filename);
  let queue = [module];
  
  for (let module of queue) {
    const dirname = path.dirname(module.id);
    module.dependencies.forEach((relativePath) = > {
      const absolutePath = path.join(dirname, relativePath);
       // See here, dependencies are constantly created
      const child = fileToModule(absolutePath);
      queue.push(child);
    });
  }
    // This is an array. Convert to object
  let modules = {}
  queue.forEach((item) = > {
    modules[item.id] = item.code;
  })
  return modules;
}
Copy the code

As shown in the figure below: the result is a collection of modules with repeated modules name.js and family.js, causing the loop to never end.

We can find that the root of this problem is to constantly add duplicate modules to the module collection, so we can determine whether it is a duplicate module before adding, if it is, do not add to it, so as to avoid the continuous cycle. The implementation is as follows:

function createGraph(filename) {
  let module = fileToModule(filename);
  let queue = [module];

  for (let module of queue) {
    const dirname = path.dirname(module.id);
    module.dependencies.forEach((relativePath) = > {
      const absolutePath = path.join(dirname, relativePath);
      // Look here to see if the module already exists in the module collection
      const result = queue.every((item) = > {
        returnitem.id ! == absolutePath; });if (result) {
          // If no, add it directly
        const child = fileToModule(absolutePath);
        queue.push(child);
      } else {
          // Exist to terminate this loop
        return false; }}); }let modules = {};
  queue.forEach((item) = > {
    modules[item.id] = item.code;
  });
  return modules;
}
Copy the code

Five, the summary

Ok, so far we’ve been able to implement a simple WebPack wrapper. The final code looks like this:

const fs = require("fs");
const path = require("path");
const {parse} = require("@babel/parser");
const traverse = require("@babel/traverse").default;

/ / 1. The shell
const fileToModule = function (path) {
  const fileContent = fs.readFileSync(path).toString();
  return {
    id: path,
    dependencies: getDependencies(path),
    code: `function (require, exports) {
      ${fileContent}; } `}; };// 2. Obtain dependencies
function getDependencies(filePath) {
  let result = null;
  let dependencies = [];
  const fileContent = fs.readFileSync(filePath).toString();
  // parse
  const ast = parse(fileContent, { sourceType: "CommonJs" });
  // transform
  traverse(ast, {
    enter: (item) = > {
      if (
        item.node.type === "CallExpression" &&
        item.node.callee.name === "require"
      ) {
        const dirname = path.dirname(filePath);
        dependencies.push(path.join(dirname, item.node.arguments[0].value));
        console.log("dependencies", dependencies); }}});return dependencies;
}
// 3. Put all dependencies into a collection
function createGraph(filename) {
  let module = fileToModule(filename);
  let queue = [module];

  for (let module of queue) {
    const dirname = path.dirname(module.id);
    module.dependencies.forEach((relativePath) = > {
      const absolutePath = path.join(dirname, relativePath);
      console.log("queue:",queue);
      console.log("absolutePath:",absolutePath);
      const result = queue.every((item) = > {
        returnitem.id ! == absolutePath; });if (result) {
        const child = fileToModule(absolutePath);
        queue.push(child);
      } else {
        return false; }}); }let modules = {};
  queue.forEach((item) = > {
    modules[item.id] = item.code;
  });
  return modules;
}

let modules = createGraph("./index.js");
// 4. Execute module
const exec = function (moduleId) {
  const fn = modules[moduleId];
  let exports = {};
  const require = function (filename) {
    const dirname = path.dirname(moduleId);
    const absolutePath = path.join(dirname, filename);
    return exec(absolutePath);
  };
  fn(require.exports);
  return exports;
};
// exec("./index.js");
// 5. Write files
function createBundle(modules){
  let __modules = "";
  for (let attr in modules) {
    __modules += `"${attr}":${modules[attr]}, `;
  }
  const result = `(function(){
    const modules = {${__modules}}; const exec = function (moduleId) { const fn = modules[moduleId]; let exports = {}; const require = function (filename) { const dirname = path.dirname(moduleId); const absolutePath = path.join(dirname, filename); return exec(absolutePath); }; fn(require, exports); return exports; }; exec("./index.js"); `}) ();
  fs.writeFileSync("./dist/bundle3.js", result);
}

createBundle(modules);

Copy the code

We can see that the final implementation process is actually:

  • Shell, convert files into modules
  • Gets the dependencies for each module
  • Form all modules into a large collection of modules
  • Execute module functions
  • Written to the file

Through the above analysis, implementing a simple WebPack module wrapper from scratch step by step is far easier and more impressive than looking at the WebPack source code yourself. Of course, our packagers are not fully functional, for example, we currently do not support built-in import, ES6 syntax conversion, CSS, etc. But these are all things we can do step by step. What is really important is that we are no longer completely ignorant of how a packager like WebPack works (after all, we already implement the same functionality) and that we can only add functionality to it if we want to explore it further. Finish scattering flowers.

Vi. Reference materials

  1. AST Explore
  2. babel-parse
  3. Hand-written simple module packer
  4. mini-pack