网络日志

油猴脚本工程化在vite的最佳实践

油猴插件

油猴插件 泛指 Tampermonkey, Violentmonkey, Greasemonkey 这类给页面注入 js 的浏览器扩展

通过在浏览器扩展在网页加载时注入特定的 js 达到更改页面布局效果等等一切 js 能做的事情

这些扩展也提供了一些网页没有的 api,使注入的 js 能突破网页默认的限制,实现其他的功能

现有油猴脚本开发模式

现有的油猴脚本一般是两种开发模式

扩展自带的编辑器

这种就是纯手写了,只能写原生 js,没有代码提示,没有模块的概念,只能通过 @require 使用 npmjs.com 的库

由于没有工程化的概念,没有模块热替换,当代码有任何改动就得重新刷新宿主页面才能查看效果

并且代码只能在一个单文件里,功能少还好,功能和项目稍微大点,就完全不适合编辑了

这种是完全落后的方式,效率和开发体验都及其低下

webpack/rollup/uglify-js/esbuild

这类开发方式相比前者是极大的进步,有了模块化和打包的概念,开发方式也从简陋编辑器改到了 vscode,代码提示,typescript 都不是问题

开发者提前在扩展编辑器里写好桥接的代码,比如把 path/dist/js 写在 userscript 的 @require,或者在 userscript 下面写 动态添加 script src=path/dist/js

但是,rollup/uglify-js/esbuild 不支持模块热替换(hmr),仅有 webpack 支持

试想一下,如果没有 模块热替换 你的代码在 vscode 里每改动一下,你都要手动刷新宿主页面,并且手动点击恢复刚刚页面的状态,这和开发 vue/react 时的体验差的不是一点半点

但是 webpack 的 模块热替换 在这种注入代码的模式下 也存在问题,由于代码的来源代码的运行环境不是一个源,而 webpack dev server 设计的时候只考虑了同源的情况

而且 webpack dev server 是一个插件,不是一个核心功能,比如我想用中间件的形式拦截任意路径的请求就做不到

导致现有的基于 webpack 的油猴脚本插件开发体验都一言难尽

vite 的优势

vite 相比 webpack,最大的提升就是 速度 和 开箱即用,相信用过 vite 的都能体验它的优点

vite 比 webpack 好的其中两个点就是 它明确区分了 开发模式 和 构建模式,并暴露出了 本地开发服务器 的 api 供外部使用

插件可以注册一个中间件,用来生成中间桥接代码,无需每次生成文件或手动填写代码

另一个点是 vite 的 hmr 服务器是 在 /@vite/client 模块里,开发模式时 vite 向 index.html 注入 <script type="module" src="/@vite/client"></script>

插件可以修改这块代码,让 vite 兼容 两个 host 工作的场景

使用插件开发

接下来我们使用 vite-plugin-monkey 这个插件来开发油猴脚本

提供的功能优势

  • 额外的脚手架一键初始化各种模板 vue/react/vanilla/svelte/preact
  • 支持 Tampermonkey 和 Violentmonkey 和 Greasemonkey 的脚本辅助开发
  • 打包自动注入脚本配置头部注释
  • 当 第一次启动 或 脚本配置注释改变时 自动在默认浏览器打开脚本安装
  • 友好的利用 @require 配置库的 cdn 的方案,大大减少构建脚本大小
  • 完全的 Typescript 和 Vite 的开发体验,比如模块热替换,秒启动

基础配置如下,详情请看文档 https://github.com/lisonge/vi...

export interface MonkeyOption {
  /**
   * 脚本文件的入口路径
   */
  entry: string;
  userscript: MonkeyUserScript;
  format?: Format;
  server?: {
    /**
     * 当 第一次启动 或 脚本配置注释改变时 自动在默认浏览器打开脚本
     * @default true
     */
    open?: boolean;

    /**
     * 开发阶段的脚本名字前缀,用以在脚本安装列表里区分构建好的脚本
     * @default 'dev:'
     */
    prefix?: string | ((name: string) => string);
  };
  build?: {
    /**
     * 打包构建的脚本文件名字 应该以 '.user.js' 结尾
     * @default (package.json.name||'monkey')+'.user.js'
     */
    fileName?: string;

    /**
     * @example
     * {
     *  vue:'Vue',
     *  // 你需要额外设置脚本配置 userscript.require = ['https://unpkg.com/vue@3.0.0/dist/vue.global.js']
     *  vuex:['Vuex', 'https://unpkg.com/vuex@4.0.0/dist/vuex.global.js'],
     *  // 插件将会自动注入 cdn 链接到 userscript.require
     *  vuex:['Vuex', (version)=>`https://unpkg.com/vuex@${version}/dist/vuex.global.js`],
     *  // 相比之前的,加了版本号,当依赖升级的时候,cdn 链接自动改变
     *  vuex:['Vuex', (version, name)=>`https://unpkg.com/${name}@${version}/dist/vuex.global.js`],
     *  // 还可以加依赖名字,不过各个依赖的 cdn basename 都不尽一致, 导致可能没什么用
     * }
     *
     */
    externalGlobals?: Record<
      string,
      string | [string, string | ((version: string, name: string) => string)]
    >;

    /**
     * 自动识别代码里用到的 浏览器插件api,然后自动配置 GM_* 或 GM.* 函数到脚本配置注释头
     *
     * 识别依据是判断代码文本里有没有特定的函数名字
     * @default true
     */
    autoGrant?: boolean;
  };
}

脚手架初始化

vite 有官方脚手架 create-vite,插件自然也有,并且使用方式完全一致

pnpm create monkey
# npm create monkey
# yarn create monkey

然后你能从以下模板选择

选择之后,进入目录安装依赖之后,和启动正常的 vite 项目一样

pnpm run dev

就会自动打开默认浏览器安装脚本,开发者只需点击安装即可,然后打开被注入的网页,即可看到效果

上图示例中初始化的 vite.config.ts 如下

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import monkey from 'vite-plugin-monkey';

export default defineConfig({
  plugins: [
    vue(),
    monkey({
      entry: 'src/main.ts',
      userscript: {
        icon: 'https://vitejs.dev/logo.svg',
        namespace: 'npm/vite-plugin-monkey',
        match: ['https://www.google.com/'],
      },
      build: {
        externalGlobals: {
          vue: [
            'Vue',
            (version) =>
              `https://cdn.jsdelivr.net/npm/vue@${version}/dist/vue.global.prod.js`,
          ],
        },
      },
    }),
  ],
});

模块热替换

模块热替换的例子,与正常 vite 项目开发体验一致

构建

构建命令 与正常 vite 项目开发体验一致

pnpm run build

构建完毕后 dist 目录会出现一个 xxx.user.js,文件头部信息如下

// ==UserScript==
// @name       vite-project
// @namespace  npm/vite-plugin-monkey
// @version    0.0.0
// @icon       https://vitejs.dev/logo.svg
// @match      https://www.google.com/
// @require    https://cdn.jsdelivr.net/npm/vue@3.2.37/dist/vue.global.prod.js
// ==/UserScript==

// use vite-plugin-monkey@1.0.0 at 2022-07-18T10:14:55.580Z

之前在插件的配置自动全部注入到构建后的文件头注释中

总结

vite 提供 速度 和 开箱即用,vite-plugin-monkey 负责处理 油猴脚本 和 vite 之前的关系

保证 油猴脚本 的开发与正常 vite 项目的开发一致,欢迎 issuesstar