- webpack打包原理分析和实现(一)
- webpack打包原理分析和实现(二)
- webpack打包原理分析和实现(三)
首先,新建一个空文件夹,编辑器(webstrom)打开文件夹,执行npm init -y,生成package.json,在根目录新建webpack.config.js,加入如下代码(webpack 4.0的基础配置)
代码语言:javascript复制const path=require('path');
module.exports={
entry:'./src/index.js',
mode:'development',
output:{
path:path.resolve(__dirname,'./dist'),
filename:'main.js'
}
};
新建src目录,添加index.js,exop.js
expo.js
代码语言:javascript复制export const add=(a,b)=>{
return a b
}
export const minus=function (a,b) {
return a-b
}
index.js
代码语言:javascript复制import {add,minus} from "./expo.js"
add(1,2)
console.log("hello webpack")
执行命令 npx webpack,看到生成的文件/dist/main.js:
代码语言: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;
}
...
// 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");
})
...
打包后生成的代码,可以直接在浏览器的控制台运行,大致的意思就是,webpack实现了一个webpack_require来实现自己的模块化,把代码都缓存在installedModules里,代码文件以对象传递进来,key是路径,value是包裹的代码字符串,并且代码内部的require,都被替换成了webpack_require处理依赖模块的路径
如何自己实现一个简单的webpack打包流程呢?
实现步骤
- 基础配置,webpack会读取配置
- 找到入口模块
- 入口分析
- 分析依赖模块(拿到模块的路径)
- 分析内容(并对内容处理)
- 编译内容
- 依赖模块(递归找到依赖)
- 分析依赖模块(并对内容处理)
- 编译内容
- 生成bundle.js,代码可以直接在浏览器中运行
自己实现一个bundle.js
- 模块分析:读取入口文件,分析代码 新建文件./lib/webpack.js,其中利用了node的fs,读取文件内容,为了拿到文件中依赖,不推荐使用字符串截取,引入的模块名越多,就越麻烦,不灵活,推荐使用@babel/parser,这是babel7的工具,分析包括es6的内部的语法,返回一个ast抽象树 npm i @babel/parser --save
const fs = require('fs')//node的核心模块fs
constructor(options) {
console.log(options)
const {entry, output} = options
this.entry = entry
this.output = output
//存所有模块信息
this.modules = []
}
run() {//入口函数
const info = this.parse(this.entry)
console.log(info)
}
parse(entryFile) {
//! 分析入口模块的内容
const content = fs.readFileSync(entryFile, 'utf-8')
console.log(content)
//!分析出哪些是依赖?以及依赖的路径
const ast = parser.parse(content, {
sourceType: 'module'
})
console.log(ast.program.body)
}
新建build.js
代码语言:javascript复制//! 拿到webpack的配置文件
const options =require("./webpack.config.js")
const webpack=require('./lib/webpack')
//类实例化
new webpack(options).run()
执行node build.js,打印ast body部分的内容
代码语言:javascript复制[ Node {
type: 'ImportDeclaration',
start: 0,
end: 35,
loc: SourceLocation { start: [Position], end: [Position] },
specifiers: [ [Node], [Node] ],
source:
Node {
type: 'StringLiteral',
start: 24,
end: 35,
loc: [SourceLocation],
extra: [Object],
value: './expo.js' },
trailingComments: [ [Object] ] },
Node {
type: 'ExpressionStatement',
start: 44,
end: 52,
loc: SourceLocation { start: [Position], end: [Position] },
expression:
Node {
type: 'CallExpression',
start: 44,
end: 52,
loc: [SourceLocation],
callee: [Node],
arguments: [Array] } },
Node {
type: 'ExpressionStatement',
start: 54,
end: 82,
loc: SourceLocation { start: [Position], end: [Position] },
expression:
Node {
type: 'CallExpression',
start: 54,
end: 82,
loc: [SourceLocation],
callee: [Node],
arguments: [Array] },
trailingComments: [ [Object] ] } ]
可以看到,index.js中的三行代码分别被解析成导入、表达式、表达式
代码语言:javascript复制import {add,minus} from "./expo.js" //解析导入
add(1,2)解析成表达式
console.log("hello webpack")//解析成表达式