webpack的定位是一个bundler,解决的是将多个JS模块打包成可以在浏览器上运行的代码。接下来我们将实现一个简易的bundler:由入口文件对代码进行打包,打包成可以在浏览器运行的代码。
整个演示项目的目录结构如下所示,其中src下的文件是bundler.js需要打包的代码。
├── bundler.js
├── package-lock.json
└── src
├── index.js
├── msg.js
└── word.js
src下各文件内容如下:
word.js
const word = ‘miniWebpack‘;
export default word;
msg.js
import word from ‘./word.js‘
const msg = `hello ${word}`;
export default msg;
index.js
import msg from ‘./msg.js‘
console.log(msg)
export default index;
要实现bundler,我们需要实现3部分功能:
moduleAnalyser:模块分析
makeDependenciesGraph:生成依赖图谱
generateCode:生成可执行代码
const moduleAnalyser = (fileName) => {
// 1.fs模块根据路径读取到了入口文件的内容
const content = fs.readFileSync(fileName, ‘utf-8‘);
// 2.使用@babel/parser将文件内容转换成抽象语法树AST
const ast = parser.parse(content, {
sourceType: ‘module‘
})
// 3.使用@babel/traverse遍历了AST ,对每个ImportDeclaration节点(保存的相对于入口文件的路径)做映射,把依赖关系拼装在 dependencies对象里
let dependencies = {};
traverse(ast, {
ImportDeclaration({ node }) {
const dirName = path.dirname(fileName);
const newFile = ‘./‘ + path.join(dirName, node.source.value);
// key是相对于当前模块的路径,value为相对于bundler.js的路径。
dependencies[node.source.value] = newFile;
}
})
// 4.使用@babel/core结合@babel/preset-env预设,将AST转换成了浏览器可以执行的代码
const { code } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
})
return {
fileName,
dependencies,
code
}
}
模块分析流程图如下:
调用console.log(moduleAnalyser(‘./src/index.js‘)),可以在控制台打印出以下内容:
{ fileName: ‘./src/index.js‘,
dependencies: { ‘./msg.js‘: ‘./src/msg.js‘ },
code: ‘"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports["default"] = void 0;\n\nvar _msg = _interopRequireDefault(require("./msg.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nconsole.log(_msg["default"]);\nvar _default = index;\nexports["default"] = _default;‘ }
import语法已经变成一个require函数了,export语法,也变成了在给一个exports变量赋值。
调用moduleAnalyser(‘./src/index.js‘)拿到了入口文件的dependencies映射,接下来再把入口文件的依赖路径再一次做模块分析,再把依赖模板的依赖路径再一次做模块分析...... 其实就是广度优先遍历,可以很轻松得到这次打包所有需要的模块的分析结果。
//生成依赖图谱
const makeDependenciesGraph = (entry) => {
//entryModule:入口文件的dependencies映射
const entryModule = moduleAnalyser(entry);
//graphArray:图谱动态数组,初始只有一个元素entryModule
const graphArray = [entryModule];
for (let i = 0; i < graphArray.length; i++) {
const item = graphArray[i];
//dependencies:当前模块的dependencies映射
const { dependencies } = item;
//如果当前模块有依赖文件,则遍历dependencies,调用moduleAnalyser,对依赖文件进行模板分析
if (dependencies) {
for (let j in dependencies) {
graphArray.push(moduleAnalyser(dependencies[j]))
}
}
}
//graph:遍历graphArray生成更利于打包使用的graph。其中key为fileName,value为dependencies和code
const graph = {};
graphArray.forEach(item => {
graph[item.fileName] = {
dependencies: item.dependencies,
code: item.code
}
})
return graph;
}
生成依赖图谱流程图如下:
调用console.log(makeDependenciesGraph(‘./src/index.js‘)),可以在控制台打印出以下内容
{ ‘./src/index.js‘:
{ dependencies: { ‘./msg.js‘: ‘./src/msg.js‘ },
code: ‘"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports["default"] = void 0;\n\nvar _msg = _interopRequireDefault(require("./msg.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nconsole.log(_msg["default"]);\nvar _default = index;\nexports["default"] = _default;‘ },
‘./src/msg.js‘:
{ dependencies: { ‘./word.js‘: ‘./src/word.js‘ },
code: ‘"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports["default"] = void 0;\n\nvar _word = _interopRequireDefault(require("./word.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nvar msg = "hello ".concat(_word["default"]);\nvar _default = msg;\nexports["default"] = _default;‘ },
‘./src/word.js‘:
{ dependencies: {},
code: ‘"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports["default"] = void 0;\nvar word = \‘miniWebpack\‘;\nvar _default = word;\nexports["default"] = _default;‘ } }
我们需要开始生成最终可运行的代码了。
为了不污染全局作用域,我们使用立即执行函数来包装我们的代码,将依赖图谱graph作为参数传入:
原文:https://www.cnblogs.com/superlizhao/p/13653499.html