手写一个webpack

184 阅读1分钟

代码地址: github.com/chengliu050…

一、步骤

1.解析单个文件,传入路径filename

+ 给文件编号id  
+ 读取filename对应的文件内容并解析为ast树  
+ 根据ast树找到文件其他依赖存入数组dependencies(require的文件)  
+ 根据规则转换代码(这里使用插件@babel/preset-env将es6转为es5)
    var ID = 0
function createAsset(filename) { //解析单个文件
   const content = fs.readFileSync(filename,'utf-8');
   const ast = parser.parse(content,{
       sourceType:'module'
   })

   const dependencies = []
   traverse(ast,{
        ImportDeclaration:({node})=>{
            dependencies.push(node.source.value)
        }
   })
   
   const {code} = babel.transformFromAstSync(ast,null,{
       presets:['@babel/preset-env']
   })

   let id = ID++
   return{
       id,
       filename,
       code,
       dependencies
   }

}

返回如下格式的对象,这里简称为asset

    {
      id: 0,
      filename: './src/index.js',
      code: '
         var _info = require("./info.js");
         console.log(_info["default"]);
      ',
      dependencies: [ './info.js' ]
    }

2.得到各个文件转换的对象asset组成的数组queue

 + 从入口开始,得到一个如上的对象asset  
 + 根据dependencies递归各个文件,转换的asset放到数组queue
 + 给每个asset加一个属性,将filename与id关联起来
    asset.mapping[dependencies[i]] = child.id
function createGraph(entry){  //从入口开始,根据dependencies递归各个文件,将createAsset(filename)处理结果放到数组queue
  const mainAsset = createAsset(entry);

  const queue = []
  queue.push(mainAsset)
  for(const asset of queue){
      const dirname = path.dirname(asset.filename)
      asset.mapping = {}

      asset.dependencies.forEach(relativePath =>{
          const absolutePath = path.join(dirname,relativePath);
          const child = createAsset(absolutePath);

          asset.mapping[relativePath] = child.id

          queue.push(child);
      })
  }

  return queue
}

3.将queue对象关联起来为可执行的代码

 + 将数组queue转换成对象,key为id,value为数组[]
 + 重写一个require函数
 + 从入口开始执行 require(0)
    function bundle(graph){ //打包
        let modules = ''
        graph.forEach(mod=>{
            modules += `${mod.id}:[
                function(require,module,exports){
                    ${mod.code}
                },
                ${JSON.stringify(mod.mapping)}
            ],`
        })
       const result = `
        (function(modules){
            function require(id){
                const [fn,mapping] = modules[id];

                function localRequire(relativePath){
                    return require(mapping[relativePath])
                }

                const module = {
                    exports:{}
                }

                fn(localRequire,module,module.exports)

                return module.exports;
            }
            require(0);
        })({${modules}})
    `

       return result
  }

二、细节

  • 函数require 接受的是id,所以需要mapping对象,将路径转为id
  • es6的子文件最终输出
    module.export.default = xxx module.export.fn = xxx
  • 父函数重写require的module,module.exports,从而拿到default,fn的代码

三、用到的库

  • 3.1 @babel/parser

  • 3.2 @babel/traverse

  • 3.3 @babel/core下的transformFromAstSync方法

欢迎关注我的前端自检清单,我和你一起成长