15158846557 在线咨询 在线咨询
15158846557 在线咨询
所在位置: 首页 > 营销资讯 > 网站运营 > Webpack(2) - 如何把多个模块打包成一个文件

Webpack(2) - 如何把多个模块打包成一个文件

时间:2023-06-10 08:45:02 | 来源:网站运营

时间:2023-06-10 08:45:02 来源:网站运营

Webpack(2) - 如何把多个模块打包成一个文件:Webpack(1)- Babel 是如何转译我们的代码的

平时我们写代码的时候都是直接使用 ES6 或者更高级的语法,导入导出模块也是使用 import/export 语法,但是这样也会带来一些问题。

1.非现代浏览器(IE 全系等等)并不支持 import/export,而现代浏览器只需使用

<script type="module">2.但是使用 import/export 会带来页面请求过多的问题,一个文件会产生一个请求。

首先解决请求过多的问题,一个项目一般有一个入口文件,可以通过这个文件找到所有的依赖文件,并进行打包整合,先来尝试收集文件间的依赖关系:

假如 project_1 目录下有如下三个文件

index.js

import a from './a.js'import b from './b.js'console.log(a.getB())console.log(b.getA())a.js

import b from './b.js'const a = { value:'a', getB:()=> b.value + ' from a.js'}export default ab.js

import 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 是一个对象,保存了文件的依赖关系。

现在 code 中的代码还是未转译过的格式,很简单,只需要 babel 帮我们转译一下:

// 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,成功打印:

最终达到了我们要的效果:

  1. 多个文件打包成一个文件,减少请求量
  2. 兼容旧版模拟器
目前 bundler.js 的缺点:

  1. 无法打包 CommonJS 文件
  2. 无法打包 CSS 文件
  3. 无法设置入口文件名和出口文件名(目前为固定的 index.js 和 dist.js)



完。

关键词:打包,文件

74
73
25
news

版权所有© 亿企邦 1997-2025 保留一切法律许可权利。

为了最佳展示效果,本站不支持IE9及以下版本的浏览器,建议您使用谷歌Chrome浏览器。 点击下载Chrome浏览器
关闭