当前位置: 首页 > Web前端 > JavaScript

esbuild前段时间为庆祝思维10周年,搭建了油猴脚本

时间:2023-03-27 11:42:30 JavaScript

,举办了问答签到活动。参加打卡活动的小伙伴,需在问题答案末尾加一条“小尾巴”。添加小尾巴并不难,但是由于官方没有提供快捷方式,所以每次都需要从某个地方复制,有点麻烦。前不久刚装上油猴插件,心想:要不给编辑器注入一个按钮加个小尾巴怎么样?在使用OilMonkey之前,我用过一个叫做“UserJavaScriptandCSS”的插件,它可以为特定的网页注入脚本和样式。不过这个插件在Edge市场是没有的,只能从Chrome市场安装,安装难度有点大。后来去Edge市场找了一个可以实现类似功能的“PageManipulator”。之所以没用油猴,主要是油猴要自己写代码注入样式表,懒得写了。注入小尾巴脚本并不难,不是本文的重点。重点是脚本分享后,收到一些反馈“脚本不可用”。虽然浏览器几乎都是用Chrome/Edge或者Chrome核心浏览器,但是毕竟版本有差异,有的版本不支持??,?。和??=等。说起来,更改运算符并不难。毕竟,如果没有这些新的运算符,JavaScript程序就不会以同样的方式编写。但是有新的语法不能用,真的很难受。如果你还想使用新的语法,想兼容更多的浏览器,唯一的办法就是“编译”。webpack有点重,为了这几行脚本去建一个项目不划算。想到之前听过的轻量快速的esbuild,我决定尝试一下。果然一行命令搞定:npxesbuildsrc/add-tail.js--outfile=dist/add-tail.js--target=chrome77?。和??翻译过来就是和null比较,虽然是用==代替===,但是结果还是让人满意的。毕竟,如果使用===,则需要将其与undefined进行比较。甚至,如果加上--bundle参数,还可以拆分源文件,使用ESM分块写代码,解耦复用不耽误。正准备收工,突然发现一个问题——注释里写的脚本头信息不见了!虽然可以找个地方保存header信息,然后在翻译结果前手动填写,但是这样做很累!在网上逛了半天,实在找不到解决办法。esbuild虽然提供了--banner参数,但是有两个问题:脚本头太长,而且还是多行,不容易加上--banner参数;如果需要同时翻译多个脚本,没办法动态翻译每个脚本修改banner。想了想,唯一的办法就是用esbuild的API接口写一个程序来翻译,翻译后用程序填入脚本头。该程序是用build.js编写的。基本的翻译过程无非就是把命令行参数改成函数调用,很简单,元文件:true,}).catch(()=>process.exit(1));constanalyzeResult=awaitanalyzeMetafile(result.metafile);console.log(analyzeResult);其中,distDir配置为“dist”目录。而entryPoints是使用Node的fs接口在“src”目录下找到的一级脚本文件,有多少就有多少,没有找子目录(这样拆分出来的子模块就可以放在子目录下):constsrcDir=path.解决("./src");constdistDir=path.resolve("./dist");constentryNames=(awaitfs.readdir(srcDir,{withFileTypes:true})).filter(entry=>entry.isFile()&&/\.js$/.test(entry.name)).map(({名称})=>name);constentryPoints=entryNames.map(filename=>path.resolve(srcDir,filename));只是输出分析结果这里费了些脑筋,命令行是一个参数,这里需要调用另外一个接口。处理脚本头的思路很清晰:在build()之前,可以读取源文件,提取脚本头。build()之后,读取输出文件,添加脚本头并再次保存。查了esbuild的文档,发现可以用它的插件机制来实现。该文件需要在插件的onLoad事件中读取一次。如果你在这里阅读它,你不需要在构建之前再次阅读它。在onEnd事件中,可以先判断构建过程是否有错误,没有错误则注入脚本头。constplugin={name:"sf-script-plugin",setup(build){build.headers={};build.onLoad({filter:/src[\\/][^/\\]+\.js$/},async(args)=>{constcontents=awaitfs.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));});}};functionextractHeaders(contents){returncontents.match(/^.*?\/\/==\/UserScript==/s)?.[0];}asyncfunctioninsertHeader(filename,header){constfilePath=path.resolve(distDir,文件名);constcontent=awaitfs.readFile(filePath,"utf8");fs.writeFile(filePath,[header,content].join("\n\n"));}当然,build过程不要忘记了加plugins参数awaitbuild({...plugins:[plugin],}写onLoad的时候踩了个坑,主要是filter应该包含src目录下所有.js,但是排除所有子目录文件代码完整,试试出来,完美!node./build.js