_userscript.ts 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { readFile } from 'fs/promises';
  2. import { 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 _userscript(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:${options.port}/${chunk.name}.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. }