webpack打包思路
- 读取配置 入口(从哪个文件开始分析?) 出口 (放到什么位置?叫什么名字?) 其他 暂时忽略...
- 入口函数,run开始编=》chunk
chunk包含了模块的依赖关系、依赖图谱
从入口文件开始,
- 进入模块,
- 处理模块依赖
进入依赖的模块、处理依赖模块的依赖、处理依赖模块内容 所有依赖模块递归处理
- 处理模块内容
处理文件内容 借助babel工具,帮助我们把内容编译成AST语法数,提取模块路径 借助babel语法转义
创建 生成bundle文件
依赖图普为实参(对象格式) webpack启动函数, 配备上下文中,require和exports
实操
初始化工程
代码语言:javascript复制npm init -y
根目录创建webpack.config.js
代码语言:javascript复制//webpack基础配置项
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'main.js'
},
mode: 'develoment'
}
根目录下创建src目录,./src/目录下创建index.js、a.js、b.js、c.js
代码语言:javascript复制// index.js
import str1 from './a.js'
import str2 from './b.js'
console.log('hellow' str1 str2);
// a.js
const a = 'aaa你好'
export default a
// b.js
import strc from './c.js'
const a = 'bbb好的' strc
export default a
// c.js
const c = 'ccc罗';
export default c;
根目录创建bundle.js,该文件主要写node代码,执行webpack打包脚本
代码语言:javascript复制// 读取配置
// 引入webpack
// 执行webpack run方法
const webpack = require('./lib/webpack')
const webpackConfig = require('./webpack.config')
new webpack(webpackConfig).run()
根目录创建文件夹 lib,创建webpack.js 路径./lib/wepback.js webpack文件导出一个类
- constructor轻松获取到webpack的配置参数
- 这个类有一个run方法,首先执行this.parse解析入口文件
首先利用node.js的fs读取到入口文件的内容
代码语言:javascript复制const fs=require('fs')
class webpack {
constructor(options) {
this.$options = options
this.entry = options.entry;
this.output = options.output;
this.modules=[];//将来用来保存this.parse解析后模块
}
run() {
this.parse(this.entry)
}
parse(entryFile) {
const content = fs.readFileSync(entryFile, 'utf-8')
}
}
安装@babel/parser工具,解析静态代码为ast 语法树,Node节点类型,
代码语言:javascript复制npm install @babel/parser -S
代码语言:javascript复制//引入
const parser = require('@babel/parser')
// 使用api parser.parse
parse(entryFile) {
const content = fs.readFileSync(entryFile, 'utf-8')
const ast = parser.parse(content, {
sourceType: 'module'
})
console.log(ast)
}
//console.log(ast)打印结果
Node {
type: 'File',
start: 0,
end: 90,
loc: SourceLocation {
start: Position { line: 1, column: 0 },
end: Position { line: 3, column: 36 },
filename: undefined,
identifierName: undefined
},
range: undefined,
leadingComments: undefined,
trailingComments: undefined,
innerComments: undefined,
extra: undefined,
errors: [],
program: Node {
type: 'Program',
start: 0,
end: 90,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: undefined
},
range: undefined,
leadingComments: undefined,
trailingComments: undefined,
innerComments: undefined,
extra: undefined,
sourceType: 'module',
interpreter: null,
body: [ [Node], [Node], [Node] ],
directives: []
},
comments: []
}
安装 @babel/traverse
代码语言:javascript复制npm install --save @babel/traverse
遍历ImportDeclaration类型的节点,获取文件路径 node.source.path 利用path获取 当前文件目录 path.dirname(entry) 拼接得到依赖相对根目录的路径 保存两种路径在对象dependencies中
代码语言:javascript复制 const path=require('path')
//引入存在一点问题,要加default
const traverse = require('@babel/traverse').default
//方法
const dependencies = {}
traverse(ast, {
ImportDeclaration({ node }) {
const pathName = '.\' path.join(path.dirname(entryFile), node.source.value)
dependencies[node.source.value] = pathName;
}
})
安装@babel/core、@babel/preset-env
代码语言:javascript复制npm install @babel/core @babel/preset-env -S
处理内容,将ast转换成code
代码语言:javascript复制 // 处理内容,将ast转换成code
const code = transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
})
// 解析ast结果
{
metadata: {},
options: {
cloneInputAst: true,
babelrc: false,
configFile: false,
passPerPreset: false,
envName: 'development',
cwd: 'D:\learn-webpack\webpack-yuanli',
root: 'D:\learn-webpack\webpack-yuanli',
plugins: [
[Plugin], [Plugin], [Plugin], [Plugin],
[Plugin], [Plugin], [Plugin], [Plugin],
[Plugin], [Plugin], [Plugin], [Plugin],
[Plugin], [Plugin], [Plugin], [Plugin],
[Plugin], [Plugin], [Plugin], [Plugin],
[Plugin], [Plugin], [Plugin], [Plugin],
[Plugin], [Plugin], [Plugin], [Plugin],
[Plugin], [Plugin], [Plugin], [Plugin],
[Plugin], [Plugin], [Plugin], [Plugin],
[Plugin], [Plugin], [Plugin], [Plugin]
],
presets: [],
parserOpts: {
sourceType: 'module',
sourceFileName: undefined,
plugins: [Array]
},
generatorOpts: {
filename: undefined,
auxiliaryCommentBefore: undefined,
auxiliaryCommentAfter: undefined,
retainLines: undefined,
comments: true,
shouldPrintComment: undefined,
compact: 'auto',
minified: undefined,
sourceMaps: false,
sourceRoot: undefined,
sourceFileName: 'unknown'
}
},
ast: null,
code: '"use strict";n'
'n'
'var _a = _interopRequireDefault(require("./a.js"));n'
'n'
'var _b = _interopRequireDefault(require("./b.js"));n'
'n'
'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }n'
'n'
`console.log('hellow' _a["default"] _b["default"]);`,
map: null,
sourceType: 'script'
}
parse方法最终返回一个对象
代码语言:javascript复制return {
entryFile,//模块入口
dependencies,//模块依赖路径映射关系
code//模块内容
}
run方法中接收this.parse的返回值,push都this.modules中
代码语言:javascript复制 const info = this.parse(this.entry)
this.modules.push(info);
for (var i = 0; i < this.modules.length; i ) {
const module = this.modules[i];
// 该模块是否有依赖模块
if (module.dependencies) {
for (var j in module.dependencies) {
// 每次 执行这条语句,外层循环this.modules就变化,然后完美的完成所有依赖模块的解析
this.modules.push(this.parse(module.dependencies[j]))
}
}
}
将modules数组转化成对象
代码语言:javascript复制 // 将数组转成对象格式
let fileModules = {}
this.modules.forEach(item => {
fileModules[item.entryFile] = {
dependencies: item.dependencies,
code: item.code
}
})
// 对象转json,方便字符串拼接使用
代码语言:javascript复制 this.file(JSON.stringify(fileModules))
this.file实现,拼接输出目录路径,生成文件内容,eval执行moudel内部代码,为其提供require方法和exports 对象
代码语言:javascript复制 file(modules) {
// 生成代码内容,webpack启动函数
// 获取输入文件目录路径
const filePath = path.join(this.output.path, this.output.filename);
// 生成bundle代码
const bundle = `(function(modules){
function require(module){
function pathRequire(path){
return require(modules[module].dependencies[path])
}
const exports={};
(function(require,exports,code){
eval(code)
})(pathRequire,exports,modules[module].code)
return exports;
}
require('${this.entry}')
})(${modules})`
// 生成main.js,位置是dist目录
fs.writeFileSync(filePath, bundle, 'utf-8')
}
webpack.js最终结构
代码语言:javascript复制const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const { transformFromAst } = require('@babel/core')
// @babel/core
// babel-preset-env
class webpack {
constructor(options) {
this.$options = options
this.entry = options.entry;
this.output = options.output;
this.modules = [];
}
run() {
const info = this.parse(this.entry)
this.modules.push(info);
for (var i = 0; i < this.modules.length; i ) {
const module = this.modules[i];
// 该模块是否有依赖模块
if (module.dependencies) {
for (var j in module.dependencies) {
// 每次 执行这条语句,外层循环this.modules就变化,然后完美的完成所有依赖模块的解析
this.modules.push(this.parse(module.dependencies[j]))
}
}
}
// 将数组转成对象格式
let fileModules = {}
this.modules.forEach(item => {
fileModules[item.entryFile] = {
dependencies: item.dependencies,
code: item.code
}
})
// 对象转json,方便字符串拼接使用
this.file(JSON.stringify(fileModules))
}
parse(entryFile) {
// 读取文件内容
const content = fs.readFileSync(entryFile, 'utf-8')
// 解析文件内容为ast
const ast = parser.parse(content, {
sourceType: 'module'
})
// 获取依赖路径映射图
const dependencies = {}
traverse(ast, {
ImportDeclaration({ node }) {
const pathName = '.\' path.join(path.dirname(entryFile), node.source.value)
dependencies[node.source.value] = pathName;
}
})
// 处理内容,将ast转换成code
const { code } = transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
})
return {
entryFile,
dependencies,
code
}
}
file(modules) {
// 生成代码内容,webpack启动函数
// 获取输入文件目录路径
const filePath = path.join(this.output.path, this.output.filename);
// 生成bundle代码
const bundle = `(function(modules){
function require(module){
function pathRequire(path){
return require(modules[module].dependencies[path])
}
const exports={};
(function(require,exports,code){
eval(code)
})(pathRequire,exports,modules[module].code)
return exports;
}
require('${this.entry}')
})(${modules})`
// 生成main.js,位置是dist目录
fs.writeFileSync(filePath, bundle, 'utf-8')
}
}
module.exports = webpack;