directory

  1. Webpack profile
  2. The principle of analysis
  3. Function implementation
  4. conclusion
  5. The author video: www.bilibili.com/video/BV1dV…

Introduction to WebPack

Webpack is a packaging tool whose purpose is that all static resources can be packaged.

Two, principle analysis

Assume that there are two JS modules. Let’s assume that these two modules are composite CommomJS standard ES5 modules.

Syntax and modular specification conversions will be discussed later.

Our goal is to package these two modules into a file that can be run in the browser. This file is actually called bundle.js.

Such as

// index.js
var add = require('add.js').default
console.log(add(1 , 2))

// add.js
exports.default = function(a,b) {return a + b}
Copy the code

The main problem is that the browser doesn’t have an exports object or require method so it’s bound to report an error.

We need to emulate the exports object and require method

1. Mimic exports objects

First we know that if we use fs.readfilesync () to read the JS file while nodeJS is packed. In this case the JS file will be a string. If you need to run code in a string, there are two methods: New Function and Eval.

In this case, we chose eval, which is more efficient to perform.

exports = {}
eval('exports.default = function(a,b) {return a + b}') // Node file read after the code string
exports.default(1.3)

Copy the code

The result of running this code is to bind the methods in the module to exports objects. Since variables are declared in submodules, we use a self-executing function to encapsulate them so as not to pollute the world.

var exports= {},function (exports, code) {
	eval(code)
})(exports.'exports.default = function(a,b){return a + b}')
Copy the code

2. Simulate require function

The require function is relatively simple, which is to load the corresponding module according to the provided file name.

First let’s look at what we should do if we only have one fixed module.

function require(file) {
	var exports = {};
	(function (exports, code) {
		eval(code)
	})(exports.'exports.default = function(a,b){return a + b}')
  return exports
}
var add = require('add.js').default
console.log(add(1 , 2))

Copy the code

Now that the fixed module is complete, we need to make a few changes, and organize the file names and code strings of all modules into a key-value table to load different modules based on the filename passed in.

(function (list) {
  function require(file) {
    var exports = {};
    (function (exports, code) {
      eval(code); }) (exports, list[file]);
    return exports;
  }
  require("index.js"); ({})"index.js": ` var add = require('add.js').default console.log(add(1 , 2)) `."add.js": `exports.default = function(a,b){return a + b}`});Copy the code

One thing to note, of course, is that the bundle.js files generated by real WebPack also need to add dependencies between modules.

Dependency Graph

{
  "./src/index.js": {
    "deps": { "./add.js": "./src/add.js" },
    "code": "..."
  },
  "./src/add.js": {
    "deps": {},
    "code": "..."}}Copy the code

In addition, since most front-end programs are used to es6 syntax, you need to pre-convert es6 syntax to ES5 syntax.

To summarize the idea, Webpack packaging can be divided into the following three steps:

  1. Analysis rely on
  2. Turn ES6 ES5
  3. Replace exports and require

Now enter the function realization stage.

Three, function realization

Our goal is to package the following two interdependent ES6Modules into a single JS file (bundle.js) that can be run in the browser.

  • Processing modularization
  • Multi-module merge packaging – optimize network requests

/src/add.js

export default (a, b) => a + b 
Copy the code

/src/index.js

import add from "./add.js";
console.log(add(1 , 2))
Copy the code

1. Analysis module

The analysis module is divided into the following three steps:

The analysis of a module is equivalent to parsing a string of file code read. This step is consistent with the compilation process for a high-level language. The module needs to be parsed into an abstract syntax tree AST. We do this with Babel/Parser.

AST (Abstract Syntax Tree) in computer science, or Syntax Tree for short, is an Abstract representation of the syntactic structure of source code. It represents the syntactic structure of a programming language as a tree, with each node in the tree representing a structure in the source code. (astexplorer.net/)

yarn add @babel/parser
yarn add @babel/traverse
yarn add @babel/core
yarn add @babel/preset-env
Copy the code
  • Read the file
  • Collect rely on
  • Compilation and AST parsing
const fs = require("fs"); const path = require("path"); const parser = require("@babel/parser"); const traverse = require("@babel/traverse").default; const babel = require("@babel/core"); Const body = fs.readfilesync (file, "utF-8 "); function getModuleInfo(file) {const body = fs.readfilesync (file," utF-8 "); Const AST = parser.parse(body, {sourceType: "module", // indicates that we are parsing ES module}); // Rely on collection const deps = {}; traverse(ast, { ImportDeclaration({ node }) { const dirname = path.dirname(file); const abspath = "./" + path.join(dirname, node.source.value); deps[node.source.value] = abspath; }}); Const {code} = babel.transformFromAst(ast, null, {presets: ["@babel/preset-env"],}); const moduleInfo = { file, deps, code }; return moduleInfo; } const info = getModuleInfo("./src/index.js"); console.log("info:", info);Copy the code

2. Collect dependencies

The function developed in the previous step can parse a module by itself. In this step, we need to develop a function to parse recursively from the entry module based on dependencies. Finally, form Dependency Graph.

@param {*} file * @returns */ function parseModules(file) {const entry = getModuleInfo(file); const temp = [entry]; const depsGraph = {}; getDeps(temp, entry); temp.forEach((moduleInfo) => { depsGraph[moduleInfo.file] = { deps: moduleInfo.deps, code: moduleInfo.code, }; }); return depsGraph; } @param {*} param1 */ function getDeps(temp, { deps }) { Object.keys(deps).forEach((key) => { const child = getModuleInfo(deps[key]); temp.push(child); getDeps(temp, child); }); }Copy the code

3. Generate the bundle file

In this step we need to combine the execution function and dependency diagram we just wrote and output the final package file.

function bundle(file) {
  const depsGraph = JSON.stringify(parseModules(file));
  return `(function (graph) {
        function require(file) {
            function absRequire(relPath) {
                return require(graph[file].deps[relPath])
            }
            var exports = {};
            (function (require,exports,code) {
                eval(code)
            })(absRequire,exports,graph[file].code)
            return exports
        }
        require('${file}')
    })(${depsGraph})`;
}


!fs.existsSync("./dist") && fs.mkdirSync("./dist");
fs.writeFileSync("./dist/bundle.js", content);
Copy the code

Finally, you can write a simple test program to test the results.

<script SRC ="./dist/bundle.js"></scriptCopy the code

reference

Description: this article is written well, but, and I wrote two years ago that Webpack principle can be integrated once, will be better, temporarily no time. Pure reproduced:

  • Webpack compilation process

conclusion

  1. Analysis rely on
  2. Turn ES6 ES5
  3. Replace exports and require
  • The author video: www.bilibili.com/video/BV1dV…