如何使用 Vite 开发油猴脚本
倒霉の忍者
编辑于 2021年12月15日 00:14

21-12-15 EDIT: 修正构建时注入css代码的字符串

  • 如果你还没使用过油猴脚本https://greasyfork.org/zh-CN

  • 如果你还没使用过 Vite 进行前端开发https://vitejs.dev/

开发模式

开发模式下所使用的油猴脚本:

代码块
JavaScript
自动换行
复制代码
// ==UserScript==
// @name         Your Script (dev mode)
// @namespace    https://your.site/
// @version      0.1.0
// @description  What does your script do
// @author       You
// @include      /https://match\.site/
// @grant        GM_addElement
// @noframes
// @require      https://cdn.jsdelivr.net/npm/vue@3.2.6/dist/vue.global.min.js
// ==/UserScript==

(function() {
    'use strict';
		// source: https://cn.vitejs.dev/guide/backend-integration.html
		// 注意端口是否正确,以及是否修改了默认入口文件
    GM_addElement('script', {
        src: 'http://localhost:3000/@vite/client',
        type: 'module'
    });
    GM_addElement('script', {
        src: 'http://localhost:3000/src/main.ts',
        type: 'module'
    });
})();
复制成功

上述油猴脚本中引用的 localhost 域下脚本,一般情况下是不能在一个正常网页下运行的,会违反浏览器与网站的安全策略(CSP),安装浏览器扩展以绕过目标网页安全策略(出于安全考虑,开发结束务必关闭扩展!):

    下载扩展:

        https://chrome.google.com/webstore/detail/lhieoncdgamiiogcllfmboilhgoknmpi

    编辑选项:

代码块
JavaScript
自动换行
复制代码
[
    ["https://targetsite\\.com/some/.*/url/", [
        ["script-src", "script-src http://localhost:3000"],
        ["connect-src", "connect-src ws://localhost:3000/"]
    ]]
]
复制成功

修改HMR(默认情况下hmr会使用基于 window.location 的相对地址):

代码块
JavaScript
自动换行
复制代码
// vite.config.js

import ...

export default defineConfig({
	...,

	server: {
		...,

    hmr: {
      protocol: 'ws',
      host: 'localhost',
    },
  },
})
复制成功

项目开发像正常开发一样即可,main.ts/js 即为脚本入口(根据vite设置修改)

根据脚本功能可能需要手动添加挂载元素:

代码块
JavaScript
自动换行
复制代码
// ./src/App.vue

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

const appRoot = document.createElement('div')
appRoot.id = 'us-appRoot'
document.body.appendChild(appRoot)

app.mount('#us-appRoot')
复制成功

如须对原有页面元素进行监控操作,可考虑 MutationObserver、vue 3.0 teleport 标签 等


生产模式

修改设置文件在选项对象中添加以下内容:

代码块
JavaScript
自动换行
复制代码
// vite.config.js

import ...

export default defineConfig({
	build: {
    lib: {
      entry: path.resolve(__dirname, 'src/main.ts'),
      name: 'userscript',
      formats: ['iife'], // 自运行打包格式,与默认模版一致
      fileName: format => `yourscript.${format}.user.js`, // 非函数的常量会自动添加后缀
    },
    rollupOptions: {
      external: ['vue'], // 分离库以降低最终代码体积
      output: {
        globals: {
          vue: 'Vue',
          GM_addStyle: 'GM_addStyle', // 油猴脚本API,用于添加样式到页面
        },
        inlineDynamicImports: true, // 库构建模式下不能进行代码分割,开启此功能可将本应分割的代码整合在一起避免报错(代码分割可能由其他插件引起)
      },
    },
    minify: 'terser',
    terserOptions: {
      mangle: false, // 关闭名称混淆,遵守Greasefork规则
      format: {
        beautify: true, // 美化代码开启缩进,遵守Greasefork规则
      },
    },
  },
	...
	plugins: [
		...,
		// custom plugin
    (() => {
		 /**
			* 如果用到了额外的 GM_functions,需要添加对应 @grant
			* 虽然可以全部不添加,但只有TamperMonkey会自动推断,其他扩展不一定
			* 在上面 extenral 声明的库,此处需要添加对应的 @require 要注意全局变量名称
			*/
      const headers = `\
// ==UserScript==
// @name         Your Script (prod mode)
// @namespace    https://your.site/
// @version      0.1.0
// @description  What does your script do
// @author       You
// @include      /https://match\.site/
// @grant        GM_addStyle
// @noframes
// @require      https://cdn.jsdelivr.net/npm/vue@3.2.6/dist/vue.global.min.js
// ==/UserScript==
`

      return {
        name: 'inject-css',
        apply: 'build', // 仅在构建模式下启用
        enforce: 'post', // 在最后处理
        generateBundle(options, bundle) {
					// 从 bundle 中提取 style.css 内容,并加入到脚本中
          const keyword = 'user.js'
          if (!bundle['style.css'] || bundle['style.css'].type !== 'asset') return
          const css = bundle['style.css'].source
          const [, target] = Object.entries(bundle).find(([name]) => {
            return name.includes(keyword)
          }) ?? []
          if (!target || target.type !== 'chunk') return
          target.code = `${headers}\nGM_addStyle(\`${css}\`)\n${target.code}`
        },
      } as Plugin
    })(),
	]
})
复制成功

编译后的js文件应在 /dist 目录下,像普通油猴脚本一样使用即可

若使用了 tailwindcss/windicss 等,内置的css reset影响了原本元素,可以在对应设置文件中关闭 preflight


扩展阅读:

  • 自动化构建+推送到greasefork (Planned)

  • 可用作油猴脚本项目脚手架的模板库 https://github.com/antfu/vitesse-lite

EOF