chore: extract ssr as single app

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-03-03 20:01:18 +08:00
parent cead6dffd0
commit 3e84f73aec
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
110 changed files with 544 additions and 698 deletions

View File

@ -42,4 +42,4 @@ jobs:
run: pnpm install
- name: Build web and SSR server
run: |
npm exec turbo run @follow/desktop#build:web @follow/server#build
npm exec turbo run @follow/desktop#build:web @follow/ssr#build

View File

@ -24,7 +24,7 @@ jobs:
const execSync = require("child_process").execSync;
const fs = require("fs");
const commitMessage = execSync("git log -1 --pretty=%B").toString("utf-8");
const semverRegex = /chore: Release (v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-.]+)?)/;
const semverRegex = /release\(desktop\): Release (v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-.]+)?)/;
const match = commitMessage.match(semverRegex);
if (match) {
fs.appendFileSync(process.env.GITHUB_ENV, `tag_version=${match[1]}\n`);

3
.gitignore vendored
View File

@ -15,9 +15,6 @@ stats.html
electron.vite.config.*.mjs
vite.config.*.mjs
# apps/server/**/fonts-data.ts
# apps/server/**/index.template.ts
.generated
.turbo

View File

@ -18,8 +18,8 @@
],
"tailwindCSS.experimental.configFile": {
"apps/mobile/tailwind.config.ts": "apps/mobile/**",
"apps/desktop/src/server/tailwind.config.ts": "apps/desktop/src/server/**",
"apps/desktop/tailwind.config.ts": ["!apps/mobile/**", "!apps/desktop/src/server/**", "**"]
"apps/ssr/tailwind.config.ts": "apps/ssr/**",
"apps/desktop/tailwind.config.ts": ["!apps/mobile/**", "!apps/ssr/**", "**"]
},
"typescript.tsserver.maxTsServerMemory": 8096,
"typescript.tsserver.nodePath": "node",

View File

@ -17,14 +17,13 @@
"build:macos": "electron-vite build && electron-forge make --arch=x64 --platform=darwin && electron-forge make --arch=arm64 --platform=darwin && tsx scripts/merge-yml.ts",
"build:mas": "electron-vite build && electron-forge make --arch=universal --platform=mas",
"build:render": "vite build -c vite.config.electron-render.ts",
"build:rn-web": "rm -rf out/rn-web && cross-env RN_BUILD=1 vite build",
"build:web": "rm -rf out/web && cross-env WEB_BUILD=1 vite build",
"dedupe:locales": "eslint --fix locales/**/*.json",
"depcheck": "npx depcheck --quiet",
"dev": "turbo run @follow/web#dev @follow/server#dev",
"dev": "turbo run @follow/web#dev @follow/ssr#dev",
"dev:debug": "export DEBUG=true && vite --debug",
"dev:electron": "electron-vite dev",
"dev:server": "pnpm run --filter=server dev",
"dev:server": "pnpm run --filter=ssr dev",
"dev:web": "cross-env WEB_BUILD=1 vite",
"generator:i18n-template": "tsx scripts/generate-i18n-locale.ts",
"publish": "electron-vite build && electron-forge publish",

View File

@ -1,88 +0,0 @@
// This file is auto-generated by scripts/generate-i18n-locale.ts
// DONT EDIT THIS FILE MANUALLY
import common_ardz from "../../../../locales/common/ar-DZ.json"
import common_ariq from "../../../../locales/common/ar-IQ.json"
import common_arkw from "../../../../locales/common/ar-KW.json"
import common_arma from "../../../../locales/common/ar-MA.json"
import common_arsa from "../../../../locales/common/ar-SA.json"
import common_artn from "../../../../locales/common/ar-TN.json"
import common_de from "../../../../locales/common/de.json"
import common_en from "../../../../locales/common/en.json"
import common_es from "../../../../locales/common/es.json"
import common_fi from "../../../../locales/common/fi.json"
import common_fr from "../../../../locales/common/fr.json"
import common_it from "../../../../locales/common/it.json"
import common_ja from "../../../../locales/common/ja.json"
import common_ko from "../../../../locales/common/ko.json"
import common_pt from "../../../../locales/common/pt.json"
import common_ru from "../../../../locales/common/ru.json"
import common_tr from "../../../../locales/common/tr.json"
import common_zhCN from "../../../../locales/common/zh-CN.json"
import common_zhHK from "../../../../locales/common/zh-HK.json"
import common_zhTW from "../../../../locales/common/zh-TW.json"
import errors_en from "../../../../locales/errors/en.json"
import external_en from "../../../../locales/external/en.json"
import lang_ardz from "../../../../locales/lang/ar-DZ.json"
import lang_ariq from "../../../../locales/lang/ar-IQ.json"
import lang_arkw from "../../../../locales/lang/ar-KW.json"
import lang_arma from "../../../../locales/lang/ar-MA.json"
import lang_arsa from "../../../../locales/lang/ar-SA.json"
import lang_artn from "../../../../locales/lang/ar-TN.json"
import lang_de from "../../../../locales/lang/de.json"
import lang_en from "../../../../locales/lang/en.json"
import lang_es from "../../../../locales/lang/es.json"
import lang_fi from "../../../../locales/lang/fi.json"
import lang_fr from "../../../../locales/lang/fr.json"
import lang_it from "../../../../locales/lang/it.json"
import lang_ja from "../../../../locales/lang/ja.json"
import lang_ko from "../../../../locales/lang/ko.json"
import lang_pt from "../../../../locales/lang/pt.json"
import lang_ru from "../../../../locales/lang/ru.json"
import lang_tr from "../../../../locales/lang/tr.json"
import lang_zhCN from "../../../../locales/lang/zh-CN.json"
import lang_zhHK from "../../../../locales/lang/zh-HK.json"
import lang_zhTW from "../../../../locales/lang/zh-TW.json"
/**
* This file is the language resource that is loaded in full when the app is initialized.
* When switching languages, the app will automatically download the required language resources,
* we will not load all the language resources to minimize the first screen loading time of the app.
* Generally, we only load english resources synchronously by default.
* In addition, we attach common resources for other languages, and the size of the common resources must be controlled.
*/
export const defaultResources = {
en: {
lang: lang_en,
common: common_en,
external: external_en,
errors: errors_en,
},
"zh-CN": {
lang: lang_zhCN,
common: common_zhCN,
},
"zh-HK": {
lang: lang_zhHK,
common: common_zhHK,
},
ja: {
lang: lang_ja,
common: common_ja,
},
ru: { lang: lang_ru, common: common_ru },
fi: { lang: lang_fi, common: common_fi },
it: { lang: lang_it, common: common_it },
"ar-DZ": { lang: lang_ardz, common: common_ardz },
"ar-SA": { lang: lang_arsa, common: common_arsa },
"ar-MA": { lang: lang_arma, common: common_arma },
"zh-TW": { lang: lang_zhTW, common: common_zhTW },
es: { lang: lang_es, common: common_es },
fr: { lang: lang_fr, common: common_fr },
pt: { lang: lang_pt, common: common_pt },
"ar-IQ": { lang: lang_ariq, common: common_ariq },
"ar-KW": { lang: lang_arkw, common: common_arkw },
"ar-TN": { lang: lang_artn, common: common_artn },
de: { lang: lang_de, common: common_de },
ko: { lang: lang_ko, common: common_ko },
tr: { lang: lang_tr, common: common_tr },
}

View File

@ -1,12 +0,0 @@
// This file is generated by `pnpm run meta`
import i0 from ".././client/pages/(login)/login/metadata"
import i3 from ".././client/pages/(main)/share/feeds/[id]/metadata"
import i2 from ".././client/pages/(main)/share/lists/[id]/metadata"
import i1 from ".././client/pages/(main)/share/users/[id]/metadata"
export default {
"/login": i0,
"/share/users/:id": i1,
"/share/lists/:id": i2,
"/share/feeds/:id": i3,
}

View File

@ -44,10 +44,9 @@ const devPrint = (): PluginOption => ({
},
})
const isRNBuild = process.env.RN_BUILD === "1"
const isWebBuild = process.env.WEB_BUILD === "1"
// eslint-disable-next-line no-console
console.log(green("Build type:"), isRNBuild ? "RN" : isWebBuild ? "Web" : "Unknown")
console.log(green("Build type:"), isWebBuild ? "Web" : "Unknown")
export default ({ mode }) => {
const env = loadEnv(mode, process.cwd())
@ -56,10 +55,10 @@ export default ({ mode }) => {
return defineConfig({
...viteRenderBaseConfig,
root: ROOT,
base: isRNBuild ? "./" : "/",
base: "/",
envDir: resolve(__dirname, "."),
build: {
outDir: resolve(__dirname, isRNBuild ? "out/rn-web" : "out/web"),
outDir: resolve(__dirname, "out/web"),
target: "ES2022",
sourcemap: isCI,
rollupOptions: {

View File

@ -1,150 +0,0 @@
import { callWebviewExpose } from "@follow/shared"
import * as Linking from "expo-linking"
import type { RefObject } from "react"
import { useCallback, useEffect, useState } from "react"
import { Platform } from "react-native"
import type { WebViewProps } from "react-native-webview"
import { WebView } from "react-native-webview"
import { useWebViewNavigation } from "@/src/hooks/useWebViewNavigation"
import { signOut } from "@/src/lib/auth"
/**
* @deprecated
*/
const presetUri = Platform.select({
ios: "rn-web/index.html",
android: "file:///android_asset/raw/index.html",
default: "https://app.follow.is",
})
interface FollowWebViewProps extends WebViewProps {
customUrl?: string
}
const injectedJavaScript = [
`window.__RN__ = true`,
// TODO use more reliable way to detect the page is ready
`window.setTimeout(() => window.ReactNativeWebView.postMessage("ready"), 2000)`,
].join(";")
const styles = {
// https://github.com/react-native-webview/react-native-webview/issues/318#issuecomment-503979211
webview: { backgroundColor: "transparent" },
webviewContainer: { width: "100%" },
} as const
export const FollowWebView = ({
webViewRef,
customUrl,
...props
}: { webViewRef: RefObject<WebView> } & FollowWebViewProps) => {
const { onNavigationStateChange } = useWebViewNavigation({ webViewRef })
const [ready, setReady] = useState(false)
useDeepLink({ webViewRef, ready })
return (
<WebView
ref={webViewRef}
source={{ uri: customUrl ?? presetUri }}
injectedJavaScriptBeforeContentLoaded={injectedJavaScript}
sharedCookiesEnabled
originWhitelist={["*"]}
allowUniversalAccessFromFileURLs
startInLoadingState
allowsBackForwardNavigationGestures
/* Open chrome://inspect/#devices , or Development menu on Safari to debug the WebView. https://github.com/react-native-webview/react-native-webview/blob/master/docs/Debugging.md#debugging-webview-contents */
webviewDebuggingEnabled={__DEV__}
style={styles.webview}
containerStyle={styles.webviewContainer}
onNavigationStateChange={onNavigationStateChange}
// onLoad={() => setReady(false)}
// onLoadProgress={({ nativeEvent }) => setProgress(nativeEvent.progress)}
onError={(e) => {
console.error("WebView error:", e)
}}
onContentProcessDidTerminate={() => webViewRef.current?.reload()}
onMessage={(e) => {
const message = e.nativeEvent.data
if (message === "sign-out") {
signOut()
return
}
if (message === "ready") {
setReady(true)
return
}
}}
{...props}
/>
)
}
// We only need to handle deep link at the first time the app is opened
let lastInitialUrl: string | null = null
const useDeepLink = ({
webViewRef,
ready = true,
}: {
webViewRef: RefObject<WebView>
ready?: boolean
}) => {
const handleDeepLink = useCallback(
async (url: string) => {
const { queryParams, path, hostname } = Linking.parse(url)
const pathname = (hostname || "") + (path || "")
const pathnameTrimmed = pathname?.endsWith("/") ? pathname.slice(0, -1) : pathname
switch (pathnameTrimmed) {
case "/add":
case "/follow": {
if (!queryParams) {
console.error("Invalid URL! queryParams is not available", url)
return
}
const id = queryParams["id"] ?? undefined
const isList = queryParams["type"] === "list"
// const urlParam = queryParams["url"] ?? undefined
if (!id || typeof id !== "string") {
console.error("Invalid URL! id is not a string", url)
return
}
const injectJavaScript = webViewRef.current?.injectJavaScript
if (!injectJavaScript) {
console.error("injectJavaScript is not available")
return
}
callWebviewExpose(injectJavaScript).follow({ id, isList })
return
}
}
},
[webViewRef],
)
// https://reactnative.dev/docs/linking#handling-deep-links
//
// The handler added with Linking.addEventListener() is only triggered when app is already open.
//
// When the app is not already open and the deep link triggered app launch,
// the URL can be obtained with Linking.getInitialURL().
useEffect(() => {
if (!ready) return
const getUrlAsync = async () => {
const initialUrl = await Linking.getInitialURL()
if (!initialUrl) return
if (initialUrl === lastInitialUrl) return
lastInitialUrl = initialUrl
handleDeepLink(initialUrl)
}
getUrlAsync()
// eslint-disable-next-line @eslint-react/web-api/no-leaked-event-listener
const deepLinkListener = Linking.addEventListener("url", (event: { url: string }) => {
handleDeepLink(event.url)
})
return () => deepLinkListener.remove()
}, [handleDeepLink, ready])
}

View File

@ -0,0 +1,88 @@
// This file is auto-generated by scripts/generate-i18n-locale.ts
// DONT EDIT THIS FILE MANUALLY
import common_ardz from "../../../desktop/locales/common/ar-DZ.json"
import common_ariq from "../../../desktop/locales/common/ar-IQ.json"
import common_arkw from "../../../desktop/locales/common/ar-KW.json"
import common_arma from "../../../desktop/locales/common/ar-MA.json"
import common_arsa from "../../../desktop/locales/common/ar-SA.json"
import common_artn from "../../../desktop/locales/common/ar-TN.json"
import common_de from "../../../desktop/locales/common/de.json"
import common_en from "../../../desktop/locales/common/en.json"
import common_es from "../../../desktop/locales/common/es.json"
import common_fi from "../../../desktop/locales/common/fi.json"
import common_fr from "../../../desktop/locales/common/fr.json"
import common_it from "../../../desktop/locales/common/it.json"
import common_ja from "../../../desktop/locales/common/ja.json"
import common_ko from "../../../desktop/locales/common/ko.json"
import common_pt from "../../../desktop/locales/common/pt.json"
import common_ru from "../../../desktop/locales/common/ru.json"
import common_tr from "../../../desktop/locales/common/tr.json"
import common_zhCN from "../../../desktop/locales/common/zh-CN.json"
import common_zhHK from "../../../desktop/locales/common/zh-HK.json"
import common_zhTW from "../../../desktop/locales/common/zh-TW.json"
import errors_en from "../../../desktop/locales/errors/en.json"
import external_en from "../../../desktop/locales/external/en.json"
import lang_ardz from "../../../desktop/locales/lang/ar-DZ.json"
import lang_ariq from "../../../desktop/locales/lang/ar-IQ.json"
import lang_arkw from "../../../desktop/locales/lang/ar-KW.json"
import lang_arma from "../../../desktop/locales/lang/ar-MA.json"
import lang_arsa from "../../../desktop/locales/lang/ar-SA.json"
import lang_artn from "../../../desktop/locales/lang/ar-TN.json"
import lang_de from "../../../desktop/locales/lang/de.json"
import lang_en from "../../../desktop/locales/lang/en.json"
import lang_es from "../../../desktop/locales/lang/es.json"
import lang_fi from "../../../desktop/locales/lang/fi.json"
import lang_fr from "../../../desktop/locales/lang/fr.json"
import lang_it from "../../../desktop/locales/lang/it.json"
import lang_ja from "../../../desktop/locales/lang/ja.json"
import lang_ko from "../../../desktop/locales/lang/ko.json"
import lang_pt from "../../../desktop/locales/lang/pt.json"
import lang_ru from "../../../desktop/locales/lang/ru.json"
import lang_tr from "../../../desktop/locales/lang/tr.json"
import lang_zhCN from "../../../desktop/locales/lang/zh-CN.json"
import lang_zhHK from "../../../desktop/locales/lang/zh-HK.json"
import lang_zhTW from "../../../desktop/locales/lang/zh-TW.json"
/**
* This file is the language resource that is loaded in full when the app is initialized.
* When switching languages, the app will automatically download the required language resources,
* we will not load all the language resources to minimize the first screen loading time of the app.
* Generally, we only load english resources synchronously by default.
* In addition, we attach common resources for other languages, and the size of the common resources must be controlled.
*/
export const defaultResources = {
en: {
lang: lang_en,
common: common_en,
external: external_en,
errors: errors_en,
},
"zh-CN": {
lang: lang_zhCN,
common: common_zhCN,
},
"zh-HK": {
lang: lang_zhHK,
common: common_zhHK,
},
ja: {
lang: lang_ja,
common: common_ja,
},
ru: { lang: lang_ru, common: common_ru },
fi: { lang: lang_fi, common: common_fi },
it: { lang: lang_it, common: common_it },
"ar-DZ": { lang: lang_ardz, common: common_ardz },
"ar-SA": { lang: lang_arsa, common: common_arsa },
"ar-MA": { lang: lang_arma, common: common_arma },
"zh-TW": { lang: lang_zhTW, common: common_zhTW },
es: { lang: lang_es, common: common_es },
fr: { lang: lang_fr, common: common_fr },
pt: { lang: lang_pt, common: common_pt },
"ar-IQ": { lang: lang_ariq, common: common_ariq },
"ar-KW": { lang: lang_arkw, common: common_arkw },
"ar-TN": { lang: lang_artn, common: common_artn },
de: { lang: lang_de, common: common_de },
ko: { lang: lang_ko, common: common_ko },
tr: { lang: lang_tr, common: common_tr },
}

View File

@ -1,5 +1,5 @@
{
"name": "@follow/server",
"name": "@follow/ssr",
"private": true,
"scripts": {
"build": "cross-env NODE_ENV=production vite build && tsx scripts/prepare-vercel-build.ts && tsup && tsx scripts/cleanup-vercel-build.ts",

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -5,7 +5,7 @@ LAST_DEPLOY_COMMIT=$(git rev-parse HEAD^)
CHANGED_FILES=$(git diff --name-only $LAST_DEPLOY_COMMIT HEAD)
# Define an array of paths and files to check
CHECK_PATHS_AND_FILES=("apps/desktop/src/server/" "packages/" "package.json" "pnpm-lock.yaml")
CHECK_PATHS_AND_FILES=("apps/ssr/" "packages/" "package.json" "pnpm-lock.yaml")
ONLY_SERVER_OR_PACKAGES_CHANGES=true
for file in $CHANGED_FILES; do

View File

@ -6,7 +6,7 @@ import type { AppType } from "@follow/shared/hono"
import { hc } from "hono/client"
import { ofetch } from "ofetch"
import PKG from "../../../../package.json"
import PKG from "../../../desktop/package.json"
import { isDev } from "./env"
const getBaseURL = () => {

View File

@ -0,0 +1,12 @@
// This file is generated by `pnpm run meta`
import i0 from "../client/pages/(login)/login/metadata"
import i3 from "../client/pages/(main)/share/feeds/[id]/metadata"
import i2 from "../client/pages/(main)/share/lists/[id]/metadata"
import i1 from "../client/pages/(main)/share/users/[id]/metadata"
export default {
"/login": i0,
"/share/users/:id": i1,
"/share/lists/:id": i2,
"/share/feeds/:id": i3,
}

View File

@ -2,7 +2,7 @@ import daisyui from "daisyui"
import { omit } from "es-toolkit/compat"
import resolveConfig from "tailwindcss/resolveConfig"
import { baseTwConfig } from "../../../../configs/tailwind.base.config"
import { baseTwConfig } from "../../configs/tailwind.base.config"
/** @type {import('tailwindcss').Config} */
export default resolveConfig({
...baseTwConfig,

Some files were not shown because too many files have changed in this diff Show More