esbuild 构建油猴脚本

更新日期: 2022-06-27阅读: 709标签: 脚本作者: 边城

前段时间思否十周年,搞了个问答打卡活动。参与打卡活动的人需要在回答问题的结尾加一个“小尾巴”。加小尾巴本身并不难,但是由于官方没有提供快捷方式,每次都需要自己从某个地方拷贝过去,稍嫌繁琐。正好前不久刚装了油猴插件,就想:自己给编辑器注入一个按钮用来添加小尾巴如何?

在使用油猴之前,使用过一个叫“User JavaScript and css”的插件,可以对特定的网页注入脚本和样式。不过这个插件在 Edge 市场中没有,只能从 Chrome 市场安装,安装有点困难。后来又去 Edge 市场中找到一个“Page Manipulator”也能实现类似的功能。之所以一直没用油猴,主要是油猴要注入样式表得自己写代码,懒得写。

注入小尾巴的脚本不难,也不是本文的重点。重点是脚本分享出去之后,收到一些“脚本不可用”的反馈。虽然说浏览几乎都是用的 Chrome/Edge 或者 Chrome 核心的浏览器,但毕竟版本存在差异,有些版本还不支持 ?? 、 ?. 和 ??= 等。

说起来,改一下运算符并不难,毕竟没有这些新运算符的时候,JavaScript 程序还不是一样的写。不过有新语法不能用是真的难受。如果仍然想用新语法,又想兼容更多浏览器,那就只有“编译”这个办法了。

webpack 有点重,为这几行脚本建个工程,引入 Webpack 不太值得。想起之前听说过的轻量快速的 esbuild,决定试试。

果然,一行命令搞定:

npx esbuild src/add-tail.js --outfile=dist/add-tail.js --target=chrome77

?. 和 ?? 都被翻译成了跟 null 进行比较,虽然是用的 == 而不是 ===,但是这个结果还算满意。毕竟如果用 === 还需要跟 undefined 进行对比。

甚至,如果加上了 --bundle 参数,还可以对源文件进行拆分,使用 ESM 来分块编写代码,解耦和复用也不耽误了。

正准备完美收工,突然就发现了问题 —— 用注释写的脚本头信息不见了!虽然可以找个地方保存头信息,再手工补到转译结果之前,但是这样做累啊!在网上转悠了半天,确实没找到什么解决方案。

esbuild 虽然提供了 --banner 参数,但有两个问题:

  1. 脚本头太长,还是多行,用 --banner 参数也不好加;
  2. 如果需要同时转译多个脚本,没办法动态地为每个脚本修改 banner。

思来想去,只有利用 esbuild 的 api 接口,写段程序来转译,并在转译之后用程序把脚本头补进去。程序写在 build.js 中,基本的转译过程无非就是把命令行参数改为函数调用,倒也简单

const result = await build({
    logLevel: "info",
    outdir: distDir,
    entryPoints,
    bundle: true,
    target: ["chrome77"],
    metafile: true,
}).catch(() => process.exit(1));

const analyzeResult = await analyzeMetafile(result.metafile);
console.log(analyzeResult);

其中, distDir 配置为 "dist" 目录。而 entryPoints 则是用 Node 的 fs 接口在 "src" 目录下找出来的第一层脚本文件,有多少算多少,不找子目录(这样就可以把拆分的子模块放在子目录中去):

const srcDir = path.resolve("./src");
const distDir = path.resolve("./dist");

const entryNames = (await fs.readdir(srcDir, { withFileTypes: true }))
    .filter(entry => entry.isFile() && /\.js$/.test(entry.name))
    .map(({ name }) => name);

const entryPoints = entryNames.map(filename => path.resolve(srcDir, filename));

只有输出分析结果这里费了点脑筋,命令行下是一个参数,这里需要调用另一个接口。

处理脚本头的思路很清晰:在 build() 之前,可以先读取源文件,把脚本头提取出来。在 build() 之后,读取输出文件,把脚本头加进去重新保存一次。

