写了个插件,给代码里藏东西,线上可用!

插件用途

大家好,我是石小石!


相信你一定也遇到过这样的场景:有时候我们想在代码中"夹带一点私货",比如留下一些自定义信息或标识,如部署文档、关联资料、构建时间等信息,让这些内容跟着上线代码一并部署。当然,这些信息的查看方式只有我们知道,这样的话,代码开发会方便不少!

基于这个想法,我开发了一个小插件,能够自动记录当前代码的 Git 信息,还可以注入任意自定义内容,同时兼容 Webpack 和 Vite 项目。

使用这个插件后,只需要在浏览器控制台输入 info,就能轻松查看代码中的构建信息或你的专属小彩蛋啦!

使用Trae实现插件

插件原理

要实现插件的效果,我们的大致思路如下:

  • 打包或编译时,通过node获取当前的git信息
  • 打包结束后,将获取的git信息注入打包后的代码中
  • 在浏览器控制台,通过特殊方式展示打包信息
js 复制代码
├── 核心逻辑层
│   ├── Git信息采集(借助node能力)
│   ├── 信息处理与注入(通过webapck或vite钩子实现)
├── 构建工具适配层(判断构建工具类型)
│   ├── Webpack插件实现
│   └── Vite插件实现
└── 输出呈现层
    ├── 控制台样式优化(console美化)
    └── 信息展示机制(window劫持)

使用Trae-Builder快速搭建项目

在开始项目之前,我们先创建dist-info文件夹,然后用Trae打开项目。

要开发一个webpack或vite插件,我们的项目应该包含下面的结构:

js 复制代码
├── index.js           // 插件核心逻辑
├── README.md          // 插件使用说明
├── rollup.config.js   // 打包配置
├── package.json       // 依赖管理

Trae提供了一个Trae-Builder模式 ,可以轻松完成从零到一的项目构建。在 Builder 模式下,我们对代码文件的任何更改都会自动保存。

我们可以借助Trae-Builder直接在空项目中创建上述项目结构

Trae-Builder运行完毕后,一个完成的项目就被快速创建出来了。现在,我们只需要审查项目,保留删减代码即可。如下图,Trae-Builder生成的代码基本完全满足我的需求,直接点击【接受】即可。

至此,我们借助Trae-Builder就快速实现了项目的搭建。

使用Trae实现核心代码

  • 代码基本架构

根据插件的需求,我们的index.js基本架构应该如下:

js 复制代码
const isVite =
  process.argv.some((arg) => arg.includes("vite")) ||
  process.env.VITE_ !== undefined;

function DistInfoPlugin() {
  try {
    //获取js内容(内部包含git信息)
    const jsContent = getJsContent();
    // 根据脚手架类型,执行不同js注入逻辑
    return isVite ? vitePlugin(jsContent) : webpackPlugin(jsContent);
  } catch (err) {
    console.log("DistInfoPlugin", err);
  }
}
// 确保插件作为默认导出
module.exports.default = DistInfoPlugin;
module.exports = DistInfoPlugin;

当项目打包时,插件会判断当前脚手架类型,执行webapck或vite对应的钩子函数。打包过程中,node会获取当前的git信息,并生成js内容(getJsContent)。在打包结束时,对应的webapck或vite钩子激活,生成的js脚本被注入在打包文件的index.html中。

  • git信息的获取

getJsContent的第一步应该是通过node获取git信息,我们通过Trae-Builder让Trae帮我们完善代码。

Trae-Builder生成的代码基本可用,我们接受修改后的代码,然后做适当修改:

js 复制代码
const { execSync } = require('child_process') //同步子进程

const getGitInfo = (gitMeta) => {
  try {
    return execSync(gitMeta)?.toString().trim();
  } catch {
    return "--";
  }
};

