Ver código fonte

feat: only css files are fetched via resource directives now

Sv443 4 meses atrás
pai
commit
68a437447b
7 arquivos alterados com 87 adições e 66 exclusões
  1. 2 1
      assets/README.md
  2. 1 0
      assets/icons/speed.svg
  3. 54 50
      assets/resources.json
  4. 1 0
      changelog.md
  5. 22 10
      src/tools/post-build.ts
  6. 3 3
      src/types.ts
  7. 4 2
      src/utils/misc.ts

+ 2 - 1
assets/README.md

@@ -42,8 +42,9 @@ The keys of the object are the locale codes, and the values are the locale objec
 ### [`resources.json`](resources.json)
 This file contains the resources that are loaded into the runtime through the `@resource` userscript directive.  
 That includes all icons, images, CSS files, fonts, translations and other assets.  
+Configure which resources will always be fetched from the external asset source (GitHub's CDN) by editing the regexp patterns in the `alwaysExternalAssetPatterns` property.  
   
-Inside the file is an object, whose keys are the resource names and the values are the path to the resource or a configuration object.  
+Inside the file is an object under the `resources` prop, whose keys are the resource names and the values are the path to the resource or a configuration object (props are listed in the table below).  
 The path to the resource can be relative, in which case it's resolved relative to the `assets` directory.  
 If it starts with a slash, it will be resolved relative to the project root (where `package.json` is).  
   

+ 1 - 0
assets/icons/speed.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#ffffff"><path d="M424.15-346.15q21.31 21.3 54.89 20.23 33.57-1.08 49.27-24.23l217.07-314.46-315.23 216.3q-23.53 15.7-25.42 48.27-1.88 32.58 19.42 53.89ZM480-780q57.08 0 106.96 14.96 49.89 14.96 96.35 45.27l-56.39 36.46q-34.15-18.15-70.8-27.42Q519.46-720 480-720q-133 0-226.5 93.5T160-400q0 42 11.5 83t32.5 77h552q23-38 33.5-79t10.5-85q0-36-8.88-72.88-8.89-36.89-27.43-70.04l36.46-56.39q29.62 48.54 44.43 97.12 14.8 48.58 15.42 100.96.61 54.31-12.23 102.65-12.85 48.35-38.69 94.58-7.93 13-22.31 20.5Q772.38-180 756-180H204q-16 0-30.19-7.69-14.19-7.7-22.89-21.85-22.92-40-36.92-87.23Q100-344 100-400q0-78.38 29.74-147.37 29.74-68.99 81.19-120.85 51.46-51.86 120.99-81.82Q401.46-780 480-780Zm4.31 295.69Z"/></svg>

+ 54 - 50
assets/resources.json

@@ -1,52 +1,56 @@
 {
-  "css-above_queue_btns": "style/aboveQueueBtns.css",
-  "css-above_queue_btns_sticky": "style/aboveQueueBtnsSticky.css",
-  "css-anchor_improvements": "style/anchorImprovements.css",
-  "css-auto_like": "style/autoLike.css",
-  "css-fix_hdr": "style/fixHDR.css",
-  "css-fix_playerpage_theming": "style/fixPlayerPageTheming.css",
-  "css-fix_spacing": "style/fixSpacing.css",
-  "css-fix_sponsorblock": "style/fixSponsorBlock.css",
-  "css-hide_themesong_logo": "style/hideThemeSongLogo.css",
-  "css-show_votes": "style/showVotes.css",
-  "css-vol_slider_size": "style/volSliderSize.css",
-  "doc-license": {
-    "path": "/LICENSE.txt",
-    "ref": "$BRANCH",
-    "integrity": false
-  },
-  "font-cousine_ttf": "fonts/Cousine/Cousine-Regular.ttf",
-  "font-cousine_woff": "fonts/Cousine/Cousine-Regular.woff",
-  "font-cousine_woff2": "fonts/Cousine/Cousine-Regular.woff2",
-  "icon-advanced_mode": "icons/plus_circle_small.svg",
-  "icon-alert": "icons/alert.svg",
-  "icon-arrow_down": "icons/arrow_down.svg",
-  "icon-auto_like_enabled": "icons/auto_like_enabled.svg",
-  "icon-auto_like": "icons/auto_like.svg",
-  "icon-clear_list": "icons/clear_list.svg",
-  "icon-copy": "icons/copy.svg",
-  "icon-delete": "icons/delete.svg",
-  "icon-edit": "icons/edit.svg",
-  "icon-error": "icons/error.svg",
-  "icon-experimental": "icons/beaker_small.svg",
-  "icon-globe_small": "icons/globe_small.svg",
-  "icon-globe": "icons/globe.svg",
-  "icon-help": "icons/help.svg",
-  "icon-image_filled": "icons/image_filled.svg",
-  "icon-image": "icons/image.svg",
-  "icon-link": "icons/link.svg",
-  "icon-lyrics": "icons/lyrics.svg",
-  "icon-prompt": "icons/help.svg",
-  "icon-reload": "icons/refresh.svg",
-  "icon-restore_time": "icons/restore_time.svg",
-  "icon-skip_to": "icons/skip_to.svg",
-  "icon-spinner": "icons/spinner.svg",
-  "icon-upload": "icons/upload.svg",
-  "img-close": "images/close.png",
-  "img-discord": "images/external/discord.png",
-  "img-github": "images/external/github.png",
-  "img-greasyfork": "images/external/greasyfork.png",
-  "img-logo_dev": "images/logo/logo_dev_48.png",
-  "img-logo": "images/logo/logo_48.png",
-  "img-openuserjs": "images/external/openuserjs.png"
+  "alwaysExternalAssetPatterns": ["^icon-", "^img-", "^font-", "^doc-", "^trans-"],
+  "resources": {
+    "css-above_queue_btns": "style/aboveQueueBtns.css",
+    "css-above_queue_btns_sticky": "style/aboveQueueBtnsSticky.css",
+    "css-anchor_improvements": "style/anchorImprovements.css",
+    "css-auto_like": "style/autoLike.css",
+    "css-fix_hdr": "style/fixHDR.css",
+    "css-fix_playerpage_theming": "style/fixPlayerPageTheming.css",
+    "css-fix_spacing": "style/fixSpacing.css",
+    "css-fix_sponsorblock": "style/fixSponsorBlock.css",
+    "css-hide_themesong_logo": "style/hideThemeSongLogo.css",
+    "css-show_votes": "style/showVotes.css",
+    "css-vol_slider_size": "style/volSliderSize.css",
+    "doc-license": {
+      "path": "/LICENSE.txt",
+      "ref": "$BRANCH",
+      "integrity": false
+    },
+    "font-cousine_ttf": "fonts/Cousine/Cousine-Regular.ttf",
+    "font-cousine_woff": "fonts/Cousine/Cousine-Regular.woff",
+    "font-cousine_woff2": "fonts/Cousine/Cousine-Regular.woff2",
+    "icon-advanced_mode": "icons/plus_circle_small.svg",
+    "icon-alert": "icons/alert.svg",
+    "icon-arrow_down": "icons/arrow_down.svg",
+    "icon-auto_like_enabled": "icons/auto_like_enabled.svg",
+    "icon-auto_like": "icons/auto_like.svg",
+    "icon-clear_list": "icons/clear_list.svg",
+    "icon-copy": "icons/copy.svg",
+    "icon-delete": "icons/delete.svg",
+    "icon-edit": "icons/edit.svg",
+    "icon-error": "icons/error.svg",
+    "icon-experimental": "icons/beaker_small.svg",
+    "icon-globe_small": "icons/globe_small.svg",
+    "icon-globe": "icons/globe.svg",
+    "icon-help": "icons/help.svg",
+    "icon-image_filled": "icons/image_filled.svg",
+    "icon-image": "icons/image.svg",
+    "icon-link": "icons/link.svg",
+    "icon-lyrics": "icons/lyrics.svg",
+    "icon-prompt": "icons/help.svg",
+    "icon-reload": "icons/refresh.svg",
+    "icon-restore_time": "icons/restore_time.svg",
+    "icon-skip_to": "icons/skip_to.svg",
+    "icon-speed": "icons/speed.svg",
+    "icon-spinner": "icons/spinner.svg",
+    "icon-upload": "icons/upload.svg",
+    "img-close": "images/close.png",
+    "img-discord": "images/external/discord.png",
+    "img-github": "images/external/github.png",
+    "img-greasyfork": "images/external/greasyfork.png",
+    "img-logo_dev": "images/logo/logo_dev_48.png",
+    "img-logo": "images/logo/logo_48.png",
+    "img-openuserjs": "images/external/openuserjs.png"
+  }
 }

+ 1 - 0
changelog.md

@@ -6,6 +6,7 @@
 - **Changes:**
   - The above-queue-button container's sticky positioning can now be turned off with a new advanced feature toggle in the config menu
   - Made finding the preferred locale based on the browser's language settings more reliable
+  - Heavily reduced amount of `@resource` directives to improve installation and updating time
 
 <details><summary>Click to expand internal and plugin changes</summary>
 

+ 22 - 10
src/tools/post-build.ts

@@ -2,14 +2,15 @@ import { access, readFile, writeFile, constants as fsconst } from "node:fs/promi
 import { dirname, join, relative, resolve } from "node:path";
 import { fileURLToPath } from "node:url";
 import { exec } from "node:child_process";
+import { createHash } from "node:crypto";
+import { createReadStream } from "node:fs";
 import k from "kleur";
 import "dotenv/config";
+import type { RollupArgs } from "../types.js";
 import { outputDir as rollupCfgOutputDir, outputFile as rollupCfgOutputFile } from "../../rollup.config.mjs";
-import locales from "../../assets/locales.json" with { type: "json" };
+import localesJson from "../../assets/locales.json" with { type: "json" };
+import resourcesJson from "../../assets/resources.json" with { type: "json" };
 import pkg from "../../package.json" with { type: "json" };
-import type { RollupArgs } from "../types.js";
-import { createHash } from "node:crypto";
-import { createReadStream } from "node:fs";
 
 const { argv, env, exit, stdout } = process;
 
@@ -272,13 +273,22 @@ function resolveResourceVal(value: string, buildNbr: string) {
 /** Returns a string of resource directives, as defined in `assets/resources.json` or undefined if the file doesn't exist or is invalid */
 async function getResourceDirectives(ref: string) {
   try {
-    const directives: string[] = [];
-    const resourcesFile = String(await readFile(join(assetFolderPath, "resources.json")));
-    const resources = JSON.parse(resourcesFile) as Record<string, string> | Record<string, { path: string; buildNbr: string }>;
+    const directives: string[] = [],
+      resourcesRaw = JSON.parse(String(await readFile(join(assetFolderPath, "resources.json")))),
+      resources = "resources" in resourcesRaw
+        ? resourcesRaw.resources as Record<string, string> | Record<string, { path: string; buildNbr: string }>
+        : undefined,
+      resourcesHashed = {} as Record<string, Record<"path" | "ref", string> & Partial<Record<"hash", string>>>;
 
-    const resourcesHashed = {} as Record<string, Record<"path" | "ref", string> & Partial<Record<"hash", string>>>;
+    if(!resources)
+      throw new Error("No resources found in 'assets/resources.json'");
 
+    const externalAssetRegexes = resourcesJson.alwaysExternalAssetPatterns.map((p) => new RegExp(p));
     for(const [name, val] of Object.entries(resources)) {
+      // skip over all external assets
+      if(externalAssetRegexes.some((re) => re.test(name)))
+        continue;
+
       const pathVal = typeof val === "object" ? val.path : val;
       const hash = (
         assetSource !== "local"
@@ -294,6 +304,8 @@ async function getResourceDirectives(ref: string) {
 
     const addResourceHashed = async (name: string, path: string, ref: string) => {
       try {
+        if(externalAssetRegexes.some((re) => re.test(name)))
+          return;
         if(assetSource === "local" || path.match(/^https?:\/\//)) {
           resourcesHashed[name] = { path: getResourceUrl(path, ref), ref, hash: undefined };
           return;
@@ -307,7 +319,7 @@ async function getResourceDirectives(ref: string) {
 
     await addResourceHashed("css-bundle", "/dist/BetterYTM.css", ref);
 
-    for(const [locale] of Object.entries(locales))
+    for(const [locale] of Object.entries(localesJson))
       await addResourceHashed(`trans-${locale}`, `translations/${locale}.json`, ref);
 
     let longestName = 0;
@@ -368,7 +380,7 @@ function getRequireEntry(entry: RequireObjPkg) {
 function getLocalizedDescriptions() {
   try {
     const descriptions: string[] = [];
-    for(const [locale, { userscriptDesc, ...rest }] of Object.entries(locales)) {
+    for(const [locale, { userscriptDesc, ...rest }] of Object.entries(localesJson)) {
       let loc = locale;
       if(loc.length < 5)
         loc += " ".repeat(5 - loc.length);

+ 3 - 3
src/types.ts

@@ -9,8 +9,8 @@ import type { BytmDialog, ExImDialog, createCircularBtn, createHotkeyInput, crea
 import type { fetchLyricsUrlTop, sanitizeArtists, sanitizeSong } from "./features/lyrics.js";
 import type { getLyricsCacheEntry } from "./features/lyricsCache.js";
 import type { showPrompt } from "./dialogs/prompt.js";
-import type resources from "../assets/resources.json";
-import type locales from "../assets/locales.json";
+import resources from "../assets/resources.json" with { type: "json" }; // eslint-disable-line @typescript-eslint/no-unused-vars
+import locales from "../assets/locales.json" with { type: "json" }; // eslint-disable-line @typescript-eslint/no-unused-vars
 
 //#region other
 
@@ -45,7 +45,7 @@ export type SiteSelection = Domain | "all";
 export type SiteSelectionOrNone = SiteSelection | "none";
 
 /** Key of a resource in `assets/resources.json` and extra keys defined by `tools/post-build.ts` */
-export type ResourceKey = keyof typeof resources | `trans-${keyof typeof locales}` | "changelog" | "css-bundle";
+export type ResourceKey = keyof typeof resources["resources"] | `trans-${keyof typeof locales}` | "css-bundle";
 
 /** Describes a single hotkey */
 export type HotkeyObj = {

+ 4 - 2
src/utils/misc.ts

@@ -255,6 +255,8 @@ export async function reloadTab() {
 
 //#region resources
 
+const alwaysExternalAssetRegexes = resourcesJson.alwaysExternalAssetPatterns.map(pat => new RegExp(pat));
+
 /**
  * Returns the blob-URL of a resource by its name, as defined in `assets/resources.json`, from GM resource cache - [see GM.getResourceUrl docs](https://wiki.greasespot.net/GM.getResourceUrl)  
  * Falls back to a `raw.githubusercontent.com` URL or base64-encoded data URI if the resource is not available in the GM resource cache  
@@ -265,13 +267,13 @@ export async function getResourceUrl(name: ResourceKey | "_", uncached = false)
   let url = !uncached && await GM.getResourceUrl(name);
 
   if(!url || url.length === 0) {
-    const resObjOrStr = resourcesJson?.[name as keyof typeof resourcesJson];
+    const resObjOrStr = resourcesJson.resources?.[name as keyof typeof resourcesJson.resources];
 
     if(typeof resObjOrStr === "object" || typeof resObjOrStr === "string") {
       const pathName = typeof resObjOrStr === "object" && "path" in resObjOrStr ? resObjOrStr.path : resObjOrStr;
       const ghRef = typeof resObjOrStr === "object" && "ref" in resObjOrStr ? resObjOrStr.ref : branch;
 
-      if(pathName && pathName.startsWith("/") && pathName.length > 1)
+      if(alwaysExternalAssetRegexes.some(re => re.test(name)) || (pathName?.startsWith("/") && pathName.length > 1))
         return `https://raw.githubusercontent.com/${repo}/${ghRef}${pathName}`;
       else if(pathName && pathName.startsWith("http"))
         return pathName;