As a front-end "siege lion", Webpack is all too familiar. Webpack can do too many things. It can package all resources (including JS, TS, JSX, images, fonts, CSS, etc.) and place them in dependencies. , allowing you to reference dependencies to use resources according to your needs. Webpack has done an excellent job of translating multiple file resources on the front end and analyzing complex module dependencies. We can also customize the loader and load our own resources freely. So how does Webpack implement packaging? Come and take a look today.
1. What is require?
When it comes to require, the first thing that comes to mind may be import. Import is a syntax standard of es6.
– require is a runtime call, so require can theoretically be used anywhere in the code;
– import is a compile-time call, so it must be placed at the beginning of the file. ;
When we use Webpack to compile, we will use babel to translate import into require. In CommonJS, there is a global method require(), which is used to load modules. AMD and CMD also use the require method to reference.
For example:
var add = require('./a.js');In simple terms,
add(1,2)
requires is actually a function, and the referenced ./a.js
is just a parameter of the function.
2. What are exports?
Here we can think of exports as an object. You can see the specific usage of MDN export.
Let's first look at the code structure after our packaging. We can find that require and exports will appear after packaging.
Not all browsers can execute require exports. You must implement require and exports yourself to ensure the normal operation of the code. The packaged code is a self-executing function. The parameters have dependency information and the code of the file. The executed function body executes the code through eval.
The overall design drawing is as follows:
Step 1: Write our configuration file.
The configuration file configures our packaged entry and packaged exit output to prepare for the subsequent generated files.
const path = require("path"); module.exports = { entry: "./src/index.js", output: { path: path.resolve(__dirname, "./dist"),//The file address output after packaging requires an absolute path, so path is required filename:"main.js" }, mode: "development"
Step 2: The overall idea of module analysis
: In summary, it is to use the fs file to read the entry file and obtain the path of the import-dependent file through AST. If the dependent file still has dependencies, keep recursing until the dependency analysis is clear. , maintained in a map.
Detailed breakdown : Some people may wonder why AST is used because AST is born with this function. Its ImportDeclaration can help us quickly filter out the import syntax. Of course, it is also possible to use regular matching. After all, the file is a string after being read. By writing The awesome regex is useful for getting file dependency paths, but it’s not elegant enough.
index.js file
import { str } from "./a.js"; console.log(`${str} Webpack`)
a.js file
import { b} from "./b.js" export const str = "hello"
b.js file
export const b="bbb"
module analysis : use AST's @babel/parser to convert the string read from the file into an AST tree, and @babel/traverse for syntax Analyze and use ImportDeclaration to filter out imports and find file dependencies.
const content = fs.readFileSync(entryFile, "utf-8"); const ast = parser.parse(content, { sourceType: "module" }); const dirname = path.dirname(entryFile); const dependents = {}; traverse(ast, { ImportDeclaration({ node }) { //Filter out imports const newPathName = "./" + path.join(dirname, node.source.value); dependents[node.source.value] = newPathName; } }) const { code } = transformFromAst(ast, null, { presets: ["@babel/preset-env"] }) return { entryFile, dependents, code }
The results are as follows:
Use recursion or loop to import files one by one for dependency analysis. Note here that we use for loop to analyze all dependencies. The reason why loops can analyze all dependencies is that the length of modules changes. When there are dependencies.modules .push new dependencies, modules.length will change.
for (let i = 0; i < this.modules.length; i++) { const item = this.modules[i]; const { dependents } = item; if (dependents) { for (let j in dependents) { this.modules.push(this.parse(dependents[j])); } } }
Step 3: Write the WebpackBootstrap function + generate the output file.
Write the WebpackBootstrap function : The first thing we need to do here is the WebpackBootstrap function. After compilation, the import of our source code will be parsed into require. Since the browser does not recognize require, then we must declare it first. After all, require is a method. When writing functions, you also need to pay attention to scope isolation to prevent variable pollution. We also need to declare the exports in our code to ensure that the exports already exist when the code is executed.
Generate the output file : We have already written the address of the generated file in the configuration file, and then use fs.writeFileSync to write it to the output folder.
file(code) { const filePath = path.join(this.output.path, this.output.filename) const newCode = JSON.stringify(code); // Generate bundle file content const bundle = `(function(modules){ function require(module){ function pathRequire(relativePath){ return require(modules[module].dependents[relativePath]) } const exports={}; (function(require,exports,code){ eval(code) })(pathRequire,exports,modules[module].code); return exports } require('${this.entry}') })(${newCode})`; // WebpackBoostrap // Generate file. Put it in the dist directory fs.writeFileSync(filePath,bundle,'utf-8') }
Step 4: Analyze the execution sequence
We can run the packaged result in the browser console. If it works normally, hello Webpack should be printed.
Through the above analysis, we should have a basic understanding of the general process of Webpack. Using AST to parse the code is just a way of this demonstration, not the real implementation of Webpack. Webpack has its own AST parsing method, which is ever-changing. The Webpack ecosystem is very complete. Interested children can consider the following three questions: