该工具可以实现以下两个功能

  • ES6 转换为 ES5
  • 支持在 JS 文件中 import CSS 文件

通过这个工具的实现,大家可以理解到打包工具的原理到底是什么

实现

因为涉及到 ES6ES5,所以我们首先需要安装一些 Babel 相关的工具

yarn add babylon babel-traverse babel-core babel-preset-env

接下来我们将这些工具引入文件中

const fs = require('fs')
const path = require('path')
const babylon = require('babylon')
const traverse = require('babel-traverse').default
const { transformFromAst } = require('babel-core')

首先,我们先来实现如何使用 Babel 转换代码

function readCode(filePath) {// 读取文件内容const content = fs.readFileSync(filePath, 'utf-8')// 生成 ASTconst ast = babylon.parse(content, {sourceType: 'module'})// 寻找当前文件的依赖关系const dependencies = []traverse(ast, {ImportDeclaration: ({ node }) => {dependencies.push(node.source.value)}})// 通过 AST 将代码转为 ES5const { code } = transformFromAst(ast, null, {presets: ['env']})return {filePath,dependencies,code}
}
  • 首先我们传入一个文件路径参数,然后通过 fs 将文件中的内容读取出来
  • 接下来我们通过 babylon 解析代码获取 AST,目的是为了分析代码中是否还引入了别的文件
  • 通过 dependencies 来存储文件中的依赖,然后再将 AST 转换为 ES5 代码
  • 最后函数返回了一个对象,对象中包含了当前文件路径、当前文件依赖和当前文件转换后的代码

接下来我们需要实现一个函数,这个函数的功能有以下几点

  • 调用 readCode 函数,传入入口文件
  • 分析入口文件的依赖
  • 识别 JSCSS 文件
function getDependencies(entry) {// 读取入口文件const entryObject = readCode(entry)const dependencies = [entryObject]// 遍历所有文件依赖关系for (const asset of dependencies) {// 获得文件目录const dirname = path.dirname(asset.filePath)// 遍历当前文件依赖关系asset.dependencies.forEach(relativePath => {// 获得绝对路径const absolutePath = path.join(dirname, relativePath)// CSS 文件逻辑就是将代码插入到 `style` 标签中if (/\.css$/.test(absolutePath)) {const content = fs.readFileSync(absolutePath, 'utf-8')const code = `const style = document.createElement('style')style.innerText = ${JSON.stringify(content).replace(/\\r\\n/g, '')}document.head.appendChild(style)`dependencies.push({filePath: absolutePath,relativePath,dependencies: [],code})} else {// JS 代码需要继续查找是否有依赖关系const child = readCode(absolutePath)child.relativePath = relativePathdependencies.push(child)}})}return dependencies
}
  • 首先我们读取入口文件,然后创建一个数组,该数组的目的是存储代码中涉及到的所有文件
  • 接下来我们遍历这个数组,一开始这个数组中只有入口文件,在遍历的过程中,如果入口文件有依赖其他的文件,那么就会被 push 到这个数组中
  • 在遍历的过程中,我们先获得该文件对应的目录,然后遍历当前文件的依赖关系
  • 在遍历当前文件依赖关系的过程中,首先生成依赖文件的绝对路径,然后判断当前文件是 CSS 文件还是 JS 文件
  • 如果是 CSS 文件的话,我们就不能用 Babel 去编译了,只需要读取 CSS 文件中的代码,然后创建一个 style 标签,将代码插入进标签并且放入 head 中即可
  • 如果是 JS 文件的话,我们还需要分析 JS 文件是否还有别的依赖关系
  • 最后将读取文件后的对象 push 进数组中
  • 现在我们已经获取到了所有的依赖文件,接下来就是实现打包的功能了
function bundle(dependencies, entry) {let modules = ''// 构建函数参数,生成的结构为// { './entry.js': function(module, exports, require) { 代码 } }dependencies.forEach(dep => {const filePath = dep.relativePath || entrymodules += `'${filePath}': (function (module, exports, require) { ${dep.code} }),`})// 构建 require 函数,目的是为了获取模块暴露出来的内容const result = `(function(modules) {function require(id) {const module = { exports : {} }modules[id](module, module.exports, require)return module.exports}require('${entry}')})({${modules}})`// 当生成的内容写入到文件中fs.writeFileSync('./bundle.js', result)
}

这段代码需要结合着 Babel 转换后的代码来看,这样大家就能理解为什么需要这样写了

// entry.js
var _a = require('./a.js')
var _a2 = _interopRequireDefault(_a)
function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj }
}
console.log(_a2.default)
// a.js
Object.defineProperty(exports, '__esModule', {value: true
})
var a = 1
exports.default = a

Babel 将我们 ES6的模块化代码转换为了 CommonJS的代码,但是浏览器是不支持 CommonJS 的,所以如果这段代码需要在浏览器环境下运行的话,我们需要自己实现 CommonJS 相关的代码,这就是 bundle 函数做的大部分事情。

接下来我们再来逐行解析 bundle 函数

  • 首先遍历所有依赖文件,构建出一个函数参数对象

  • 对象的属性就是当前文件的相对路径,属性值是一个函数,函数体是当前文件下的代码,函数接受三个参数 moduleexportsrequire

    module 参数对应 CommonJS 中的 module
    exports 参数对应 CommonJS 中的 module.export
    require 参数对应我们自己创建的 require 函数

  • 接下来就是构造一个使用参数的函数了,函数做的事情很简单,就是内部创建一个 require函数,然后调用 require(entry),也就是 require('./entry.js'),这样就会从函数参数中找到 ./entry.js 对应的函数并执行,最后将导出的内容通过 module.export 的方式让外部获取到

  • 最后再将打包出来的内容写入到单独的文件中

