前言
一般我们前端工程化都是离不开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;
}
}
})
结束
最后,欢迎走过路过的老铁们点赞~