diff --git a/packages/frontend/admin/src/setup.ts b/packages/frontend/admin/src/setup.ts index dd29d6bab..c89c68027 100644 --- a/packages/frontend/admin/src/setup.ts +++ b/packages/frontend/admin/src/setup.ts @@ -1,3 +1 @@ -import { setupBrowser } from '@affine/core/bootstrap'; - -await setupBrowser(); +import '@affine/core/bootstrap/browser'; diff --git a/packages/frontend/apps/electron/renderer/setup.ts b/packages/frontend/apps/electron/renderer/setup.ts index 9970cae66..2b5935864 100644 --- a/packages/frontend/apps/electron/renderer/setup.ts +++ b/packages/frontend/apps/electron/renderer/setup.ts @@ -1,6 +1,3 @@ +import '@affine/core/bootstrap/electron'; import '@affine/component/theme'; import './global.css'; - -import { setupElectron } from '@affine/core/bootstrap'; - -setupElectron(); diff --git a/packages/frontend/apps/electron/renderer/shell/setup.ts b/packages/frontend/apps/electron/renderer/shell/setup.ts index 5e76f1368..fe6c84404 100644 --- a/packages/frontend/apps/electron/renderer/shell/setup.ts +++ b/packages/frontend/apps/electron/renderer/shell/setup.ts @@ -1,6 +1,3 @@ +import '@affine/core/bootstrap/electron'; import '@affine/component/theme'; import '../global.css'; - -import { setupElectron } from '@affine/core/bootstrap'; - -setupElectron(); diff --git a/packages/frontend/apps/mobile/src/setup.ts b/packages/frontend/apps/mobile/src/setup.ts index a2a83a681..27bd3f467 100644 --- a/packages/frontend/apps/mobile/src/setup.ts +++ b/packages/frontend/apps/mobile/src/setup.ts @@ -1,6 +1,3 @@ +import '@affine/core/bootstrap/browser'; import '@affine/component/theme'; import '@affine/core/mobile/styles/mobile.css'; - -import { setupBrowser } from '@affine/core/bootstrap'; - -await setupBrowser(); diff --git a/packages/frontend/apps/web/src/setup.ts b/packages/frontend/apps/web/src/setup.ts index 472648fde..d7348bf50 100644 --- a/packages/frontend/apps/web/src/setup.ts +++ b/packages/frontend/apps/web/src/setup.ts @@ -1,5 +1,2 @@ +import '@affine/core/bootstrap/browser'; import '@affine/component/theme'; - -import { setupBrowser } from '@affine/core/bootstrap'; - -await setupBrowser(); diff --git a/packages/frontend/core/src/bootstrap/app.ts b/packages/frontend/core/src/bootstrap/app.ts deleted file mode 100644 index 2a1ead92b..000000000 --- a/packages/frontend/core/src/bootstrap/app.ts +++ /dev/null @@ -1,4 +0,0 @@ -import '@affine/env/constant'; -import '../types/types.d.ts'; - -export { setupGlobal as setupEnvironment } from '@affine/env/global'; diff --git a/packages/frontend/core/src/bootstrap/browser.ts b/packages/frontend/core/src/bootstrap/browser.ts new file mode 100644 index 000000000..cac814e59 --- /dev/null +++ b/packages/frontend/core/src/bootstrap/browser.ts @@ -0,0 +1,5 @@ +// ORDER MATTERS +import './global-error-handler'; +import './env'; +import './public-path'; +import './polyfill/browser'; diff --git a/packages/frontend/core/src/bootstrap/electron.ts b/packages/frontend/core/src/bootstrap/electron.ts new file mode 100644 index 000000000..f0391ff46 --- /dev/null +++ b/packages/frontend/core/src/bootstrap/electron.ts @@ -0,0 +1,4 @@ +// ORDER MATTERS +import './env'; +import './public-path'; +import './polyfill/electron'; diff --git a/packages/frontend/core/src/bootstrap/env.ts b/packages/frontend/core/src/bootstrap/env.ts new file mode 100644 index 000000000..3d5837888 --- /dev/null +++ b/packages/frontend/core/src/bootstrap/env.ts @@ -0,0 +1,6 @@ +import '@affine/env/constant'; +import '../types/types.d.ts'; + +import { setupGlobal } from '@affine/env/global'; + +setupGlobal(); diff --git a/packages/frontend/core/src/bootstrap/error.png b/packages/frontend/core/src/bootstrap/error.png new file mode 100644 index 000000000..03327d40e Binary files /dev/null and b/packages/frontend/core/src/bootstrap/error.png differ diff --git a/packages/frontend/core/src/bootstrap/global-error-handler.ts b/packages/frontend/core/src/bootstrap/global-error-handler.ts new file mode 100644 index 000000000..597769e85 --- /dev/null +++ b/packages/frontend/core/src/bootstrap/global-error-handler.ts @@ -0,0 +1,81 @@ +/* eslint-disable no-var */ +import errorImg from './error.png'; + +var errorEl: HTMLDivElement | null = null; +function showGlobalErrorPage() { + if (errorEl) { + return; + } + + errorEl = document.createElement('div'); + errorEl.innerHTML = [ + '', + '
', + '', + '
', + '

Unsupported Environment

', + '

', + 'It looks like AFFiNE cannot run in this environment.', + "Please ensure you are using a supported browser or update your device's operating system to the latest version.", + 'If the issue persists, visit our support page for further assistance.', + '

