preparation

In WebPack, we found that we can use esModule’s modular syntax naturally in configuration. Have you ever wondered? How did he do it? Let’s explore how WebPack actually parses the packaged ESModule syntax.

Before we start, we need some basic knowledge of Node, because if we want to implement webpack-like functions, we must use some modules of Node, such as path module, such as fs module, etc., which are the basic modules of Node. Next, we need some modules of Babel. Give us some transformations such as Babel/Parser module, **@babel/traverse module, Babel /core module, etc. Next, we will introduce each of these modules

Module is introduced

path

NodeJS Path object, used to process directory objects, improve development efficiency

We also use it a lot when configuring WebPack. Its common use is in our directory conversion, for example:

// const path = require('path'); // concatenate these links console.log(path.join('/Users'.'node/path'.'.. / '.'join.js'));
Copy the code

fs

The FS module can perform some read and write operations on files

In Webpack, we need to escape the syntax, so we need to read and write files, and it’s very easy to use

// import module const fs = require('fs'); // Read the file,readFileSync(filename) const content = fs.readFileSync(filename,'utf-8');
Copy the code

babel/parser

Babel/Parser is a module of Babel that parses code and transforms long AST, or abstract syntax trees

It’s very simple to use

Const parser = require(const parser = require('@babel/parser'); The first argument represents our code, and the second argument is a set of configurationssourceConst ast = parser.parse(content, {sourceType: 'module'
	});
Copy the code

babel/traverse

Babel/Traverse can parse the entire ESModule code by parsing dependencies in the code based on information in the abstract syntax tree

It’s very simple to use

// introduce module const traverse = require('@babel/traverse').default; // The first argument takes the abstract syntax tree, and the second argument is an object with traverse(ast, {ImportDeclaration({node}) {}}) of dependencies we need to find;Copy the code

babel/core

Babel’s core module, which gives me our code to be turned into browser-aware code

It’s not that difficult to use

// insert module const Babel = require('@babel/core'); // Use the transformFormAst method // The first parameter is ast // the last parameter is conversion rule, conversion to what const code = babel.transformFromAst(ast, null, {presets: ["@babel/preset-env"]});Copy the code

babel/preset-env

Babel /preset-env, which is configured in. Babelrc to tell us which rule to use to transform our ES6 syntax,

Scaffolding construction

First we need to create a new webpack-like directory with SRC and index.js entry files, but we need to create a new webpack.js directory to replace the webpack interface as follows:

To explore the principle of

With all the preparatory work done, we’ll start handcrafting a Webpack that parses the syntax for packaging modularity

1, find the entry file, parsing the entry file syntax

First we need to find the entry file parse the entry file js syntax

// import node module const fs = require('fs');
const path = require('path'); Const parser = require(const parser = require('@babel/parser'); Const webpack=(filename)=>{const content= fs.readfilesync (filename,'utf-8') // Print content console.log(content) // Parse into ast tree using parser const ast = parser.parse(content, {sourceType: 'module'}); Console. log(ast)} webpack('./src/index.js')
Copy the code

In the code above, we can get the AST abstract syntax tree. Let’s see what it looks like first

Then we need to get the dependency, how should we deal with it?

Const ast = parser.parse(content, {sourceType: 'module'}); // console.log(ast.program.body) const dependencies = {}; Traverse (ast, {// if you need to find the dependencies to put into the object, ImportDeclaration({node}) {// Use node's path module, Const dirname = path.dirname(filename); // Splice the path to the root of the local project file const newFile ='/'+ path.join(dirname, node.source.value); Dependencies [node.source.value] = newFile; }}); console.log(dependencies)Copy the code

In fact, it is very simple, we just need to reference the Babel module, in the callback to do a little bit of processing, and get the print result

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

His conversion looks like this

2. Analyze the dependent code and complete the package of the whole project

Us in writing at the top of the webpck method, we found him in addition to parse the entrance to the code, in fact all depend on the code can also be used the same routines parse out, and stored in one place, so we have to give him into a general method, and add a function to recursive call analytical method in this function, analyze the dependent files, Store it in an array so we can get all the converted files. So cut the crap and get to work

Const entryModule = webpack(entry); const entryModule = webpack(entry) const entryModule = webpack(entry); Const graphArray = [entryModule]; // Store parsed objects into an array const graphArray = [entryModule]; Grapharray.length = grapharray.length = grapharray.lengthfor(leti = 0; i < graphArray.length; I ++) {// Get every const item in the array = graphArray[I]; Const {dependencies} = item;if(dependencies) {
            //for inGo through the objectfor(let j in< span style = "box-sizing: border-box! Important; Grapharray. push(webpack(dependencies[j])); }}}}Copy the code

We define a separate method to parse all dependencies and store them in an array, which uses the magic of loop times as the length of the array to parse out the entire dependency graph. As shown in the figure below, we find that all dependencies are in this array

// Create an empty object to store converted code const graph = {}; Graph [item.filename] = {dependencies: item.dependencies, code: item.code } }); console.log(graph)Copy the code

3. Package to generate merge dependency graphs and merge them into browser-executable code

In the previous two steps, we used two methods to get the final parsed code, and we used another method to get the final generated code in the initial stage and directly to the code

Const generateCode=(entry)=>{// Const graph = json.stringify (DependenceMap(entry)) must be returned as a string; // When we look at the code after parsing, we find that it has the syntax of require, so we need to simulate a similar method when exporting to prevent errorsreturn `
    
    (function(graph){// The browser emulates the require methodfunctionRequire (module) {require(module) {require(module) {require(module) {require(module) {require(module)function localRequire(relativePath) {
                returnrequire(graph[module].dependencies[relativePath]); Var exports = {}; var exports = {}; // Add a closure to prevent the impression that external variables have been defined (function(require, exports, code){// execute codeeval(code)
            })(localRequire, exports, graph[module].code);
            returnexports; }; // execute the require syntax require('${entry}')
    })(${graph}); `}Copy the code

In the code above, we found that we could execute the entire dependency graph without polluting the global environment with a custom require syntax. Let’s take a look at the exported result

The last

First, attach the completion code

// import node module const fs = require('fs');
const path = require('path'); Const parser = require(const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const babel = require('@babel/core'); Const webpack=(filename)=>{const content= fs.readfilesync (filename,'utf-8') // Print content console.log(content) // Parse into ast tree using parser const ast = parser.parse(content, {sourceType: 'module'}); // console.log(ast.program.body) const dependencies = {}; Traverse (ast, {// if you need to find the dependencies to put into the object, ImportDeclaration({node}) {// Use node's path module, Const dirname = path.dirname(filename); // Splice the path to the root of the local project file const newFile ='/'+ path.join(dirname, node.source.value); Dependencies [node.source.value] = newFile; }}); Const {code} = babel.transformFromAst(ast, null, {presets: const {code} = babel.transformFromAst(ast, null, {presets: ["@babel/preset-env"]}); // Export the information usedreturn{ filename, dependencies, Code}} const entryModule = webpack(entry) const entryModule = webpack(entry); Const graphArray = [entryModule]; // Store parsed objects into an array const graphArray = [entryModule]; Grapharray.length = grapharray.length = grapharray.lengthfor(leti = 0; i < graphArray.length; I ++) {// Get every const item in the array = graphArray[I]; Const {dependencies} = item;if(dependencies) {
            //for inGo through the objectfor(let j in< span style = "box-sizing: border-box! Important; Grapharray. push(webpack(dependencies[j])); }}} //console.log(graphArray) // Create an empty object to store converted code const graph = {}; Graph [item.filename] = {dependencies: item.dependencies, code: item.code } }); // console.log(graph)returngraph; } const generateCode=(entry)=>{// Const graph = json.stringify (DependenceMap(entry)); // When we look at the code after parsing, we find that it has the syntax of require, so we need to simulate a similar method when exporting to prevent errorsreturn `
    
    (function(graph){// The browser emulates the require methodfunctionRequire (module) {require(module) {require(module) {require(module) {require(module) {require(module)function localRequire(relativePath) {
                returnrequire(graph[module].dependencies[relativePath]); Var exports = {}; var exports = {}; // Add a closure to prevent the impression that external variables have been defined (function(require, exports, code){// execute codeeval(code)
            })(localRequire, exports, graph[module].code);
            returnexports; }; // execute the require syntax require('${entry}')
    })(${graph});
    `
}

const code=generateCode('./src/index.js')
 console.log(code)
Copy the code

After we have seen the complete packaging process of an ES module, I believe you have already understood it. Anyway, I have solved many puzzles before after the study. Moreover, after we have mastered the complete process, we have also mastered the basic principle of Webpack 7 and 8. Webpack is all about adding lorders and plugins to the process of converting code. So if you want to go to dachang, is not the heart of a little more confidence!

That’s it. Dell Lee, it’s great to stand on the shoulders of giants.