refactor!: remove next.js (#3267)

This commit is contained in:
Alex Yang 2023-07-19 00:53:10 +08:00 committed by GitHub
parent 79227a1e7c
commit 47f12f77f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
296 changed files with 4115 additions and 3617 deletions

View File

@ -10,7 +10,7 @@
"tasks": {
"start-web": {
"name": "Start Web",
"command": "yarn nx dev @affine/web --port 8080",
"command": "yarn dev-core",
"runAtStart": true,
"preview": {
"port": 8080

View File

@ -1,6 +1,6 @@
FROM openresty/openresty:1.21.4.1-0-buster
WORKDIR /app
COPY ./apps/web/out ./dist
COPY ./apps/core/dist ./dist
COPY ./.github/deployment/front/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
COPY ./.github/deployment/front/affine.nginx.conf /etc/nginx/conf.d/affine.nginx.conf

View File

@ -101,8 +101,8 @@ jobs:
path: ./apps/storybook/storybook-static
if-no-files-found: error
build-web:
name: Build @affine/web
build-core:
name: Build @affine/core
runs-on: ubuntu-latest
environment: development
@ -110,13 +110,13 @@ jobs:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Build Web
run: yarn nx build @affine/web
- name: Upload artifact
- name: Build Core
run: yarn nx build @affine/core
- name: Upload core artifact
uses: actions/upload-artifact@v3
with:
name: next-js-static
path: ./apps/web/out
name: core
path: ./apps/core/dist
if-no-files-found: error
server-test:
@ -215,7 +215,7 @@ jobs:
matrix:
shard: [1, 2, 3, 4, 5]
environment: development
needs: build-web
needs: build-core
steps:
- uses: actions/checkout@v3
@ -224,11 +224,11 @@ jobs:
with:
playwright-install: true
electron-install: false
- name: Download artifact
- name: Download core artifact
uses: actions/download-artifact@v3
with:
name: next-js-static
path: ./apps/web/out
name: core
path: ./apps/core/dist
- name: Run playwright tests
run: yarn e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
@ -260,7 +260,7 @@ jobs:
name: E2E Migration Test
runs-on: ubuntu-latest
environment: development
needs: [build-web]
needs: build-core
steps:
- uses: actions/checkout@v3
@ -270,11 +270,11 @@ jobs:
playwright-install: true
electron-install: false
- name: Download next static
- name: Download core artifact
uses: actions/download-artifact@v3
with:
name: next-js-static
path: ./apps/web/out
name: core
path: ./apps/core/dist
- name: Unzip
run: yarn unzip
@ -333,7 +333,7 @@ jobs:
target: x86_64-pc-windows-msvc,
test: true,
}
needs: [build-web]
needs: build-core
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
@ -353,10 +353,10 @@ jobs:
env:
NATIVE_TEST: 'true'
- name: Download static resource artifact
- name: Download core artifact
uses: actions/download-artifact@v3
with:
name: next-js-static
name: core
path: apps/electron/resources/web-static
- name: Build Plugins
@ -443,11 +443,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Download next static
- name: Download core artifact
uses: actions/download-artifact@v3
with:
name: next-js-static
path: ./apps/web/out
name: core
path: ./apps/core/dist
- name: Download server dist
uses: actions/download-artifact@v3
with:

View File

@ -62,10 +62,10 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
RELEASE_VERSION: ${{ needs.set-build-version.outputs.version }}
- name: Upload Artifact (web-static)
- name: Upload core artifact
uses: actions/upload-artifact@v3
with:
name: before-make-web-static
name: core
path: apps/electron/resources/web-static
make-distribution:

View File

@ -66,10 +66,10 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
RELEASE_VERSION: ${{ github.event.inputs.version || steps.get-canary-version.outputs.RELEASE_VERSION }}
- name: Upload Artifact (web-static)
- name: Upload core artifact
uses: actions/upload-artifact@v3
with:
name: before-make-web-static
name: core
path: apps/electron/resources/web-static
make-distribution:
@ -120,7 +120,7 @@ jobs:
nx_token: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
- uses: actions/download-artifact@v3
with:
name: before-make-web-static
name: core
path: apps/electron/resources/web-static
- name: Build Plugins

1
.gitignore vendored
View File

@ -13,6 +13,7 @@
/out-tsc
.nyc_output
.coverage
.swc
# dependencies
node_modules

View File

@ -0,0 +1,84 @@
import { hash } from './utils.js';
function testPackageName(regexp: RegExp): (module: any) => boolean {
return (module: any) =>
module.nameForCondition && regexp.test(module.nameForCondition());
}
export const productionCacheGroups = {
asyncVendor: {
test: /[\\/]node_modules[\\/]/,
name(module: any) {
// https://hackernoon.com/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758
const name =
module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)?.[1] ??
'unknown';
return `npm-async-${hash(name)}`;
},
priority: Number.MAX_SAFE_INTEGER,
chunks: 'async' as const,
},
mui: {
name: `npm-mui`,
test: testPackageName(/[\\/]node_modules[\\/](mui|@mui)[\\/]/),
priority: 200,
enforce: true,
},
blocksuite: {
name: `npm-blocksuite`,
test: testPackageName(/[\\/]node_modules[\\/](@blocksuite)[\\/]/),
priority: 200,
enforce: true,
},
react: {
name: `npm-react`,
test: testPackageName(
/[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/
),
priority: 200,
enforce: true,
},
jotai: {
name: `npm-jotai`,
test: testPackageName(/[\\/]node_modules[\\/](jotai)[\\/]/),
priority: 200,
enforce: true,
},
rxjs: {
name: `npm-rxjs`,
test: testPackageName(/[\\/]node_modules[\\/]rxjs[\\/]/),
priority: 200,
enforce: true,
},
lodash: {
name: `npm-lodash`,
test: testPackageName(/[\\/]node_modules[\\/]lodash[\\/]/),
priority: 200,
enforce: true,
},
emotion: {
name: `npm-emotion`,
test: testPackageName(/[\\/]node_modules[\\/](@emotion)[\\/]/),
priority: 200,
enforce: true,
},
vendor: {
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 190,
enforce: true,
},
styles: {
name: 'styles',
test: (module: any) =>
module.nameForCondition &&
/\.css$/.test(module.nameForCondition()) &&
!/^javascript/.test(module.type),
chunks: 'all' as const,
minSize: 1,
minChunks: 1,
reuseExistingChunk: true,
priority: 1000,
enforce: true,
},
};

View File

@ -0,0 +1,306 @@
import { join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
import HTMLPlugin from 'html-webpack-plugin';
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
import { PerfseePlugin } from '@perfsee/webpack';
import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
import CopyPlugin from 'copy-webpack-plugin';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import TerserPlugin from 'terser-webpack-plugin';
import webpack from 'webpack';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import { productionCacheGroups } from './cache-group.js';
import type { BuildFlags } from '@affine/cli/config';
import { projectRoot } from '@affine/cli/config';
import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin';
import { computeCacheKey } from './utils.js';
import type { RuntimeConfig } from '@affine/env/global';
const IN_CI = !!process.env.CI;
export const rootPath = fileURLToPath(new URL('..', import.meta.url));
const require = createRequire(rootPath);
const OptimizeOptionOptions: (
buildFlags: BuildFlags
) => webpack.Configuration['optimization'] = buildFlags => ({
minimize: buildFlags.mode === 'production',
minimizer: [
new TerserPlugin({
minify: TerserPlugin.swcMinify,
parallel: true,
extractComments: true,
terserOptions: {
ecma: 2020,
compress: {
unused: true,
},
mangle: true,
},
}),
],
removeEmptyChunks: true,
providedExports: true,
usedExports: true,
sideEffects: true,
removeAvailableModules: true,
runtimeChunk: {
name: 'runtime',
},
splitChunks: {
chunks: 'all',
minSize: 1,
minChunks: 1,
maxInitialRequests: Number.MAX_SAFE_INTEGER,
maxAsyncRequests: Number.MAX_SAFE_INTEGER,
cacheGroups:
buildFlags.mode === 'production'
? productionCacheGroups
: {
default: false,
vendors: false,
},
},
});
export const createConfiguration: (
buildFlags: BuildFlags,
runtimeConfig: RuntimeConfig
) => webpack.Configuration = (buildFlags, runtimeConfig) => {
let publicPath = process.env.PUBLIC_PATH ?? '/';
const cacheKey = computeCacheKey(buildFlags);
const config = {
name: 'affine',
// to set a correct base path for the source map
context: projectRoot,
output: {
environment: {
module: true,
dynamicImport: true,
},
filename: 'js/[name].js',
// In some cases webpack will emit files starts with "_" which is reserved in web extension.
chunkFilename: 'js/chunk.[name].js',
assetModuleFilename: 'assets/[hash][ext][query]',
devtoolModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]',
hotUpdateChunkFilename: 'hot/[id].[fullhash].js',
hotUpdateMainFilename: 'hot/[runtime].[fullhash].json',
path: join(rootPath, 'dist'),
clean: buildFlags.mode === 'production',
globalObject: 'globalThis',
publicPath,
},
target: ['web', 'es2022'],
mode: buildFlags.mode,
devtool:
buildFlags.mode === 'production'
? buildFlags.distribution === 'desktop'
? 'inline-source-map'
: 'hidden-nosources-source-map'
: 'eval-cheap-module-source-map',
resolve: {
extensionAlias: {
'.js': ['.js', '.tsx', '.ts'],
'.mjs': ['.mjs', '.mts'],
},
extensions: ['.js', '.ts', '.tsx'],
},
cache: {
type: 'filesystem',
buildDependencies: {
config: [fileURLToPath(import.meta.url)],
},
version: cacheKey,
},
module: {
parser: {
javascript: {
// Treat as missing export as error
strictExportPresence: true,
},
},
rules: [
{
test: /\.m?js?$/,
resolve: {
fullySpecified: false,
},
},
{
oneOf: [
{
test: /\.tsx?$/,
// Compile all ts files in the workspace
include: resolve(rootPath, '..', '..'),
loader: require.resolve('swc-loader'),
options: {
// https://swc.rs/docs/configuring-swc/
jsc: {
preserveAllComments: true,
parser: {
syntax: 'typescript',
dynamicImport: true,
topLevelAwait: false,
tsx: true,
},
target: 'es2022',
externalHelpers: true,
transform: {
react: {
runtime: 'automatic',
refresh: buildFlags.mode === 'development' && {
refreshReg: '$RefreshReg$',
refreshSig: '$RefreshSig$',
emitFullSignatures: true,
},
},
},
experimental: {
keepImportAssertions: true,
plugins: [
buildFlags.coverage && [
'swc-plugin-coverage-instrument',
{},
],
].filter(Boolean),
},
},
},
},
{
test: /\.svg$/,
use: [
'thread-loader',
{
loader: '@svgr/webpack',
options: {
icon: true,
},
},
],
exclude: [/node_modules/],
},
{
test: /\.(png|jpg|gif|svg|webp)$/,
type: 'asset/resource',
},
{
test: /\.(ttf|eot|woff|woff2)$/i,
type: 'asset/resource',
},
{
test: /\.txt$/,
loader: 'raw-loader',
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
url: false,
sourceMap: false,
modules: false,
import: true,
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: resolve(
rootPath,
'.webpack',
'postcss.config.cjs'
),
},
},
},
],
},
],
},
],
},
plugins: [
...(IN_CI ? [] : [new webpack.ProgressPlugin({ percentBy: 'entries' })]),
...(buildFlags.mode === 'development'
? [new ReactRefreshWebpackPlugin({ overlay: false, esModule: true })]
: []),
new HTMLPlugin({
template: join(rootPath, '.webpack', 'template.html'),
inject: 'body',
scriptLoading: 'defer',
minify: false,
chunks: ['index'],
filename: 'index.html',
}),
new MiniCssExtractPlugin({
filename: `[name].[chunkhash:8].css`,
ignoreOrder: true,
}),
new VanillaExtractPlugin(),
new webpack.DefinePlugin({
'process.env': JSON.stringify({}),
runtimeConfig: JSON.stringify(runtimeConfig),
}),
new CopyPlugin({
patterns: [
{ from: resolve(rootPath, 'public'), to: resolve(rootPath, 'dist') },
],
}),
],
optimization: OptimizeOptionOptions(buildFlags),
devServer: {
hot: 'only',
liveReload: false,
client: undefined,
historyApiFallback: true,
static: {
directory: resolve(rootPath, 'public'),
publicPath: '/',
},
} as DevServerConfiguration,
} satisfies webpack.Configuration;
if (buildFlags.mode === 'production' && process.env.PERFSEE_TOKEN) {
config.devtool = 'hidden-nosources-source-map';
config.plugins.push(
new PerfseePlugin({
project: 'affine-toeverything',
})
);
}
if (
process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT
) {
config.plugins.push(
sentryWebpackPlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
})
);
}
return config;
};

