userscript.ts 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { readFile } from "fs/promises";
  2. import { join, basename, dirname } from "path";
  3. import { LibraryFormats, Plugin, ResolvedConfig } from "vite";
  4. export interface UserScriptOptions {
  5. entry: string;
  6. format: LibraryFormats;
  7. name?: string;
  8. port?: number;
  9. cdn?: string;
  10. }
  11. export function UserScriptPlugin(options: UserScriptOptions): Plugin {
  12. let config: ResolvedConfig;
  13. return {
  14. name: "userscript",
  15. config() {
  16. const name = options.name ?? basename(dirname(options.entry));
  17. return {
  18. build: {
  19. lib: {
  20. name: name,
  21. formats: [options.format],
  22. entry: {
  23. [name]: options.entry
  24. }
  25. },
  26. rollupOptions: {
  27. output: {
  28. entryFileNames: "[name].user.js"
  29. }
  30. }
  31. }
  32. };
  33. },
  34. configResolved(resolvedConfig) {
  35. config = resolvedConfig;
  36. },
  37. async generateBundle(outputOptions, bundle) {
  38. for (const chunk of Object.values(bundle)) {
  39. if (chunk.type !== "chunk") continue;
  40. if (!chunk.isEntry) continue;
  41. if (!chunk.facadeModuleId) continue;
  42. const code = await readFile(chunk.facadeModuleId, { encoding: "utf8" });
  43. let header = "";
  44. for (const line of code.split("\n")) {
  45. if (!line.startsWith("//")) break;
  46. header += line + "\n";
  47. }
  48. let url = "";
  49. if (config.mode === "production") {
  50. if (options.cdn) url = `${options.cdn}${chunk.name}.user.js`;
  51. } else {
  52. if (options.port) url = `http://localhost:${join(options.port.toString(), config.build.outDir, chunk.name).replaceAll("\\", "/")}.user.js`;
  53. }
  54. if (url) {
  55. header = header.replaceAll(
  56. `// ==/UserScript==`,
  57. `// @downloadURL ${url}\n` +
  58. `// @updateURL ${url}\n` +
  59. `// ==/UserScript==`
  60. );
  61. }
  62. chunk.code = header + "\n" + chunk.code;
  63. }
  64. }
  65. };
  66. }