一. webpack
介绍
webpack
是前端模块构建工具,支持模块化规范解析处理。
代码示例如下
javascript
// utils/helloworld.js
export function helloworld() {
console.log('are you ok?')
}
// index.js
import { helloworld } from './utils/helloworld.js'
helloworld()
代码产物如下
javascript
;(function () {
// 依赖模块
const modules = {
'./src/index.js': function (require, module, exports) {
'use strict'
var _helloworld = require('./src/utils/helloworld.js')
;(0, _helloworld.helloworld)()
},
'./src/utils/helloworld.js': function (require, module, exports) {
'use strict'
Object.defineProperty(exports, '__esModule', {
value: true,
})
exports.helloworld = helloworld
function helloworld() {
console.log('are you ok?')
}
},
}
// 模块缓存
const cached = {}
// 模块执行方法
function require(moduleId) {
if (cached[moduleId]) return cached[moduleId]
const module = {
exports: {},
}
cached[moduleId] = module
const fn = modules[moduleId]
// 执行模块代码逻辑
fn(require, module, module.exports)
return module.exports
}
require('./src/index.js')
})()
二. 实现webpack
2.1 tinywebpack.config.js
该文件负责提供webpack
构建需要的配置,如入口依赖模块文件路径。
javascript
const path = require('path')
const resolvePath = (...paths) => path.join(__dirname, ...paths)
module.exports = {
entry: resolvePath('./src/index.js'),
output: {
filename: 'main.js',
path: resolvePath('./dist'),
},
}
2.2 bundle
该方法负责组织整个构建流程,具体逻辑如下:
- 获取配置文件
- 解析入口依赖模块,获取依赖模块,转换依赖模块代码
- 递归解析依赖模块,构建依赖图谱
- 生成代码,输出到指定文件
javascript
const config = require(`${process.cwd()}/tinywebpack.config.js`)
// 项目路径
const rootPath = process.cwd()
// 获取文件相对路径
function relativePath(filePath) {
return `./${path.relative(rootPath, filePath)}`
}
function bundle() {
// 依赖模块图谱
const moduleGraph = {}
const { entry } = config
// 解析入口依赖模块
parse(moduleGraph, entry)
// 生成代码
generate(entry, moduleGraph)
}
2.2.1 parse
转换依赖模块代码主要通过
babel
实现,读者可自行查阅官网了解,本文不再赘述。
该方法负责讲解依赖模块,如转换依赖模块代码,获取子依赖模块。
javascript
const babel = require('@babel/parser')
const traverse = require('@babel/traverse').default
const { transformFromAstSync } = require('@babel/core')
function parse(moduleGraph, filePath) {
// 如果依赖图谱中已存在说明已经解析过,不需要重复解析直接跳过即可
if (moduleGraph[filePath]) return
// 读取依赖模块文件内容
const code = fs.readFileSync(filePath, { encoding: 'utf-8' })
// 将代码转换成ast树
const ast = bable.parse(code, { sourceType: 'module' })
// 依赖模块
const dependencies = []
// 当前模块目录路径
const dirname = path.dirname(filePath)
// 遍历ast树,收集依赖模块
traverse(ast, {
ImportDeclaration(nodePath) {
const { node } = nodePath
const value = path.resolve(dirname, node.source.value)
node.source.value = relativePath(value)
dependencies.push(value)
},
})
// 转换代码
const result = transformFromAstSync(ast, code, {
presets: ['@babel/preset-env'],
})
const dependency = {
path: relativePath(filePath),
code: result.code,
}
moduleGraph[filePath] = dependency
// 递归依赖模块
for (let i = 0; i < dependencies.length; i++) {
parse(moduleGraph, dependencies[i])
}
}
2.2.2 generate
该方法负责生成依赖模块对应代码,具体逻辑如下:
- 提供
template
模版,注入require
方法 - 将依赖模块图谱转换成
modules
对象,key
是依赖模块文件路径,value
是函数,函数体即依赖模块代码 - 输出到指定文件
javascript
function generate(entry, moduleGraph) {
const template = `
;(function () {
// 依赖模块
const modules = {
${Object.keys(moduleGraph)
.map(
filePath => `'${moduleGraph[filePath].path}': function (require, module, exports) {
${moduleGraph[filePath].code}
}`,
)
.join(',\n')}
}
// 模块缓存
const cached = {}
// 模块执行方法
function require(moduleId) {
if (cached[moduleId]) return cached[moduleId]
const module = {
exports: {},
}
cached[moduleId] = module
const fn = modules[moduleId]
// 执行模块代码逻辑
fn(require, module, module.exports)
return module.exports
}
require('${moduleGraph[entry].path}')
})()
`
const { output } = config
const exist = fs.existsSync(output.path)
if (!exist) fs.mkdirSync(output.path)
fs.writeFile(`${output.path}/${output.filename}`, template, err => {
if (err) {
console.log(err)
return
}
console.log('success')
})
}
三. 总结
webpack
整个构建流程核心两步,一是构建依赖图谱,二是输出代码产物,值得注意的是模块化规范的解析处理流程。代码仓库
创作不易,如果文章对你有帮助,那点个小小的赞吧,你的支持是我持续更新的动力!