如果你对于上面的实现还有疑惑的话,可以阅读下打包后的部分简化代码

(function(modules) {function require(id) {// 构造一个 CommonJS 导出代码const module = { exports: {} }// 去参数中获取文件对应的函数并执行modules[id](module, module.exports, require)return module.exports}require('./entry.js')
})({'./entry.js': function(module, exports, require) {// 这里继续通过构造的 require 去找到 a.js 文件对应的函数var _a = require('./a.js')console.log(_a2.default)},'./a.js': function(module, exports, require) {var a = 1// 将 require 函数中的变量 module 变成了这样的结构// module.exports = 1// 这样就能在外部取到导出的内容了exports.default = a}// 省略
})

虽然实现这个工具只写了不到 100 行的代码,但是打包工具的核心原理就是这些了

  • 找出入口文件所有的依赖关系
  • 然后通过构建 CommonJS 代码来获取 exports 导出的内容

前端实现小型打包工具相关推荐

  1. 前端开发--webpack打包工具

    #博学谷IT学习技术支持# 目录 webpack基本概念 webpack的安装 webpack的配置 入口和出口相关配置 插件-自动生成html文件 加载器 - 处理css文件问题 加载器 - 处理图 ...

  2. vite(一)前端打包工具发展史

    现如今前端打包工具最火的无疑是vite!但是vite也不是凭空产生的,而是踩着前人肩膀发展起来的.所以在学习vite之前,有必要了解前端打包工具发展史. 前端打包工具发展史分为哪些阶段? 每一次前端打 ...

  3. Webpack前端打包工具

    1.概述 Webpack 是一个前端资源的打包工具,它可以将js.image.css等资源当成一个模块进行打包. 中文官方网站:https://www.webpackjs.com/ 打包的作用: - ...

  4. webpack打开项目命令_webpack前端模块打包工具基本使用的详细记录(一)

    webpack简介 我们都知道,Webpack 是当下最热门的前端资源模块化管理和打包工具.它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源.还可以将按需加载的模块进行代码分隔,等 ...

  5. 前端自动化打包工具--webpack

    前端自动化打包工具–webpack 背景 记得2004年的时候,互联网开发就是做网页,那时也没有前端和后端的区分,有时一个网站就是一些纯静态的html,通过链接组织在一起.用过Dreamweaver的 ...

  6. 前端打包工具webpack和Vite

    我们见证了 webpack.Rollup 和 Parcel 等工具的变迁,它们极大地改善了前端开发者的开发体验. 但当我们开始构建越来越大型的应用时,通常需要很长时间才能启动开发服务器,文件修改后的效 ...

  7. Webpack前端资源加载/打包工具

    文章目录 一.Webpack 1.什么是Webpack 2.Webpack安装 2.1全局安装 2.2安装后查看版本号 3.创建项目 3.1初始化项目 3.2创建src文件夹 3.3 src下创建co ...

  8. 前端打包工具rollup、webpack、vite的区别

    前端打包工具rollup.webpack.vite的区别 文章目录 前端打包工具rollup.webpack.vite的区别 一.结论 二.rollup 三.webpack 1. 核心概念 2. 重要 ...

  9. 浅谈前端项目打包工具webpack和gulp

    webpack:一个模块打包工具(更适合单页面spa模块开发) Webpack更侧重于模块打包,把开发中的所有资源(图片.js文件.css文件等)看成模块.Webpack是通过loader(加载器)和 ...

最新文章

  1. 谈 JavaScript 浮点数计算精度问题(如0.1+0.2!==0.3)
  2. DELL服务器利用OMSA修改BIOS设置
  3. 【嵌入式】C语言高级编程-长度为0的数组(05)
  4. ng-repeat part2 - How li ng-repeat=nameF in Ionames{{nameF}}/li is parsed
  5. rabbitmq 查看消费者_RabbitMQ 和 Kafka 的比较
  6. How to Create Your Own CentOS Vagrant Box
  7. solr 高并发_预防帕金森病的运动并发症,比治疗更重要!
  8. mysql数据库产生的背景_MySql路途之mysql背景
  9. 【Pygame】在 Pygame 屏幕中添加文字
  10. Mac otool替代readelf命令
  11. 20170830 - A - Java IO操作
  12. MATLAB 棋类小游戏合集(围棋,六子棋,走四棋,九子棋,含禁手五子棋等等)
  13. html中内部css样式怎么写,HTMLCSS基础-内联样式和内部样式表
  14. html链接打开excel表格,Excel表格中怎么设置超链接的形式打开其他Sheet工作簿
  15. cygwin里安装apt-cyg网络工具及使用
  16. 泰山OFFICE技术讲座:FreeType获取kerning函数FT_Get_Kerning研究
  17. 编辑距离算法原理及其实现
  18. 数据库的创建及使用方法
  19. SecoClient在win10系统中连接失败解决方案
  20. OKPay遭到DDoS攻击,本周进一步威胁网络攻击

热门文章

  1. (转)最优化问题简介
  2. Winform中pictureBox控件SizeMode属性
  3. virt-install 使用说明
  4. java中换行字符怎么用?
  5. python简单程序
  6. LFWA人脸属性数据集解析
  7. weblogic(一).简介与安装
  8. windows安装Rocket因为JAVAHOME空格导致找不到加载类问题
  9. 中国设计在重庆丨5G+VR直播直击秋冬风尚大秀
  10. 微信小程序开发—引用公共js里的方法