Compilation类:
    Compilation描述一个编译过程

    文件结构:
    ./src/entry1.js中依赖了./title1
    ./src/entry2.js中依赖了./title2

    entry的结构:

    1. {
    2. entry1:'./src/entry1.js',
    3. entry2:'./src/entry2.js'
    4. }

    一个entry对应一个chunk,每个chunk的结构如下:

    1. let chunk = {
    2. name: entryName,
    3. entryModule,
    4. modules
    5. }

    每个module的结构如下:

    1. let module = {
    2. id: './src/entry1.js',
    3. dependencies: [
    4. {
    5. depModuleId: './src/title1.js',
    6. depModulePath: 'C:/aproject/zhufengwebpack202106/4.flow/src/title1'
    7. }
    8. ],
    9. name: 'entry1',
    10. extraNames: []
    11. }

    入口模块(entryModule):

    1. let entryModule = {
    2. id: './src/entry1.js',
    3. dependencies: [
    4. {
    5. depModuleId: './src/title1.js',
    6. depModulePath: 'C:/aproject/zhufengwebpack202106/4.flow/src/title1'
    7. }
    8. ],
    9. name: 'entry1',
    10. extraNames: []
    11. }

    非入口模块,或叫依赖模块(depModule):
    注:
    name用来标识当前模块属于哪个入口模块
    extraNames用来标识哪些入口模块依赖了该模块

    let depModule = {
        id: './src/title.js',
      dependencies: [],
      name: 'entry1',
      extraNames: ['entry2']
    }
    
    const path = require('path');
    const fs = require('fs');
    const types = require('babel-types');
    const parser = require('@babel/parser');
    const traverse = require('@babel/traverse').default;
    const generator = require('@babel/generator').default;
    const baseDir = toUnitPath(process.cwd());//\
    function toUnitPath(filePath) {
        return filePath.replace(/\\/g, '/');
    }
    class Complication {
        constructor(options) {
            this.options = options;
            //webpack4 数组  webpack5 set
            this.entries = [];//存放所有的入口
            this.modules = [];// 存放所有的模块
            this.chunks = [];//存放所的代码块
            this.assets = {};//所有产出的资源
            this.files = [];//所有产出的文件
        }
        build(callback) {
            //5. 根据配置中的entry找出入口文件
            let entry = {};
            if (typeof this.options.entry === 'string') {
                entry.main = this.options.entry;
            } else {
                entry = this.options.entry;
            }
            //entry={entry1:'./src/entry1.js',entry2:'./src/entry2.js'}
            for (let entryName in entry) {
                //5.获取 entry1的绝对路径
                let entryFilePath = toUnitPath(path.join(this.options.context, entry[entryName]));
                //6.从入口文件出发,调用所有配置的Loader对模块进行编译
                let entryModule = this.buildModule(entryName, entryFilePath);
                //this.modules.push(entryModule);
                //8. 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
                let chunk = {
                    name: entryName, entryModule, modules: this.modules.filter(item => {
                        return item.name === entryName || item.extraNames.includes(entryName);
                    })
                };
                this.entries.push(chunk);
                this.chunks.push(chunk);
            }
            //9. 再把每个Chunk转换成一个单独的文件加入到输出列表
            this.chunks.forEach(chunk => {
                let filename = this.options.output.filename.replace('[name]', chunk.name);
                // this.assets就是输出列表 key输出的文件名 值就是输出的内容
                this.assets[filename] = getSource(chunk);
            });
    
            callback(null, {
                entries: this.entries,
                chunks: this.chunks,
                modules: this.modules,
                files: this.files,
                assets: this.assets
            });
        }
        //name=名称,modulePath=模块的绝对路径
        buildModule(name, modulePath) {
            //6. 从入口文件出发,调用所有配置的Loader对模块进行编译
            //1.读取模块文件的内容 
            let sourceCode = fs.readFileSync(modulePath, 'utf8');//console.log('entry1');
            let rules = this.options.module.rules;
            let loaders = [];///寻找匹配的loader
            for (let i = 0; i < rules.length; i++) {
                let { test } = rules[i];
                //如果此rule的正则和模块的路径匹配的话
                if (modulePath.match(test)) {
                    loaders = [...loaders, ...rules[i].use];
                }
            }
            sourceCode = loaders.reduceRight((sourceCode, loader) => {
                return require(loader)(sourceCode);
            }, sourceCode);
    
            /*  for(let i=loaders.length-1;i>=0;i--){
                 let loader = loaders[i];
                 sourceCode = require(loader)(sourceCode);
             } */
            //console.log('entry1');//2//1
            //console.log(sourceCode);
            //7. 再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
            //获得当前模块模块ID ./src/index.js
            let moduleId = './' + path.posix.relative(baseDir, modulePath);
            let module = { id: moduleId, dependencies: [], name, extraNames: [] };
            let ast = parser.parse(sourceCode, { sourceType: 'module' });
            traverse(ast, {
                CallExpression: ({ node }) => {
                    if (node.callee.name === 'require') {
                        //依赖的模块的相对路径
                        let moduleName = node.arguments[0].value;//./title1
                        //获取当前模块的所有的目录
                        let dirname = path.posix.dirname(modulePath);// /
                        //C:/aproject/zhufengwebpack202106/4.flow/src/title1
                        let depModulePath = path.posix.join(dirname, moduleName);
                        let extensions = this.options.resolve.extensions;
                        depModulePath = tryExtensions(depModulePath, extensions);//已经包含了拓展名了
                        //得到依赖的模块ID C:/aproject/zhufengwebpack202106/4.flow/src/title1.js
                        //相对于项目根目录 的相对路径 ./src/title1.js
                        let depModuleId = './' + path.posix.relative(baseDir, depModulePath);
                        //require('./title1');=>require('./src/title1.js');
                        node.arguments = [types.stringLiteral(depModuleId)];
                        //依赖的模块绝对路径放到当前的模块的依赖数组里
                        module.dependencies.push({ depModuleId, depModulePath });
                    }
                }
            });
            let { code } = generator(ast);
            module._source = code;//模块源代码指向语法树转换后的新生成的源代码
            //7. 再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
            module.dependencies.forEach(({ depModuleId, depModulePath }) => {
                let depModule = this.modules.find(item => item.id === depModuleId);
                if (depModule) {
                    depModule.extraNames.push(name);
                } else {
                    let dependencyModule = this.buildModule(name, depModulePath);
                    this.modules.push(dependencyModule);
                }
            });
            return module;
        }
    }
    function getSource(chunk) {
        return `
        (() => {
            var modules = ({
                ${chunk.modules.map(module => `
                        "${module.id}":(module,exports,require)=>{
                            ${module._source}
                        }
                    `).join(',')
            }
            });
            var cache = {};
            function require(moduleId) {
              var cachedModule = cache[moduleId];
              if (cachedModule !== undefined) {
                return cachedModule.exports;
              }
              var module = cache[moduleId] = {
                exports: {}
              };
              modules[moduleId](module, module.exports, require);
              return module.exports;
            }
            var exports = {};
            (() => {
             ${chunk.entryModule._source}
            })();
          })()
            ;
        `
    }
    function tryExtensions(modulePath, extensions) {
        extensions.unshift('');
        for (let i = 0; i < extensions.length; i++) {
            let filePath = modulePath + extensions[i];// ./title.js
            if (fs.existsSync(filePath)) {
                return filePath;
            }
        }
        throw new Error(`Module not found`);
    }
    module.exports = Complication;