如何本身实现一个Webpack

前言

深刻理解 webpack 打包机制。node

模块分析

依赖环境 node.jswebpack

  1. 建立文件,最简单的字符串拼接
// index.js

import { message } from './message.js'
console.log(message)

// message.js

import { word } from './word.js'
export const message = `say ${ word }`

// word.js

export const word = 'hello world !'
复制代码
  1. 编写 bundler.js 文件

须要安装以下 babelweb

  1. @babel/parser: 分析咱们经过 fs.readFileSync 读取的文件内容,返回 AST (抽象语法树)
  2. @babel/traverse: 能够遍历 AST, 拿到必要的数据
  3. @babel/core: babel 核心模块,其有个transformFromAst方法,能够将 AST 转化为浏览器能够运行的代码
  4. @babel/preset-env: 将代码转化成 ES5 代码
const fs = require('fs');
const path = require('path')
const parser = require('@babel/parser'); // 帮助咱们分析字符串块,即咱们经过 fs 来读取文件时遇到的 import、export 语法
const traverse = require('@babel/traverse').default // traverse 采用的 ES Module 导出,咱们经过 requier 引入的话就加个 .default
const babel = require('@babel/core');

const moduleAnalyser = (filename) => {
	const content = fs.readFileSync(filename, 'utf-8');

	// parser 解析咱们的 content 以后会返回一个 AST (抽象语法树)
	const AST = parser.parse(content, {
		sourceType: "module"
	})

	// 依赖收集
	const dependencies = {};

	// 使用 traverse 来遍历 AST
	traverse(AST, {
		ImportDeclaration({ node }) { // 函数名是 AST 中包含的内容,参数是一些节点,node 表示这些节点下的子内容
			const dirname = path.dirname(filename); // 咱们从抽象语法树里面拿到的路径是相对路径,而后咱们要处理它,在 bundler.js 中才能正确使用
			const newDirname = './' + path.join(dirname, node.source.value).replace('\\', '/'); // 将dirname 和 获取到的依赖联合生成绝对路径
			dependencies[node.source.value] = newDirname; // 将源路径和新路径以 key-value 的形式存储起来
		}
	})

	// 将抽象语法树转换成浏览器能够运行的代码
	const { code } = babel.transformFromAst(AST, null, {
		presets: ['@babel/preset-env']
	})

	return {
		filename,
		dependencies,
		code
	}
}

// 建立依赖图谱函数, 递归遍历全部依赖模块
const makeDependenciesGraph = (entry) => {
	const entryModule = moduleAnalyser(entry)
	const graghArray = [ entryModule ]; // 首先将咱们分析的入口文件结果放入图谱数组中
	for (let i = 0; i < graghArray.length; i ++) {
		const item = graghArray[i];
		const { dependencies } = item; // 拿到当前模块所依赖的模块
		if (dependencies) {
			for ( let j in dependencies ) { // 经过 for-in 遍历对象
				graghArray.push(moduleAnalyser(dependencies[j])); // 若是子模块又依赖其它模块,就分析子模块的内容
			}
		}
	}

	const gragh = {}; // 将图谱的数组形式转换成对象形式
	graghArray.forEach( item => {
		gragh[item.filename] = {
			dependencies: item.dependencies,
			code: item.code
		}
	})
	return gragh;
}

const graghInfo = makeDependenciesGraph('./src/index.js')

console.log(graghInfo)

复制代码

下图graghArray的数据结构数组

  • 生成能够运行的代码

建立生成代码的函数,传入咱们的路口文件的路径,整合以前的代码 。浏览器

const generateCode = (entry) => {
	// 注意:咱们的 gragh 是一个对象,key是咱们全部模块的绝对路径,须要经过 JSON.stringify 来转换
	const gragh = JSON.stringify(makeDependenciesGraph(entry));

	// 咱们知道,webpack 是将咱们的全部模块放在闭包里面执行的,因此咱们写一个自执行的函数
	// 注意: 咱们生成的代码里面,都是使用的 require 和 exports 来引入导出模块的,而咱们的浏览器是不认识的,因此须要构建这样的函数
	return `
		(function( gragh ) {
			function require( module ) {
				// 相对路径转换成绝对路径的方法
				function localRequire(relativePath) {
					return require(gragh[module].dependencies[relativePath])
				}
				const exports = {};
				(function( require, exports, code ) {
					eval(code)
				})( localRequire, exports, gragh[module].code )

				return exports;
			}
			require('${ entry }')
		})(${ gragh })
	`;
}

const code = generateCode('./src/index.js');

console.log(code)
复制代码

总结

将代码整合以后,在命令行运行,会打印出一段代码,将其复制到浏览器中运行。bash

大功告成 !babel

相关文章
相关标签/搜索