手动实现一个webpack

420 阅读3分钟

前言

一般我们前端工程化都是离不开webpack,我们不要对于webpack原理有太多的抗拒,现在来手动实现一个非常简易的webpack,初步了解webpack的构建流程。

webpack4.x的使用

1.npm init -y后,安装webpack webpack-cli

npm i webpack@4.44.1 webpack-cli@3.3.12 -D

2.创建webpack.config.js、index.js、greeting.js

// webpack.config.js
const path = require('path')
module.exports = {
  entry: './src/index.js',
  mode: 'development',
  output: {
    path: path.join(__dirname, './dist'),
    filename: 'bundle.js'
  }
}

// index.js
import { greeting } from './greeting.js'
document.write(greeting('Jane'))

// greeting.js
export function greeting(name) {
  return 'hello ' + name
}

3.构建打包后的精简代码

 (function(modules) {
 	var installedModules = {}; // 缓存依赖
         // 自己实现的require方法。
 	function __webpack_require__(moduleId) {
                // 模块已经require过了,直接使用缓存。
		if(installedModules[moduleId]) {
 			return installedModules[moduleId].exports;
 		}
 		var module = installedModules[moduleId] = {
 			i: moduleId,
 			l: false,
 			exports: {}
 		};
                // 递归的调用require,构建依赖树
 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
 		module.l = true;
                // 返回reuqire的结果
 		return module.exports;
 	}
        // 从入口文件开始执行。
 	return __webpack_require__(__webpack_require__.s = "./src/index.js");
 })
 ({
"./src/greeting.js": function(module, __webpack_exports__, __webpack_require__) {

"use strict";
function greeting(name) { return 'hello ' + name}
},
"./src/index.js": function(module, __webpack_exports__, __webpack_require__) {
  "use strict";
  var _greeting_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/greeting.js");
  document.write(Object(_greeting_js__WEBPACK_IMPORTED_MODULE_0__["greeting"])('Jane'))
  }
});

webpack的实现

业务代码

src代码和webpack.config.js和上面一样

// webpack.config.js
const path = require('path')
module.exports = {
  entry: './src/index.js',
  mode: 'development',
  output: {
    path: path.join(__dirname, './dist'),
    filename: 'bundle.js'
  }
}

// index.js
import { greeting } from './greeting.js'
document.write(greeting('Jane'))

// greeting.js
export function greeting(name) {
  return 'hello ' + name
}

实现代码

创建lib文件夹,里面存放webpack实现代码

index.js

const  Compiler = require('./compiler')
const options = require('../webpack.config.js')
const compiler = new Compiler(options).run()

parse.js

npm i @babel/preset-env babel-core babel-preset-env babel-traverse babylon

  • 首先通过fs读取字符串,通过babel插件转换成ast树
  • 通过traverse,获取ast中的require的路径
  • 将ast语法树通过babel-core转换成es5的代码
const fs = require('fs')
// 将字符串代码转换成ast
const babylon = require('babylon')
// 获取每个模块依赖
const traverse = require('babel-traverse').default
// 将ast代码转换成js代码
const { transformFromAst }  = require('babel-core')

module.exports = {
  getAST: (path) => {
    const source = fs.readFileSync(path, 'utf-8')

    return babylon.parse(source, {
      sourceType: 'module'
    })
  },
  getDependencies: (ast)=>{
    const dependencies = []
    traverse(ast, {
      ImportDeclaration: ({node}) =>{
        dependencies.push(node.source.value)
      }
    })
    return dependencies
  },
  transform: (ast) =>{
    const {code} = transformFromAst(ast,null,{
      presets: ['env']
    })
    return code
  }
}

compiler.js

  • 从入口文件开始构建
  • 生成入口文件生成三类数据,ast,es5代码,依赖的路径
  • 根据依赖的路径,递归的生成这三种数据,将其全部收集到一个modules数组中
  • 最终,参数webpack4.x打包后的文件,将文件路径作为key,该路径下模块代码source作为值,存放在一个对象中,作为实参传入自执行函数。
const {getAST, getDependencies, transform}= require('./parse')
const path = require('path')
const fs = require('fs')

module.exports= class Compiler {

  constructor(options) {
    // 获取配置参数
    const { entry, output } = options
    this.entry = entry
    this.output = output
    this.modules = []
  }
  // 运行
  run(){
    // 从入口开始构建
    const entryModule = this.buildModule(this.entry, true)
    this.modules.push(entryModule)
    this.modules.map(_module=>{
      _module.dependencies.map(dependency=>{
        this.modules.push(this.buildModule(dependency))
      })
    })
    this.emitFiles()
  }
  
  // 构建模块,将es6代码生成ast,将ast树生成es5代码,返回依赖
  buildModule(filename, isEntry){
    let ast
    if(isEntry) {
      ast = getAST(filename)
    } else {
      const absoultePath = path.join(process.cwd(), './src',filename)
      ast = getAST(absoultePath)
    }
    return {
      filename,
      dependencies: getDependencies(ast),
      source: transform(ast)
    }
  }

  // 依赖树生成后,输出到指定目录
  emitFiles(){
    const outputpath = path.join(this.output.path, this.output.filename)
    const list = this.modules.map(_module=>{
      return (`
      '${_module.filename}': function(module, exports, require){
        ${_module.source}
      }
      `)
    })
    let modules = list.join(',')
    const bundle = `(function(modules){
      var installedModules = {}
      function __webpack_require__(filename){
        if(installedModules[filename]) {
          return installedModules[filename].exports
        }
        var module = installedModules[filename] = {
          i: filename,
          l: false,
          exports: {}
        }
        modules[filename].call(module.exports, module, module.exports, __webpack_require__)
        module.l = true
        return module.exports
      }
      __webpack_require__('${this.entry}')
    })({${modules}})`;
    console.log(bundle);
    fs.writeFileSync(outputpath, bundle, 'utf-8')
  }
}

生成代码

node ./lib/index.js

(function(modules){
      var installedModules = {}
      function __webpack_require__(filename){
        if(installedModules[filename]) {
          return installedModules[filename].exports
        }
        var module = installedModules[filename] = {
          i: filename,
          l: false,
          exports: {}
        }
        modules[filename].call(module.exports, module, module.exports, __webpack_require__)
        module.l = true
        return module.exports
      }
      __webpack_require__('/Users/openlee/Desktop/simplepack/src/index.js')
    })({
      '/Users/openlee/Desktop/simplepack/src/index.js': function(module, exports, require){
        "use strict";
        var _greeting = require("./greeting.js");
        document.write((0, _greeting.greeting)('Jane'));
      }
      ,
      './greeting.js': function(module, exports, require){
        "use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.greeting = greeting;
function greeting(name) {
  return 'hello ' + name;
}
      }
})

结束

最后,欢迎走过路过的老铁们点赞~