What is Webpack

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

Two, the principle analysis

First we made a prototype of a package file.

Let’s assume that there are two JS modules. Here we assume that these two modules are es5 modules based on the CommomJS standard.

Let’s leave the syntax and modularity specification transformation for a moment.

Our goal is to package these two modules into a file that can be run in the browser. This file is 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 there are no exports or require methods in the browser, so there will always be an error.

We need to emulate the exports object and require method

1. Simulate exports

First we know that we will use fs.readFilesync () to read js files when nodeJS is packaged. In this case, the js file will be a string. And if you want to run code in a string there are two methods called New Function and Eval.

In this case, we use the efficient eval.

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 this code is to bind a method from a module to an exports object. Since variables are declared in submodules, we use a self-running function to encapsulate them in order not to pollute the global environment.

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

2. Simulate the require function

The require function simply loads the corresponding module based on the file name provided.

Let’s first look at what we would do if we only had 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 we have fixed the module, we only need to make a few changes. We can arrange the file names and code strings of all modules into a key-value table and load different modules according to the file names 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 file generated by the real WebPack also needs to add dependencies between modules.

It’s called a Dependency Graph.

Something like this.

{
  "./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 convert es6 syntax to ES5 syntax in advance.

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

The next step is to implement the function.

Iii. Functional 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.

  • Process 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 module’s analysis is equivalent to parsing the file code string that is read. This step is actually consistent with the high-level language compilation process. You need to parse the module into an abstract syntax tree AST. We do this with Babel/Parser.

Abstract Syntax Tree (AST) In computer science, or Syntax Tree for short, is an Abstract representation of the Syntax structure of source code. It represents the syntactic structure of a programming language in the form of 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

  • Compile with 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");

function getModuleInfo(file) {
  // Read the file
  const body = fs.readFileSync(file, "utf-8");

  // Convert the AST syntax tree
  const ast = parser.parse(body, {
    sourceType: "module".// specifies the ES module to parse
  });

  // Dependency 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; }});// Convert from ES6 to ES5
  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

The running results are as follows:

! [image-20210507152817221](/Users/xiaran/Library/Application Support/typora-user-images/image-20210507152817221.png)

2. Collect dependencies

The function developed in the previous step can be used to parse a single module. In this step, we need to develop a function to recursively resolve the dependencies starting from the entry module. Finally, the dependencies are formed into a 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;
}

/** * Get dependencies *@param {*} temp 
 * @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 functions and dependency diagrams we just wrote to output the final package.

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"></script>
Copy the code

Ok tuition.

If you are interested, you can think about how to load the CSS file or the image base64 Vue SFC. Vue.

🔥 source

Wechat 🔍 search and follow the “front-end bus”, reply ‘webpack’ to get ‘vue’ source code