diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml index 7abd011a72..1054d304af 100644 --- a/.github/workflows/build-images.yml +++ b/.github/workflows/build-images.yml @@ -135,83 +135,6 @@ jobs: path: ./packages/frontend/apps/mobile/dist if-no-files-found: error - build-web-selfhost: - name: Build @affine/web selfhost - runs-on: ubuntu-latest - environment: ${{ github.event.inputs.flavor }} - steps: - - uses: actions/checkout@v4 - - name: Setup Version - id: version - uses: ./.github/actions/setup-version - - name: Setup Node.js - uses: ./.github/actions/setup-node - - name: Build Core - run: yarn nx build @affine/web --skip-nx-cache - env: - BUILD_TYPE: ${{ github.event.inputs.flavor }} - PUBLIC_PATH: '/' - SELF_HOSTED: true - MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }} - - name: Download selfhost fonts - run: node ./scripts/download-blocksuite-fonts.mjs - - name: Upload web artifact - uses: actions/upload-artifact@v4 - with: - name: selfhost-web - path: ./packages/frontend/apps/web/dist - if-no-files-found: error - - build-mobile-selfhost: - name: Build @affine/mobile selfhost - runs-on: ubuntu-latest - environment: ${{ github.event.inputs.flavor }} - steps: - - uses: actions/checkout@v4 - - name: Setup Version - id: version - uses: ./.github/actions/setup-version - - name: Setup Node.js - uses: ./.github/actions/setup-node - - name: Build Mobile - run: yarn nx build @affine/mobile --skip-nx-cache - env: - BUILD_TYPE: ${{ github.event.inputs.flavor }} - PUBLIC_PATH: '/' - SELF_HOSTED: true - MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }} - - name: Upload mobile artifact - uses: actions/upload-artifact@v4 - with: - name: selfhost-mobile - path: ./packages/frontend/apps/mobile/dist - if-no-files-found: error - - build-admin-selfhost: - name: Build @affine/admin selfhost - runs-on: ubuntu-latest - environment: ${{ github.event.inputs.flavor }} - steps: - - uses: actions/checkout@v4 - - name: Setup Version - id: version - uses: ./.github/actions/setup-version - - name: Setup Node.js - uses: ./.github/actions/setup-node - - name: Build admin - run: yarn nx build @affine/admin --skip-nx-cache - env: - BUILD_TYPE: ${{ github.event.inputs.flavor }} - PUBLIC_PATH: '/admin/' - SELF_HOSTED: true - MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }} - - name: Upload admin artifact - uses: actions/upload-artifact@v4 - with: - name: selfhost-admin - path: ./packages/frontend/admin/dist - if-no-files-found: error - build-server-native: name: Build Server native - ${{ matrix.targets.name }} runs-on: ubuntu-latest @@ -256,9 +179,6 @@ jobs: - build-web - build-mobile - build-admin - - build-web-selfhost - - build-mobile-selfhost - - build-admin-selfhost - build-server-native steps: - uses: actions/checkout@v4 @@ -334,24 +254,6 @@ jobs: name: admin path: ./packages/frontend/admin/dist - - name: Download selfhost web artifact - uses: actions/download-artifact@v4 - with: - name: selfhost-web - path: ./packages/frontend/apps/web/dist/selfhost - - - name: Download selfhost mobile artifact - uses: actions/download-artifact@v4 - with: - name: selfhost-mobile - path: ./packages/frontend/apps/mobile/dist/selfhost - - - name: Download selfhost admin artifact - uses: actions/download-artifact@v4 - with: - name: selfhost-admin - path: ./packages/frontend/admin/dist/selfhost - - name: Install Node.js dependencies run: | yarn config set --json supportedArchitectures.cpu '["x64", "arm64", "arm"]' diff --git a/packages/backend/server/src/app.ts b/packages/backend/server/src/app.ts index e63a185b19..28fc0da701 100644 --- a/packages/backend/server/src/app.ts +++ b/packages/backend/server/src/app.ts @@ -24,6 +24,10 @@ export async function createApp() { logger: AFFiNE.affine.stable ? ['log'] : ['verbose'], }); + if (AFFiNE.server.path) { + app.setGlobalPrefix(AFFiNE.server.path); + } + app.use(serverTimingAndCache); app.use( diff --git a/packages/backend/server/src/core/doc-renderer/controller.ts b/packages/backend/server/src/core/doc-renderer/controller.ts index 5e270112c9..243ad9ea68 100644 --- a/packages/backend/server/src/core/doc-renderer/controller.ts +++ b/packages/backend/server/src/core/doc-renderer/controller.ts @@ -18,7 +18,6 @@ interface RenderOptions { } interface HtmlAssets { - html: string; css: string[]; js: string[]; publicPath: string; @@ -27,7 +26,6 @@ interface HtmlAssets { } const defaultAssets: HtmlAssets = { - html: '', css: [], js: [], publicPath: '/', @@ -152,9 +150,15 @@ export class DocRendererController { return null; } + // @TODO(@forehalo): pre-compile html template to accelerate serializing _render(opts: RenderOptions | null, assets: HtmlAssets): string { - if (!opts && assets.html) { - return assets.html; + // TODO(@forehalo): how can we enable the type reference to @affine/env + const env: Record = { + publicPath: assets.publicPath, + }; + + if (this.config.isSelfhosted) { + env.isSelfHosted = true; } const title = opts?.title @@ -182,7 +186,7 @@ export class DocRendererController { ${title} - + ${assets.publicPath.startsWith('/') ? '' : ``} @@ -199,6 +203,10 @@ export class DocRendererController { + + ${Object.entries(env) + .map(([key, val]) => ``) + .join('\n')} ${assets.css.map(url => ``).join('\n')} @@ -214,11 +222,20 @@ export class DocRendererController { */ private readHtmlAssets(path: string): HtmlAssets { const manifestPath = join(path, 'assets-manifest.json'); - const htmlPath = join(path, 'index.html'); try { - const assets = JSON.parse(readFileSync(manifestPath, 'utf-8')); - assets.html = readFileSync(htmlPath, 'utf-8'); + const assets: HtmlAssets = JSON.parse( + readFileSync(manifestPath, 'utf-8') + ); + + const publicPath = this.config.isSelfhosted + ? this.config.server.host + '/' + : assets.publicPath; + + assets.publicPath = publicPath; + assets.js = assets.js.map(path => publicPath + path); + assets.css = assets.css.map(path => publicPath + path); + return assets; } catch (e) { if (this.config.node.prod) { diff --git a/packages/backend/server/src/core/selfhost/index.ts b/packages/backend/server/src/core/selfhost/index.ts index cd0b1a072c..e588028044 100644 --- a/packages/backend/server/src/core/selfhost/index.ts +++ b/packages/backend/server/src/core/selfhost/index.ts @@ -9,6 +9,7 @@ import { import { HttpAdapterHost } from '@nestjs/core'; import type { Application, Request, Response } from 'express'; import { static as serveStatic } from 'express'; +import isMobile from 'is-mobile'; import { Config } from '../../fundamentals'; import { AuthModule } from '../auth'; @@ -58,50 +59,106 @@ export class SelfhostModule implements OnModuleInit { ) {} onModuleInit() { - // selfhost static file location - // web => 'static/selfhost' - // admin => 'static/admin/selfhost' - // mobile => 'static/mobile/selfhost' - const staticPath = join(this.config.projectRoot, 'static'); // in command line mode if (!this.adapterHost.httpAdapter) { return; } const app = this.adapterHost.httpAdapter.getInstance(); + // for example, '/affine' in host [//host.com/affine] const basePath = this.config.server.path; + const staticPath = join(this.config.projectRoot, 'static'); + // web => { + // affine: 'static/index.html', + // selfhost: 'static/selfhost.html' + // } + // admin => { + // affine: 'static/admin/index.html', + // selfhost: 'static/admin/selfhost.html' + // } + // mobile => { + // affine: 'static/mobile/index.html', + // selfhost: 'static/mobile/selfhost.html' + // } + // NOTE(@forehalo): + // the order following routes should be respected, + // otherwise the app won't work properly. + + // START REGION: /admin + // do not allow '/index.html' url, redirect to '/' app.get(basePath + '/admin/index.html', (_req, res) => { - res.redirect(basePath + '/admin'); + return res.redirect(basePath + '/admin'); }); + + // serve all static files app.use( - basePath + '/admin', - serveStatic(join(staticPath, 'admin', 'selfhost'), { + basePath, + serveStatic(join(staticPath, 'admin'), { redirect: false, index: false, + fallthrough: true, }) ); + // fallback all unknown routes app.get( [basePath + '/admin', basePath + '/admin/*'], this.check.use, (_req, res) => { - res.sendFile(join(staticPath, 'admin', 'selfhost', 'index.html')); + res.sendFile( + join( + staticPath, + 'admin', + this.config.isSelfhosted ? 'selfhost.html' : 'index.html' + ) + ); } ); + // END REGION - app.get(basePath + '/index.html', (_req, res) => { - res.redirect(basePath); - }); + // START REGION: /mobile + // serve all static files app.use( basePath, - serveStatic(join(staticPath, 'selfhost'), { + serveStatic(join(staticPath, 'mobile'), { redirect: false, index: false, + fallthrough: true, }) ); - app.get('*', this.check.use, (_req, res) => { - res.sendFile(join(staticPath, 'selfhost', 'index.html')); + // END REGION + + // START REGION: / + // do not allow '/index.html' url, redirect to '/' + app.get(basePath + '/index.html', (_req, res) => { + return res.redirect(basePath); }); + + // serve all static files + app.use( + basePath, + serveStatic(staticPath, { + redirect: false, + index: false, + fallthrough: true, + }) + ); + + // fallback all unknown routes + app.get([basePath, basePath + '/*'], this.check.use, (req, res) => { + const mobile = isMobile({ + ua: req.headers['user-agent'] ?? undefined, + }); + + return res.sendFile( + join( + staticPath, + mobile ? 'mobile' : '', + this.config.isSelfhosted ? 'selfhost.html' : 'index.html' + ) + ); + }); + // END REGION } } diff --git a/packages/backend/server/src/fundamentals/nestjs/config.ts b/packages/backend/server/src/fundamentals/nestjs/config.ts index eb3b210d9d..46dcb45468 100644 --- a/packages/backend/server/src/fundamentals/nestjs/config.ts +++ b/packages/backend/server/src/fundamentals/nestjs/config.ts @@ -3,7 +3,7 @@ import { defineStartupConfig, ModuleConfig } from '../../fundamentals/config'; export interface ServerStartupConfigurations { /** * Base url of AFFiNE server, used for generating external urls. - * default to be `[AFFiNE.protocol]://[AFFiNE.host][:AFFiNE.port]?[AFFiNE.path]` if not specified + * default to be `[AFFiNE.protocol]://[AFFiNE.host][:AFFiNE.port]/[AFFiNE.path]` if not specified */ externalUrl: string; /** diff --git a/packages/backend/server/tests/app/selfhost.e2e.ts b/packages/backend/server/tests/app/selfhost.e2e.ts index d51f839fc9..d295f33a2b 100644 --- a/packages/backend/server/tests/app/selfhost.e2e.ts +++ b/packages/backend/server/tests/app/selfhost.e2e.ts @@ -17,12 +17,17 @@ const test = ava as TestFn<{ db: PrismaClient; }>; +const mobileUAString = + 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Mobile Safari/537.36'; + function initTestStaticFiles(staticPath: string) { const files = { - 'selfhost/index.html': `AFFiNE` + ) + ); +}); + +test('should render correct mobile html', async t => { + const res = await request(t.context.app.getHttpServer()) + .get('/workspace/xxxx/xxx') + .set('user-agent', mobileUAString) + .expect(200); + + t.true( + res.text.includes( + `` + ) + ); +}); + +test.todo('should render correct page preview'); diff --git a/packages/common/env/src/global.ts b/packages/common/env/src/global.ts index 2ea19d7da7..6e5774222d 100644 --- a/packages/common/env/src/global.ts +++ b/packages/common/env/src/global.ts @@ -31,12 +31,12 @@ export type BUILD_CONFIG_TYPE = { // see: tools/workers imageProxyUrl: string; linkPreviewUrl: string; - - // TODO(@forehalo): remove - isSelfHosted: boolean; }; export type Environment = { + // Variant + isSelfHosted: boolean; + // Device isLinux: boolean; isMacOs: boolean; @@ -47,8 +47,10 @@ export type Environment = { isMobile: boolean; isChrome: boolean; isPwa: boolean; - chromeVersion?: number; + + // runtime configs + publicPath: string; }; export function setupGlobal() { @@ -56,24 +58,25 @@ export function setupGlobal() { return; } - let environment: Environment; + let environment: Environment = { + isLinux: false, + isMacOs: false, + isSafari: false, + isWindows: false, + isFireFox: false, + isChrome: false, + isIOS: false, + isPwa: false, + isMobile: false, + isSelfHosted: false, + publicPath: '/', + }; - if (!globalThis.navigator) { - environment = { - isLinux: false, - isMacOs: false, - isSafari: false, - isWindows: false, - isFireFox: false, - isChrome: false, - isIOS: false, - isPwa: false, - isMobile: false, - }; - } else { + if (globalThis.navigator) { const uaHelper = new UaHelper(globalThis.navigator); environment = { + ...environment, isMobile: uaHelper.isMobile, isLinux: uaHelper.isLinux, isMacOs: uaHelper.isMacOs, @@ -96,7 +99,35 @@ export function setupGlobal() { } } - globalThis.environment = environment; + applyEnvironmentOverrides(environment); + globalThis.environment = environment; globalThis.$AFFINE_SETUP = true; } + +function applyEnvironmentOverrides(environment: Environment) { + if (typeof document === 'undefined') { + return; + } + + const metaTags = document.querySelectorAll('meta'); + + metaTags.forEach(meta => { + if (!meta.name.startsWith('env:')) { + return; + } + + const name = meta.name.substring(4); + + // all environments should have default value + // so ignore non-defined overrides + if (name in environment) { + // @ts-expect-error safe + environment[name] = + // @ts-expect-error safe + typeof environment[name] === 'string' + ? meta.content + : JSON.parse(meta.content); + } + }); +} diff --git a/packages/frontend/admin/src/index.tsx b/packages/frontend/admin/src/index.tsx index ced476bc9f..cbe23db833 100644 --- a/packages/frontend/admin/src/index.tsx +++ b/packages/frontend/admin/src/index.tsx @@ -1,4 +1,5 @@ import './global.css'; +import './setup'; import { createRoot } from 'react-dom/client'; diff --git a/packages/frontend/admin/src/setup.ts b/packages/frontend/admin/src/setup.ts new file mode 100644 index 0000000000..dd29d6baba --- /dev/null +++ b/packages/frontend/admin/src/setup.ts @@ -0,0 +1,3 @@ +import { setupBrowser } from '@affine/core/bootstrap'; + +await setupBrowser(); diff --git a/packages/frontend/core/public/fonts/BebasNeue-Light.woff b/packages/frontend/core/public/fonts/BebasNeue-Light.woff new file mode 100644 index 0000000000..ba29a8ab7d Binary files /dev/null and b/packages/frontend/core/public/fonts/BebasNeue-Light.woff differ diff --git a/packages/frontend/core/public/fonts/BebasNeue-Regular.woff b/packages/frontend/core/public/fonts/BebasNeue-Regular.woff new file mode 100644 index 0000000000..543e7f7279 Binary files /dev/null and b/packages/frontend/core/public/fonts/BebasNeue-Regular.woff differ diff --git a/packages/frontend/core/public/fonts/Inter-Italic.woff b/packages/frontend/core/public/fonts/Inter-Italic.woff new file mode 100644 index 0000000000..b2ec2f64b7 Binary files /dev/null and b/packages/frontend/core/public/fonts/Inter-Italic.woff differ diff --git a/packages/frontend/core/public/fonts/Inter-Light-BETA.woff b/packages/frontend/core/public/fonts/Inter-Light-BETA.woff new file mode 100644 index 0000000000..cede0bfd0b Binary files /dev/null and b/packages/frontend/core/public/fonts/Inter-Light-BETA.woff differ diff --git a/packages/frontend/core/public/fonts/Inter-LightItalic-BETA.woff b/packages/frontend/core/public/fonts/Inter-LightItalic-BETA.woff new file mode 100644 index 0000000000..873143932c Binary files /dev/null and b/packages/frontend/core/public/fonts/Inter-LightItalic-BETA.woff differ diff --git a/packages/frontend/core/public/fonts/Inter-Regular.woff b/packages/frontend/core/public/fonts/Inter-Regular.woff new file mode 100644 index 0000000000..749f863641 Binary files /dev/null and b/packages/frontend/core/public/fonts/Inter-Regular.woff differ diff --git a/packages/frontend/core/public/fonts/Inter-SemiBold.woff b/packages/frontend/core/public/fonts/Inter-SemiBold.woff new file mode 100644 index 0000000000..b804c34542 Binary files /dev/null and b/packages/frontend/core/public/fonts/Inter-SemiBold.woff differ diff --git a/packages/frontend/core/public/fonts/Inter-SemiBoldItalic.woff b/packages/frontend/core/public/fonts/Inter-SemiBoldItalic.woff new file mode 100644 index 0000000000..9390f0c066 Binary files /dev/null and b/packages/frontend/core/public/fonts/Inter-SemiBoldItalic.woff differ diff --git a/packages/frontend/core/public/fonts/Kalam-Bold.woff b/packages/frontend/core/public/fonts/Kalam-Bold.woff new file mode 100644 index 0000000000..458f3a4fb0 Binary files /dev/null and b/packages/frontend/core/public/fonts/Kalam-Bold.woff differ diff --git a/packages/frontend/core/public/fonts/Kalam-Light.woff b/packages/frontend/core/public/fonts/Kalam-Light.woff new file mode 100644 index 0000000000..92030cdc30 Binary files /dev/null and b/packages/frontend/core/public/fonts/Kalam-Light.woff differ diff --git a/packages/frontend/core/public/fonts/Kalam-Regular.woff b/packages/frontend/core/public/fonts/Kalam-Regular.woff new file mode 100644 index 0000000000..8ad3069dca Binary files /dev/null and b/packages/frontend/core/public/fonts/Kalam-Regular.woff differ diff --git a/packages/frontend/core/public/fonts/Lora-Bold.woff b/packages/frontend/core/public/fonts/Lora-Bold.woff new file mode 100644 index 0000000000..3d2e1ecdeb Binary files /dev/null and b/packages/frontend/core/public/fonts/Lora-Bold.woff differ diff --git a/packages/frontend/core/public/fonts/Lora-BoldItalic.woff b/packages/frontend/core/public/fonts/Lora-BoldItalic.woff new file mode 100644 index 0000000000..cc53adb544 Binary files /dev/null and b/packages/frontend/core/public/fonts/Lora-BoldItalic.woff differ diff --git a/packages/frontend/core/public/fonts/Lora-Italic.woff b/packages/frontend/core/public/fonts/Lora-Italic.woff new file mode 100644 index 0000000000..2c2c863a82 Binary files /dev/null and b/packages/frontend/core/public/fonts/Lora-Italic.woff differ diff --git a/packages/frontend/core/public/fonts/Lora-Regular.woff b/packages/frontend/core/public/fonts/Lora-Regular.woff new file mode 100644 index 0000000000..c7d539f369 Binary files /dev/null and b/packages/frontend/core/public/fonts/Lora-Regular.woff differ diff --git a/packages/frontend/core/public/fonts/OrelegaOne-Regular.woff b/packages/frontend/core/public/fonts/OrelegaOne-Regular.woff new file mode 100644 index 0000000000..9b07c8f720 Binary files /dev/null and b/packages/frontend/core/public/fonts/OrelegaOne-Regular.woff differ diff --git a/packages/frontend/core/public/fonts/Poppins-Italic.woff b/packages/frontend/core/public/fonts/Poppins-Italic.woff new file mode 100644 index 0000000000..341cbb3df0 Binary files /dev/null and b/packages/frontend/core/public/fonts/Poppins-Italic.woff differ diff --git a/packages/frontend/core/public/fonts/Poppins-Light.woff b/packages/frontend/core/public/fonts/Poppins-Light.woff new file mode 100644 index 0000000000..61f6a5cba6 Binary files /dev/null and b/packages/frontend/core/public/fonts/Poppins-Light.woff differ diff --git a/packages/frontend/core/public/fonts/Poppins-LightItalic.woff b/packages/frontend/core/public/fonts/Poppins-LightItalic.woff new file mode 100644 index 0000000000..2706cd41c8 Binary files /dev/null and b/packages/frontend/core/public/fonts/Poppins-LightItalic.woff differ diff --git a/packages/frontend/core/public/fonts/Poppins-Medium.woff b/packages/frontend/core/public/fonts/Poppins-Medium.woff new file mode 100644 index 0000000000..e2b717d2cd Binary files /dev/null and b/packages/frontend/core/public/fonts/Poppins-Medium.woff differ diff --git a/packages/frontend/core/public/fonts/Poppins-Regular.woff b/packages/frontend/core/public/fonts/Poppins-Regular.woff new file mode 100644 index 0000000000..609eb3dc37 Binary files /dev/null and b/packages/frontend/core/public/fonts/Poppins-Regular.woff differ diff --git a/packages/frontend/core/public/fonts/Poppins-SemiBold.woff b/packages/frontend/core/public/fonts/Poppins-SemiBold.woff new file mode 100644 index 0000000000..c097e14cc8 Binary files /dev/null and b/packages/frontend/core/public/fonts/Poppins-SemiBold.woff differ diff --git a/packages/frontend/core/public/fonts/Poppins-SemiBoldItalic.woff b/packages/frontend/core/public/fonts/Poppins-SemiBoldItalic.woff new file mode 100644 index 0000000000..902dcfd00f Binary files /dev/null and b/packages/frontend/core/public/fonts/Poppins-SemiBoldItalic.woff differ diff --git a/packages/frontend/core/public/fonts/Satoshi-Bold.woff b/packages/frontend/core/public/fonts/Satoshi-Bold.woff new file mode 100644 index 0000000000..e79c96fe7c Binary files /dev/null and b/packages/frontend/core/public/fonts/Satoshi-Bold.woff differ diff --git a/packages/frontend/core/public/fonts/Satoshi-BoldItalic.woff b/packages/frontend/core/public/fonts/Satoshi-BoldItalic.woff new file mode 100644 index 0000000000..448e132858 Binary files /dev/null and b/packages/frontend/core/public/fonts/Satoshi-BoldItalic.woff differ diff --git a/packages/frontend/core/public/fonts/Satoshi-Italic.woff b/packages/frontend/core/public/fonts/Satoshi-Italic.woff new file mode 100644 index 0000000000..4b3f49172a Binary files /dev/null and b/packages/frontend/core/public/fonts/Satoshi-Italic.woff differ diff --git a/packages/frontend/core/public/fonts/Satoshi-Light.woff b/packages/frontend/core/public/fonts/Satoshi-Light.woff new file mode 100644 index 0000000000..33e04efd89 Binary files /dev/null and b/packages/frontend/core/public/fonts/Satoshi-Light.woff differ diff --git a/packages/frontend/core/public/fonts/Satoshi-LightItalic.woff b/packages/frontend/core/public/fonts/Satoshi-LightItalic.woff new file mode 100644 index 0000000000..eda3160034 Binary files /dev/null and b/packages/frontend/core/public/fonts/Satoshi-LightItalic.woff differ diff --git a/packages/frontend/core/public/fonts/Satoshi-Regular.woff b/packages/frontend/core/public/fonts/Satoshi-Regular.woff new file mode 100644 index 0000000000..6dfd5dd662 Binary files /dev/null and b/packages/frontend/core/public/fonts/Satoshi-Regular.woff differ diff --git a/packages/frontend/core/src/bootstrap/index.ts b/packages/frontend/core/src/bootstrap/index.ts index 6ef4229a54..e15e37ed8c 100644 --- a/packages/frontend/core/src/bootstrap/index.ts +++ b/packages/frontend/core/src/bootstrap/index.ts @@ -4,11 +4,12 @@ import { setupEnvironment } from './app'; import { polyfillBrowser, polyfillElectron } from './polyfill'; export function setupElectron() { - polyfillElectron(); setupEnvironment(); + polyfillElectron(); } export async function setupBrowser() { - await polyfillBrowser(); setupEnvironment(); + __webpack_public_path__ = environment.publicPath; + await polyfillBrowser(); } diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/font-extension.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/font-extension.ts index 72456a5fc1..89bb2b2e73 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/font-extension.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/font-extension.ts @@ -5,12 +5,9 @@ import { export function getFontConfigExtension() { return FontConfigExtension( - BUILD_CONFIG.isSelfHosted - ? AffineCanvasTextFonts.map(font => ({ - ...font, - // self-hosted fonts are served from /assets - url: '/assets/' + new URL(font.url).pathname.split('/').pop(), - })) - : AffineCanvasTextFonts + AffineCanvasTextFonts.map(font => ({ + ...font, + url: environment.publicPath + 'fonts/' + font.url.split('/').pop(), + })) ); } diff --git a/packages/frontend/track/src/mixpanel.ts b/packages/frontend/track/src/mixpanel.ts index 96d4a1bf6c..caffa4fa0e 100644 --- a/packages/frontend/track/src/mixpanel.ts +++ b/packages/frontend/track/src/mixpanel.ts @@ -35,8 +35,8 @@ function createMixpanel() { appVersion: BUILD_CONFIG.appVersion, environment: BUILD_CONFIG.appBuildType, editorVersion: BUILD_CONFIG.editorVersion, - isSelfHosted: BUILD_CONFIG.isSelfHosted, isDesktop: BUILD_CONFIG.isElectron, + isSelfHosted: environment.isSelfHosted, }); }, reset() { diff --git a/scripts/download-blocksuite-fonts.mjs b/scripts/download-blocksuite-fonts.mjs index a310b85482..d14031caf1 100644 --- a/scripts/download-blocksuite-fonts.mjs +++ b/scripts/download-blocksuite-fonts.mjs @@ -10,10 +10,9 @@ const fontPath = join( '..', 'packages', 'frontend', - 'apps', - 'web', - 'dist', - 'assets' + 'core', + 'public', + 'fonts' ); await Promise.all( diff --git a/tools/cli/src/webpack/config.ts b/tools/cli/src/webpack/config.ts index 1df716829e..f81337b58a 100644 --- a/tools/cli/src/webpack/config.ts +++ b/tools/cli/src/webpack/config.ts @@ -71,19 +71,23 @@ export const getPublicPath = (buildFlags: BuildFlags) => { if (typeof process.env.PUBLIC_PATH === 'string') { return process.env.PUBLIC_PATH; } - const publicPath = '/'; - if (process.env.COVERAGE || buildFlags.distribution === 'desktop') { - return publicPath; + + if ( + buildFlags.mode === 'development' || + process.env.COVERAGE || + buildFlags.distribution === 'desktop' + ) { + return '/'; } - if (BUILD_TYPE === 'canary') { - return `https://dev.affineassets.com/`; - } else if (BUILD_TYPE === 'beta') { - return `https://beta.affineassets.com/`; - } else if (BUILD_TYPE === 'stable') { - return `https://prod.affineassets.com/`; + switch (BUILD_TYPE) { + case 'stable': + return 'https://prod.affineassets.com/'; + case 'beta': + return 'https://beta.affineassets.com/'; + default: + return 'https://dev.affineassets.com/'; } - return publicPath; }; export const createConfiguration: ( @@ -126,7 +130,8 @@ export const createConfiguration: ( path: join(cwd, 'dist'), clean: buildFlags.mode === 'production', globalObject: 'globalThis', - publicPath: getPublicPath(buildFlags), + // NOTE(@forehalo): always keep it '/' + publicPath: '/', workerPublicPath: '/', }, target: ['web', 'es2022'], diff --git a/tools/cli/src/webpack/s3-plugin.ts b/tools/cli/src/webpack/s3-plugin.ts index 4cbbe796d8..c6cc3aaf10 100644 --- a/tools/cli/src/webpack/s3-plugin.ts +++ b/tools/cli/src/webpack/s3-plugin.ts @@ -26,7 +26,7 @@ export class WebpackS3Plugin implements WebpackPluginInstance { compiler.hooks.assetEmitted.tapPromise( 'WebpackS3Plugin', async (asset, { outputPath }) => { - if (asset === 'index.html') { + if (asset.endsWith('.html')) { return; } const assetPath = join(outputPath, asset); diff --git a/tools/cli/src/webpack/template.html b/tools/cli/src/webpack/template.html index 792ff67b9c..5addb86c3e 100644 --- a/tools/cli/src/webpack/template.html +++ b/tools/cli/src/webpack/template.html @@ -16,7 +16,7 @@ AFFiNE - + <%= PRECONNECT %> diff --git a/tools/cli/src/webpack/webpack.config.ts b/tools/cli/src/webpack/webpack.config.ts index 018f5edba0..9fda8dd2d0 100644 --- a/tools/cli/src/webpack/webpack.config.ts +++ b/tools/cli/src/webpack/webpack.config.ts @@ -5,10 +5,16 @@ import type { BuildFlags } from '@affine/cli/config'; import { Repository } from '@napi-rs/simple-git'; import HTMLPlugin from 'html-webpack-plugin'; import { once } from 'lodash-es'; +import type { Compiler } from 'webpack'; import webpack from 'webpack'; import { merge } from 'webpack-merge'; -import { createConfiguration, rootPath, workspaceRoot } from './config.js'; +import { + createConfiguration, + getPublicPath, + rootPath, + workspaceRoot, +} from './config.js'; import { getBuildConfig } from './runtime-config.js'; const DESCRIPTION = `There can be more than Notion and Miro. AFFiNE is a next-gen knowledge base that brings planning, sorting and creating all together.`; @@ -41,46 +47,106 @@ export function createWebpackConfig(cwd: string, flags: BuildFlags) { } : flags.entry; - const createHTMLPlugin = (entryName = 'app') => { - return new HTMLPlugin({ + const publicPath = getPublicPath(flags); + const cdnOrigin = publicPath.startsWith('/') + ? undefined + : new URL(publicPath).origin; + + const templateParams = { + GIT_SHORT_SHA: gitShortHash(), + DESCRIPTION, + PRECONNECT: cdnOrigin + ? `` + : '', + VIEWPORT_FIT: flags.distribution === 'mobile' ? 'cover' : 'auto', + }; + + const createHTMLPlugins = (entryName: string) => { + const htmlPluginOptions = { template: join(rootPath, 'webpack', 'template.html'), inject: 'body', + filename: 'index.html', minify: false, + templateParameters: templateParams, chunks: [entryName], - filename: `${entryName === 'app' ? 'index' : entryName}.html`, // main entry should take name index.html - templateParameters: (compilation, assets) => { - if (entryName === 'app') { - // emit assets manifest for ssr - compilation.emitAsset( - `assets-manifest.json`, - new webpack.sources.RawSource( - JSON.stringify( - { - ...assets, - gitHash: gitShortHash(), - description: DESCRIPTION, - }, - null, - 2 - ) - ), - { - immutable: true, - } - ); - } - return { - GIT_SHORT_SHA: gitShortHash(), - DESCRIPTION, - PUBLIC_PATH: config.output?.publicPath, - VIEWPORT_FIT: flags.distribution === 'mobile' ? 'cover' : 'auto', - }; - }, - }); + } satisfies HTMLPlugin.Options; + + if (entryName === 'app') { + return [ + { + apply(compiler: Compiler) { + compiler.hooks.compilation.tap( + 'assets-manifest-plugin', + compilation => { + HTMLPlugin.getHooks(compilation).beforeAssetTagGeneration.tap( + 'assets-manifest-plugin', + arg => { + if (!compilation.getAsset('assets-manifest.json')) { + compilation.emitAsset( + `assets-manifest.json`, + new webpack.sources.RawSource( + JSON.stringify( + { + ...arg.assets, + js: arg.assets.js.map(file => + file.substring(arg.assets.publicPath.length) + ), + css: arg.assets.css.map(file => + file.substring(arg.assets.publicPath.length) + ), + gitHash: templateParams.GIT_SHORT_SHA, + description: templateParams.DESCRIPTION, + }, + null, + 2 + ) + ), + { + immutable: false, + } + ); + } + + return arg; + } + ); + } + ); + }, + }, + new HTMLPlugin({ + ...htmlPluginOptions, + publicPath, + meta: { + 'env:publicPath': publicPath, + }, + }), + // selfhost html + new HTMLPlugin({ + ...htmlPluginOptions, + meta: { + 'env:isSelfHosted': 'true', + 'env:publicPath': '/', + }, + filename: 'selfhost.html', + templateParameters: { + ...htmlPluginOptions.templateParameters, + PRECONNECT: '', + }, + }), + ]; + } else { + return [ + new HTMLPlugin({ + ...htmlPluginOptions, + filename: `${entryName}.html`, + }), + ]; + } }; return merge(config, { - entry: entry, - plugins: Object.keys(entry).map(createHTMLPlugin), + entry, + plugins: Object.keys(entry).map(createHTMLPlugins).flat(), }); }