View File

@ -0,0 +1,20 @@
const cssnano = require('cssnano');
module.exports = function (context) {
const plugins = [
cssnano({
preset: [
'default',
{
convertValues: false,
},
],
}),
];
return {
from: context.from,
plugins,
to: context.to,
};
};

View File

@ -0,0 +1,115 @@
import type { BlockSuiteFeatureFlags, RuntimeConfig } from '@affine/env/global';
import type { BuildFlags } from '@affine/cli/config';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const packageJson = require('../package.json');
const editorFlags: BlockSuiteFeatureFlags = {
enable_database: true,
enable_slash_menu: true,
enable_edgeless_toolbar: true,
enable_block_hub: true,
enable_drag_handle: true,
enable_surface: true,
enable_linked_page: true,
enable_bookmark_operation: false,
};
export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
const buildPreset: Record<string, RuntimeConfig> = {
stable: {
enablePlugin: false,
enableTestProperties: false,
enableBroadcastChannelProvider: true,
enableDebugPage: true,
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0717',
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
enablePreloading: true,
enableNewSettingModal: true,
enableNewSettingUnstableApi: false,
enableSQLiteProvider: true,
enableMoveDatabase: false,
enableNotificationCenter: false,
enableCloud: false,
serverAPI: 'https://localhost:3010',
editorFlags,
appVersion: packageJson.version,
editorVersion: packageJson.dependencies['@blocksuite/editor'],
},
// canary will be aggressive and enable all features
canary: {
enablePlugin: true,
enableTestProperties: true,
enableBroadcastChannelProvider: true,
enableDebugPage: true,
changelogUrl: 'https://affine.pro/blog/what-is-new-affine-0717',
imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image',
enablePreloading: true,
enableNewSettingModal: true,
enableNewSettingUnstableApi: false,
enableSQLiteProvider: true,
enableMoveDatabase: false,
enableNotificationCenter: true,
enableCloud: false,
serverAPI: 'https://localhost:3010',
editorFlags,
appVersion: packageJson.version,
editorVersion: packageJson.dependencies['@blocksuite/editor'],
},
};
// beta and internal versions are the same as stable
buildPreset.beta = buildPreset.stable;
buildPreset.internal = buildPreset.stable;
const currentBuild = buildFlags.channel;
if (!(currentBuild in buildPreset)) {
throw new Error(`BUILD_TYPE ${currentBuild} is not supported`);
}
const currentBuildPreset = buildPreset[currentBuild];
const environmentPreset = {
enablePlugin: process.env.ENABLE_PLUGIN
? process.env.ENABLE_PLUGIN === 'true'
: currentBuildPreset.enablePlugin,
enableTestProperties: process.env.ENABLE_TEST_PROPERTIES
? process.env.ENABLE_TEST_PROPERTIES === 'true'
: currentBuildPreset.enableTestProperties,
enableBroadcastChannelProvider: process.env.ENABLE_BC_PROVIDER
? process.env.ENABLE_BC_PROVIDER !== 'false'
: currentBuildPreset.enableBroadcastChannelProvider,
changelogUrl: process.env.CHANGELOG_URL ?? currentBuildPreset.changelogUrl,
enablePreloading: process.env.ENABLE_PRELOADING
? process.env.ENABLE_PRELOADING === 'true'
: currentBuildPreset.enablePreloading,
enableNewSettingModal: process.env.ENABLE_NEW_SETTING_MODAL
? process.env.ENABLE_NEW_SETTING_MODAL === 'true'
: currentBuildPreset.enableNewSettingModal,
enableSQLiteProvider: process.env.ENABLE_SQLITE_PROVIDER
? process.env.ENABLE_SQLITE_PROVIDER === 'true'
: currentBuildPreset.enableSQLiteProvider,
enableNewSettingUnstableApi: process.env.ENABLE_NEW_SETTING_UNSTABLE_API
? process.env.ENABLE_NEW_SETTING_UNSTABLE_API === 'true'
: currentBuildPreset.enableNewSettingUnstableApi,
enableNotificationCenter: process.env.ENABLE_NOTIFICATION_CENTER
? process.env.ENABLE_NOTIFICATION_CENTER === 'true'
: currentBuildPreset.enableNotificationCenter,
enableCloud: process.env.ENABLE_CLOUD
? process.env.ENABLE_CLOUD === 'true'
: currentBuildPreset.enableCloud,
enableMoveDatabase: process.env.ENABLE_MOVE_DATABASE
? process.env.ENABLE_MOVE_DATABASE === 'true'
: currentBuildPreset.enableMoveDatabase,
};
return {
...currentBuildPreset,
// environment preset will overwrite current build preset
// this environment variable is for debug proposes only
// do not put them into CI
...(process.env.CI ? {} : environmentPreset),
};
}