查了一下 esbuild 的文档,发现可以用它的插件机制来实现。在插件 onLoad 事件中需要读一次文件,在这里读了就不需要构建之前多读一次了。而 onEnd事件中可以先判断构建过程是否出错,在没出错的情况下注入脚本头就好。

const plugin = {
    name: "sf-script-plugin",
    setup(build) {
        build.headers = {};
        build.onLoad({ filter: /src[\\/][^/\\]+\.js$/ }, async (args) => {
            const contents = await fs.readFile(args.path, "utf8");
            build.headers[path.relative(srcDir, args.path)] = extractHeaders(contents);
            return { contents };
        });
        build.onEnd(result => {
            if (result.errors.length) { return; }
            Object.entries(build.headers)
                .forEach(([filename, header]) => insertHeader(filename, header));
        });
    }
};

function extractHeaders(contents) {
    return contents.match(/^.*?\/\/ ==\/UserScript==/s)?.[0];
}

async function insertHeader(filename, header) {
    const filePath = path.resolve(distDir, filename);
    const content = await fs.readFile(filePath, "utf8");
    fs.writeFile(filePath, [header, content].join("\n\n"));
}

当然,build 过程不要忘了加 plugins 参数

await build({
    ...
    plugins: [plugin],
}

在写 onLoad 的时候踩了点坑,主要就是 filter 要把 src 目录下的所有 .js 包含在内,但要排除掉所有子目录下的文件。

代码完成,尝试了一下,完美!

node ./build.js

来源:https://segmentfault.com/a/1190000042036660

链接: https://www.fly63.com/article/detial/11811

javascript 3d 脚本库 - three.js的进行简要介绍

本文的目标是对three.js进行简要介绍。我们将从设置一个(案例)场景开始,使用一个旋转的立方体。在页面底部提供一个工作示例,以防您遇到困难,需要帮助。

mongodb shell 运行js脚本的四种方式

MongoDB 是一个基于分布式文件存储的数据库。是一个介于关系数据库和非关系数据库之间的产品。这篇文章讲解mongodb shell 运行js脚本的四种方式。

defer和async的区别

async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的,最典型的例子:Google Analytics

如何用网页脚本追踪用户

本文介绍如何编写 JavaScript 脚本,将用户数据发回服务器。数据发回服务器的常见做法是,将收集好的用户数据,放在unload事件里面,用 AJAX 请求发回服务器。

npm脚本执行多个任务

如果遇到一个类似的需求,比如执行npm publish命令前想先升级下项目的版本,基础操作是在shell中输入两次命令分别为npm run version和npm run publish,但是有没有更简单的方法呢?请看下面的内容:

8个实用Python的脚本,收藏备用

脚本写的好,下班下得早!程序员的日常工作除了编写程序代码,还不可避免地需要处理相关的测试和验证工作。例如,访问某个网站一直不通,需要确定此地址是否可访问,服务器返回什么,进而确定问题在于什么

两个实用的shell脚本实例

今天主要分享两个shell脚本实例,内容不重要,重点是看如何去实现。批量创建特殊要求用户,需求:批量创建10个系统帐号hwb01-hwb10并设置密码(密码为随机数,要求字符和数字等混合)。

如何将 FIBJS 脚本打包成 exe 可执行文件

本文将会介绍如何将 FIBJS 脚本打包成Windows 上的 exe 可执行文件。FIBJS 是一个主要为 Web 后端开发而设计的应用服务器开发框架,它建立在 Google v8 JavaScript 引擎基础上

Hershell:一款功能强大的跨平台反向Shell生成器

Hershell是一款功能强大的跨平台反向Shell生成器,该工具使用Go语言开发,基于TCP反向Shell实现其功能。该工具使用了TLS来保障数据通讯的安全性,并且提供了证书公共密钥指纹绑定功能来防止通信数据被拦截。

javascript中defer的作用是什么

defer是脚本程序强大功能中的一个“无名英雄”。它告诉浏览器Script段包含了无需立即执行的代码,并且,与SRC属性联合使用,它还可以使这些脚本在后台被下载,前台的内容则正常显示给用户。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!