先来看下用webpack打包后的文件,
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
};
在src文件夹中有greeting.js 和 index.js 两个文件:
greeting.js
export function greeting (name) {
return "hello " + name;
}
index.js
import { greeting } from './greeting.js';
document.write(greeting('world!'))
执行webpack打包后生成的main.js
(() => { // webpackBootstrap
"use strict";
var __webpack_modules__ = ({
"./src/greeting.js":
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"greeting\": () => (/* binding */ greeting)\n/* harmony export */ });\nfunction greeting (name) {\r\n return \"hello \" + name;\r\n}\n\n//# sourceURL=webpack://demo01/./src/greeting.js?");
}),
"./src/index.js":
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _greeting_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./greeting.js */ \"./src/greeting.js\");\n\r\n\r\ndocument.write((0,_greeting_js__WEBPACK_IMPORTED_MODULE_0__.greeting)('world!'))\n\n//# sourceURL=webpack://demo01/./src/index.js?");
})
});
// The module cache
var __webpack_module_cache__ = {};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
// Create a new module (and put it into the cache)
var module = __webpack_module_cache__[moduleId] = {
// no module.id needed
// no module.loaded needed
exports: {}
};
// Execute the module function
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// Return the exports of the module
return module.exports;
}
/* webpack/runtime/define property getters */
(() => {
// define getter functions for harmony exports
__webpack_require__.d = (exports, definition) => {
for(var key in definition) {
if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
}
}
};
})();
/* webpack/runtime/hasOwnProperty shorthand */
(() => {
__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
})();
/* webpack/runtime/make namespace object */
(() => {
// define __esModule on exports
__webpack_require__.r = (exports) => {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
})();
/************************************************************************/
// startup
// Load entry module and return exports
// This entry module can't be inlined because the eval devtool is used.
var __webpack_exports__ = __webpack_require__("./src/index.js");
})();
可以看出经过webpack打包出来的文件是一个匿名闭包函数, 该函数里边只有三个变量和一个函数方法:
__webpack_modules__
存放了编译后的各个文件模块的JS内容;
__webpack_module_cache__
用来做模块缓存;
__webpack_require__
是Webpack
内部实现的一套依赖引入函数(我们在模块化开发的时候,通常会使用ES Module
或者CommonJS
规范导出/引入依赖模块,webpack
打包编译的时候,会统一替换成自己的__webpack_require__
来实现模块的引入和导出,从而实现模块缓存机制,以及抹平不同模块规范之间的一些差异性。)。最后一句则是代码运行的起点,从入口文件开始,启动整个项目。
__webpack_require__
函数内部会优先从缓存对象中获取 moduleId
对应的模块,若该模块已存在,就会返回该模块对象上 exports
属性的值。如果缓存对象中不存在 moduleId
对应的模块,则会创建一个包含 exports
属性的 module
对象,然后会根据 moduleId
从 __webpack_modules__
对象中,获取对应的函数并使用相应的参数进行调用,最终返回 module.exports
的值。
准备工作
执行npm init -y初始化一个项目,并新建以下文件:
|-- simplepack
|-- dist
| |-- bundle.js
| |-- index.html
|-- lib
| |-- compiler.js
| |-- index.js
| |-- parser.js
| |-- test.js
|-- src
| |-- greeting.js
| |-- index.js
|-- .babelrc
|-- simplepack.config.js
|-- package.json
文件及文件夹说明:
- dist : 打包目录
- lib : 核心文件, 其中compiler.js , parse.js
- src: 源代码;
- .babelrc:babel的配置
- simplepack.config.js : 配置文件,类似于webpack.config.js;
- package.json
src中两个文件内容还是使用上面的greeting.js 和 index.js
simplepack.config.js配置文件内容:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
};
接着编写compiler.js,
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 () {
}
/**
* 构建模块相关
*/
buildModule (filename, isEntry) {
}
// 输出文件
emitFiles () {
}
};
compile.js主要做了几个事情:
1. 读取config.js 配置参数,并初始化entry、output
2. 开启编译run方法,处理构建模块、收集依赖、输出文件等;
3. buildModule方法,用于构建模块;
4. emitFiles方法,输出文件
parse.js主要做了几个事情:
1. 需要将源文件解析生成AST(利用babylon模块);
2. 对AST节点进行递归遍历得到依赖(利用babel-traverse模块);
3. 将AST重新生成源码(利用babel-core模块)
所以我们需要先执行安装这几个依赖npm i babylon babel-traverse babel-core babel-preset-env -S
babylon
babel中使用的 JavaScript 解析器
babel-traverse
对ast进行遍历的工具
babel-core
可以看做 babel 的编译器。babel 的核心 api 都在这里面,比如 transform,主要都是处理转码的。它会把我们的 js 代码,转化成抽象语法树AST(源代码的抽象语法结构的树状表现形式)。最后转化成 es5
babel-preset-env
babel预设
const fs = require('fs');
const babylon = require('babylon');
const traverse = require('babel-traverse').default;
const { transformFromAst } = require('babel-core');
module.exports = {
// 生成AST
getAST: (path) => {
// 读取文件内容
const source = fs.readFileSync(path, 'utf-8');
return babylon.parse(source, {
sourceType: 'module'
})
},
// 对AST节点进行递归遍历, 获取模块的依赖属性
getDependencies: (ast) => {
const dependencies = [];
traverse(ast, {
// 类型为 ImportDeclaration 的 AST 节点 (即为import 语句)
ImportDeclaration: ({ node }) => {
dependencies.push(node.source.value)
}
});
return dependencies;
},
// 将获得的ES6的AST转化成ES5
transform: (ast) => {
const { code } = transformFromAst(ast, null, {
presets: ['env']
});
return code;
}
}
还得配置.babelrc文件
{
"presets": [
"@babel/preset-env"
]
}
接着我们写个test.js来测试下:
const path = require('path');
const { getAST, getDependencies, transform } = require('./parse');
const ast = getAST(path.join(__dirname, '../src/index.js'))
console.log(ast);
const dependencies = getDependencies(ast);
console.log(dependencies);
const source = transform(ast);
console.log(source)
执行node lib/test.js 得到以下结果(截图不全):
完善compiler.js:
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.forEach((dependency) => {
this.modules.push(this.buildModule(dependency));
});
});
// console.log(this.modules);
this.emitFiles();
}
/**
* 构建模块相关
* @param filename 文件名称
* @param isEntry 是否是入口文件
*/
buildModule (filename, isEntry) {
let ast;
if (isEntry) {
ast = getAST(filename);
} else {
const absolutePath = path.join(process.cwd(), "./src", filename);
ast = getAST(absolutePath);
}
return {
filename, // 文件名称
dependencies: getDependencies(ast), // 依赖列表
transformCode: transform(ast), // 转化后的代码
};
}
// 输出文件
emitFiles () {
const outputPath = path.join(this.output.path, this.output.filename);
let modules = [];
this.modules.forEach((_module) => {
modules.push(`'${_module.filename}' : function(require, module, exports) {${_module.transformCode}}`);
});
modules = modules.join(',')
const bundle = `
(function(modules) {
function require(fileName) {
const fn = modules[fileName];
const module = { exports:{}};
fn(require, module, module.exports)
return module.exports
}
require('${this.entry}')
})({${modules}})
`;
fs.writeFileSync(outputPath, bundle, "utf-8");
}
};
lib/index.js:
const Compiler = require('./compiler');
const options = require('../simplepack.config');
new Compiler(options).run();
执行node lib/index.js
(function (modules) {
function require (fileName) {
const fn = modules[fileName];
const module = { exports: {} };
fn(require, module, module.exports)
return module.exports
}
require('./src/index.js')
})({
'./src/index.js': function (require, module, exports) {
"use strict";
var _greeting = require("./greeting.js");
document.write((0, _greeting.greeting)('world!'));
}, './greeting.js': function (require, module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.greeting = greeting;
function greeting (name) {
return 'hello ' + name;
}
}
})
完整代码地址simplepack
webpack系列:
参考资料: