webpack(5)

243 阅读3分钟
webpackClass
	bundle 就是一个自执行的函数
	参数为 一个对象, key : 模块的路径, value : function 函数包裹, 内部使用eval 包含模块的主要内容
	webpack(options) 
	
/* 
(1)
读取配置的入口
  1 可以获取入口模块的路径
  2 入口模块的依赖
        模块的依赖 (依赖的路径)
            可以用递归的方式处理依赖模块
                获取依赖模块其对应的依赖
                依赖模块的内容进行处理  
        内容的处理  (处理后的代码)

        从而可以得到依赖图谱对象
        webpackbootstrap 函数
        生成文件
            文件存放的位置
            文件
读取配置的出口
    生成文件
        文件存放的位置
        文件的名称
*/
	执行函数, 模拟打包操作
		// 读取配置
const options = require("./webpack.config.js");
// 引入webpack
// 自己定义的webpack
const webpack = require("./lib/webpack1.js");
// webpack接收配置 启动入口函数,执行打包
// 自己定义的webpack 

new webpack(options).run();

// new webpack(options);
	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 = class webpack  {
    constructor(options) {
        const { entry, output}  = options;
        this.entry= entry;  // 保存入口配置
        this.output = output  // 保存出口配置
        this.modules = [];
    }

    run () {
        // 从入口开始
        const info =    this.parse(this.entry);
        this.modules.push(info);
      //   获取所有的module 信息, 存放在 this.modules 为一个数组
        for (let i = 0; i < this.modules.length; i += 1) {

            const item = this.modules[i];
            const { yilai} = item;
            if (yilai) {
                for (let i in yilai ) {
                    // 发现有有依赖, 继续添加
                   this.modules.push( this.parse(yilai[i]));
                }
            }

        }
      //  console.log(this.modules);
        const obj = {};
        // 将modules , 数组信息转化为 webpackbootSrap 参数
        this.modules.forEach((item) => {
            obj[item.entryFile] = {
                yilai: item.yilai,
                code: item.code,
            }
        });
      //  console.log(obj);

        // bundle 的生成
        this.file(obj);
    }
    parse(entryFile) {
       // console.log(entryFile);
         const content =   fs.readFileSync(entryFile, 'utf-8');
        // ast 树的形成
        const dirname = path.dirname(entryFile);
         const ast = parser.parse(content, {
            sourceType: "module",
          });
       //  console.log(ast);
        // console.log(ast.program.body)
         const yilai = {}; 
         traverse(ast, {
             // 过滤符合条件的 import 语句, 将import 
             ImportDeclaration({node}) {
                // 获取当前import 导入文件的路径信息 
              const pathValue =    node.source.value  
              const newPathName=  './' +  path.join(dirname , pathValue);
              // 依赖相对与入口的路径: 依赖相对于 src 的 路径
              yilai[pathValue] = newPathName;
            //  console.log(yilai);
                 // 获取依赖的位置
                // 初始ast 树
             }
            
         });
         // 简化了webpack 的打包流程
         const { code} =    transformFromAst(ast, null, {  // null 是否有错误
            presets: ["@babel/preset-env"],
        });
         // css, tsx 交给loader 来进行处理    
            // console.log(code);  
         

         return {
            code,
            yilai,
            entryFile

         }

    }

    file(code) {
     //   console.log(code);
        // 生成bundle 文件的内容
        // webpackBootstroap
        // 生成文件,放入目录
       const filePath =  path.join(this.output.path, this.output.filename);
     //  console.log(filePath);
       const newCode = JSON.stringify(code);  // 对象转化成json 
       console.log(newCode);
       // 包裹一层, 自执行函数 
       const bundle = `(function( modules){  // webpackbootSrap 参数

           function require(module) {  // 文件入口
               // 相对路径
               // relativePath : ./a.js -> ./src/a.js
               // 覆盖里面的require 函数的真实调用, 嵌套, 使用内部的require 函数
               function pathRequire(relativePath) {
                  // 获取真实的path, 从modules 中可以获取对应的module 信息 
                  return  require(modules[module].yilai[relativePath])
                  // 数据返回
               }
               const exports = {};
               //
                (function(require,exports , code) {
                    eval(code)  // 执行里面的代码, 遇到require ,会使用
                    // 从外传入的require: 为 pathRequire
                })(pathRequire,exports, modules[module].code);
                return exports;
           }
           // 入口文件的调用
           require('${this.entry}');  // 文件的入口

       })(${newCode})`;
       // 里面就是一个自执行函数
       fs.writeFileSync(filePath, bundle, 'utf-8');
    }

    // Ast 抽象语法树
    // tree 结构, 一个节点, 就是一个对象信息
    // @babel/parser     接受内容, 形成一个ast 树
    // @babel/traverse   对节点进行遍历
    //  @babel/core  处理js
    // @babel/preset-env  处理js

    // 如果模块之间相互依赖,怎么办?
}
/* 
 打包的整体流程
1 获取webpack.config.js 中的配置项信息
2 获取入口配置和出口配置信息
3 获取入口文件的依赖信息, 和执行内容, 
4 对入口文件的执行内容进行处理, 
5 遍历入口文件的依赖内容, 获取其依赖, 和对其内容进行处理
6 依次遍历, 直到整棵树都遍历完毕,内容处理完毕
7 构建整棵树的依赖图谱,
8 从入口文件开始, 执行webpack_require 函数, 从入口地址开始, 生成最终的文件
9  根据output 将代码输出到 对应文件

*/
	webpack 打包原理分析
		1 接收webpack 配置, 进行读取
		2 入口: 从哪一个文件开始分析
		3 哪些模块是依赖模块
		4 这些依赖模块的信息, 也就是路径信息
		5 内容, 对内容该处理, 处理成能被游览器正确解析的内容
		6 递归处理其他依赖模块
		7 生成chunk 代码片段
		8 补齐函数, 生成bundle 文件的内容, 启动器函数函数
		9 出口, 生成资源文件的名称和位置