一、 研究webpack原理,我们从最近单的hello webpack 开始, 以下是代码实现
// webpack.config.js
const path = require("path");
module.exports = {
entry: "./src/index.js",
mode: "development",
output: {
path: path.resolve(__dirname, "./dist"),
filename: "main.js"
},
};
// src/index.js
console.log("hello webpack");
执行webpack输出打包目录dist
/******/ (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/index.js");
/******/ })
/************************************************************************/
/******/ ({
/***/ "./src/index.js":
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/*! no static exports found */
/***/ (function(module, exports) {
eval("console.log(\"hello webpack\");\n\n\n//# sourceURL=webpack:///./src/index.js?");
/***/ })
/******/ });
以上打包输出内容是可以直接在浏览器执行的
打包输出的dist文件是一个webpackBootstrap的自启动函数,模块以文件位置"./src/index.js"
作为key, 编译后的源码(function(module, exports) {eval("console.log(\"hello webpack\");\n\n\n//# sourceURL=webpack:///./src/index.js?");/***/ })
作为value的对象为参数;另外用到的export require
等webpack会替换成自己的__webpack_require__
,简单说就是对路径的处理,便于浏览器能正常运行
二、webpack实现
看到这里来分一下webpack做了哪些事情:
- 接收一份webpack.config.js的配置
- 分析出入口模块位置
- 读取入口模块内容,分析内容
- 哪些是依赖
- 哪些是源码 - es6, jsx 需要编译处理,最终浏览器能够执行
- 分析其他模块
- 拿到对象数据结构
- 模块路径
- 处理好的内容
- 创建bundle.js
- 启动器函数,来补充代码⾥有可能出现的module exports
require,让浏览器能够顺利的执⾏
- 启动器函数,来补充代码⾥有可能出现的module exports
程序目录
.
├── dist # node脚本
│ └── main.js # 打包输出
├── lib # webpack源码目录
│ └── webpack.js # 源码
├── src # 项目代码
│ ├── a.js
│ ├── b.js
│ └── index.js # 程序入口
├── bundle.js # webpack打包入口
└── webpack.config.js # webpack配置
src/index.js
//为什么要分析依赖,因为我们需要拿到路径
import { add } from "./a.js";
console.log(add(1, 2));
console.log("hello webpack");
console.log("test");
src/a.js
export const add = function(a, b) {
return a + b;
};
export const minus = function(a, b) {
return a - b;
};
bundle.js
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const { transformFromAst } = require("@babel/core");
const tarverse = require("@babel/traverse").default;
module.exports = class webpack {
constructor(options) {
//入口信息
let { entry, output } = options;
//出口信息
this.entry = entry;
this.output = output;
this.module = [];
}
run() {
//开始分析入口模块的内容
const info = this.parse(this.entry);
this.module.push(info);
for (let i = 0; i < this.module.length; i++) {
const item = this.module[i];
const { dependencies } = item;
if (dependencies) {
for (let j in dependencies) {
this.module.push(this.parse(dependencies[j]));
}
}
}
// console.log(this.module);
// 数据结构转换
const obj = {};
this.module.forEach(item => {
obj[item.entryFile] = {
dependencies: item.dependencies,
code: item.code
};
});
this.file(obj);
}
parse(entryFile) {
const content = fs.readFileSync(entryFile, "utf-8");
//使用parser分析内容,返回ast 抽象语法树
const ast = parser.parse(content, {
sourceType: "module"
});
//提取依赖路径信息
const dependencies = {};
tarverse(ast, {
ImportDeclaration({ node }) {
//node.source.value 这个路径是相对于入口的相对路径
const newPathName =
"./" + path.join(path.dirname(entryFile), node.source.value);
dependencies[node.source.value] = newPathName;
}
});
//代码编译
const { code } = transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
});
//信息汇总,返回
return {
entryFile,
dependencies,
code
};
}
file(code) {
//生成bundle.js =》dist/main.js
const filePath = path.join(this.output.path, this.output.filename);
const newCode = JSON.stringify(code);
const bundle = `(function(graph){
function require(module){
function localRequire(relativePath){
return require(graph[module].dependencies[relativePath])
}
var exports = {};
(function(require,exports,code){
eval(code)
})(localRequire,exports,graph[module].code)
return exports;
}
require('${this.entry}')
})(${newCode})`;
//写文件操作
fs.writeFileSync(filePath, bundle, "utf-8");
}
};
dist/main.js
(function(graph){
function require(module){
function localRequire(relativePath){
return require(graph[module].dependencies[relativePath])
}
var exports = {};
(function(require,exports,code){
eval(code)
})(localRequire,exports,graph[module].code)
return exports;
}
require('./src/index.js')
})({"./src/index.js":{"dependencies":{"./a.js":"./src/a.js"},"code":"\"use strict\";\n\nvar _a = require(\"./a.js\");\n\n//为什么要分析依赖,因为我们需要拿到路径\nconsole.log((0, _a.add)(1, 2));\nconsole.log(\"hello webpack\");\nconsole.log(\"test\");"},"./src/a.js":{"dependencies":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.minus = exports.add = void 0;\n\nvar add = function add(a, b) {\n return a + b;\n};\n\nexports.add = add;\n\nvar minus = function minus(a, b) {\n return a - b;\n};\n\nexports.minus = minus;"}})
最终浏览器是可以执行的