Do you know how WebPack analyzes module dependencies? How do I compile ES6 code into browser executable code?

Project initialization

  • Creating a folder
mkdir bundler
cd bundler
Copy the code
  • Create documents Create the SRC folder in the bundler, to build the index in the SRC folder. Js, message. Js, word. Js. The content of the document is as follows:
// word.js
export const word="word";

// message.js
import {word} from "./word.js";
const message=`hello ${word}`;
export default message;

// index.js
import message from "./message.js";
console.log(message);
Copy the code

If you want to run index.js directly in the browser, of course you can’t, the browser does not recognize es6 syntax, we used to use webpack-like packaging tools to convert ES6 code into ES5 code, and then run directly in the browser.

Import file dependency analysis

Create a new Bundler file in the project root directory to implement the packaging process. The so-called WebPack compilation package is to convert the source code into browser-aware code through specific method functions

  • Define a module analysis function
const moduleAnalyser=(filename)=>{

}
moduleAnalyser("./src/index.js"); // the entry functionCopy the code
  • Here we use fs, a core module in Node.
const fs=require("fs");

const moduleAnalyser=(filename)=>{
    const content=fs.readFileSync(filename,"utf-8"); Console. log(content); } moduleAnalyser("./src/index.js"); // the entry functionCopy the code

Run the node command on the terminal

node bundler.js
Copy the code

This will output the contents of the index.js file

  • Resolving file dependencies

(1) Execute NPM init -y initialization

(2) Install a Babel module

npm install @babel/parser --save
Copy the code

(3) Use Parser

const fs=require("fs");
const parser=require("@babel/parser");

const moduleAnalyser=(filename)=>{
    const content=fs.readFileSync(filename,"utf-8"); Console. log(parser.parse(content,{parser.parse(parser.parse))sourceType:"module"
    }));
}

moduleAnalyser("./src/index.js"); // the entry functionCopy the code

Run the node command again, node bundler.js, to view the contents of the file. The output is called the AST, which describes the dependencies of the file.

Modify bundler

. const ast=parser.parse(content,{sourceType:"module"}) console.log(ast.program.body); .Copy the code

Executing the Node bundler.js command yields the following output


npm install @babel/traverse --save
Copy the code

(5) Use traverse