View File

@ -0,0 +1,45 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1"
/>
<title>AFFiNE</title>
<meta name="theme-color" content="#fafafa" />
<link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" sizes="192x192" href="/chrome-192x192.png" />
<meta name="emotion-insertion-point" content="" />
<meta property="description" content="{description}" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content="https://app.affine.pro/" />
<meta
name="twitter:title"
content="AFFiNEThere can be more than Notion and Miro."
/>
<meta name="twitter:description" content="{description}" />
<meta name="twitter:site" content="@AffineOfficial" />
<meta name="twitter:image" content="https://affine.pro/og.jpeg" />
<meta
property="og:title"
content="AFFiNEThere can be more than Notion and Miro."
/>
<meta property="og:type" content="website" />
<meta
property="og:description"
content="There can be more than Notion and Miro. AFFiNE is a next-gen knowledge base that brings planning, sorting and creating all together."
/>
<meta property="og:url" content="https://app.affine.pro/" />
<meta property="og:image" content="https://affine.pro/og.jpeg" />
<link
data-react-helmet="true"
rel="shortcut icon"
href="https://affine.pro/favicon.ico"
/>
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -0,0 +1,18 @@
import { createHash } from 'node:crypto';
import type { BuildFlags } from '@affine/cli/config';
export function hash(content: string): string {
const hash = createHash('sha512');
hash.update(content);
const pkgHash = hash.digest('hex');
return pkgHash.substring(0, 8);
}
export function computeCacheKey(buildFlags: BuildFlags) {
return [
'1',
'node' + process.version,
buildFlags.mode,
buildFlags.distribution,
].join('-');
}

