前言
先看一下webpack打包后的bundle.js,前边的直接扫一眼就过
,可以发现这个立即执行函数的形参就是一个,key为引入文件路径,value为该模块代码的函数。
所以比较重要的就是通过webpack的配置文件中的entry的入口文件,递归去生成这个modules,并把代码中require变成__webpack_require__。
javascript
(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
}
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules
// expose the module cache
__webpack_require__.c = installedModules
// define getter function for harmony exports
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter })
}
}
// define __esModule on exports
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
}
Object.defineProperty(exports, '__esModule', { value: true })
}
// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
__webpack_require__.t = function (value, mode) {
if (mode & 1) value = __webpack_require__(value)
if (mode & 8) return value
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value
var ns = Object.create(null)
__webpack_require__.r(ns)
Object.defineProperty(ns, 'default', { enumerable: true, value: value })
if (mode & 2 && typeof value != 'string') for (var key in value) __webpack_require__.d(ns, key, function (key) { return value[key] }.bind(null, key))
return ns
}
// getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function (module) {
var getter = module && module.__esModule ?
function getDefault () { return module['default'] } :
function getModuleExports () { return module }
__webpack_require__.d(getter, 'a', getter)
return getter
}
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property) }
// __webpack_public_path__
__webpack_require__.p = ""
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./src/app.js")
})
({
"./src/app.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict"
__webpack_require__.r(__webpack_exports__)
var _module__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./module */ "./src/module.js")
var _module__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_module__WEBPACK_IMPORTED_MODULE_0__)
console.log("Hello World")
}),
"./src/module.js":
(function (module, exports) {
module.exports = {
name: 'module',
description: 'module description',
version: '1.0.0',
dependencies: {
'module-a': '1.0.0',
'module-b': '1.0.0',
},
devDependencies: {
'module-c': '1.0.0',
'module-d': '1.0.0',
},
}
})
});
//# sourceMappingURL=bundle.js.map
实现思路
项目配置如下
-
读取webpack.config.js文件
-
对入口文件实现编译生成依赖对象
modules
2.1 根据入口文件递归去获取依赖及其代码,并通过ast抽象语法书,对require替换成__webpack_require__
2.2 复制webpack打包生成打的bundles.js 将其改造成模板文件(
bundlejsTemplate.ejs
),通过ejs,把modules
插入模板中,生成代码 -
将替换后的模板代码生成到webpack.config.js配置的output路径下
具体实现
index.js
javascript
#! /usr/bin/env node
/*
* 实现 webpack 打包功能
* 1. 配置文件的读取 webpack.config.js
*
* 2. 实现入口文件的编译,然后生成依赖对象 modules
*
*
* */
// console.log('jdpack打包功能');
// console.log(process.cwd()); // 打印当前命令所处的目录
/*
* 1. 配置文件的读取 webpack.config.js
* */
const Compiler = require('../lib/Compiler.js');
const path = require('path');
const configPath = path.resolve(process.cwd(), 'webpack.config.js');
const configObj = require(configPath);
// console.log(configObj); // 配置文件对象
/*
* 2. 实现入口文件的编译,然后生成依赖对象 modules
* */
const compile = new Compiler(configObj);
compile.run();
// console.log(compile.modules); // 模块依赖对象
Compiler.js (最重要的实现都在这个类里)
javascript
/*
* 编译我们的代码,生成 打包后的内容
* 1. 根据配置文件 entry 入口文件,读取入口文件对象的代码
* 2. 生成依赖对象
* 3. 传递给 webpack的 自执行的匿名函数
*
* */
const fs = require('fs');
const ejs = require('ejs');
const path = require('path');
/*
* 导入ast相关的模块
* */
const {parse} = require('@babel/parser');
const generator = require('@babel/generator').default;
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
class Compiler {
/*
* 配置文件
* */
constructor(config) {
this.config = config;
// 模块依赖对象,保存了代码里面的所有的模块依赖关系 key 依赖模块的路径 value 依赖模块对应代码的函数
this.modules = {};
}
run() {
// 1. 根据配置文件的入口文件生成依赖对象
this.buildModules(this.config.entry);
// 编译后,生成 bundle.js
this.generatorBundlejs();
}
/*
* moduleId 依赖模块的路径
*
* 如果在 代码里面有 require其他的模块代码
* 1. 先生成模块依赖对象
* 2. 将代码里面的 require 替换为 __webpack_require__ ast 实现
* 3. 将依赖模块的路径加载入口文件的目录
*
* */
buildModules(moduleId) {
let code = this.getCode(moduleId);
let {deps, newCode} = this.parseModule(code);
// console.log(deps, newCode);
this.modules[moduleId] = newCode;
// 针对 deps 里面再次做处理,引入依赖的文件里面还有可能 require
deps.forEach(item => {
this.buildModules(item);
})
}
/*
* path 依赖模块的路径
* */
getCode(modulePath) {
return fs.readFileSync(modulePath, 'utf8');
}
/*
* 将代码里面的依赖做替换
* */
parseModule(code) {
let mainRootPath = path.dirname(this.config.entry);
// 存储了代码里面所有的依赖路径
let deps = [];
const ast = parse(code);
/*
* 1. 对 require 节点做处理,替换 __webpack_require__
* */
traverse(ast, {
CallExpression(NodePath) {
let node = NodePath.node;
if (node.callee.name === 'require') {
node.callee.name = '__webpack_require__';
// 2. 对依赖路径做替换
let depPath = node.arguments[0].value;
depPath = '.\\' + path.join(mainRootPath, depPath);
depPath = depPath.replace(/\\/g, '/');
// 利用语法树将 require 里面依赖路径做修改
node.arguments[0] = t.stringLiteral(depPath);
deps.push(depPath);
}
}
});
let newCode = generator(ast).code;
// console.log(newCode);
return {deps, newCode};
}
/*
* 先根据生成的入口文件的依赖对象,生成打包文件。然后在 分析入口文件里面的内容,如果有其他的 require 进行再次生成依赖对象,在生成打包的文件
* */
generatorBundlejs() {
/*
* 使用 ejs 根据依赖对象,生成打包后的 bundle.js 文件
* 1. 读取模板
*
*
* */
let bundlePath = path.resolve(__dirname, 'bundlejsTemplate.ejs');
let bundleTemplate = fs.readFileSync(bundlePath, 'utf-8');
/*
* 2. 使用 ejs 做模板的替换
* */
let renderCode = ejs.render(bundleTemplate, {moduleId: this.config.entry, modules: this.modules});
/*
* 3. 将打包后的内容根据 webpack.config.js 里面的 output 进行保存
* */
let outputPath = this.config.output.path;
// 判断打包后的输出目录是否存在,如果不存在,则先创建目录
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath);
}
let outputFilePath = path.resolve(outputPath, this.config.output.filename);
fs.writeFileSync(outputFilePath, renderCode);
}
}
module.exports = Compiler;
bundlejsTemplate.ejs
javascript
(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;
}
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;
// expose the module cache
__webpack_require__.c = installedModules;
// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// define __esModule on exports
__webpack_require__.r = function(exports) {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
__webpack_require__.t = function(value, mode) {
if(mode & 1) value = __webpack_require__(value);
if(mode & 8) return value;
if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
var ns = Object.create(null);
__webpack_require__.r(ns);
Object.defineProperty(ns, 'default', { enumerable: true, value: value });
if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
return ns;
};
// getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function(module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
__webpack_require__.d(getter, 'a', getter);
return getter;
};
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// __webpack_public_path__
__webpack_require__.p = "";
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "<%- moduleId %>");
})
/************************************************************************/
({
<% for(let key in modules) { %>
"<%- key %>": (function(module, exports, __webpack_require__) {
<%- modules[key] %>
}),
<% } %>
});
package.json
javascript
{
"name": "jdpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"bin": {
"mypack": "./bin/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@babel/generator": "^7.18.13",
"@babel/parser": "^7.18.13",
"@babel/traverse": "^7.18.13",
"@babel/types": "^7.18.13",
"ejs": "^3.1.8"
}
}