webpack打包原理分析和实现(二)

2022-09-29 08:33:28 浏览数 (1)

  • webpack打包原理分析和实现(一)
  • webpack打包原理分析和实现(二)
  • webpack打包原理分析和实现(三)

上一篇,通过@babel/parser将index中的es6代码解析成ast(抽象语法树),接下来,我们可以根据Body里面的分析结果,遍历出所有的引入模块,但是比较麻烦,这里推荐babel的一个模块@babel/traverse,帮我们处理。

分析单个模块依赖

npm i @babel/traverse --save

为了获得模块(文件)的依赖,通过ast可以看到依赖的路径存放在Node下面的resource的value里,我们需要提取ast中type为ImportDeclaration的Node,traverse中提供提取它的同名函数,回调参数为node的对象,我们把依赖的模块路径同意存放在dependencies 里,key为入口文件路径,值为项目里的全路径

代码语言:javascript复制
moduleAnalyser(entryFile) {
        //! 分析入口模块的内容
        const content = fs.readFileSync(entryFile, 'utf-8')
        console.log(content)

        //!分析出哪些是依赖?以及依赖的路径
        const ast = parser.parse(content, {
            sourceType: 'module'
        })
        const dependencies = {}
        traverse(ast, {
            //提取哪个字段就用哪个函数
            ImportDeclaration({node}) {
                console.log(node.source.value)
                //路径拼接
                const newPathName = "./"   path.join(path.dirname(entryFile), node.source.value)
                dependencies[node.source.value] = newPathName
                console.log(dependencies)
            }
        })
        //console.log(ast.program.body)
      
    }

打印结果为

代码语言:javascript复制
./expo.js
./srcexpo.js
{ './expo.js': './src\expo.js' }

目前为止,通过moduleAnalyser这个方法,拿到了依赖的项目路径,存在dependencies对象中,供后续使用。

接下来,把代码处理成浏览器可运行的代码,需要借助@babel/core,和babel/preset-env,把ast语法树转换成合适的代码

代码语言:javascript复制
npm i @babel/core @babel/preset-env --save

webpack中添加导入和代码

代码语言:javascript复制
const babel = require('@babel/core')
const {code} = babel.transformFromAst(ast, null, {
            presets: ['@babel/preset-env']
        })

code为处理后的代码

导出所有分析出的信息

代码语言:javascript复制
return {
            entryFile,
            dependencies,//如果没有值,说明没有依赖
            code
        }

moduleAnalyser 完整代码:

代码语言:javascript复制
 moduleAnalyser(entryFile) {
        //! 分析入口模块的内容
        const content = fs.readFileSync(entryFile, 'utf-8')
        console.log(content)

        //!分析出哪些是依赖?以及依赖的路径
        const ast = parser.parse(content, {
            sourceType: 'module'
        })
        const dependencies = {}
        traverse(ast, {
            //提取哪个字段就用哪个函数
            ImportDeclaration({node}) {
                console.log(node.source.value)
                // path.dirname(entryFile)
                // console.log(path.dirname(entryFile))
                //路径拼接
                const newPathName = "./"   path.join(path.dirname(entryFile), node.source.value)
                console.log(newPathName)
                dependencies[node.source.value] = newPathName
                console.log(dependencies)
            }
        })
        console.log(ast.program.body)
        //! 处理内容,转换ast
        const {code} = babel.transformFromAst(ast, null, {
            presets: ['@babel/preset-env']
        })
        console.log('------------------------' code)
        return {
            entryFile,
            dependencies,//如果没有值,说明没有依赖
            code
        }
    }
分析所有模块依赖
  • 根据webpack入口配置的options,获取入口模块,加入modules
  • 遍历modules,判断是否有依赖,如果有,就拿到依赖的路径,通过moduleAnalyser,分析出依赖模块,加入到modules
  • 分析最开始得到main.js,打包出的代码结构其实是一个对象,因此将modules转成对象结构
代码语言:javascript复制
run() {//入口函数
        const entryModule= this.moduleAnalyser(this.entry)
        console.log(entryModule)

        //!处理其他模块,做一个信息汇总
        this.modules.push(entryModule);
        for (let i = 0; i < this.modules.length; i  ) {
            const item = this.modules[i]
            const {dependencies} = item
            if (dependencies) {
                for (let j in dependencies) {
                    this.modules.push(this.moduleAnalyser(dependencies[j]))//分析每个子模块的依赖

                }
            }
        }
        console.log(this.modules)
        //! 数组处理成对象
        const obj = {}
        this.modules.forEach((item) => {
            obj[item.entryFile] = {
                dependencies: item.dependencies,
                code: item.code
            }
        })
        console.log(obj)//已完成分析入口依赖
        // this.file(obj)
    }

到这里,我们已经将依赖的各个模块转成了浏览器需要的信息,但是还是对象,需要进一步输出为代码文件,将在下一篇实现

0 人点赞