View File

@ -0,0 +1,23 @@
import { createConfiguration, rootPath } from './config.js';
import { merge } from 'webpack-merge';
import { resolve } from 'node:path';
import type { BuildFlags } from '@affine/cli/config';
import { getRuntimeConfig } from './runtime-config.js';
export default async function (cli_env: any, _: any) {
const flags: BuildFlags = JSON.parse(
Buffer.from(cli_env.flags, 'hex').toString('utf-8')
);
console.log('build flags', flags);
const runtimeConfig = getRuntimeConfig(flags);
console.log('runtime config', runtimeConfig);
const config = createConfiguration(flags, runtimeConfig);
return merge(config, {
entry: {
index: {
asyncChunks: true,
import: resolve(rootPath, 'src/index.tsx'),
},
},
});
}

View File

@ -1,17 +1,16 @@
{
"name": "@affine/web",
"name": "@affine/core",
"type": "module",
"private": true,
"version": "0.7.0-canary.47",
"scripts": {
"dev": "next dev",
"build": "next build && next export",
"start": "NODE_ENV=development next start",
"static-server": "ts-node-esm server.mts"
"build": "yarn -T run build-core",
"dev": "yarn -T run dev-core",
"static-server": "ts-node-esm ./server.mts"
},
"dependencies": {
"@affine-test/fixtures": "workspace:*",
"@affine/component": "workspace:*",
"@affine/copilot": "workspace:*",
"@affine/debug": "workspace:*",
"@affine/env": "workspace:*",
"@affine/graphql": "workspace:*",
@ -34,23 +33,23 @@
"@emotion/styled": "^11.11.0",
"@mui/material": "^5.13.6",
"@react-hookz/web": "^23.1.0",
"@sentry/nextjs": "^7.57.0",
"@toeverything/hooks": "workspace:*",
"@toeverything/infra": "workspace:*",
"@toeverything/plugin-infra": "workspace:*",
"async-call-rpc": "^6.3.1",
"cmdk": "^0.2.0",
"css-spring": "^4.1.0",
"cssnano": "^6.0.1",
"graphql": "^16.7.1",
"jotai": "^2.2.2",
"jotai-devtools": "^0.6.0",
"lit": "^2.7.5",
"lottie-web": "^5.12.2",
"mini-css-extract-plugin": "^2.7.6",
"next-themes": "^0.2.1",
"postcss-loader": "^7.3.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "^18.2.0",
"react-is": "18.2.0",
"react-resizable-panels": "^0.0.53",
"react-router-dom": "^6.14.1",
"rxjs": "^7.8.1",
"swr": "^2.1.5",
"y-protocols": "^1.0.5",
@ -59,30 +58,23 @@
},
"devDependencies": {
"@perfsee/webpack": "^1.8.2",
"@redux-devtools/extension": "^3.2.5",
"@rich-data/viewer": "^2.15.6",
"@sentry/webpack-plugin": "^1.20.1",
"@swc-jotai/debug-label": "^0.0.10",
"@swc-jotai/react-refresh": "^0.0.8",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@sentry/webpack-plugin": "^2.4.0",
"@svgr/webpack": "^8.0.1",
"@swc/core": "^1.3.70",
"@types/webpack-env": "^1.18.1",
"@vanilla-extract/css": "^1.12.0",
"@vanilla-extract/next-plugin": "=2.1.2",
"dotenv": "^16.3.1",
"eslint": "^8.44.0",
"eslint-config-next": "^13.4.7",
"eslint-plugin-unicorn": "^47.0.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"express": "^4.18.2",
"next": "=13.4.2",
"next-debug-local": "^0.1.5",
"next-router-mock": "^0.9.7",
"html-webpack-plugin": "^5.5.3",
"raw-loader": "^4.0.2",
"redux": "^4.2.1",
"swc-plugin-coverage-instrument": "^0.0.18",
"swc-loader": "^0.2.3",
"swc-plugin-coverage-instrument": "^0.0.19",
"thread-loader": "^4.0.2",
"ts-node": "^10.9.1",
"typescript": "^5.1.6",
"webpack": "^5.88.1"
},
"stableVersion": "0.0.0"
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
"webpack-merge": "^5.9.0"
}
}

