preface
Webpack has been updated to version 5.X, but for some mid-level engineers, webPack proficiency is limited to configuration. In an era of rapid front-end development, simply knowing how to use webPack without understanding how it works can hinder their career development. This article is to take you into a more in-depth understanding of webPack packaging principles. And realize its packaging function.
When I first learned how to configure Webpack, I also wanted to read and have a look at the files packaged with Webpack, but I mistakenly thought it would be difficult to understand, so I gave up directly. Now, looking back, it is not difficult. To sum it up in one sentence, use nodeJS ‘FS module to read file contents and create a’ path-code block ‘map, write it into a JS file, and execute it using eval.
What does WebPack look like when it’s packaged
We only look at the code packaged with WebPack in the development environment, you can see what the packaging of Webpack is very intuitive, because in the production environment, Webpack will default to enable code compression, Treeshaking and other optimization methods, increasing the difficulty of understanding.
Under the SRC file//index.js
import { cute } from "./cute.js";
import add from "./add.js";
const num1 = add(1.2);
const num2 = cute(100.22);
console.log(num1, num2);
//add.js
const add = (a, b) = > {
return a + b;
};
export default add;
//cute.js
import getUrl from "./utils/index.js";
export const cute = (a, b) = > {
return a - b;
};
getUrl();
// utils/index.js
const getUrl = () = > {
const url = window.location.pathname;
return url;
};
export default getUrl;
Copy the code
We started packing using the index.js file as the entry file
//webpack.config.js
const path = require("path");
module.exports = {
mode: "development".entry: "./src/index.js".output: {
filename: "bundle.js".path: path.resolve(__dirname, "build"),},module: {
rules: [{test: /\.js$/,
exclude: /node_modules/,
include: path.resolve(__dirname, "./src"),
use: [{loader: "babel-loader".options: {
presets: ["@babel/preset-env"],},},],},},};Copy the code
Packaged results (core:
(() = > {
// webpackBootstrap
"use strict";
var __webpack_modules__ = {
"./src/add.js": (__unused_webpack_module, __webpack_exports__, __webpack_require__) = > {
eval(
'__webpack_require__.r(__webpack_exports__); \n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "default": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ }); \nvar add = function add(a, b) {\n return a + b; \n}; \n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (add); \n\n//# sourceURL=webpack:///./src/add.js? '
);
},
"./src/cute.js": (__unused_webpack_module, __webpack_exports__, __webpack_require__) = > {
eval(
'__webpack_require__.r(__webpack_exports__); \n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "cute": () => /* binding */ cute\n/* harmony export */ }); \n/* harmony import */ var _utils_index_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils/index.js */ "./src/utils/index.js"); \n\nvar cute = function cute(a, b) {\n return a - b; \n}; \n(0,_utils_index_js__WEBPACK_IMPORTED_MODULE_0__.default)(); \n\n//# sourceURL=webpack:///./src/cute.js? '
);
},
"./src/index.js": (__unused_webpack_module, __webpack_exports__, __webpack_require__) = > {
eval(
'__webpack_require__.r(__webpack_exports__); \n/* harmony import */ var _cute_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./cute.js */ "./src/cute.js"); \n/* harmony import */ var _add_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./add.js */ "./src/add.js"); \n\n\nvar num1 = (0,_add_js__WEBPACK_IMPORTED_MODULE_1__.default)(1, 2); \nvar num2 = (0,_cute_js__WEBPACK_IMPORTED_MODULE_0__.cute)(100, 22); \nconsole.log(num1, num2); \n\n//# sourceURL=webpack:///./src/index.js? '
);
},
"./src/utils/index.js": (__unused_webpack_module, __webpack_exports__, __webpack_require__) = > {
eval(
'__webpack_require__.r(__webpack_exports__); \n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "default": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ }); \nvar getUrl = function getUrl() {\n var url = window.location.pathname; \n return url; \n}; \n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (getUrl); \n\n//# sourceURL=webpack:///./src/utils/index.js? '); }};var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
if (__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].exports;
}
var module = (__webpack_module_cache__[moduleId] = {
exports: {},}); __webpack_modules__[moduleId](module.module.exports, __webpack_require__);
return module.exports;
}
(() = > {
__webpack_require__.d = (exports, definition) = > {
for (var key in definition) {
if( __webpack_require__.o(definition, key) && ! __webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true.get: definition[key], }); }}}; }) (); (() = > {
__webpack_require__.o = (obj, prop) = >
Object.prototype.hasOwnProperty.call(obj, prop); }) (); (() = > {
__webpack_require__.r = (exports) = > {
if (typeof Symbol! = ="undefined" && Symbol.toStringTag) {
Object.defineProperty(exports.Symbol.toStringTag, { value: "Module" });
}
Object.defineProperty(exports."__esModule", { value: true}); }; }) (); __webpack_require__("./src/index.js"); }) ();Copy the code
Just a quick interpretation
You can see that each file takes the current relative pathkey
.A function for value
I put in this one__webpack_modules__
Object, each of whichvalue
The eval function executes code in the current file.webpack.x
A series of methods are used to achieveimport
andexport
Function for exporting and importing variables, which we won’t go into too much here, but we’ll have our own methods later.
When the packaged JS file is executed, it will be executed from"./src/index.js"
thiskey
The corresponding value starts executing the code. Let’s look at the process:
Begin to implement
Read the code from the entry
// mypack.js
const fs = require("fs");
const getCode = (entry) = > {
const code = fs.readFileSync(entry, "utf8");
console.log(code)
}
getCode('./src/index.js')
Copy the code
node mypack
afterWe’ve got the code for the import file, and the next step is to get the dependency files from the import file and get all the imported file paths.
Access to rely on
Fetching dependencies means gathering the file paths to which each file import is imported by traversing the AST’s @babel/traverse library to find the import nodes.
const fs = require("fs");
const parser = require("@babel/parser"); / / convert the ast
const traverse = require("@babel/traverse").default; / / traverse the ast
const getCode = (entry) = > {
const code = fs.readFileSync(entry, "utf8");
const ast = parser.parse(code, {
sourceType: "module"}); traverse(ast, {ImportDeclaration(p) {
const importPath = p.get("source").node.value;
console.log(importPath)
},
});
}
getCode('./src/index.js')
Copy the code
So we get the file path that the entry file depends on, and then we recursively get the code for all the files. Because what we can get here is the relative path between the referenced file and the referenced file, but in our method fs needs to use relative to us when reading the filemypack.js
Which is the path ofsrc
Directory path, so we can useRelative paths
:SRC path
I’m going to do a mapping, and I’m going to get the converted code in the current path, and I’m going to get one{relative path :{dependency :{relative path: SRC path}, code :{... }}}
Format object.
const fs = require("fs");
const path = require('path');
const parser = require("@babel/parser"); / / convert the ast
const traverse = require("@babel/traverse").default; / / traverse the ast
const getCode = (entry) = > {
const code = fs.readFileSync(entry, "utf8");
const dirname = path.dirname(entry); // Get the directory where the current file resides
const ast = parser.parse(code, {
sourceType: "module"});const deps = {};
traverse(ast, {
ImportDeclaration(p) {
const importPath = p.get("source").node.value;
const asbPath = ". /" + path.join(dirname, importPath); // Get the path relative to the SRC directorydeps[importPath] = asbPath; }});// Get the converted code in the current entry file
const { code:transCode } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]});console.log(entry,deps,transCode)
};
getCode("./src/index.js");
Copy the code
And so we haveEntry file './ SRC /index.js'
theThe entry path,Depend on the fileandcode. We can then recursively retrieve information about all files through the dependencies of the entry file.
Recursively retrieves information about all dependencies
const fs = require("fs");
const path = require('path');
const parser = require("@babel/parser"); / / convert the ast
const traverse = require("@babel/traverse").default; / / traverse the ast
const getCode = (entry) = > {
const code = fs.readFileSync(entry, "utf8");
const dirname = path.dirname(entry); // Get the directory where the current file resides
const ast = parser.parse(code, {
sourceType: "module"});const deps = {};
traverse(ast, {
ImportDeclaration(p) {
const importPath = p.get("source").node.value;
const asbPath = ". /" + path.join(dirname, importPath); // Get the path relative to the SRC directorydeps[importPath] = asbPath; }});// Get the converted code in the current entry file
const { code:transCode } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]});return { entry, code, deps };
};
const recurrenceGetCode = (entry) = > {
const entryInfo = getCode(entry); // Get all information about the entry file
const allInfo = [entryInfo];
/ * allInfo now only the entry file information, for the [{'. / SRC/index: {deps: {'. / express. Js' : '. / SRC/express. Js', '. / add. Js' : './src/add.js' }, code:"use strict...." } }] */We also need to take the information for cute. Js, add.js, and utils/index.js, and put it in allInfoconst recurrenceDeps = (deps,modules) = > {
Object.keys(deps).forEach(key= >{
const info = getCode(deps[key])
modules.push(info);
recurrenceDeps(info.deps,modules)
})
}
recurrenceDeps(entryInfo.deps,allInfo)
console.log(allInfo) // What do I have now
}
recurrenceGetCode("./src/index.js");
Copy the code
Take it and turn it into oneStructure of the map
:
const fs = require("fs");
const path = require('path');
const parser = require("@babel/parser"); / / convert the ast
const traverse = require("@babel/traverse").default; / / traverse the ast
const getCode = (entry) = > {
const code = fs.readFileSync(entry, "utf8");
const dirname = path.dirname(entry); // Get the directory where the current file resides
const ast = parser.parse(code, {
sourceType: "module"});const deps = {};
traverse(ast, {
ImportDeclaration(p) {
const importPath = p.get("source").node.value;
const asbPath = ". /" + path.join(dirname, importPath); // Get the path relative to the SRC directorydeps[importPath] = asbPath; }});// Get the converted code in the current entry file
const { code:transCode } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]});return { entry, code, deps };
};
const recurrenceGetCode = (entry) = > {
const entryInfo = getCode(entry); // Get all information about the entry file
const allInfo = [entryInfo];
/ * allInfo now only the entry file information, for the [{'. / SRC/index: {deps: {'. / express. Js' : '. / SRC/express. Js', '. / add. Js' : './src/add.js' }, code:"use strict...." } }] */We also need to take the information for cute. Js, add.js, and utils/index.js, and put it in allInfoconst recurrenceDeps = (deps,modules) = > {
Object.keys(deps).forEach(key= >{
const info = getCode(deps[key])
modules.push(info);
recurrenceDeps(info.deps,modules)
})
}
recurrenceDeps(entryInfo.deps,allInfo)
const webpack_modules = {};
allInfo.forEach(item= >{
webpack_modules[item.entry] = {
deps:item.deps,
code:item.transCode,
}
})
return webpack_modules;
}
const webpack_modules = recurrenceGetCode("./src/index.js");
// webpack_modules is what we want in the end
Copy the code
Printing webpack_modules looks like this
{
'./src/index.js': {deps: {},code:"..."
},
'./src/cute.js': {deps: {},code:"..."}... }Copy the code
Write all dependency information to a JS file
Now we need to write the resulting object into a file, but we can’t write it directly, because the object structure cannot be written into a JS file, we need to convert it to a string, and we can only use json.stringify to get a JSON string, JSON string is not recognized in the JS file. And how? Webpack is a self-executing function (()=>{})(), so can we pass it as a parameter to a self-executing function, and then write it into the JS file? The answer is yes.
// Omit the above code, just look down
const webpack_modules = recurrenceGetCode("./src/index.js");
const writeFunction = `((content)=>{
console.log(content)
})(The ${JSON.stringify(webpack_modules)}) `;
fs.writeFileSync("./exs.js", writeFunction);
Copy the code
Let’s look at the code for the generated exs.js file:
((content) = > {
console.log(content); ({})"./src/index.js": {
deps: { "./cute.js": "./src/cute.js"."./add.js": "./src/add.js" },
code:
'"use strict"; \n\nvar _cute = require("./cute.js"); \n\nvar _add = _interopRequireDefault(require("./add.js")); \n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nvar num1 = (0, _add["default"])(1, 2); \nvar num2 = (0, _cute.cute)(100, 22); \nconsole.log(num1, num2); ',},"./src/cute.js": {
deps: { "./utils/index.js": "./src/utils/index.js" },
code:
'"use strict"; \n\nObject.defineProperty(exports, "__esModule", {\n value: true\n}); \nexports.cute = void 0; \n\nvar _index = _interopRequireDefault(require("./utils/index.js")); \n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nvar cute = function cute(a, b) {\n return a - b; \n}; \n\nexports.cute = cute; \n(0, _index["default"])(); ',},"./src/utils/index.js": {
deps: {},
code:
'"use strict"; \n\nObject.defineProperty(exports, "__esModule", {\n value: true\n}); \nexports["default"] = void 0; \n\nvar getUrl = function getUrl() {\n var url = window.location.pathname; \n return url; \n}; \n\nvar _default = getUrl; \nexports["default"] = _default; ',},"./src/add.js": {
deps: {},
code:
'"use strict"; \n\nObject.defineProperty(exports, "__esModule", {\n value: true\n}); \nexports["default"] = void 0; \n\nvar add = function add(a, b) {\n return a + b; \n}; \n\nvar _default = add; \nexports["default"] = _default; ',}});Copy the code
[“./ SRC /index.js”].code. We’ll modify the code a little bit:
// Omit the above code, just look down
const webpack_modules = recurrenceGetCode("./src/index.js");
const writeFunction = `((content)=>{
const require = (path) => {
const code = content[path].code;
eval(code)
}
})(The ${JSON.stringify(webpack_modules)}) `;
fs.writeFileSync("./exs.js", writeFunction);
Copy the code
Add require function
Repackaged and running in a browser looks like this:Why did you report this error?Look at the 3 steps marked in the picture:
Start with the entry file./src/index.js
Code, the code runs torequire('./cute.js')
Re-execute whenrequire
Function,./cute.js
Passed as a parameter, content does not./cute.js
If the value of the key exists, the code in the key cannot be retrieved. At this point our dePS for each file comes in handy again, because there is a path map in DEPS, so we are executingrequire
Function to retrieve the corresponding key-value pair from the one currently executing codeSRC path
, the corresponding key in the content.Code continues to be modified:
// Omit the above code, just look down
const webpack_modules = recurrenceGetCode("./src/index.js");
const writeFunction = `((content)=>{
const require = (path) => {
const getSrcPath = (p) => {
const srcPath = content[path].deps[p];
return require(srcPath)
}
((require)=>{
eval(content[path].code)
})(getSrcPath)
}
require('./src/index.js')
})(The ${JSON.stringify(webpack_modules)}) `;
fs.writeFileSync("./exs.js", writeFunction);
Copy the code
After packaging forThis step may be a little tricky, but I’ll explain it step by step:
The require to./cute.js
Because the require function is passed to the implementation as an argumenteval
In a self-executing function, so naturally calledgetSrcPath
This is a function of thetagetSrcPath
Is executed from Contentpath
To find the correspondingSRC path
, path is./src/index.js
, so naturally from{ "./cute.js": "./src/cute.js", "./add.js": "./src/add.js" }
It’s taken out"./cute.js"
The corresponding"./src/cute.js"
After you get the path, pass it in as pathrequire
Function, and then call ((require) => { eval(content[path].code); })(getSrcPath);
Function, so what happens when you execute the code?
Add exports
exports
Undefined? That’s right, because js passesexport
The exported module is an object that is not present in the packaged code, so we need to use theWhen each file is executedDefine one manuallyexports
And to return it
// Omit the above code, just look down
const webpack_modules = recurrenceGetCode("./src/index.js");
const writeFunction = `((content)=>{
const require = (path) => {
const getSrcPath = (p) => {
const srcPath = content[path].deps[p];
return require(srcPath)
}
const exports = {};
((require)=>{
eval(content[path].code)
})(getSrcPath)
return exports;
}
require('./src/index.js')
})(The ${JSON.stringify(webpack_modules)}) `;
fs.writeFileSync("./exs.js", writeFunction);
Copy the code
Then there are no problems in execution!
Complete code snippet
const fs = require("fs");
const path = require("path");
const babel = require("@babel/core");
const parser = require("@babel/parser"); / / convert the ast
const traverse = require("@babel/traverse").default; / / traverse the ast
const getCode = (entry) = > {
const code = fs.readFileSync(entry, "utf8");
const dirname = path.dirname(entry);
const ast = parser.parse(code, {
sourceType: "module"});const deps = {};
traverse(ast, {
ImportDeclaration(p) {
const importPath = p.get("source").node.value;
const asbPath = ". /" + path.join(dirname, importPath); // Get the path relative to the SRC directorydeps[importPath] = asbPath; }});const { code: transCode } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]});return { entry, deps, transCode };
};
const recurrenceGetCode = (entry) = > {
const entryInfo = getCode(entry); // Get all information about the entry file
const allInfo = [entryInfo];
const recurrenceDeps = (deps, modules) = > {
Object.keys(deps).forEach((key) = > {
const info = getCode(deps[key]);
modules.push(info);
recurrenceDeps(info.deps, modules);
});
};
recurrenceDeps(entryInfo.deps, allInfo);
const webpack_modules = {};
allInfo.forEach((item) = > {
webpack_modules[item.entry] = {
deps: item.deps,
code: item.transCode,
};
});
return webpack_modules;
};
const webpack = (entry) = > {
const webpack_modules = recurrenceGetCode(entry);
const writeFunction = `((content)=>{
const require = (path) => {
const getSrcPath = (p) => {
const srcPath = content[path].deps[p];
return require(srcPath)
}
const exports = {};
((require,exports,code)=>{
eval(code)
})(getSrcPath,exports,content[path].code)
return exports;
}
require('./src/index.js')
})(The ${JSON.stringify(webpack_modules)}) `;
fs.writeFileSync("./exs.js", writeFunction);
};
webpack("./src/index.js");
Copy the code