# webpack **Repository Path**: typeof/webpack ## Basic Information - **Project Name**: webpack - **Description**: webpack 学习资料 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 10 - **Forks**: 0 - **Created**: 2021-05-29 - **Last Updated**: 2022-10-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### webpack 解析流程 > webpack 模块打包 - webpack可以将不同模块(esModule,commonjs,cmd,amd)机制的js或者其他文件打包(loader机制)整合在一起。并且保证它们之间引用正确,执行有序。利用打包我们就可以在开发的时候根据我们自己的业务自由划分文件模块,保证项目结构的清晰和可读性。 #### webpack模块解析流程 1. 读取`webpack`的配置参数; 2. 启动`webpack`,创建`Compiler`对象并开始解析项目; 3. 从入口文件`entry`开始解析文件关系找到其导入的依赖模块,递归遍历分析,形成依赖关系树; 4. 对不同文件类型的依赖模块文件使用对应的`Loader`进行编译,最终转为Javascript文件; 5. 整个过程中`webpack`会通过发布订阅模式,向外抛出一些`hooks`,而`webpack`的插件即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的。 #### 浅谈模块解析流程 1. 根据`源码文件夹`分析打包出来的结果文件 ``` ├── README.md ├── app // 源码 │ ├── index.js // 入口 │ └── reduce.js // 依赖文件 ├── dist │ ├── app.js │ └── index.html ├── package-lock.json ├── package.json └── webpack-demo.js ``` ```js // 打包结果 (function(graph){ function require(file){ function absRequire(relPath){ return require(graph[file].deps[relPath]) } var exports = {}; (function(require,exports,code){ eval(code); })(absRequire, exports, graph[file].code); return exports; } require('./app/index.js') })({ "./app/index.js":{ "deps": { "./reduce.js":"./app/reduce.js" }, "code":` var _reduce = _interopRequireDefault(require("./reduce.js")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } console.log(_reduce["default"])(10, 20, 30));` }, "./app/reduce.js":{ "deps":{}, "code":` Object.defineProperty(exports, "__esModule", {value: true }); exports["default"] = _default; function _default() { for (var _len = arguments.length, arg = new Array(_len), _key = 0; _key < _len; _key++) { arg[_key] = arguments[_key]; } return arg.reduce(function (val, item) { return val + item; }, 0); }` } }) ``` 2. 解析文件 - 源码 ```js import reducesum from "./reduce.js" console.log(reducesum(10,20,30)); ``` - 解析之后 ```js var _reduce = _interopRequireDefault(require("./reduce.js")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } console.log(_reduce["default"])(10, 20, 30)); ``` ```js const fs = require('fs'); const path = require('path'); // Babel中使用的JavaScript解析器 const parser = require('@babel/parser'); // 用来遍历和更新ast节点 const traverse = require('@babel/traverse').default; const babel = require('@babel/core'); function getModuleInfo(file){ // 读取文件内容得到body字符串 const body = fs.readFileSync(file, "utf8"); // 解析ast抽象语法树 const ast = parser.parse(body, { sourceType: "module" // 指示分析代码的模式。可以是"script", "module"或"unambiguous"之一。默认为"script"。 "unambiguous"将使@babel/parser尝试根据存在的ES6导入或导出语句进行猜测。带有ES6 import和export的文件被视为"module",否则是"script" }); // 收集依赖 const deps = {}; traverse(ast, { ImportDeclaration({node}){ const dirname = path.dirname(file); // 根据入口文件取到文件夹绝对路径 const depName = node.source.value; // 文件相对路径 const abspath = "./" + path.join(dirname, depName); // 拼接成绝对路径,方便以后读取文件内容 deps[depName] = abspath; } }) // 把 es6 转 es5 生成code const { code } = babel.transformFromAst(ast, null, { presets: ['@babel/preset-env'] }); const moduleInfo = {file, deps, code}; return moduleInfo; } getModuleInfo('./app/index.js'); //'入口文件' ``` 3. 根据入口文件,递归生成依赖关系树 ```js // 解析依赖 生成依赖 function parseModules(file){ const entry = getModuleInfo(file); // 获取入口文件信息 const temp = [entry]; //先放入入口文件 const depsGraph = {}; getDeps(temp, entry); //像数组中添加依赖模块 // 所有依赖模块按顺序遍历添加到依赖对象 temp.forEach((moduleInfo) => { depsGraph[moduleInfo.file] = { deps: moduleInfo.deps, code: moduleInfo.code } }) return depsGraph; } function getDeps(temp, {deps}){ // deps 当前模块依赖 {"./reduce.js":"./app/reduce.js"} Object.keys(deps).forEach((key) => { const child = getModuleInfo(deps[key]); // 获取依赖模块信息 temp.push(child); getDeps(temp, child); // 递归查找所有依赖 }) } parseModules('./app/index.js'); ``` - 通过以上方法得到以下依赖对象 ```js { "./app/index.js":{ "deps": { "./reduce.js":"./app/reduce.js" }, "code":` var _reduce = _interopRequireDefault(require("./reduce.js")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } console.log(_reduce["default"])(10, 20, 30));` }, "./app/reduce.js":{ "deps":{}, "code":` Object.defineProperty(exports, "__esModule", {value: true }); exports["default"] = _default; function _default() { for (var _len = arguments.length, arg = new Array(_len), _key = 0; _key < _len; _key++) { arg[_key] = arguments[_key]; } return arg.reduce(function (val, item) { return val + item; }, 0); }` } } ``` 4. 根据生成的依赖对象,让所有code脚本依次去执行 ```js (function(graph){ function require(file){ function absRequire(relPath){ return require(graph[file].deps[relPath]) } var exports = {}; (function(require,exports,code){ eval(code); // 重点 在这运行code code中去调用require })(absRequire, exports, graph[file].code); return exports; } require('./app/index.js') })( // 参看上面依赖对象 ) ``` #### loader 持续更新中...