. Traverse (ast,{ImportDeclaration({node}){console.log(node)// Check node contents}})...Copy the code

Continue rewriting Bundler.js

const dependencies=[]; traverse(ast,{ ImportDeclaration({node}){ dependencies.push(node.source.value); }} console.log(dependenciesCopy the code

Continue rewriting Bundler.js

const dependencies={}; // becomes an object, where key is a dependent path and value is a relative dependent path. Traverse (ast,{ImportDeclaration({node}){const dirName =path.dirname(filename); //filename Indicates the folder path const newFile=". /"+path.join(dirname,node.source.value); dependencies[node.source.value]=newFile; }})Copy the code

(6) Install Babel /core conversion code

npm install @babel/core @babel/preset-env --save
Copy the code

(7) Conversion code

const { code }=babel.transformFromAst(ast,null,{
        presets:["@babel/preset-env"})// Convert ast console.log(code);Copy the code

Executing the Node bundler.js command yields the following output

Dependency analysis of the entry file is complete. The complete code is as follows:

const fs=require("fs");
const path=require("path");
const babel=require("@babel/core");
const parser=require("@babel/parser");
const traverse=require("@babel/traverse").default; // Default es module exports const moduleAnalyser=(filename)=>{const content= fs.readfilesync (filename,"utf-8"); Const ast=parser.parse(content,{sourceType:"module"}) const dependencies={}; traverse(ast,{ ImportDeclaration({node}){ const dirname=path.dirname(filename); //filename Indicates the folder path const newFile=". /"+path.join(dirname,node.source.value);
            dependencies[node.source.value]=newFile;
        }
    })
    const { code } = babel.transformFromAst(ast,null,{
        presets:["@babel/preset-env"})// Convert astreturn {
        filename,
        dependencies,
        code
    }
}

const moduleInfo=moduleAnalyser("./src/index.js"); // The entry function console.log(moduleInfo);Copy the code

Building dependency maps

A project cannot have only one file, which requires us to analyze the dependencies of the whole project, that is, to generate the dependency map.

  • Define methods for generating dependency graphs
const makeDependenciesGraph=(entry)=>{
    const entryModule=moduleAnalyser(entry);
    console.log(entryModule);
}
Copy the code
  • Starting at the entry, the dependencies are recursively analyzed through a loop
const makeDependenciesGraph=(entry)=>{
    const entryModule=moduleAnalyser(entry);
    const graphArray=[entryModule];
    for(leti=0; i<graphArray.length; i++){ const item=graphArray[i]; const { dependencies } = item; // Deconstruct dependenciesif(dependencies){
            for(let j in}} console.log(grapharray.push (moduleAnalyser(dependencies[j])); }Copy the code

Executing the Node bundler.js command yields the following output

  • Generate a dependent graph object with the following code:
const makeDependenciesGraph=(entry)=>{
    const entryModule=moduleAnalyser(entry);
    const graphArray=[entryModule];
    for(leti=0; i<graphArray.length; i++){ const item=graphArray[i]; const { dependencies } = item; // Deconstruct dependenciesif(dependencies){
            for(let j in< span style = "box-sizing: border-box; color: RGB (51, 51, 51); line-height: 21px; font-size: 14px! Important; word-break: break-all;" graphArray.forEach(item=>{ graph[item.filename]={ dependencies:item.dependencies, code:item.code } });return graph;
}
Copy the code

Generate browser-aware code

const generateCode=(entry)=>{
    const graph=JSON.stringify(makeDependenciesGraph(entry));
    return `
        (function(graph){
            function require(module){
                function localRequire(relativePath){
                    return require(graph[module].dependencies[relativePath])
                }
                var exports={};
                (function(require,exports,code){
                    eval(code)
                })(localRequire,exports,graph[module].code);
                return exports;
            };
            require('${entry}'); }) (${graph})
    `;
}
Copy the code

Executing the Node bundler.js command yields the following output

Bundler file complete code:

const fs=require("fs");
const path=require("path");
const babel=require("@babel/core");
const parser=require("@babel/parser");
const traverse=require("@babel/traverse").default; // Default es module exports const moduleAnalyser=(filename)=>{const content= fs.readfilesync (filename,"utf-8"); Const ast=parser.parse(content,{sourceType:"module"}) const dependencies={}; traverse(ast,{ ImportDeclaration({node}){ const dirname=path.dirname(filename); //filename Indicates the folder path const newFile=". /"+path.join(dirname,node.source.value);
            dependencies[node.source.value]=newFile;
        }
    })
    const { code }=babel.transformFromAst(ast,null,{
        presets:["@babel/preset-env"})// Convert astreturn {
        filename,
        dependencies,
        code
    }
}

const makeDependenciesGraph=(entry)=>{
    const entryModule=moduleAnalyser(entry);
    const graphArray=[entryModule];
    for(leti=0; i<graphArray.length; i++){ const item=graphArray[i]; const { dependencies } = item; // Deconstruct dependenciesif(dependencies){
            for(let j in< span style = "box-sizing: border-box; color: RGB (51, 51, 51); line-height: 21px; font-size: 14px! Important; word-break: break-all;" graphArray.forEach(item=>{ graph[item.filename]={ dependencies:item.dependencies, code:item.code } });return graph;
}

const generateCode=(entry)=>{
    const graph=JSON.stringify(makeDependenciesGraph(entry));
    return `
        (function(graph){
            function require(module){
                function localRequire(relativePath){
                    return require(graph[module].dependencies[relativePath])
                }
                var exports={};
                (function(require,exports,code){
                    eval(code)
                })(localRequire,exports,graph[module].code);
                return exports;
            };
            require('${entry}'); }) (${graph})
    `;
}

const code=generateCode("./src/index.js"); // the entry function console.log(code);Copy the code

This is the whole process of a WebPack code conversion and compilation. Continue learning!

The resources

  • Document the Parser module in Babel
  • Core module documentation in Babel