Analyze the files generated by packaging

The content of the generated file is divided into two parts: fixed template and dependency graph (module path and corresponding chunk)

** Fixed templates (patched) ** define some methods used in WebPack, and almost all packaging files will have the same content.

The focus is on the dependency graph, which includes the module path and the corresponding chunk.

We will now simulate webPack to generate a bundle file based on the file and configuration, so we will first solve the two problems of how to obtain the module path and generate the corresponding chunk.

We can know which module to start the packaging task according to the entry configuration item in the configuration. The core work of the packaging task is to compile the module.

All you have to do after you get the module is see if there are dependencies? Get a compilation of dependent paths and modules, and output chunk.

Create a basic template

Overall project Catalog:

  1. Create a new project and create a SRC folder in the project. Add index.js and other.js.

Index. Js:

import { str } from "./other.js";
console.log(str);
Copy the code

Other. Js:

export const str = "bundle";
Copy the code
import { a } from "./a.js";
export const str = "bundle" + a;
Copy the code

a.js:

export const a = "a";
Copy the code
  1. Create webpack.config.js in the project root directory:
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  entry: "./src/index.js".output: {
    filename: "[name].js".path: path.resolve(__dirname, "./dist")},mode: "development".plugins: [new HtmlWebpackPlugin()]
};
Copy the code
  1. Create a lib folder in your project root directory and create webpack.js:
module.exports = class Webapck {
  constructor(options) {
    // Read configuration file information
    console.log(options);
    this.entry = options.entry;
    this.output = options.output;
  }
  // entry function: implements compilation
  run() {
    this.parser(this.entry);
  }
  /** Compile function: implement specific compile * analyze whether there are dependencies, obtain the dependency path * compile the module to generate chunk */
  parser(){}};Copy the code
  1. Create bundle.js in the project root directory:
const webpack = require("./lib/webpack.js");
const config = require("./webpack.config.js");
// Create an instance of WebPack and pass the configuration to the instance to compile
new webpack(config).run();
Copy the code

Third, to achieve specific compilation

  1. Use compilation functions to implement concrete compilation.
  2. Compiler function implementation:
  • Analyze whether there is a dependency and obtain the dependency path.
  • The compilation module generates chunk
  1. Return: module path, module dependency, chunk
  2. Ideas:
  • To find the statements in the file that introduce dependencies, use@babel/parserParse the file contents intoAST treeWhen the node type of the statement is"ImportDeclaration"The dependency path exists on the nodesource.valueIn the field. use@babel/traverseValue of the node type is"ImportDeclaration"Gets the dependency path and stores it.
  • Compile module to generate chunk: use@babel/coreIn the plug-intransformFromAstConvert the AST tree to JS.
  1. Overall code:
const fs = require("fs");
const path = require("path");
const BabelParser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { transformFromAst } = require("@babel/core");

module.exports = class Webapck {
  constructor(options) {
    // Read configuration file information
    // console.log(options);
    this.entry = options.entry;
    this.output = options.output;
  }
  // entry function: implements compilation
  run() {
    this.parser(this.entry);
  }
  /** Compile function: implement specific compile * analyze whether there are dependencies, obtain the dependency path * compile the module to generate chunk */
  parser(modulePath) {
    // Get the file contents
    const content = fs.readFileSync(modulePath, "utf-8");
    // Convert content to AST tree to generate content types in the form of modules
    const ast = BabelParser.parse(content, { sourceType: "module" });

    // Save path dependency
    const dependencies = {};
    traverse(ast, {
      ImportDeclaration({ node }) {
        const newPath =
          "." +
          path.sep +
          path.join(path.dirname(modulePath), node.source.value);
        console.log(newPath); dependencies[node.source.value] = newPath; }});// Compile the AST into js
    const { code } = transformFromAst(ast, null, {
      presets: ["@babel/preset-env"] / / the plugin
    });
    console.log(code);
    // Return the module path, module dependencies, and chunk
    return{ modulePath, dependencies, code }; }};Copy the code

4. Collect all dependencies and sort them into a relational map

Above we get the path, module dependencies, and chunk of the incoming file from the parser method, but we also need to get the information about the dependency file of the file.

  1. Collect all dependencies in the run method and turn them into the required relationship graph: path: {dependencies, code}.

  2. Implementation:

  • Loop to get all the entry dependencies, compile the dependencies, get their path, dependency, chunk, add them to the dependency array;
  • After obtaining all dependencies, iterate to modify the required relationship graph.

3. Overall code:

// entry function: implements compilation
run() {
    const moduleParserInfo = this.parser(this.entry);
    console.log(moduleParserInfo);
    // Collect all dependencies
    this.modulesInfo.push(moduleParserInfo);

    // Loop over all entry dependencies, compile the dependencies, get their path, dependencies, and chunks, and add them to the modulesInfo array
    for (let i = 0; i < this.modulesInfo.length; i++) {
      const dependencies = this.modulesInfo[i].dependencies;
      if (dependencies) {
        for (let j in dependencies) {
          this.modulesInfo.push(this.parser(dependencies[j])); }}}// console.log(this.modulesInfo);
    // Data type conversion: convert to path: {dependency, code}
    const obj = {};
    this.modulesInfo.forEach(item= > {
      obj[item.modulePath] = {
        dependencies: item.dependencies,
        code: item.code
      };
    });
    // Generate the bundle file
    this.bundleFile(obj);
}
Copy the code
  1. The contents of the relationship graph

5. Generate bundle files

  1. Generate the bundle file in the bundleFile method.

  2. Implementation:

  • The generated file uses Node.jsfs.writeFileSync(bundlePath, content, "utf-8")Method, need to provide the package path and package content.
  • The package path is based on the configuration filewebpack.config.jsIn theoutputObtained by the configuration item.
  • The packaged content includes the patched content and the relationship graph generated in the RUN method. The code in the diagram is not working because it is missingrequireandexportsMethod, so patch these two methods.
  • parserIn the methodcodeIn therequireThe dependency path introduced is the path of the relative entry module. At this time, our code is in the dist folder, so the dependency path should be changed to the path under the relative project root node. So there are in the detected coderequireWhen writing anewRequireMethod to replace it and execute the code. There will be during code executionExports moduleSo let’s set oneExports objectThe execution of the code will gradually changeExports moduleAdded to theExports object, and finally returnsExports object.

3. Overall code:

// Generate the bundle file
bundleFile(obj) {
    // Synthesize the address of the package file from the output configuration item in the configuration
    const bundlePath = path.join(this.output.path, this.output.filename);
    // Serialize the relationship graph
    const dependenciesInfo = JSON.stringify(obj);
    // The contents of the package
    const content = '(function(modulesInfo) {// Function require(modulePath) {// Define a new require: convert the path of the relative entry module to the path of the relative project root node function newRequire(relativePath) { return require(modulesInfo[modulePath].dependencies[relativePath]) } const exports = {}; (function(require, code){ eval(code) })(newRequire, modulesInfo[modulePath].code) console.log(exports) return exports; } require('The ${this.entry}')
    })(${dependenciesInfo}) `;
    // Generate the file
    fs.writeFileSync(bundlePath, content, "utf-8");
}
Copy the code