diff --git a/.env.dev b/.env.dev index db9ad56e8..b5939db6f 100644 --- a/.env.dev +++ b/.env.dev @@ -3,3 +3,4 @@ VITE_APP_API="http://localhost:3100/v3" VITE_APP_API_GQL="http://localhost:3000/v3/gql" VITE_APP_API_REST_OLD="http://localhost:3100/v2" VITE_APP_API_EVENTS="ws://localhost:3700/v3" +VITE_APP_HOST="http://localhost:8080" diff --git a/.env.production b/.env.production index ede96af97..54dab3655 100644 --- a/.env.production +++ b/.env.production @@ -3,3 +3,4 @@ VITE_APP_API="https://7tv.io/v3" VITE_APP_API_GQL="https://7tv.io/v3/gql" VITE_APP_API_REST_OLD="https://7tv.io/v2" VITE_APP_API_EVENTS="wss://events.7tv.io/v3" +VITE_APP_HOST="https://extension.7tv.gg" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ab241047c..a74d35c88 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -28,6 +28,11 @@ on: default: false description: "Upload to CWS/AMO" + deploy: + type: boolean + default: false + description: "Deploy Hosted Build" + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -110,6 +115,10 @@ jobs: - name: Structure Files run: | + if [ -f ${{ steps.web-ext-build.outputs.target }} ]; then + mv ${{ steps.web-ext-build.outputs.target }} dist/ext.xpi + fi + mkdir -p dist/manifest cp dist/mv2/manifest.json dist/manifest/manifest.mv2.json cp dist/mv3/manifest.json dist/manifest/manifest.mv3.json @@ -211,6 +220,41 @@ jobs: name: v${{ fromJson(env.PACKAGE_JSON).version }} tag: v${{ fromJson(env.PACKAGE_JSON).version }} + deploy: + name: Deploy Hosted Build + runs-on: aws-runner + needs: [ci, release] + if: ${{ inputs.deploy }} + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "18" + + - name: Install Yarn + run: npm install -g yarn + + - name: Install Dependencies + run: | + yarn + + - name: Build (Hosted) + env: + BRANCH: ${{ inputs.nightly && 'nightly' || '' }} + run: | + yarn build-hosted:prod + + - name: Upload to Host + uses: shallwefootball/s3-upload-action@master + with: + endpoint: ${{ secrets.R2_API_ENDPOINT }} + aws_key_id: ${{ secrets.R2_API_AK }} + aws_secret_access_key: ${{ secrets.R2_API_SECRET }} + aws_bucket: 7tv-extension + source_dir: "dist-hosted/" + destination_dir: "" + push: name: Submit to CWS/AMO runs-on: aws-runner @@ -224,12 +268,18 @@ jobs: node-version: "18" # Retrieve the non-CRX MV3 zip to be uploaded to CWS - - name: Download Artifact (Chromium) + - name: Download Artifact (Build) uses: actions/download-artifact@v3 with: name: build path: ext + - name: Download Artifact (Installable) + uses: actions/download-artifact@v3 + with: + name: installable + path: ext + # Get the XPI File for Firefox # It will be uploaded to AMO - name: Download Manifest @@ -257,6 +307,7 @@ jobs: --refresh-token "$(sed '3q;d' c)" - name: Upload to AMO + if: always() uses: trmcnvn/firefox-addon@v1 with: uuid: ${{ env.NIGHTLY_EXTENSION_ID_AMO }} diff --git a/.gitignore b/.gitignore index bb24689b9..173641977 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ lerna-debug.log* node_modules dist dist-ssr +dist-hosted *.local # Editor directories and files diff --git a/CHANGELOG-nightly.md b/CHANGELOG-nightly.md index 3b0495e48..5327d30d7 100644 --- a/CHANGELOG-nightly.md +++ b/CHANGELOG-nightly.md @@ -1,5 +1,8 @@ ### Untitled Version +**The changes listed here are not assigned to an official release**. + +- Added hot-patching functionality to the extension - Fixed an issue which caused flickering when hovering on a deleted message ### Version 3.0.3.1000 diff --git a/package.json b/package.json index 0d0a85728..a1ca7fca6 100644 --- a/package.json +++ b/package.json @@ -3,17 +3,20 @@ "displayName": "7TV", "description": "Improve your viewing experience on Twitch & YouTube with new features, emotes, vanity and performance.", "private": true, - "version": "3.0.3", - "dev_version": "1.0", + "version": "3.0.4", + "dev_version": "3.0", "scripts": { "start": "NODE_ENV=dev yarn build:dev && NODE_ENV=dev vite --mode dev", "build:section:app": "vite build --config vite.config.ts", "build:section:background": "vite build --config vite.config.background.ts", "build:section:content": "vite build --config vite.config.content.ts", "build:section:worker": "vite build -c vite.config.worker.ts", + "build-hosted:section:worker": "vite build --config vite.config.worker.ts", + "build-hosted:section:site": "vite build --config vite.config.hosted.ts", "build:dev": "NODE_ENV=dev run-s build:section:*", "build:prod": "NODE_ENV=production vue-tsc --noEmit && run-s build:section:*", - "build:mv2:prod": "vue-tsc --noEmit && MV2=true vite build --minify es2021 && vite build -c vite.config.worker.ts --minify es2021", + "build-hosted:dev": "NODE_ENV=dev run-s build-hosted:section:*", + "build-hosted:prod": "NODE_ENV=production vue-tsc --noEmit && run-s build-hosted:section:*", "watch:section:background": "vite build --config vite.config.background.ts --watch", "watch:section:content": "vite build --config vite.config.content.ts --watch", "watch:section:worker": "vite build --config vite.config.worker.ts --watch", diff --git a/src/assets/blob/emoji.json b/public/assets/emoji/emoji.json similarity index 100% rename from src/assets/blob/emoji.json rename to public/assets/emoji/emoji.json diff --git a/src/assets/blob/emojis0.svg b/public/assets/emoji/emojis0.svg similarity index 100% rename from src/assets/blob/emojis0.svg rename to public/assets/emoji/emojis0.svg diff --git a/src/assets/blob/emojis1.svg b/public/assets/emoji/emojis1.svg similarity index 100% rename from src/assets/blob/emojis1.svg rename to public/assets/emoji/emojis1.svg diff --git a/src/assets/blob/emojis10.svg b/public/assets/emoji/emojis10.svg similarity index 100% rename from src/assets/blob/emojis10.svg rename to public/assets/emoji/emojis10.svg diff --git a/src/assets/blob/emojis2.svg b/public/assets/emoji/emojis2.svg similarity index 100% rename from src/assets/blob/emojis2.svg rename to public/assets/emoji/emojis2.svg diff --git a/src/assets/blob/emojis3.svg b/public/assets/emoji/emojis3.svg similarity index 100% rename from src/assets/blob/emojis3.svg rename to public/assets/emoji/emojis3.svg diff --git a/src/assets/blob/emojis4.svg b/public/assets/emoji/emojis4.svg similarity index 100% rename from src/assets/blob/emojis4.svg rename to public/assets/emoji/emojis4.svg diff --git a/src/assets/blob/emojis5.svg b/public/assets/emoji/emojis5.svg similarity index 100% rename from src/assets/blob/emojis5.svg rename to public/assets/emoji/emojis5.svg diff --git a/src/assets/blob/emojis6.svg b/public/assets/emoji/emojis6.svg similarity index 100% rename from src/assets/blob/emojis6.svg rename to public/assets/emoji/emojis6.svg diff --git a/src/assets/blob/emojis7.svg b/public/assets/emoji/emojis7.svg similarity index 100% rename from src/assets/blob/emojis7.svg rename to public/assets/emoji/emojis7.svg diff --git a/src/assets/blob/emojis8.svg b/public/assets/emoji/emojis8.svg similarity index 100% rename from src/assets/blob/emojis8.svg rename to public/assets/emoji/emojis8.svg diff --git a/src/assets/blob/emojis9.svg b/public/assets/emoji/emojis9.svg similarity index 100% rename from src/assets/blob/emojis9.svg rename to public/assets/emoji/emojis9.svg diff --git a/src/assets/svg/icons/CloudIcon.vue b/src/assets/svg/icons/CloudIcon.vue new file mode 100644 index 000000000..90a6315f1 --- /dev/null +++ b/src/assets/svg/icons/CloudIcon.vue @@ -0,0 +1,8 @@ + diff --git a/src/composable/chat/useChatEmotes.ts b/src/composable/chat/useChatEmotes.ts index 1fd003e13..1f19560c2 100644 --- a/src/composable/chat/useChatEmotes.ts +++ b/src/composable/chat/useChatEmotes.ts @@ -2,28 +2,6 @@ import { reactive } from "vue"; import type { ChannelContext } from "@/composable/channel/useChannelContext"; import { useEmoji } from "@/composable/useEmoji"; -const { emojiList } = useEmoji(); -const emojiSets = {} as Record; -const emojis = {} as Record; -emojiList.forEach((e) => { - const es = emojiSets[e.group]; - if (!es) { - emojiSets[e.group] = { - id: e.group, - name: e.group, - provider: "EMOJI", - emotes: [], - tags: [], - immutable: true, - privileged: true, - flags: 0, - }; - } - - emojiSets[e.group].emotes.push(e.emote); - emojis[e.emote.name] = e.emote; -}); - interface ChatEmotes { active: Record; sets: Record; @@ -32,6 +10,8 @@ interface ChatEmotes { } const m = new WeakMap(); +const emojiSets = {} as Record; +const emojis = {} as Record; export function useChatEmotes(ctx: ChannelContext) { let x = m.get(ctx); @@ -102,3 +82,25 @@ export function useChatEmotes(ctx: ChannelContext) { return r; } + +export function convertEmojis(): void { + const { emojiList } = useEmoji(); + emojiList.forEach((e) => { + const es = emojiSets[e.group]; + if (!es) { + emojiSets[e.group] = { + id: e.group, + name: e.group, + provider: "EMOJI", + emotes: [], + tags: [], + immutable: true, + privileged: true, + flags: 0, + }; + } + + emojiSets[e.group].emotes.push(e.emote); + emojis[e.emote.name] = e.emote; + }); +} diff --git a/src/composable/useEmoji.ts b/src/composable/useEmoji.ts index 8c15d78db..5dab2d056 100644 --- a/src/composable/useEmoji.ts +++ b/src/composable/useEmoji.ts @@ -1,5 +1,5 @@ -import { reactive } from "vue"; -import emojiList from "@/assets/blob/emoji.json"; +import { inject, reactive } from "vue"; +import { SITE_ASSETS_URL } from "@/common/Constant"; export interface Emoji { codes: string; @@ -14,25 +14,35 @@ export interface Emoji { const emojiByName = new Map(); const emojiByCode = new Map(); -for (const e of emojiList) { - const emoji = e as Emoji; +const cached = [] as Emoji[]; +export async function loadEmojiList() { + if (cached.length) return cached; - emoji.emote = { - id: emoji.codes, - name: emoji.name, - unicode: emoji.char, - provider: "EMOJI", - flags: 0, - } as SevenTV.ActiveEmote; + const assetsBase = inject(SITE_ASSETS_URL, ""); + const data = (await (await fetch(assetsBase + "/emoji/emoji.json")).json().catch(() => void 0)) as Emoji[]; - emojiByName.set(emoji.name, emoji); - emojiByCode.set(emoji.char, emoji); + for (const e of data) { + const emoji = e as Emoji; + + emoji.emote = { + id: emoji.codes, + name: emoji.name, + unicode: emoji.char, + provider: "EMOJI", + flags: 0, + } as SevenTV.ActiveEmote; + + emojiByName.set(emoji.name, emoji); + emojiByCode.set(emoji.char, emoji); + } + + cached.push(...data); } -export const useEmoji = () => { +export function useEmoji() { return reactive({ - emojiList: emojiList as Emoji[], + emojiList: cached, emojiByCode, emojiByName, }); -}; +} diff --git a/src/composable/useWorker.ts b/src/composable/useWorker.ts index 04bc3d412..07d22799c 100644 --- a/src/composable/useWorker.ts +++ b/src/composable/useWorker.ts @@ -44,9 +44,11 @@ async function init(bc: BroadcastChannel, originURL: string): Promise { - log.error("Unable to fetch worker data", err); - }); + const data = await fetch(workerURL || "") + .then((r) => r.blob()) + .catch((err) => { + log.error("Unable to fetch worker data", err); + }); if (!data) return Promise.reject("There was an error fetching worker data"); log.info("Received worker data", `(${data.size} bytes)`); diff --git a/src/content/content.ts b/src/content/content.ts index bbf7de4c1..989794b33 100644 --- a/src/content/content.ts +++ b/src/content/content.ts @@ -1,4 +1,5 @@ import { APP_BROADCAST_CHANNEL } from "@/common/Constant"; +import { insertEmojiVectors } from "./emoji"; // Inject extension into site const inject = () => { @@ -25,6 +26,11 @@ const inject = () => { } (document.head || document.documentElement).appendChild(script); + + // Insert emojis + setTimeout(() => { + insertEmojiVectors(); + }, 1e3); }; const bc = new BroadcastChannel(APP_BROADCAST_CHANNEL); diff --git a/src/content/emoji.ts b/src/content/emoji.ts new file mode 100644 index 000000000..d8db600c6 --- /dev/null +++ b/src/content/emoji.ts @@ -0,0 +1,27 @@ +/** + * Inserts the emoji vectors into the DOM. + */ +export async function insertEmojiVectors(): Promise { + const container = document.createElement("div"); + container.id = "seventv-emoji-container"; + container.style.display = "none"; + container.style.position = "fixed"; + container.style.top = "-1px"; + container.style.left = "-1px"; + + // Get path to emoji blocks in assets + const base = chrome.runtime.getURL("assets/emoji"); + const blocks = 11; + + for (let i = 0; i < blocks; i++) { + const data = (await fetch(base + "/emojis" + i + ".svg")).text(); + + const element = document.createElement("div"); + element.id = "emojis" + i; + element.innerHTML = await data; + + container.appendChild(element); + } + + document.head.appendChild(container); +} diff --git a/src/site/App.vue b/src/site/App.vue index d073244df..32dda03e1 100644 --- a/src/site/App.vue +++ b/src/site/App.vue @@ -4,10 +4,6 @@ - -
- -
@@ -24,14 +20,15 @@ import { markRaw, onMounted, ref } from "vue"; import { APP_BROADCAST_CHANNEL, SITE_WORKER_URL } from "@/common/Constant"; import { log } from "@/common/Logger"; import { db } from "@/db/idb"; +import { convertEmojis } from "@/composable/chat/useChatEmotes"; +import { loadEmojiList } from "@/composable/useEmoji"; import { useFrankerFaceZ } from "@/composable/useFrankerFaceZ"; import { fillSettings, useConfig, useSettings } from "@/composable/useSettings"; import { useWorker } from "@/composable/useWorker"; import Global from "./global/Global.vue"; -import YouTubeSite from "./youtube.com/YouTubeSite.vue"; -const EmojiContainer = defineAsyncComponent(() => import("@/site/EmojiContainer.vue")); const TwitchSite = defineAsyncComponent(() => import("@/site/twitch.tv/TwitchSite.vue")); +const YouTubeSite = defineAsyncComponent(() => import("@/site/youtube.com/YouTubeSite.vue")); if (import.meta.hot) { import.meta.hot.on("full-reload", () => { @@ -89,6 +86,9 @@ bc.addEventListener("message", (ev) => { } }); +// Load emojis +loadEmojiList().then(() => convertEmojis()); + log.setContextName(`site/${domain}`); onMounted(() => { diff --git a/src/site/EmojiContainer.vue b/src/site/EmojiContainer.vue deleted file mode 100644 index 4d79751f3..000000000 --- a/src/site/EmojiContainer.vue +++ /dev/null @@ -1,39 +0,0 @@ - - - - - diff --git a/src/site/global/settings/SettingsViewHome.vue b/src/site/global/settings/SettingsViewHome.vue index 9d5fd932c..b7a2600d0 100644 --- a/src/site/global/settings/SettingsViewHome.vue +++ b/src/site/global/settings/SettingsViewHome.vue @@ -9,7 +9,12 @@ @@ -28,6 +33,7 @@ import { storeToRefs } from "pinia"; import { useStore } from "@/store/main"; import Changelog from "@/site/global/Changelog.vue"; +import CloudIcon from "@/assets/svg/icons/CloudIcon.vue"; import UiScrollable from "@/ui/UiScrollable.vue"; const { theme } = storeToRefs(useStore()); @@ -36,6 +42,7 @@ const appName = import.meta.env.VITE_APP_NAME; const appContainer = import.meta.env.VITE_APP_CONTAINER ?? "Extension"; const appServer = import.meta.env.VITE_APP_API ?? "Offline"; const version = import.meta.env.VITE_APP_VERSION; +const isRemote = seventv.remote || false; const twitterScript = document.createElement("script"); twitterScript.async = true; @@ -75,6 +82,13 @@ document.head.appendChild(twitterScript); align-items: center; color: hsla(0deg, 0%, 50%, 90%); } + + .seventv-version-remote { + display: inline-block; + vertical-align: middle; + margin-left: 0.5rem; + color: rgba(70, 225, 150, 100%); + } } } diff --git a/src/site/site.app.ts b/src/site/site.app.ts new file mode 100644 index 000000000..e54bc7e0a --- /dev/null +++ b/src/site/site.app.ts @@ -0,0 +1,76 @@ +import { createApp, h, provide } from "vue"; +import { createPinia } from "pinia"; +import { SITE_ASSETS_URL, SITE_EXT_OPTIONS_URL, SITE_WORKER_URL } from "@/common/Constant"; +import App from "@/site/App.vue"; +import { apolloClient } from "@/apollo/apollo"; +import { TextPaintDirective } from "@/directive/TextPaintDirective"; +import { TooltipDirective } from "@/directive/TooltipDirective"; +import { setupI18n } from "@/i18n"; +import { ApolloClients } from "@vue/apollo-composable"; + +if (!("seventv" in window)) { + (window as Window & { seventv?: SeventvGlobalScope }).seventv = { host_manifest: null }; +} + +const appID = Date.now().toString(); + +// Sanity Check +// +// Detect duplicate instances +const roots = document.querySelectorAll("#seventv-root, script#seventv"); + +let dupeCount = 0; +for (let i = 0; i < roots.length; i++) { + const root = roots.item(i); + if (!root) continue; + + const rootID = root.getAttribute("data-app-id"); + if (root.tagName === "script" || (rootID && rootID === appID)) continue; + + dupeCount++; +} + +if (dupeCount > 0) { + const oldTitle = document.title; + document.title = "BIG PROBLEM - 7TV - Twitch"; + alert( + "[7TV] Woah there! It seems you're running multiple different instances of 7TV. Please disable any other version of the extension and try again.", + ); + + document.title = oldTitle; + throw new Error("Duplicate 7TV instances detected, aborting"); +} + +// Create Vue App +const root = document.createElement("div"); +root.id = "seventv-root"; +root.setAttribute("data-app-id", appID); + +document.body.append(root); + +const scr = document.querySelector("script#seventv-extension"); + +const app = createApp({ + setup() { + provide(ApolloClients, { + default: apolloClient, + }); + }, + render: () => h(App), +}); + +app.provide("app-id", appID); + +const extensionOrigin = scr?.getAttribute("extension_origin") ?? ""; +app.provide( + SITE_WORKER_URL, + seventv.remote ? seventv.host_manifest?.worker_file ?? null : null ?? scr?.getAttribute("worker_url"), +); +app.provide(SITE_ASSETS_URL, extensionOrigin + "assets"); +app.provide(SITE_EXT_OPTIONS_URL, extensionOrigin + "index.html"); + +app.use(createPinia()) + .use(setupI18n()) + .directive("tooltip", TooltipDirective) + .directive("cosmetic-paint", TextPaintDirective) + .mount("#seventv-root"); diff --git a/src/site/site.ts b/src/site/site.ts index 9f775a3fd..75db47eac 100644 --- a/src/site/site.ts +++ b/src/site/site.ts @@ -1,69 +1,45 @@ -import { createApp, h, provide } from "vue"; -import { createPinia } from "pinia"; -import { SITE_ASSETS_URL, SITE_EXT_OPTIONS_URL, SITE_WORKER_URL } from "@/common/Constant"; -import App from "@/site/App.vue"; -import { apolloClient } from "@/apollo/apollo"; -import { TextPaintDirective } from "@/directive/TextPaintDirective"; -import { TooltipDirective } from "@/directive/TooltipDirective"; -import { setupI18n } from "@/i18n"; -import { ApolloClients } from "@vue/apollo-composable"; - -const appID = Date.now().toString(); - -// Sanity Check -// -// Detect duplicate instances -const roots = document.querySelectorAll("#seventv-root, script#seventv"); - -let dupeCount = 0; -for (let i = 0; i < roots.length; i++) { - const root = roots.item(i); - if (!root) continue; - - const rootID = root.getAttribute("data-app-id"); - if (root.tagName === "script" || (rootID && rootID === appID)) continue; - - dupeCount++; -} - -if (dupeCount > 0) { - const oldTitle = document.title; - document.title = "BIG PROBLEM - 7TV - Twitch"; - alert( - "[7TV] Woah there! It seems you're running multiple different instances of 7TV. Please disable any other version of the extension and try again.", - ); - - document.title = oldTitle; - throw new Error("Duplicate 7TV instances detected, aborting"); -} - -// Create Vue App -const root = document.createElement("div"); -root.id = "seventv-root"; -root.setAttribute("data-app-id", appID); - -document.body.append(root); - -const scr = document.querySelector("script#seventv-extension"); - -const app = createApp({ - setup() { - provide(ApolloClients, { - default: apolloClient, - }); - }, - render: () => h(App), -}); - -app.provide("app-id", appID); - -const extensionOrigin = scr?.getAttribute("extension_origin") ?? ""; -app.provide(SITE_WORKER_URL, scr?.getAttribute("worker_url")); -app.provide(SITE_ASSETS_URL, extensionOrigin + "assets"); -app.provide(SITE_EXT_OPTIONS_URL, extensionOrigin + "index.html"); - -app.use(createPinia()) - .use(setupI18n()) - .directive("tooltip", TooltipDirective) - .directive("cosmetic-paint", TextPaintDirective) - .mount("#seventv-root"); +import { log } from "@/common/Logger"; +import { semanticVersionToNumber } from "@/common/Transform"; + +(async () => { + const manifestURL = `${import.meta.env.VITE_APP_HOST}/manifest${ + import.meta.env.VITE_APP_VERSION_BRANCH ? "." + import.meta.env.VITE_APP_VERSION_BRANCH.toLowerCase() : "" + }.json`; + + const manifest = await fetch(manifestURL) + .then((res) => res.json()) + .catch((err) => log.error("", "Failed to fetch host manifest", err.message)); + + const localVersion = semanticVersionToNumber(import.meta.env.VITE_APP_VERSION); + const hostedVersion = manifest ? semanticVersionToNumber(manifest.version) : 0; + + (window as Window & { seventv?: SeventvGlobalScope }).seventv = { + host_manifest: manifest ?? null, + }; + + if (manifest && hostedVersion > localVersion) { + seventv.remote = true; + + const scr = document.createElement("script"); + scr.id = "seventv-site-hosted"; + scr.src = manifest.index_file; + scr.type = "module"; + + const style = document.createElement("link"); + style.rel = "stylesheet"; + style.type = "text/css"; + style.href = manifest.stylesheet_file; + style.setAttribute("charset", "utf-8"); + style.setAttribute("content", "text/html"); + style.setAttribute("http-equiv", "content-type"); + style.id = "seventv-stylesheet"; + + document.head.appendChild(style); + document.head.appendChild(scr); + + log.info("", "Using Hosted Mode,", "v" + manifest.version); + } else { + import("./site.app"); + log.info("", "Using Local Mode,", "v" + import.meta.env.VITE_APP_VERSION); + } +})(); diff --git a/src/types/app.d.ts b/src/types/app.d.ts index 7762b7703..702f609be 100644 --- a/src/types/app.d.ts +++ b/src/types/app.d.ts @@ -1,3 +1,15 @@ +declare interface SeventvGlobalScope { + host_manifest: null | { + version: string; + worker_file: string; + stylesheet_file: string; + index_file: string; + }; + remote?: boolean; +} + +declare const seventv: SeventvGlobalScope; + declare namespace SevenTV { interface Emote { id: ObjectID; diff --git a/vite.config.hosted.ts b/vite.config.hosted.ts new file mode 100644 index 000000000..90aa3a756 --- /dev/null +++ b/vite.config.hosted.ts @@ -0,0 +1,111 @@ +import { appName, getFullVersion, r } from "./vite.utils"; +import vuei18n from "@intlify/unplugin-vue-i18n/vite"; +import vue from "@vitejs/plugin-vue"; +import fs from "fs-extra"; +import path from "path"; +import { defineConfig, loadEnv } from "vite"; + +export default defineConfig(() => { + const mode = process.env.NODE_ENV ?? ""; + const isNightly = process.env.BRANCH === "nightly"; + const outDir = process.env.OUT_DIR || ""; + const fullVersion = getFullVersion(isNightly); + + const stylesheetFileName = `seventv.style.${fullVersion}.css`; + const indexFileName = `index.${fullVersion}.js`; + + process.env = { + ...process.env, + ...loadEnv(mode, process.cwd()), + VITE_APP_NAME: appName, + VITE_APP_VERSION: fullVersion, + VITE_APP_VERSION_BRANCH: process.env.BRANCH, + VITE_APP_STYLESHEET_NAME: stylesheetFileName, + VITE_APP_CHANGELOG: fs.readFileSync( + r( + { + nightly: "CHANGELOG-nightly.md", + }[process.env.BRANCH ?? ""] ?? "CHANGELOG.md", + ), + "utf-8", + ), + }; + + return { + mode, + resolve: { + alias: { + "@": path.resolve(__dirname, "src"), + "@locale": path.resolve(__dirname, "locale"), + "vue-i18n": "vue-i18n/dist/vue-i18n.runtime.esm-bundler.js", + }, + }, + define: { + "process.env": {}, + }, + build: { + emptyOutDir: true, + outDir: "dist-hosted" + "/" + outDir, + lib: { + formats: ["es"], + entry: r("src/site/site.app.ts"), + fileName: () => `v${fullVersion}/index.${fullVersion}.js`, + name: "seventv-site", + }, + rollupOptions: { + output: { + inlineDynamicImports: false, + assetFileNames: `v${fullVersion}/seventv.[name].${fullVersion}[extname]`, + chunkFileNames: `v${fullVersion}/seventv.[name].${fullVersion}.js`, + + sanitizeFileName: (name: string) => name.toLowerCase(), + + manualChunks: { + "site.twitch.tv": ["src/site/twitch.tv/TwitchSite.vue"], + "site.youtube.com": ["src/site/youtube.com/YouTubeSite.vue"], + "site.global": ["src/site/global/Global.vue"], + }, + }, + }, + }, + + plugins: [ + vue(), + vuei18n({ + include: r("./locale/*"), + }), + + // Create hosted manifest + { + name: "create-hosted-manifest", + async writeBundle(this) { + const man = { + version: getFullVersion(isNightly), + index_file: `${process.env.VITE_APP_HOST}/v${fullVersion}/${indexFileName}`, + stylesheet_file: `${process.env.VITE_APP_HOST}/v${fullVersion}/${stylesheetFileName}`, + worker_file: `${process.env.VITE_APP_HOST}/v${fullVersion}/worker.${fullVersion}.js`, + }; + + const manifestName = process.env.BRANCH + ? `manifest.${process.env.BRANCH.toLowerCase()}.json` + : "manifest.json"; + + setTimeout(() => { + const p = r("dist-hosted") + (outDir ? "/" + outDir : ""); + + // Copy worker to version scope (if it's there) + const workerPath = r("dist/worker.js"); + if (fs.existsSync(workerPath)) { + fs.copySync(workerPath, `${p}/v${fullVersion}/worker.${fullVersion}.js`); + } else { + man.worker_file = ""; + } + + // Set up manifest + fs.writeJSONSync(p + "/" + manifestName, man); + }); + }, + }, + ], + }; +}); diff --git a/vite.config.ts b/vite.config.ts index bdd92c9ce..9181af0e7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -22,7 +22,7 @@ const ignoreHMR = [ "SettingsModule.vue", ]; -const alwaysHot = ["src/background/background.ts"]; +const alwaysHot = ["src/background/background.ts", "src/content/content.ts", "src/content/emoji.ts"]; // https://vitejs.dev/config/ export default defineConfig(() => {