refactor!: remove next.js (#3267)

This commit is contained in:
Alex Yang authored and GitHub committed 2023-07-18 16:53:10 +00:00
1 parent 79227a1e7c
commit 47f12f77f2
296 files changed
+4115 -3617

No files matched your search

+1 -1
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
+1 -1
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
+21 -21
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:
+2 -2
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:
+3 -3
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
View File
@@ -13,6 +13,7 @@
/out-tsc
.nyc_output
.coverage
.swc
# dependencies
node_modules
+84
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,
},
};
+306
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;
};
+20
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,
};
};
+115
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),
};
}
+45
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="AFFiNE:There 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="AFFiNE:There 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>
+18
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('-');
}
+23
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'),
},
},
});
}
@@ -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"
}
}
@@ -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"]
}
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+15
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}`);
});
File renamed without changes.
File renamed without changes.
File renamed without changes.
+158
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>
);
});
File renamed without changes.
+52
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;
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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>
</>
);
Loaded 100 of 296 files, more files were not shown because too many files have changed in this diff. Show more