The output
First create a new project and install dependencies
npm init -y
npm install --save-dev webpack
npm install --save-dev webpack-cli
Copy the code
Create new files SRC /index.js and SRC /hello.js with index.js as the default entry file:
const sayHello = require('./hello')
console.log(sayHello('nick'))
Copy the code
hello.js
module.exports = function(name) {
return 'hello' + name
}
Copy the code
Run NPX webpack –mode=development from the command line to open the dist/main.js compilation file:
*/ /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ "./src/hello.js": /*! * * * * * * * * * * * * * * * * * * * * * *! * \! *** ./src/hello.js ***! \**********************/ /***/ ((module) => { eval("module.exports = function(name) {\r\n return 'hello' + name\r\n}\n\n//# sourceURL=webpack://commonJS/./src/hello.js?" ); /***/ }), /***/ "./src/index.js": /*! * * * * * * * * * * * * * * * * * * * * * *! * \! *** ./src/index.js ***! \**********************/ /***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { eval("const sayHello = __webpack_require__(/*! ./hello */ \"./src/hello.js\")\r\nconsole.log(sayHello('nick'))\n\n//# sourceURL=webpack://commonJS/./src/index.js?" ); / /}) / * * * * * * * * * /}); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule ! == undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; / * * * * * * /} / / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / / / / / / / * * * * * * startup /******/ // Load entry module and return exports /******/ // This entry module can't be inlined because the eval devtool is used. /******/ var __webpack_exports__ = __webpack_require__("./src/index.js"); / * * * * * * * * * * * * / / /}) ();Copy the code
There’s actually an IIFE wrapped around it (call function expression now)
(() => { var __webpack_modules__ = ({... }) var __webpack_module_cache__ = {}; function __webpack_require__(moduleId){... } var __webpack_exports__ = __webpack_require__("./src/index.js"); }) ()Copy the code
That’s the core code
- The packaging result of webpack is an IIFE, called WebPack Bootstrap
- Webpack_modules is a module-loading function that defines two functions, which are our packaged files:
./src/hello.js:(() => {})
and./src/index.js:(() => {})
- Defines the Webpack_module_cache cache object
- The webpack_require function takes the path of an entry file as an argument and determines whether the parameter is in the cache object. If so, it returns the value in the cache object. If not, write the parameter to the cache object first and then create a new one
module
Object and executewebpack_modules
Function, which actually calls webpack_modules./src/index.js:(() => {})
The working process
Entry - options start
First webPack reads the configuration file in the projectwenpack.config.js
Or get the necessary parameters from shell statements, which is how Webapck receives business information from within.The run is instantiated
Compiler initializes the Compiler object with the parameters obtained in the previous step, loads the configured plug-in, and executes the object’srun
Method starts compilingentry
Determine entry: Locate all entry files according to the entry in the configurationmake
Compile module: from the entry file, call all configuredloader
Translate the module, recursively find the module that the module depends on,build module
Complete module compilation: After using loader to translate all modules above, obtain the final content of each module after translation and the dependencies between them.seal
Output resources are assembled into chunks containing multiple modules according to the dependency between the entry and modules, and then each chunk is converted into a separate file and added to the output list.emit
Output complete: After determining the output content, determine the output path and file name based on the configuration, and write the file content to the system file
Abstract syntax tree
In computer science, Abtract Syntax Tree (AST) is an abstract representation of the Syntax structure of source code. It represents the syntax structure of a programming language in the form of a tree, each node in the tree represents a structure and source code
The syntax is abstract because it does not represent every detail that occurs in real grammar.
The purpose of WebPack’s conversion of files to AST is to make it easy for developers to extract key information from module files so that we know exactly what the developer has written and can analyze and extend it based on that writing.
Can code on https://esprima.org/demo/parse.html# parsed into the AST tree.
var answer = 6 * 7;
Copy the code
{
"type": "Program"."body": [{"type": "VariableDeclaration"."declarations": [{"type": "VariableDeclarator"."id": {
"type": "Identifier"."name": "answer"
},
"init": {
"type": "BinaryExpression"."operator": "*"."left": {
"type": "Literal"."value": 6."raw": "6"
},
"right": {
"type": "Literal"."value": 7."raw": "Seven"}}}]."kind": "var"}]."sourceType": "script"
}
Copy the code
The compiler and compilation
Compiler and Compilation are two of the most important concepts in the core principles of WebPack. They are the basis for understanding how WebPack works, loaders, and plug-ins.
- Compiler object: Its instance contains the complete WebPack configuration, and there is only one compiler instance globally, so it is like the skeleton or nerve center of WebPack. When the plug-in is instantiated, it receives a Compiler object that provides access to the internal environment of the WebPack.
- Compilation object: When Webapck is running in development mode, a new compilation object is created whenever a file change is detected. This object contains information about the current module resources, build resources, changed files, and so on. That is, all build data generated during the build process is stored on this object, which controls every part of the build process. The object also provides a number of event callbacks for plug-ins to extend.
The construction process of Webpack is controlled by compiler flow and code parsing is done through compilation. When developing the plug-in, we can get all the content associated with the WebPack main environment, including the event hooks, from the Compiler object
Both the Compiler object and the compilation object inherit from the Tapable library, which exposes all event-related publish-subscribe methods. The tapable library based on event flow in Webpack not only ensures the order of plug-ins, but also makes the whole system more extensible.
Hand write a simple Webpack
Project initialization
mkdir wpk
npm init -y
Copy the code
Create the files SRC /index.js and SRC /greeting.js for our business code to be packaged.
Create the compiler file lib/compiler.js, build the module output file, lib/index.js instantiates the Compiler class, pass in configuration parameters, and lib/parser.js is responsible for parsing.
Create the configuration file wpk.config.js
const path = require("path");
global.filename = path.join(__dirname,'./src')
module.exports = {
entry: path.join(__dirname, "./src/index.js"),
output: {
path: path.join(__dirname, "./dist"),
filename: "bundle.js",}};Copy the code
Here the entry and exit are defined
At the same time our business code
src/index.js
import { greeting } from "./greeting.js"; Document. Write (greeting(" god covers earth tiger "));Copy the code
src/greeting.js
Export function greeting(name) {return "password:" + name; }Copy the code
The required dependencies of this project
package.json
"Dependencies" : {" @ Babel/preset - env ":" ^ 7.15.6 ", "Babel - core" : "^ 6.26.3", "Babel - preset - env" : "^ 1.7.0", "Babel - traverse by" : "^ 6.26.0", "Babylon" : "^ 6.18.0"}Copy the code
And babelrc.
{
"presets": [
"@babel/preset-env"
]
}
Copy the code
parsing
The project initialization is complete, and the parse.js writing is completed first.
const fs = require("fs");
const babylon = require("babylon");
module.exports = {
getAST: (path) = > {
const source = fs.readFileSync(path, "utf-8");
return babylon.parse(source,{
sourceType:'module'})}};Copy the code
Using Babylon, parse the file into an AST tree.
Babylon is the JavaScript parser used in Babel.
Babylon generates an AST based on the format of Babel AST. It is based on the ESTree specification with the following differences (which can now be removed using the ESTree plug-in) :
- Literal symbols are replaced with strings, numbers, booleans, Null, and regular expressions
- Property symbols are replaced with ObjectProperty and ObjectMethod
- Method definitions are replaced with class methods
- instructionandGrammar blockthe
directives
Field contains additionalinstructionandInstruction character set- Properties of class methods, object properties, and object method value properties in function expressions are forced/into the main method node.
Create a new test file, SRC /test.js
const path = require("path"); const { getAST} = require('./parser'); let ast = getAST(path.join(__dirname,'.. /src/index.js')) console.log(ast)Copy the code
With the generated AST visible on the command line, use babel-traverse to parse all of the file’s dependencies
getDependencies: (ast) = > {
const dependencies = [];
traverse(ast, {
ImportDeclaration: ({ node }) = >{ dependencies.push(node.source.value); }});return dependencies;
},
Copy the code
Next, convert the ES6 code to ES5
transform: (ast) => {
const { code } = transformFromAst(ast, null, {
presets: ["env"],
});
return code;
},
Copy the code
There are three main methods in parser.js:
getAST
: Parses the obtained module content intoAST
The syntax treegetDependencies
: traversalAST
To collect the dependencies usedtransform
: Take the acquiredES6
theAST
Converted intoES5
compile
Next, start writing Compiler.js and create the Compiler class to do the following
- receive
wpk.config.js
Configure the parameters and initialize thementry
,output
- Open the compilation
run
Methods. Handles building blocks, collecting dependencies, output files, and so on. buildModule
Methods. Mainly used to build modules (byrun
Method call)emitFiles
Methods. Output files (also byrun
Method call)
const path = require("path");
const fs = require("fs");
module.exports = class Compiler {
constructor(options) {
const { entry, output } = options;
this.entry = entry;
this.output = output;
this.modules = [];
}
// Enable compilation
run() {}
// Build module related
buildModule(filename, isEntry) {
// filename: indicates the filename
// isEntry: whether it is an entry file
}
// Output file
emitFiles(){}};Copy the code
New Compiler(options).run(); , initialize entry, output, and modules in the constructor.
Start building modules
run() {
const entryModule = this.buildModule(this.entry, true);
console.log(entryModule)
this.modules.push(entryModule);
this.modules.map((_module) = > {
_module.dependencies.map((dependency) = > {
this.modules.push(this.buildModule(dependency));
});
});
console.log(this.modules);
}
buildModule(filename, isEntry) {
let ast;
if (isEntry) {
ast = getAST(filename);
} else {
// const absolutePath = path.join(process.cwd(), './src',filename);
const absolutePath = path.join(global.filename, filename);
ast = getAST(absolutePath);
}
return {
filename, // File name
dependencies: getDependencies(ast), // Dependency list
transformCode: transform(ast), // The converted code
};
}
Copy the code
The buildModule function parses the file into an AST based on the name of the file passed in and returns the built Module, which is essentially an object containing the filename, dependency list, and transformed code.
In the run function, we pass the entry file path defined in the configuration file to buildModule, then store the module built by the entry file into Modules, and start recursively iterating through all the dependencies in the entry file and building them into modules.
Once you have all the modules lists, output the lists as a file. Iterating through the modules list, turning all modules into an anonymous function named with the file name, and passing in an IIFE that mimics the webpack4 output file
emitFiles() {
const outputPath = path.join(this.output.path, this.output.filename);
let modules = "";
this.modules.map((_module) = > {
modules += ` '${_module.filename}' : function(require, module, exports) {${_module.transformCode}}, `;
});
const bundle = `
(function(modules) {
function require(fileName) {
const fn = modules[fileName];
const module = { exports:{}};
fn(require, module, module.exports)
return module.exports
}
require('The ${this.entry}') ({})${modules}})
`;
// console.log(bundle)
fs.writeFileSync(outputPath, bundle, "utf-8");
}
Copy the code
The IIFE output from Webpack4
(function(modules) {// installedModules = {}; Function __webpack_require__(moduleId) {if(installedModules[moduleId]) {return installedModules[moduleId].exports; } var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); module.l = true; return module.exports; } __webpack_require__(0); })([ /* 0 module */ (function(module, exports, __webpack_require__) { ... }), /* 1 module */ (function(module, exports, __webpack_require__) { ... }), /* n module */ (function(module, exports, __webpack_require__) { ... })]);Copy the code
webpack
Wrap all the modules (easily understood as files) in a function, pass in the default parameters, and put all the modules into an array calledmodules
And is represented by the index of the arraymoduleId
.- will
modules
Pass in a self-executing function that contains ainstalledModules
Already loaded modules and a module loading function, finally loading the entry module and returning. __webpack_require__
Check whether the module is loadedinstalledModules
Check whether it is loaded. If it is loaded, return directlyexports
Data, pass without loading the modulemodules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
Execute the module and willmodule.exports
To return.
If you open dist/bundle.js, you can see the packaged file, create dist/index.html, and see the output content of the page.
At present simple Webpack has been written, the fly in the ointment is that the authentic Webpack running commands are Webpack, we also here to improve the next. Add it in package.json
"bin": {
"wpk": "lib/index.js" // Import file
},
Copy the code
Run NPM link in the root directory to link the specified file globally, WPK can do the packaging. If you want to use NPM run build, you can also add “build”: “WPK” to the script in package.json.
The complete code