时间:2023-06-10 08:45:02 | 来源:网站运营
时间:2023-06-10 08:45:02 来源:网站运营
Webpack(2) - 如何把多个模块打包成一个文件:Webpack(1)- Babel 是如何转译我们的代码的<script type="module">
2.但是使用 import/export 会带来页面请求过多的问题,一个文件会产生一个请求。import a from './a.js'import b from './b.js'console.log(a.getB())console.log(b.getA())
a.jsimport b from './b.js'const a = { value:'a', getB:()=> b.value + ' from a.js'}export default a
b.jsimport a from './a.js'const b = { value: 'b', getA: ()=> a.value + ' from b.js'}
可以看到 index 依赖 a 和 b,a 和 b 又相互依赖。// bundler.jsconst {readFileSync} = require('fs')const {resolve} = require('path')// 使用 HashMap 存储依赖关系const depRelation = {}function collectCodeAndDeps(filepath){ // 使用文件的项目路径做 key,如 index.js。getProjectPath 内部实现略 const key = getProjectPath(filepath) // 防止循环引用时会造成的无限调用 if(Object.keys(depRelation).includes(key)){ return } // 获取文件内容,并存放 const code = readFileSync(filepath).toString() depRelation[key] = {deps:[], code} // 将代码转换 AST,方便后续操作 const ast = parse(code,{sourceType:'module'}) traverse(ast,{ enter:path => { if(path.node.type === 'ImportDeclaration'){ // 获取依赖文件的绝对路径 const depAbsolutePath = resolve(dirname(filepath), path.node.source.value) // 获取在项目中的路径并保存 const depProjectPath = getProjectPath(depAbsolutePath) depRelation[key].deps.push(depProjectPath) // 继续查找依赖 collectCodeAndDeps(depAbsolutePath) } } }) }
depRelation 是一个对象,保存了文件的依赖关系。// bundler.jsimport babel from '@babel/core'... // 获取文件内容,并存放const code = readFileSync(filepath).toString()const {code: es5Code} = babel.transform(code,{ presets: ['@babel/preset-env']})depRelation[key] = {deps:[], code: es5Code}...
现在依赖也收集好了,代码也转译好了,只剩下如何将代码都写入到一个文件并执行了,这时我们先来看看转译后的代码,看看如何执行:// a.js"use strict";Object.defineProperty(exports, "__esModule", {value: true}); // 设置 esModuleexports["default"] = void 0; var _b = _interopRequireDefault(require("./b.js")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj };}var a = { value: 'a', getB: function getB() { return _b["default"].value + ' from a.js'; }};var _default = a; exports["default"] = _default;
其中 require 和 exports 都不知道从哪来的,这是我们在执行这段代码时需要实现的,至于为什么叫这两个名字,是根据 CommonJS 规范命名的,只需要遵守即可。// bundler.jsimport { writeFileSync } from 'fs'...function generateCode(){ let code = '' code += 'var depRelation = [' + depRelation.map(item => { const { key, deps, code } = item return `{ key: ${JSON.stringify(key)}, deps: ${JSON.stringify(deps)}, code: function(require, module, exports){ ${code} } }` }).join(',') + '];/n' return code}// 将生成的代码写入文件writeFileSync('dist.js', generateCode())
这里还有一个细节,depRelation 变量的类型由对象改为了数组,因为对象是无序的,无法确定入口文件时哪个,数组则将入口文件放在第一个:// bundler.js...const depRelation = []function collectCodeAndDeps(filepath){ ... // depRelation[key] = {deps:[], code: es5Code} const item = { key, deps: [], code: es5Code } depRelation.push(item) ... travers(ast, { enter:path=>{ ... // depRelation[key].deps.push(depProjectPath) item.deps.push(depProjectPath) ... } }) ...}
接下来调用执行的函数:// 用 HashMap 将引入过的模块保存var modules = {};// 执行入口文件execute(depRelation[0].key);function execute(key) { // 判断是否已存在 if (modules[key]) { return modules[key]; } var item = depRelation.find((i) => i.key === key); if (!item) { throw new Error(`${key} is not found`); } var pathTokey = (path) => { var dirname = key.substring(0, key.lastIndexOf("/") + 1); var projectPath = (dirname + path) .replace(//.///g, "") .replace(//////, "/"); return projectPath; }; // 实现 require var require = (path) => { return execute(pathTokey(path)); }; // 初始化 exports modules[key] = { __esModule: true, }; var module = { export: modules[key] }; // 兼容操作 item.code(require, module, module.export); // 执行代码 // 返回最终模块值 return modules[key];}
执行路线是,先 execute('index.js') => 发现 require('./a.js') => execute('a.js') => 发现 require('./b.js') => execute('b.js'),最后还是用模板字符串,一起写到文件中:function generateCode(){ let code = '' code += 'var depRelation = [' + depRelation.map(item => { const { key, deps, code } = item return `{ key: ${JSON.stringify(key)}, deps: ${JSON.stringify(deps)}, code: function(require, module, exports){ ${code} } }` }).join(',') + '];/n' code += 'var modules = {};/n' code += `execute(depRelation[0].key)/n` code += ` function execute(key) { if (modules[key]) { return modules[key] } var item = depRelation.find(i => i.key === key) if (!item) { throw new Error(/`/${item} is not found/`) } var pathToKey = (path) => { var dirname = key.substring(0, key.lastIndexOf('/') + 1) var projectPath = (dirname + path).replace(////.//////g, '').replace(////////////, '/') return projectPath } var require = (path) => { return execute(pathToKey(path)) } modules[key] = { __esModule: true } var module = { exports: modules[key] } item.code(require, module, module.exports) return modules[key] } ` return code}
最后使用 node 命令行执行 dist.js,成功打印:关键词:打包,文件