const getJsContent = () => {
  const consoleList = [
    {
      description: "提交人员",
      value: getGitInfo("git show -s --format=%cn"),
    },
    {
      description: "版本信息",
      value: getGitInfo("git show -s --format=%h"),
    },
    {
      description: "代码分支",
      value: getGitInfo("git symbolic-ref --short -q HEAD"),
    },
    {
      description: "提交说明",
      value: getGitInfo("git show -s --format=%s"),
    },
    {
      description: "提交时间",
      value: getGitInfo("git show -s --format=%cd"),
    },
  ];
};

上述代码的核心功能是通过 child_process 模块执行 Git 命令,并提取 Git 信息。

child_process 是 Node.js 的一个核心模块,允许你、我们在 Node.js 中启动子进程并与之进行交互。execSync(gitMeta) 执行传入的 Git 命令,返回的是一个 Buffer 对象,通过 .toString() 将其转换为字符串。

由于git show生成的时间是原始数据类型,我们可以将时间进行格式化,方便阅读。

  • 网页端的信息输出与js生成

把代码的打包信息直接输出在控制台是不安全的,我们并不希望所有人打开控制台都可以看到打包信息。参考vue2的数据劫持原理,我们可以对window对象的特殊属性进行数据劫持:

js 复制代码
Object.defineProperty(window, 'info', {
  get: function() {
    // console.log("window的info属性被访问了")
  }
})

根据上述代码的效果,当我们读取window的info属性时(相当于控制台输入info),会触发getter的回调函数。

那么,如果我们在get的方法中输出所有的git信息,然后将这段js注入打包后的index.html,就可以实现如下效果了

在控制台输入"info",控制台打印代码的git信息。

根据上述原理,我们的getJsContent方法完善如下:

js 复制代码
const getJsContent = () => {

  const consoleList = [
    // ...
  ];

  return `(function(window){
    const BUILD_INFO_CONSOLE_LIST = ${consoleList}
    Object.defineProperty(window, 'info', {
        get: function() {
            console.clear();
            BUILD_INFO_CONSOLE_LIST.forEach(res=>{
                console.log (res.description, res.value) 
            })
        }
    })
})(window)`;
  
};
  • 打包完成时的js脚本注入

我们插件的最后一步就是将生成的js脚本,在打包完毕后插入项目的index.html中实现注入,这一过程的实现要根据不同的脚手架类型做不同处理。

js 复制代码
function webpackPlugin(jsContent) {
	const createAsset = (content) => {
		return {
			source: () => content,
			size: () => content.length
		}
	}
	return {
		apply(compiler) {
			compiler.hooks.emit.tap('distInfoPlugin', (compilation) => {
				// 获取 HTML 和 JS 文件的路径
				const jsAsset = Object.keys(compilation.assets).find((assetPath) => assetPath.endsWith('.js'))
				const htmlAsset = Object.keys(compilation.assets).find((assetPath) => assetPath.endsWith('.html'))

				if (!jsAsset || !htmlAsset) return

				// 生成唯一的 JS 文件路径
				const jsPathParts = jsAsset.split('/')
				const timestamp = Date.now().toString()
				jsPathParts[jsPathParts.length - 1] = `dist-info-${timestamp}.js`
				const jsFilePath = jsPathParts.join('/')

				// 修改 HTML 文件内容,插入新的 JS 文件路径
				const originalHtmlContent = compilation.assets[htmlAsset]?.source()
				if (!originalHtmlContent) return

				const updatedHtmlContent = originalHtmlContent.replace(
					/(<head[^>]*>)/,
					`$1<script src="${compiler.options.output.publicPath}${jsFilePath}"></script>`
				)

				// 更新 HTML 文件内容到编译输出
				compilation.assets[htmlAsset] = createAsset(updatedHtmlContent)

				compilation.assets[jsFilePath] = createAsset(jsContent)
			})
		}
	}
}

上面的代码实现了一个自定义的 Webpack 插件,用于在构建时处理 HTML 和 JavaScript 文件的输出,并动态地插入一个新的 JavaScript 文件到 HTML 中。

上面的代码使用到了webpack插件开发的一些基础知识,如果有疑问,可以参考博主的webpack插件开发文章

  • vite脚本插入

由于vite提供了transformIndexHtml钩子