View File

@ -1,9 +1,9 @@
{
"name": "@affine/web",
"name": "@affine/core",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"root": "apps/web",
"sourceRoot": "apps/web/src",
"root": "apps/core",
"sourceRoot": "apps/core/src",
"targets": {
"build": {
"executor": "nx:run-script",
@ -12,26 +12,29 @@
"{projectRoot}/**/*",
"{workspaceRoot}/packages/component/src/**/*",
"{workspaceRoot}/packages/debug/src/**/*",
"{workspaceRoot}/packages/debug/graphql/**/*",
"{workspaceRoot}/packages/graphql/src/**/*",
"{workspaceRoot}/packages/hooks/src/**/*",
"{workspaceRoot}/packages/jotai/src/**/*",
"{workspaceRoot}/packages/templates/src/**/*",
"{workspaceRoot}/packages/workspace/src/**/*",
{
"env": "BUILD_TYPE"
},
{
"env": "PERFSEE_TOKEN"
}
],
"options": {
"script": "build"
},
"outputs": ["{projectRoot}/out"]
"outputs": ["{projectRoot}/dist"]
},
"dev": {
"executor": "nx:run-script",
"options": {
"script": "dev"
},
"outputs": ["{projectRoot}/.next"]
"outputs": ["{projectRoot}/dist"]
}
}
}

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

