# 实现一个mini-webpack

本来记录自己实现一个mini-webpack时候遇到的坑和总结webpack的大致流程

# 初始化

我们需要写一个webpack的包,首先需要初始化一下项目



npm init -y

然后我们配置一下package.jsonbin选项




 

// package.json
"bin": {
    "cy-pack": "./bin/cy-pack.js"
},

然后我们需要新建一个bin文件夹和新起一个cy-pack.js文件,我们看下文件的内容




 









#! /usr/bin/env node

// 拿到当前执行名的路径 拿到webpack.config.js
const path = require('path')

const config = require(path.resolve(process.cwd(), 'webpack.config.js'))

const Compiler = require('./lib/Compiler.js')

const compiler = new Compiler(config)

compiler.run()

#! /usr/bin/env node表示当前运行环境是node环境,下面就是对应的代码,这里不做解释了

最后通过命令npm link将这个包映射到全局npm上去,在另外一个文件夹使用我们的包的时候只需要npm link cy-pack就好啦

# 手写一个最简单的打包工具




 





































































// Compiler.js
const path = require('path')
const fs = require('fs')
const babylon = require('babylon') //将源码转化为AST
const traverse = require('@babel/traverse').default
const t = require('@babel/types')
const generator = require('@babel/generator').default
const ejs = require('ejs')


class Compiler {
    constructor (config) {
        this.config = config
        this.entryId //入口文件的路径
        this.modules = {} // 所有依赖的模块
        this.entry = config.entry // 入口路径
        this.root = process.cwd() //当前工作路径
    }
    getSource (modulePath) {
        return fs.readFileSync(modulePath, 'utf8')
    }
    parse (source, parentPath) { // 靠AST解析语法树解析源码
       const ast =  babylon.parse(source)
       const dependencies = [] //依赖的数组
       traverse(ast, {
           CallExpression(p) {
               let node = p.node
               if (node.callee.name  === 'require') { //找到require
                 node.callee.name = '__webpack_require__'
                 let moduleName = node.arguments[0].value //取到require引入的名字
                 moduleName = moduleName + (path.extname(moduleName) ? '' : '.js') //如果没有后缀就补充.js
                 moduleName = './' + path.join(parentPath, moduleName)
                 dependencies.push(moduleName)
                 node.arguments = [t.stringLiteral(moduleName)]
               }
           }
       })
       let sourceCode = generator(ast).code
       return { sourceCode, dependencies }
    }
    buildModule (modulePath, isEntry) { // 构建模块
        const source = this.getSource(modulePath) //拿到模块的内容
        const moduleName = './' + path.relative(this.root, modulePath)
        if (isEntry) { //如果是根模块
            this.entryId = moduleName
        }
        const { sourceCode, dependencies } = this.parse(source, path.dirname(moduleName))
        dependencies.forEach(dep => { //递归加载
            this.buildModule(path.join(this.root, dep), false)
        })
        this.modules[moduleName] = sourceCode
    }
    emitFile () { //发射打包后的文件
        // 用数据渲染我们的模板 然后输出到对应的文件去
        const main = path.join(this.config.output.path, this.config.output.filename)
        const templateStr = this.getSource(path.join(__dirname, 'main.ejs'))
        const code = ejs.render(templateStr, { //将数据传入ejs 然后得到代码块
            entryId: this.entryId,
            modules: this.modules
        })
        this.assets = {}
        this.assets[main] = code
        fs.writeFileSync(main, this.assets[main])
    }
    run () {
        // 执行并创建模块的依赖关系
        this.buildModule(path.resolve(this.root, this.entry), true)
        this.emitFile()
    }
}

module.exports = Compiler

我们来总结一下基本的流程

  • 通过在使用我们包的目录使用npx cy-pack运行我们的打包工具,然后我们可以拿到当前执行这个命令的路径

  • 根据这个路径我们可以拿到webpack.config.js,并将对应的数据进行存储

  • 然后调用buildModule创建我们的模块关系,主要思路是通过配置中的入口路径读取到入口文件的内容,然后调用bodylon.parse将读到的内容转化为AST, 然后通过traverse找到代码中的require,将他替换成我们自己实现的__webpack_require__,然后替换对应require中的路径,最后生成依赖表单就好了(依赖表单是处理过的路径)

  • 然后通过遍历依赖表单调用buildModule方法递归处理完全部依赖的文件

  • 最后可以通过ejs模板等方式将数据写入模板中,最后输出到配置的output对应的路径和文件就好了

我们来看下最基本的模板




 






























// main.ejs
(function (modules) { // webpackBootstrap
    // The module cache
    var installedModules = {};
    // The require function
    function __webpack_require__(moduleId) {
      // Check if module is in cache
      if (installedModules[moduleId]) {
        return installedModules[moduleId].exports;
      }
      // Create a new module (and put it into the cache)
      var module = installedModules[moduleId] = {
        i: moduleId,
        l: false,
        exports: {}
      };
      // Execute the module function
      modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
      // Flag the module as loaded
      module.l = true;
      // Return the exports of the module
      return module.exports;
    }
    return __webpack_require__(__webpack_require__.s = "<%-entryId%>");
  })
    ({
        <%for(let key in modules){%>
            "<%-key%>":
            (function (module, exports, __webpack_require__) {
            eval(`<%-modules[key]%>`);
            }),
        <%}%>  
    });

# 支持loader

# 支持plugin