', + '
', + '
', + ].join(''); + errorEl.setAttribute( + 'style', + 'position:absolute;top:0;left:0;height:100vh;width:100vw;display:flex;flex-direction:column;align-items:center;justify-content:center;background:white;z-index:999;' + ); + document.body.append(errorEl); +} + +function registerGlobalErrorHandler() { + function handleGlobalUnrecoverableError( + e: ErrorEvent | PromiseRejectionEvent + ) { + var error = + 'error' in e ? e.error : e.reason instanceof Error ? e.reason : null; + console.error('unhandled unrecoverable error', error); + + const shouldCache = + // syntax error + error && error instanceof SyntaxError; + + if (!shouldCache) { + return; + } + + e.stopImmediatePropagation(); + showGlobalErrorPage(); + } + if (typeof document !== 'undefined') { + globalThis.addEventListener( + 'unhandledrejection', + handleGlobalUnrecoverableError + ); + globalThis.addEventListener('error', handleGlobalUnrecoverableError); + } +} + +function ensureBasicEnvironment() { + var globals = ['Promise', 'Map', 'fetch', 'customElements']; + + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (var i = 0; i < globals.length; i++) { + if (!(globals[i] in globalThis)) { + showGlobalErrorPage(); + return; + } + } +} + +registerGlobalErrorHandler(); +ensureBasicEnvironment(); diff --git a/packages/frontend/core/src/bootstrap/index.ts b/packages/frontend/core/src/bootstrap/index.ts deleted file mode 100644 index e15e37ed8..000000000 --- a/packages/frontend/core/src/bootstrap/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import '../types/types.d.ts'; - -import { setupEnvironment } from './app'; -import { polyfillBrowser, polyfillElectron } from './polyfill'; - -export function setupElectron() { - setupEnvironment(); - polyfillElectron(); -} - -export async function setupBrowser() { - setupEnvironment(); - __webpack_public_path__ = environment.publicPath; - await polyfillBrowser(); -} diff --git a/packages/frontend/core/src/bootstrap/polyfill/index.ts b/packages/frontend/core/src/bootstrap/polyfill/browser.ts similarity index 51% rename from packages/frontend/core/src/bootstrap/polyfill/index.ts rename to packages/frontend/core/src/bootstrap/polyfill/browser.ts index 3551f4994..b84ee578d 100644 --- a/packages/frontend/core/src/bootstrap/polyfill/index.ts +++ b/packages/frontend/core/src/bootstrap/polyfill/browser.ts @@ -3,13 +3,7 @@ import { polyfillPromise } from './promise-with-resolvers'; import { polyfillEventLoop } from './request-idle-callback'; import { polyfillResizeObserver } from './resize-observer'; -export function polyfillElectron() { - polyfillResizeObserver(); -} - -export async function polyfillBrowser() { - polyfillResizeObserver(); - polyfillEventLoop(); - await polyfillPromise(); - await polyfillDispose(); -} +polyfillResizeObserver(); +polyfillEventLoop(); +await polyfillPromise(); +await polyfillDispose(); diff --git a/packages/frontend/core/src/bootstrap/polyfill/electron.ts b/packages/frontend/core/src/bootstrap/polyfill/electron.ts new file mode 100644 index 000000000..707662820 --- /dev/null +++ b/packages/frontend/core/src/bootstrap/polyfill/electron.ts @@ -0,0 +1,3 @@ +import { polyfillResizeObserver } from './resize-observer'; + +polyfillResizeObserver(); diff --git a/packages/frontend/core/src/bootstrap/public-path.ts b/packages/frontend/core/src/bootstrap/public-path.ts new file mode 100644 index 000000000..a5b6398c2 --- /dev/null +++ b/packages/frontend/core/src/bootstrap/public-path.ts @@ -0,0 +1 @@ +__webpack_public_path__ = environment.publicPath; diff --git a/tools/cli/src/webpack/cache-group.ts b/tools/cli/src/webpack/cache-group.ts index 1ec621cf7..032900971 100644 --- a/tools/cli/src/webpack/cache-group.ts +++ b/tools/cli/src/webpack/cache-group.ts @@ -5,6 +5,11 @@ function testPackageName(regexp: RegExp): (module: any) => boolean { // https://hackernoon.com/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758 export const productionCacheGroups = { + errorHandler: { + test: /global-error-handler/, + priority: 1000, + enforce: true, + }, asyncVendor: { test: /[\\/]node_modules[\\/]/, name(module: any) { diff --git a/tools/cli/src/webpack/config.ts b/tools/cli/src/webpack/config.ts index f81337b58..a4a63c05d 100644 --- a/tools/cli/src/webpack/config.ts +++ b/tools/cli/src/webpack/config.ts @@ -56,13 +56,7 @@ const OptimizeOptionOptions: ( minChunks: 1, maxInitialRequests: Number.MAX_SAFE_INTEGER, maxAsyncRequests: Number.MAX_SAFE_INTEGER, - cacheGroups: - buildFlags.mode === 'production' - ? productionCacheGroups - : { - default: false, - vendors: false, - }, + cacheGroups: productionCacheGroups, }, }); @@ -404,6 +398,11 @@ export const createConfiguration: ( maxInitialRequests: Infinity, chunks: 'all', cacheGroups: { + errorHandler: { + test: /global-error-handler/, + priority: 1000, + enforce: true, + }, defaultVendors: { test: `[\\/]node_modules[\\/](?!.*vanilla-extract)`, priority: -10,