15
apps/core/server.mts Normal file
View File

@ -0,0 +1,15 @@
// static server for web app
import express from 'express';
const app = express();
const PORT = process.env.PORT || 8080;
app.use('/', express.static('dist'));
app.get('/*', (req, res) => {
res.sendFile('index.html', { root: 'dist' });
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

158
apps/core/src/app.tsx Normal file
View File

@ -0,0 +1,158 @@
import '@affine/component/theme/global.css';
import '@affine/component/theme/theme.css';
import { AffineContext } from '@affine/component/context';
import { WorkspaceFallback } from '@affine/component/workspace';
import { createI18n, setUpLanguage } from '@affine/i18n';
import { CacheProvider } from '@emotion/react';
import type { RouterState } from '@remix-run/router';
import {
currentPageIdAtom,
currentWorkspaceIdAtom,
} from '@toeverything/plugin-infra/manager';
import type { PropsWithChildren, ReactElement } from 'react';
import { lazy, memo, Suspense, useEffect } from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { historyBaseAtom, MAX_HISTORY } from './atoms/history';
import createEmotionCache from './utils/create-emotion-cache';
const router = createBrowserRouter([
{
path: '/',
lazy: () => import('./pages/index'),
},
{
path: '/404',
lazy: () => import('./pages/404'),
},
{
path: '/workspace/:workspaceId/all',
lazy: () => import('./pages/workspace/all-page'),
},
{
path: '/workspace/:workspaceId/trash',
lazy: () => import('./pages/workspace/trash-page'),
},
{
path: '/workspace/:workspaceId/:pageId',
lazy: () => import('./pages/workspace/detail-page'),
},
]);
//#region atoms bootstrap
currentWorkspaceIdAtom.onMount = set => {
const callback = (state: RouterState) => {
const value = state.location.pathname.split('/')[2];
if (value) {
set(value);
localStorage.setItem('last_workspace_id', value);
} else {
set(null);
}
};
callback(router.state);
const unsubscribe = router.subscribe(callback);
return () => {
unsubscribe();
};
};
currentPageIdAtom.onMount = set => {
const callback = (state: RouterState) => {
const value = state.location.pathname.split('/')[3];
if (value) {
set(value);
} else {
set(null);
}
};
callback(router.state);
const unsubscribe = router.subscribe(callback);
return () => {
unsubscribe();
};
};
historyBaseAtom.onMount = set => {
const unsubscribe = router.subscribe(state => {
set(prev => {
const url = state.location.pathname;
console.log('push', url, prev.skip, prev.stack.length, prev.current);
if (prev.skip) {
return {
stack: [...prev.stack],
current: prev.current,
skip: false,
};
} else {
if (prev.current < prev.stack.length - 1) {
const newStack = prev.stack.slice(0, prev.current);
newStack.push(url);
if (newStack.length > MAX_HISTORY) {
newStack.shift();
}
return {
stack: newStack,
current: newStack.length - 1,
skip: false,
};
} else {
const newStack = [...prev.stack, url];
if (newStack.length > MAX_HISTORY) {
newStack.shift();
}
return {
stack: newStack,
current: newStack.length - 1,
skip: false,
};
}
}
});
});
return () => {
unsubscribe();
};
};
//#endregion
const i18n = createI18n();
const cache = createEmotionCache();
const DevTools = lazy(() =>
import('jotai-devtools').then(m => ({ default: m.DevTools }))
);
const DebugProvider = ({ children }: PropsWithChildren): ReactElement => {
return (
<>
<Suspense>{process.env.DEBUG_JOTAI === 'true' && <DevTools />}</Suspense>
{children}
</>
);
};
export const App = memo(function App() {
useEffect(() => {
document.documentElement.lang = i18n.language;
// todo(himself65): this is a hack, we should use a better way to set the language
setUpLanguage(i18n)?.catch(error => {
console.error(error);
});
}, []);
return (
<CacheProvider value={cache}>
<AffineContext>
<DebugProvider>
<Suspense fallback={<WorkspaceFallback key="RootPageLoading" />}>
<RouterProvider router={router} />
</Suspense>
</DebugProvider>
</AffineContext>
</CacheProvider>
);
});

View File

@ -0,0 +1,52 @@
import { useAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
export type History = {
stack: string[];
current: number;
skip: boolean;
};
export const MAX_HISTORY = 50;
export const historyBaseAtom = atomWithStorage<History>('router-history', {
stack: [],
current: 0,
skip: false,
});
export function useHistoryAtom() {
const navigate = useNavigate();
const [base, setBase] = useAtom(historyBaseAtom);
return [
base,
useCallback(
(forward: boolean) => {
setBase(prev => {
if (forward) {
const target = Math.min(prev.stack.length - 1, prev.current + 1);
const url = prev.stack[target];
navigate(url);
return {
...prev,
current: target,
skip: true,
};
} else {
const target = Math.max(0, prev.current - 1);
const url = prev.stack[target];
navigate(url);
return {
...prev,
current: target,
skip: true,
};
}
});
},
[setBase, navigate]
),
] as const;
}

View File

@ -12,14 +12,11 @@ import {
} from '@toeverything/plugin-infra/manager';
import { useAtomValue } from 'jotai/react';
import { Provider } from 'jotai/react';
import type { NextRouter } from 'next/router';
import type { ErrorInfo, ReactElement, ReactNode } from 'react';
import type React from 'react';
import { Component } from 'react';
export type AffineErrorBoundaryProps = React.PropsWithChildren<{
router: NextRouter;
}>;
import { useLocation, useParams } from 'react-router-dom';
export type AffineErrorBoundaryProps = React.PropsWithChildren;
type AffineError =
| QueryParamError
@ -32,13 +29,13 @@ interface AffineErrorBoundaryState {
error: AffineError | null;
}
export const DumpInfo = (props: Pick<AffineErrorBoundaryProps, 'router'>) => {
const router = props.router;
export const DumpInfo = () => {
const location = useLocation();
const metadata = useAtomValue(rootWorkspacesMetadataAtom);
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom);
const currentPageId = useAtomValue(currentPageIdAtom);
const path = router.asPath;
const query = router.query;
const path = location.pathname;
const query = useParams();
return (
<>
<div>
@ -91,24 +88,6 @@ export class AffineErrorBoundary extends Component<
Cannot find page {error.pageId} in workspace{' '}
{error.workspace.id}
</span>
<button
onClick={() => {
this.props.router
.replace({
pathname: '/workspace/[workspaceId]/[pageId]',
query: {
workspaceId: error.workspace.id,
pageId: error.workspace.meta.pageMetas[0].id,
},
})
.finally(() => {
this.setState({ error: null });
});
}}
>
{' '}
refresh{' '}
</button>
</>
</>
);
@ -124,7 +103,7 @@ export class AffineErrorBoundary extends Component<
<>
{errorDetail}
<Provider key="JotaiProvider" store={rootStore}>
<DumpInfo router={this.props.router} />
<DumpInfo />
</Provider>
</>
);

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