An overview of
Get the configuration Start the WebPack and perform the build based on the configuration information
- Start from the entry module analysis, which dependencies, conversion code;
- Recursive analysis of other dependency modules, which dependencies, conversion code;
- Generate bundles that can be executed on the browser side.
Implement bundle.js yourself
Module analysis
Read the entry file and analyze the code:
let fs = require("fs");
let build = entryFile= > {
let content = fs.readFileSync(entryFile, "utf-8");
};
build("./index.js");
Copy the code
Generate AST abstract syntax tree
Here we recommend using @babel/ Parser, a babel7 utility that helps us parse internal syntaxes, including ES6, and returns an AST abstract syntax tree
/ / install @ Babel/parser
yarn add @babel/parser -D
let fs = require("fs");
const parser = require("@babel/parser");
let build = entryFile= > {
let content = fs.readFileSync(entryFile, "utf-8");
const Ast = parser.parse(content, {
sourceType: "module"
});
};
build("./index.js");
Copy the code
Parser. parse converts the contents of our index.js file into an AST abstract syntax tree
//AST Abstract syntax tree
{
type: 'File'.start: 0.end: 155.loc: SourceLocation {
start: Position { line: 1.column: 0 },
end: Position { line: 6.column: 0}},errors: [].program: Node {
type: 'Program'.start: 0.end: 155.loc: SourceLocation { start: [Position], end: [Position] },
sourceType: 'module'.interpreter: null.body: [ [Node], [Node], [Node], [Node] ],
directives: []},comments: []}Copy the code
Get the dependencies in the file
Then we can traverse all the incoming modules according to the analysis results in AST, but it is more troublesome. Here we recommend @babel/traverse, a module recommended by Babel, to help us deal with it.
/ / install @ Babel/parser
yarn add @babel/traverse -D
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
let build = entryFile= > {
let content = fs.readFileSync(entryFile, "utf-8");
const Ast = parser.parse(content, {
sourceType: "module"
});
const dependencies = {}; // You can keep both relative path and root path information
traverse(ast, {
ImportDeclaration({ node }) {
// dependencies.push(node.source.value); Relative paths
// Parses the AST abstract syntax tree to return the absolute path of dependent modules.
const dirname = path.dirname(entryFile);
const newPath = ". /"+ path.join(dirname, node.source.value); dependencies[node.source.value] = newPath; }}); }; build("./index.js");
Copy the code
The dependent module in index.js is hello.js in the same directory,
import { say } from "./hello.js";
let str = "hello" + say("webpack");
document.body.innerHTML = `<h1>${str}</h1>`;
console.log("hello" + say("webpack"));
/ / value dependencies:
{ './hello.js': './src\\hello.js' }
Copy the code
To code that the browser can recognize
To process the code into browser-runnable code, use @babel/core and @babel/preset-env to convert the AST syntax tree into appropriate code.
// Install @babel/preset-env and @babel/core. yarn add @babel/preset-env -D yarn add @babel/core -D
const { transformFromAst } = require("@babel/core");
const { code } = transformFromAst(Ast, null, {presets: ["@babel/preset-env"]});
Copy the code
Put the above three methods in a parsing file:
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { transformFromAst } = require("@babel/core");
module.exports = {
// The analysis module gets the AST
getAst: (fileName) = > {
let content = fs.readFileSync(fileName, "utf-8");
let ast = parser.parse(content, {
sourceType: "module"});return ast;
},
// Get dependencies
getDependencies: (ast, fileName) = > {
const dependencies = {}; // You can keep both relative path and root path information
traverse(ast, {
ImportDeclaration({ node }) {
// dependencies.push(node.source.value); Relative paths
const dirname = path.dirname(fileName);
const newPath = ". /"+ path.join(dirname, node.source.value); dependencies[node.source.value] = newPath; }});console.log(dependencies);
return dependencies;
},
// Convert the code
getCode: (ast) = > {
const { code } = transformFromAst(ast, null, {
presets: ["@babel/preset-env"]});returncode; }};Copy the code
Returned information
{
fileName: './src/index.js'.dependencies: { './hello.js': './src\\hello.js' },
code: '"use strict"; \n' +
'\n' +
'var _hello = require("./hello.js"); \n' +
'\n' +
'var str = "hello" + (0, _hello.say)("webpack"); \n' +
'document.body.innerHTML = "".concat(str, "
"); \n' +
'console.log("hello" + (0, _hello.say)("webpack")); '
}
Copy the code
Introduce these three methods in the Complier build file
const fs = require("fs");
const path = require("path");
const { getAst, getDependencies, getCode } = require("./parser");
module.exports = class Complier {
constructor(options) {
this.entry = options.entry;
this.output = options.output;
this.modules = [];
}
run() {
const info = this.build(this.entry);
this.modules.push(info);
for (let i = 0; i < this.modules.length; i++) {
const item = this.modules[i];
const { dependencies } = item;
if (dependencies) {
// Generate all module information into modules by iterating through all modules
for (let j in dependencies) {
this.modules.push(this.build(dependencies[j])); }}}// Convert the data structure
const obj = {};
this.modules.forEach((item) = > {
obj[item.fileName] = {
dependencies: item.dependencies,
code: item.code,
};
});
// Generate the code file
this.file(obj);
}
// Introduce these three methods in the build method, playing with an object containing the file path, dependencies, and executable code
build(fileName) {
let ast = getAst(fileName);
let dependencies = getDependencies(ast, fileName);
let code = getCode(ast);
return {
fileName,
dependencies,
code,
};
}
file(code) {
// Get the output... /dist/main.js
const filePath = path.join(this.output.path, this.output.filename);
const newCode = JSON.stringify(code);
const bundle = `(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('The ${this.entry}') //./src/index.js
})(${newCode}) `;
fs.writeFileSync(filePath, bundle, "utf-8"); }};Copy the code
Generate a webpack.js file that mimics the webpack command to generate a bundle file that can be executed on the browser side
const Complier = require("./lib/complier");
const options = require("./webpack.config.js");
new Complier(options).run();
Copy the code
The following is the webpack.config.js file
const path = require("path");
module.exports = {
entry: "./src/index.js".output: {
filename: "main.js".path: path.resolve(__dirname, "./dist")}};Copy the code
Run the node webpack.js command to generate the main.js file
(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("./src/index.js"); //./src/index.js({})"./src/index.js": {
dependencies: { "./hello.js": "./src\\hello.js" },
code:
'"use strict"; \n\nvar _hello = require("./hello.js"); \n\nvar str = "hello" + (0, _hello.say)("webpack"); \ndocument.body.innerHTML = "".concat(str, "
"); \nconsole.log("hello" + (0, _hello.say)("webpack")); ',},"./src\\hello.js": {
dependencies: {},
code:
'"use strict"; \n\nObject.defineProperty(exports, "__esModule", {\n value: true\n}); \nexports.say = say; \n\nfunction say(str) {\n return str; \n}',}});Copy the code
At this point, the whole Process of Webpack is completed. The core of Webpack is to closely associate each module through the AST abstract syntax tree to generate a tightly combined file for the browser to execute.