将js注入到打包后的html中是非常容易的

js 复制代码
function vitePlugin(jsContent) {
  return {
    name: "vite-plugin-dist-info",
    transformIndexHtml(html) {
      const scriptTag = `<script>${jsContent}</script>`;
      return html.replace(/</body>/, `${scriptTag}</body>`);
    },
  };
}

如果你对vite的插件有疑问,可以参考博主的vite插件入门教程

  • 效果验证

插件开发完毕后,我们可以在任意一个webpck5或vite项目中引入index.js中的代码,查看效果。

  • webpack项目

如图,在webpack项目中引入代码(distInfo就是开发的插件代码)

然后启动项目,打开控制台,输入"info"可以看到,打包的信息已经实现了。

  • vite项目

如图,在vite项目中引入代码(distInfo就是开发的插件代码)

启动项目后,输入指令"info"也可以看到git信息。

代码优化

通过上述的代码,我们已经实现了一个功能完备的网站打包信息追踪插件,但它还不完善,以下是一些可以改进的地方:

  • 信息加密

生成的js脚本是明文的,这意味着用户的git信息也会被明文展示,这显然是不安全的。

为了解决上述问题,我们可以对生成的信息在编译时加密,运行时解密。

  • console美化

直接通过console.log输出的信息不够醒目,在控制台与普通打印文本不好区分。我们可以通过重写log方法实现输出信息的美化:

js 复制代码
function log (description, value) {
  console.log(
    "%c 信息名称 %c 信息值 ",
    "background:#ff4d4f;border:1px solid #ff4d4f; padding: 1px; border-radius: 2px 0 0 2px; color: #fff",
    "border:1px solid #ff4d4f; padding: 1px; border-radius: 0 2px 2px 0; color: #ff4d4f",
  )
}

参考博主的console.log的美化教程

  • 增加配置项

根据基础代码,我们只能通过在控制台输入"info"触发信息打印,而且不能够自定义输出信息。因此,我们可以给BuildInfoPlugin插件增加options配置项即可。

js 复制代码
function BuildInfoPlugin(options = {}) {
    try {
        const jsContent = getJsContent(options);
        return isVite ? vitePlugin(jsContent) : webpackPlugin(jsContent);
    } catch (err) {
        console.log('BuildInfo', err);
    }
}

篇幅问题,不在此文章中具体展示优化过程,感兴趣的同学可以直接fork源码。

源码地址:github.com/1139874527/...

参与项目与代码贡献

本文中提到的插件已经基本完善,并且稳定,可以供大家使用。目前,插件已发布至npm,欢迎安装使用。dist-info是一个简单而值得学习的项目,它仍有不少优化空间。如果你有兴趣参与维护并成为贡献者,欢迎🌟star并提交PR(合理必通过)。

插件使用地址:www.npmjs.com/package/dis...

github仓库地址:github.com/1139874527/...

相关推荐
夕水1 小时前
自动化按需导入组件库的工具rust版本完成开源了
前端·rust·trae
用户4099322502123 小时前
FastAPI与Tortoise-ORM模型配置及aerich迁移工具
后端·ai编程·trae
Goboy5 小时前
用Trae,找初恋,代码写人生,Trae圆你初恋梦。
llm·trae
星际码仔5 小时前
让大模型“活在当下”: 你必须要了解的 Context7 MCP
ai编程·mcp·trae
酱酱们的每日掘金20 小时前
用了 Trae 后,感觉离京东外卖不远了、Cursor 的系统级提示词逆向、前端也需要了解的 mcp 知识-AI Coding 周刊第 6 期
人工智能·ai编程·trae
用户4099322502121 天前
异步IO与Tortoise-ORM的数据库
后端·ai编程·trae
沉默王贰1 天前
使用Cursor 打造了一款记账APP
ai编程·cursor·trae
不客观说1 天前
豆包MarsCode实现贪吃蛇
trae
编程指北1 天前
字节 Trae 最新更新,支持 MCP 和智能体,最新一手实测!
前端·trae