mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
refactor(core): split web entry from core (#6082)
This pr is trying to split `web` and `electron` entries from `core`. It allows more platform-related optimization to be addressed in each entry. We should remove all browser/electron only codes from `core` eventually, this is the very first step for that.
This commit is contained in:
@@ -2,25 +2,45 @@
|
||||
"name": "@affine/cli",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"bin": {
|
||||
"build-core": "./src/bin/build-core.mjs",
|
||||
"dev-core": "./src/bin/dev-core.mjs"
|
||||
},
|
||||
"exports": {
|
||||
"./config": "./src/config/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@aws-sdk/client-s3": "3.536.0",
|
||||
"@blocksuite/presets": "0.13.0-canary-202403140735-2367cd5",
|
||||
"@clack/core": "^0.3.4",
|
||||
"@clack/prompts": "^0.7.0",
|
||||
"@magic-works/i18n-codegen": "^0.5.0",
|
||||
"ts-node": "^10.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@napi-rs/simple-git": "^0.1.16",
|
||||
"@perfsee/webpack": "^1.12.2",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
|
||||
"@sentry/webpack-plugin": "^2.14.2",
|
||||
"@types/webpack-env": "^1.18.4",
|
||||
"@vanilla-extract/webpack-plugin": "^2.3.7",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"css-loader": "^6.10.0",
|
||||
"cssnano": "^6.1.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"vite": "^5.1.4"
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mime-types": "^2.1.35",
|
||||
"mini-css-extract-plugin": "^2.8.1",
|
||||
"postcss-loader": "^8.1.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"source-map-loader": "^5.0.0",
|
||||
"style-loader": "^3.3.4",
|
||||
"swc-loader": "^0.2.6",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"thread-loader": "^4.0.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"vite": "^5.1.4",
|
||||
"webpack": "^5.90.3",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^5.0.2",
|
||||
"webpack-merge": "^5.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ts-node": "*"
|
||||
"scripts": {
|
||||
"build": "node --loader ts-node/esm/transpile-only.mjs ./src/bin/build.ts",
|
||||
"dev": "node --loader ts-node/esm/transpile-only.mjs ./src/bin/dev.ts"
|
||||
},
|
||||
"version": "0.14.0"
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const child = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--loader',
|
||||
'ts-node/esm/transpile-only',
|
||||
fileURLToPath(new URL('./build-core.ts', import.meta.url)),
|
||||
...process.argv.slice(2),
|
||||
],
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
|
||||
if (child.status) process.exit(child.status);
|
||||
@@ -1,11 +1,13 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
|
||||
import webpack from 'webpack';
|
||||
|
||||
import type { BuildFlags } from '../config/index.js';
|
||||
import { projectRoot } from '../config/index.js';
|
||||
import { buildI18N } from '../util/i18n.js';
|
||||
import { createWebpackConfig } from '../webpack/webpack.config.js';
|
||||
|
||||
const cwd = path.resolve(projectRoot, 'packages/frontend/core');
|
||||
let cwd: string;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const buildType = process.env.BUILD_TYPE_OVERRIDE || process.env.BUILD_TYPE;
|
||||
@@ -31,15 +33,20 @@ const getChannel = () => {
|
||||
}
|
||||
};
|
||||
|
||||
let entry: string | undefined;
|
||||
|
||||
const { DISTRIBUTION } = process.env;
|
||||
|
||||
const getDistribution = () => {
|
||||
switch (process.env.DISTRIBUTION) {
|
||||
switch (DISTRIBUTION) {
|
||||
case 'browser':
|
||||
case 'desktop':
|
||||
return process.env.DISTRIBUTION;
|
||||
case undefined: {
|
||||
console.log('DISTRIBUTION is not set, defaulting to browser');
|
||||
case undefined:
|
||||
cwd = path.join(projectRoot, 'packages/frontend/web');
|
||||
return 'browser';
|
||||
}
|
||||
case 'desktop':
|
||||
cwd = path.join(projectRoot, 'packages/frontend/electron');
|
||||
entry = path.join(cwd, 'renderer', 'index.tsx');
|
||||
return DISTRIBUTION;
|
||||
default: {
|
||||
throw new Error('DISTRIBUTION must be one of browser, desktop');
|
||||
}
|
||||
@@ -51,24 +58,19 @@ const flags = {
|
||||
mode: 'production',
|
||||
channel: getChannel(),
|
||||
coverage: process.env.COVERAGE === 'true',
|
||||
entry,
|
||||
} satisfies BuildFlags;
|
||||
|
||||
buildI18N();
|
||||
spawn(
|
||||
'node',
|
||||
[
|
||||
'--loader',
|
||||
'ts-node/esm/transpile-only',
|
||||
'../../../node_modules/webpack/bin/webpack.js',
|
||||
'--mode',
|
||||
'production',
|
||||
'--env',
|
||||
'flags=' + Buffer.from(JSON.stringify(flags), 'utf-8').toString('hex'),
|
||||
].filter((v): v is string => !!v),
|
||||
{
|
||||
cwd,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
env: process.env,
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
webpack(createWebpackConfig(cwd!, flags), (err, stats) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
);
|
||||
if (stats?.hasErrors()) {
|
||||
console.error(stats.toString('errors-only'));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const child = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--loader',
|
||||
'ts-node/esm/transpile-only',
|
||||
fileURLToPath(new URL('./dev-core.ts', import.meta.url)),
|
||||
...process.argv.slice(2),
|
||||
],
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
|
||||
if (child.status) process.exit(child.status);
|
||||
@@ -1,207 +0,0 @@
|
||||
import type { ChildProcess } from 'node:child_process';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { existsSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import * as p from '@clack/prompts';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
import { type BuildFlags, projectRoot } from '../config/index.js';
|
||||
import { watchI18N } from '../util/i18n.js';
|
||||
|
||||
const cwd = path.resolve(projectRoot, 'packages/frontend/core');
|
||||
|
||||
const flags: BuildFlags = {
|
||||
distribution: 'browser',
|
||||
mode: 'development',
|
||||
channel: 'canary',
|
||||
coverage: process.env.COVERAGE === 'true',
|
||||
localBlockSuite: undefined,
|
||||
};
|
||||
|
||||
if (process.argv.includes('--static')) {
|
||||
await awaitChildProcess(
|
||||
spawn(
|
||||
'node',
|
||||
[
|
||||
'--loader',
|
||||
'ts-node/esm/transpile-only',
|
||||
'../../../node_modules/webpack/bin/webpack.js',
|
||||
'serve',
|
||||
'--mode',
|
||||
'development',
|
||||
'--no-client-overlay',
|
||||
'--no-live-reload',
|
||||
'--env',
|
||||
'flags=' + Buffer.from(JSON.stringify(flags), 'utf-8').toString('hex'),
|
||||
].filter((v): v is string => !!v),
|
||||
{
|
||||
cwd,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
env: process.env,
|
||||
}
|
||||
)
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const files = ['.env', '.env.local'];
|
||||
|
||||
for (const file of files) {
|
||||
if (existsSync(path.resolve(projectRoot, file))) {
|
||||
config({
|
||||
path: path.resolve(projectRoot, file),
|
||||
});
|
||||
console.log(`${file} loaded`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const buildFlags = await p.group(
|
||||
{
|
||||
distribution: () =>
|
||||
p.select({
|
||||
message: 'Distribution',
|
||||
options: [
|
||||
{
|
||||
value: 'browser',
|
||||
},
|
||||
{
|
||||
value: 'desktop',
|
||||
},
|
||||
],
|
||||
initialValue: 'browser',
|
||||
}),
|
||||
mode: () =>
|
||||
p.select({
|
||||
message: 'Mode',
|
||||
options: [
|
||||
{
|
||||
value: 'development',
|
||||
},
|
||||
{
|
||||
value: 'production',
|
||||
},
|
||||
],
|
||||
initialValue: 'development',
|
||||
}),
|
||||
channel: () =>
|
||||
p.select({
|
||||
message: 'Channel',
|
||||
options: [
|
||||
{
|
||||
value: 'canary',
|
||||
},
|
||||
{
|
||||
value: 'beta',
|
||||
},
|
||||
{
|
||||
value: 'stable',
|
||||
},
|
||||
],
|
||||
initialValue: 'canary',
|
||||
}),
|
||||
coverage: () =>
|
||||
p.confirm({
|
||||
message: 'Enable coverage',
|
||||
initialValue: process.env.COVERAGE === 'true',
|
||||
}),
|
||||
debugBlockSuite: () =>
|
||||
p.confirm({
|
||||
message: 'Debug blocksuite locally?',
|
||||
initialValue: false,
|
||||
}),
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
p.cancel('Operation cancelled.');
|
||||
process.exit(0);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (buildFlags.debugBlockSuite) {
|
||||
const { config } = await import('dotenv');
|
||||
const envLocal = config({
|
||||
path: path.resolve(cwd, '.env.local'),
|
||||
});
|
||||
|
||||
const localBlockSuite = await p.text({
|
||||
message: 'local blocksuite PATH',
|
||||
initialValue: envLocal.error
|
||||
? undefined
|
||||
: envLocal.parsed?.LOCAL_BLOCK_SUITE,
|
||||
});
|
||||
if (typeof localBlockSuite !== 'string') {
|
||||
throw new Error('local blocksuite PATH is required');
|
||||
}
|
||||
if (!existsSync(localBlockSuite)) {
|
||||
throw new Error(`local blocksuite not found: ${localBlockSuite}`);
|
||||
}
|
||||
flags.localBlockSuite = localBlockSuite;
|
||||
}
|
||||
|
||||
flags.distribution = buildFlags.distribution as any;
|
||||
flags.mode = buildFlags.mode as any;
|
||||
flags.channel = buildFlags.channel as any;
|
||||
flags.coverage = buildFlags.coverage;
|
||||
|
||||
watchI18N();
|
||||
|
||||
function awaitChildProcess(child: ChildProcess): Promise<number> {
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
const handleExitCode = (code: number | null) => {
|
||||
if (code) {
|
||||
reject(
|
||||
new Error(
|
||||
`Child process at ${
|
||||
(child as any).cwd
|
||||
} fails: ${child.spawnargs.join(' ')}`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
resolve(0);
|
||||
}
|
||||
};
|
||||
|
||||
child.on('error', () => handleExitCode(child.exitCode));
|
||||
child.on('exit', code => handleExitCode(code));
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await awaitChildProcess(
|
||||
spawn('node', ['build-edgeless.mjs'], {
|
||||
cwd: path.resolve(projectRoot, 'packages/frontend/templates'),
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
env: process.env,
|
||||
})
|
||||
);
|
||||
// Start webpack
|
||||
await awaitChildProcess(
|
||||
spawn(
|
||||
'node',
|
||||
[
|
||||
'--loader',
|
||||
'ts-node/esm/transpile-only',
|
||||
'../../../node_modules/webpack/bin/webpack.js',
|
||||
flags.mode === 'development' ? 'serve' : undefined,
|
||||
'--mode',
|
||||
flags.mode === 'development' ? 'development' : 'production',
|
||||
'--env',
|
||||
'flags=' + Buffer.from(JSON.stringify(flags), 'utf-8').toString('hex'),
|
||||
].filter((v): v is string => !!v),
|
||||
{
|
||||
cwd,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
env: process.env,
|
||||
}
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error during build:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
143
tools/cli/src/bin/dev.ts
Normal file
143
tools/cli/src/bin/dev.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import * as p from '@clack/prompts';
|
||||
import { config } from 'dotenv';
|
||||
import webpack from 'webpack';
|
||||
import WebpackDevServer from 'webpack-dev-server';
|
||||
|
||||
import { type BuildFlags, projectRoot } from '../config/index.js';
|
||||
import { watchI18N } from '../util/i18n.js';
|
||||
import { createWebpackConfig } from '../webpack/webpack.config.js';
|
||||
|
||||
const flags: BuildFlags = {
|
||||
distribution: 'browser',
|
||||
mode: 'development',
|
||||
channel: 'canary',
|
||||
coverage: process.env.COVERAGE === 'true',
|
||||
localBlockSuite: undefined,
|
||||
};
|
||||
|
||||
const files = ['.env', '.env.local'];
|
||||
|
||||
for (const file of files) {
|
||||
if (existsSync(join(projectRoot, file))) {
|
||||
config({
|
||||
path: join(projectRoot, file),
|
||||
});
|
||||
console.log(`${file} loaded`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const buildFlags = process.argv.includes('--static')
|
||||
? { ...flags, debugBlockSuite: false }
|
||||
: ((await p.group(
|
||||
{
|
||||
distribution: () =>
|
||||
p.select({
|
||||
message: 'Distribution',
|
||||
options: [
|
||||
{
|
||||
value: 'browser',
|
||||
},
|
||||
{
|
||||
value: 'desktop',
|
||||
},
|
||||
],
|
||||
initialValue: 'browser',
|
||||
}),
|
||||
mode: () =>
|
||||
p.select({
|
||||
message: 'Mode',
|
||||
options: [
|
||||
{
|
||||
value: 'development',
|
||||
},
|
||||
{
|
||||
value: 'production',
|
||||
},
|
||||
],
|
||||
initialValue: 'development',
|
||||
}),
|
||||
channel: () =>
|
||||
p.select({
|
||||
message: 'Channel',
|
||||
options: [
|
||||
{
|
||||
value: 'canary',
|
||||
},
|
||||
{
|
||||
value: 'beta',
|
||||
},
|
||||
{
|
||||
value: 'stable',
|
||||
},
|
||||
],
|
||||
initialValue: 'canary',
|
||||
}),
|
||||
coverage: () =>
|
||||
p.confirm({
|
||||
message: 'Enable coverage',
|
||||
initialValue: process.env.COVERAGE === 'true',
|
||||
}),
|
||||
debugBlockSuite: () =>
|
||||
p.confirm({
|
||||
message: 'Debug blocksuite locally?',
|
||||
initialValue: false,
|
||||
}),
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
p.cancel('Operation cancelled.');
|
||||
process.exit(0);
|
||||
},
|
||||
}
|
||||
)) as BuildFlags & { debugBlockSuite: boolean });
|
||||
|
||||
flags.distribution = buildFlags.distribution;
|
||||
flags.mode = buildFlags.mode;
|
||||
flags.channel = buildFlags.channel;
|
||||
flags.coverage = buildFlags.coverage;
|
||||
|
||||
const cwd =
|
||||
flags.distribution === 'browser'
|
||||
? join(projectRoot, 'packages', 'frontend', 'web')
|
||||
: join(projectRoot, 'packages', 'frontend', 'electron');
|
||||
|
||||
if (buildFlags.debugBlockSuite) {
|
||||
const { config } = await import('dotenv');
|
||||
const envLocal = config({
|
||||
path: join(cwd, '.env.local'),
|
||||
});
|
||||
|
||||
const localBlockSuite = await p.text({
|
||||
message: 'local blocksuite PATH',
|
||||
initialValue: envLocal.error
|
||||
? undefined
|
||||
: envLocal.parsed?.LOCAL_BLOCK_SUITE,
|
||||
});
|
||||
if (typeof localBlockSuite !== 'string') {
|
||||
throw new Error('local blocksuite PATH is required');
|
||||
}
|
||||
if (!existsSync(localBlockSuite)) {
|
||||
throw new Error(`local blocksuite not found: ${localBlockSuite}`);
|
||||
}
|
||||
flags.localBlockSuite = localBlockSuite;
|
||||
}
|
||||
|
||||
watchI18N();
|
||||
|
||||
try {
|
||||
// @ts-expect-error no types
|
||||
await import('@affine/templates/build-edgeless');
|
||||
const config = createWebpackConfig(cwd, flags);
|
||||
const compiler = webpack(config);
|
||||
// Start webpack
|
||||
const devServer = new WebpackDevServer(config.devServer, compiler);
|
||||
|
||||
await devServer.start();
|
||||
} catch (error) {
|
||||
console.error('Error during build:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export type BuildFlags = {
|
||||
channel: 'stable' | 'beta' | 'canary' | 'internal';
|
||||
coverage?: boolean;
|
||||
localBlockSuite?: string;
|
||||
entry?: string;
|
||||
};
|
||||
|
||||
export const projectRoot = fileURLToPath(
|
||||
|
||||
80
tools/cli/src/webpack/cache-group.ts
Normal file
80
tools/cli/src/webpack/cache-group.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
function testPackageName(regexp: RegExp): (module: any) => boolean {
|
||||
return (module: any) =>
|
||||
module.nameForCondition && regexp.test(module.nameForCondition());
|
||||
}
|
||||
|
||||
// https://hackernoon.com/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758
|
||||
export const productionCacheGroups = {
|
||||
asyncVendor: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name(module: any) {
|
||||
// monorepo linked in node_modules, so it's not a npm package
|
||||
if (!module.context.includes('node_modules')) {
|
||||
return `app-async`;
|
||||
}
|
||||
const name = module.context.match(
|
||||
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
|
||||
)?.[1];
|
||||
return `npm-async-${name}`;
|
||||
},
|
||||
priority: Number.MAX_SAFE_INTEGER,
|
||||
chunks: 'async' as const,
|
||||
},
|
||||
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()) &&
|
||||
!module.type.startsWith('javascript'),
|
||||
chunks: 'all' as const,
|
||||
minSize: 1,
|
||||
minChunks: 1,
|
||||
reuseExistingChunk: true,
|
||||
priority: 1000,
|
||||
enforce: true,
|
||||
},
|
||||
};
|
||||
464
tools/cli/src/webpack/config.ts
Normal file
464
tools/cli/src/webpack/config.ts
Normal file
@@ -0,0 +1,464 @@
|
||||
import { createRequire } from 'node:module';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import type { RuntimeConfig } from '@affine/env/global';
|
||||
import { PerfseePlugin } from '@perfsee/webpack';
|
||||
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
|
||||
import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
|
||||
import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin';
|
||||
import CopyPlugin from 'copy-webpack-plugin';
|
||||
import { compact } from 'lodash-es';
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
import TerserPlugin from 'terser-webpack-plugin';
|
||||
import webpack from 'webpack';
|
||||
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
|
||||
|
||||
import { type BuildFlags, projectRoot } from '../config/index.js';
|
||||
import { productionCacheGroups } from './cache-group.js';
|
||||
import { WebpackS3Plugin } from './s3-plugin.js';
|
||||
|
||||
const IN_CI = !!process.env.CI;
|
||||
|
||||
export const rootPath = join(fileURLToPath(import.meta.url), '..', '..');
|
||||
export const workspaceRoot = join(rootPath, '..', '..', '..');
|
||||
|
||||
const require = createRequire(rootPath);
|
||||
|
||||
const OptimizeOptionOptions: (
|
||||
buildFlags: BuildFlags
|
||||
) => webpack.Configuration['optimization'] = buildFlags => ({
|
||||
minimize: buildFlags.mode === 'production',
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
minify: TerserPlugin.swcMinify,
|
||||
exclude: [/plugins\/.+\/.+\.js$/, /plugins\/.+\/.+\.mjs$/],
|
||||
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 getPublicPath = (buildFlags: BuildFlags) => {
|
||||
const { BUILD_TYPE } = process.env;
|
||||
if (typeof process.env.PUBLIC_PATH === 'string') {
|
||||
return process.env.PUBLIC_PATH;
|
||||
}
|
||||
const publicPath = '/';
|
||||
if (process.env.COVERAGE || buildFlags.distribution === 'desktop') {
|
||||
return publicPath;
|
||||
}
|
||||
|
||||
if (BUILD_TYPE === 'canary') {
|
||||
return `https://dev.affineassets.com/`;
|
||||
} else if (BUILD_TYPE === 'beta' || BUILD_TYPE === 'stable') {
|
||||
return `https://prod.affineassets.com/`;
|
||||
}
|
||||
return publicPath;
|
||||
};
|
||||
|
||||
export const createConfiguration: (
|
||||
cwd: string,
|
||||
buildFlags: BuildFlags,
|
||||
runtimeConfig: RuntimeConfig
|
||||
) => webpack.Configuration = (cwd, buildFlags, runtimeConfig) => {
|
||||
const blocksuiteBaseDir = buildFlags.localBlockSuite;
|
||||
const config = {
|
||||
name: 'affine',
|
||||
// to set a correct base path for the source map
|
||||
context: cwd,
|
||||
experiments: {
|
||||
topLevelAwait: true,
|
||||
outputModule: false,
|
||||
syncWebAssembly: true,
|
||||
},
|
||||
output: {
|
||||
environment: {
|
||||
module: true,
|
||||
dynamicImport: true,
|
||||
},
|
||||
filename:
|
||||
buildFlags.mode === 'production'
|
||||
? 'js/[name]-[contenthash:8].js'
|
||||
: 'js/[name].js',
|
||||
// In some cases webpack will emit files starts with "_" which is reserved in web extension.
|
||||
chunkFilename:
|
||||
buildFlags.mode === 'production'
|
||||
? 'js/chunk.[name]-[contenthash:8].js'
|
||||
: 'js/chunk.[name].js',
|
||||
assetModuleFilename:
|
||||
buildFlags.mode === 'production'
|
||||
? 'assets/[name]-[contenthash:8][ext][query]'
|
||||
: '[name][ext]',
|
||||
devtoolModuleFilenameTemplate: 'webpack://[namespace]/[resource-path]',
|
||||
hotUpdateChunkFilename: 'hot/[id].[fullhash].js',
|
||||
hotUpdateMainFilename: 'hot/[runtime].[fullhash].json',
|
||||
path: join(cwd, 'dist'),
|
||||
clean: buildFlags.mode === 'production',
|
||||
globalObject: 'globalThis',
|
||||
publicPath: getPublicPath(buildFlags),
|
||||
},
|
||||
target: ['web', 'es2022'],
|
||||
|
||||
mode: buildFlags.mode,
|
||||
|
||||
devtool:
|
||||
buildFlags.mode === 'production'
|
||||
? 'source-map'
|
||||
: 'eval-cheap-module-source-map',
|
||||
|
||||
resolve: {
|
||||
symlinks: true,
|
||||
extensionAlias: {
|
||||
'.js': ['.js', '.tsx', '.ts'],
|
||||
'.mjs': ['.mjs', '.mts'],
|
||||
},
|
||||
extensions: ['.js', '.ts', '.tsx'],
|
||||
fallback:
|
||||
blocksuiteBaseDir === undefined
|
||||
? undefined
|
||||
: {
|
||||
events: false,
|
||||
},
|
||||
alias: {
|
||||
yjs: require.resolve('yjs'),
|
||||
'@blocksuite/block-std': blocksuiteBaseDir
|
||||
? join(blocksuiteBaseDir, 'packages', 'framework', 'block-std', 'src')
|
||||
: join(
|
||||
workspaceRoot,
|
||||
'node_modules',
|
||||
'@blocksuite',
|
||||
'block-std',
|
||||
'dist'
|
||||
),
|
||||
'@blocksuite/blocks': blocksuiteBaseDir
|
||||
? join(blocksuiteBaseDir, 'packages', 'blocks', 'src')
|
||||
: join(
|
||||
workspaceRoot,
|
||||
'node_modules',
|
||||
'@blocksuite',
|
||||
'blocks',
|
||||
'dist'
|
||||
),
|
||||
'@blocksuite/presets': blocksuiteBaseDir
|
||||
? join(blocksuiteBaseDir, 'packages', 'presets', 'src')
|
||||
: join(
|
||||
workspaceRoot,
|
||||
'node_modules',
|
||||
'@blocksuite',
|
||||
'presets',
|
||||
'dist'
|
||||
),
|
||||
'@blocksuite/global': blocksuiteBaseDir
|
||||
? join(blocksuiteBaseDir, 'packages', 'framework', 'global', 'src')
|
||||
: join(
|
||||
workspaceRoot,
|
||||
'node_modules',
|
||||
'@blocksuite',
|
||||
'global',
|
||||
'dist'
|
||||
),
|
||||
'@blocksuite/store/providers/broadcast-channel': blocksuiteBaseDir
|
||||
? join(
|
||||
blocksuiteBaseDir,
|
||||
'packages',
|
||||
'framework',
|
||||
'store',
|
||||
'src/providers/broadcast-channel'
|
||||
)
|
||||
: join(
|
||||
workspaceRoot,
|
||||
'node_modules',
|
||||
'@blocksuite',
|
||||
'store',
|
||||
'dist',
|
||||
'providers',
|
||||
'broadcast-channel.js'
|
||||
),
|
||||
'@blocksuite/store': blocksuiteBaseDir
|
||||
? join(blocksuiteBaseDir, 'packages', 'framework', 'store', 'src')
|
||||
: join(workspaceRoot, 'node_modules', '@blocksuite', 'store', 'dist'),
|
||||
'@blocksuite/inline': blocksuiteBaseDir
|
||||
? join(blocksuiteBaseDir, 'packages', 'framework', 'inline', 'src')
|
||||
: join(
|
||||
workspaceRoot,
|
||||
'node_modules',
|
||||
'@blocksuite',
|
||||
'inline',
|
||||
'dist'
|
||||
),
|
||||
'@blocksuite/lit': blocksuiteBaseDir
|
||||
? join(blocksuiteBaseDir, 'packages', 'framework', 'lit', 'src')
|
||||
: join(workspaceRoot, 'node_modules', '@blocksuite', 'lit', 'dist'),
|
||||
},
|
||||
},
|
||||
|
||||
module: {
|
||||
parser: {
|
||||
javascript: {
|
||||
// Do not mock Node.js globals
|
||||
node: false,
|
||||
requireJs: false,
|
||||
import: true,
|
||||
// Treat as missing export as error
|
||||
strictExportPresence: true,
|
||||
},
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
test: /\.m?js?$/,
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
enforce: 'pre',
|
||||
include: /@blocksuite/,
|
||||
use: ['source-map-loader'],
|
||||
},
|
||||
{
|
||||
oneOf: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: require.resolve('swc-loader'),
|
||||
options: {
|
||||
// https://swc.rs/docs/configuring-swc/
|
||||
jsc: {
|
||||
preserveAllComments: true,
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
dynamicImport: true,
|
||||
topLevelAwait: false,
|
||||
tsx: true,
|
||||
decorators: true,
|
||||
},
|
||||
target: 'es2022',
|
||||
externalHelpers: false,
|
||||
transform: {
|
||||
react: {
|
||||
runtime: 'automatic',
|
||||
refresh: buildFlags.mode === 'development' && {
|
||||
refreshReg: '$RefreshReg$',
|
||||
refreshSig: '$RefreshSig$',
|
||||
emitFullSignatures: true,
|
||||
},
|
||||
},
|
||||
useDefineForClassFields: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg|webp|mp4)$/,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
{
|
||||
test: /\.(ttf|eot|woff|woff2)$/,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
{
|
||||
test: /\.txt$/,
|
||||
loader: 'raw-loader',
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
buildFlags.mode === 'development'
|
||||
? 'style-loader'
|
||||
: MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
url: true,
|
||||
sourceMap: false,
|
||||
modules: false,
|
||||
import: true,
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
config: resolve(
|
||||
rootPath,
|
||||
'webpack',
|
||||
'postcss.config.cjs'
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: compact([
|
||||
IN_CI ? null : new webpack.ProgressPlugin({ percentBy: 'entries' }),
|
||||
buildFlags.mode === 'development'
|
||||
? new ReactRefreshWebpackPlugin({ overlay: false, esModule: true })
|
||||
: new MiniCssExtractPlugin({
|
||||
filename: `[name].[contenthash:8].css`,
|
||||
ignoreOrder: true,
|
||||
}),
|
||||
new VanillaExtractPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': JSON.stringify({}),
|
||||
'process.env.COVERAGE': JSON.stringify(!!buildFlags.coverage),
|
||||
'process.env.NODE_ENV': JSON.stringify(buildFlags.mode),
|
||||
'process.env.SHOULD_REPORT_TRACE': JSON.stringify(
|
||||
Boolean(process.env.SHOULD_REPORT_TRACE === 'true')
|
||||
),
|
||||
'process.env.TRACE_REPORT_ENDPOINT': JSON.stringify(
|
||||
process.env.TRACE_REPORT_ENDPOINT
|
||||
),
|
||||
'process.env.CAPTCHA_SITE_KEY': JSON.stringify(
|
||||
process.env.CAPTCHA_SITE_KEY
|
||||
),
|
||||
'process.env.SENTRY_DSN': JSON.stringify(process.env.SENTRY_DSN),
|
||||
'process.env.BUILD_TYPE': JSON.stringify(process.env.BUILD_TYPE),
|
||||
runtimeConfig: JSON.stringify(runtimeConfig),
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
// copy the shared public assets into dist
|
||||
from: join(workspaceRoot, 'packages', 'frontend', 'core', 'public'),
|
||||
to: join(cwd, 'dist'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
buildFlags.mode === 'production' && process.env.R2_SECRET_ACCESS_KEY
|
||||
? new WebpackS3Plugin()
|
||||
: null,
|
||||
]),
|
||||
|
||||
optimization: OptimizeOptionOptions(buildFlags),
|
||||
|
||||
devServer: {
|
||||
hot: 'only',
|
||||
liveReload: true,
|
||||
client: {
|
||||
overlay: process.env.DISABLE_DEV_OVERLAY === 'true' ? false : undefined,
|
||||
},
|
||||
historyApiFallback: true,
|
||||
static: [
|
||||
{
|
||||
directory: join(
|
||||
projectRoot,
|
||||
'packages',
|
||||
'frontend',
|
||||
'core',
|
||||
'public'
|
||||
),
|
||||
publicPath: '/',
|
||||
watch: true,
|
||||
},
|
||||
{
|
||||
directory: join(cwd, 'public'),
|
||||
publicPath: '/',
|
||||
watch: true,
|
||||
},
|
||||
],
|
||||
proxy: [
|
||||
{
|
||||
context: '/api/worker/',
|
||||
target: 'https://affine.fail',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
{ context: '/api', target: 'http://localhost:3010' },
|
||||
{ context: '/socket.io', target: 'http://localhost:3010', ws: true },
|
||||
{ context: '/graphql', target: 'http://localhost:3010' },
|
||||
{ context: '/oauth', target: 'http://localhost:3010' },
|
||||
],
|
||||
} 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 (buildFlags.mode === 'development') {
|
||||
config.optimization = {
|
||||
...config.optimization,
|
||||
minimize: false,
|
||||
runtimeChunk: false,
|
||||
splitChunks: {
|
||||
maxInitialRequests: Infinity,
|
||||
chunks: 'all',
|
||||
cacheGroups: {
|
||||
defaultVendors: {
|
||||
test: `[\\/]node_modules[\\/](?!.*vanilla-extract)`,
|
||||
priority: -10,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
default: {
|
||||
minChunks: 2,
|
||||
priority: -20,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
styles: {
|
||||
name: 'styles',
|
||||
type: 'css/mini-extract',
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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
tools/cli/src/webpack/postcss.config.cjs
Normal file
20
tools/cli/src/webpack/postcss.config.cjs
Normal 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,
|
||||
};
|
||||
};
|
||||
155
tools/cli/src/webpack/runtime-config.ts
Normal file
155
tools/cli/src/webpack/runtime-config.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import type { RuntimeConfig } from '@affine/env/global';
|
||||
|
||||
import packageJson from '../../package.json' assert { type: 'json' };
|
||||
import type { BuildFlags } from '../config';
|
||||
|
||||
export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
const buildPreset: Record<BuildFlags['channel'], RuntimeConfig> = {
|
||||
stable: {
|
||||
enableTestProperties: false,
|
||||
enableBroadcastChannelProvider: true,
|
||||
enableDebugPage: true,
|
||||
githubUrl: 'https://github.com/toeverything/AFFiNE',
|
||||
changelogUrl: 'https://affine.pro/what-is-new',
|
||||
downloadUrl: 'https://affine.pro/download',
|
||||
imageProxyUrl: '/api/worker/image-proxy',
|
||||
linkPreviewUrl: '/api/worker/link-preview',
|
||||
enablePreloading: true,
|
||||
enableNewSettingModal: true,
|
||||
enableNewSettingUnstableApi: false,
|
||||
enableSQLiteProvider: true,
|
||||
enableMoveDatabase: false,
|
||||
enableCloud: true,
|
||||
enableCaptcha: true,
|
||||
enableEnhanceShareMode: false,
|
||||
enablePayment: true,
|
||||
enablePageHistory: true,
|
||||
allowLocalWorkspace: false,
|
||||
serverUrlPrefix: 'https://app.affine.pro',
|
||||
appVersion: packageJson.version,
|
||||
editorVersion: packageJson.devDependencies['@blocksuite/presets'],
|
||||
appBuildType: 'stable',
|
||||
},
|
||||
get beta() {
|
||||
return {
|
||||
...this.stable,
|
||||
enablePageHistory: true,
|
||||
serverUrlPrefix: 'https://insider.affine.pro',
|
||||
appBuildType: 'beta' as const,
|
||||
};
|
||||
},
|
||||
get internal() {
|
||||
return {
|
||||
...this.stable,
|
||||
serverUrlPrefix: 'https://insider.affine.pro',
|
||||
appBuildType: 'internal' as const,
|
||||
};
|
||||
},
|
||||
// canary will be aggressive and enable all features
|
||||
canary: {
|
||||
enableTestProperties: true,
|
||||
enableBroadcastChannelProvider: true,
|
||||
enableDebugPage: true,
|
||||
githubUrl: 'https://github.com/toeverything/AFFiNE',
|
||||
changelogUrl: 'https://github.com/toeverything/AFFiNE/releases',
|
||||
downloadUrl: 'https://affine.pro/download',
|
||||
imageProxyUrl: '/api/worker/image-proxy',
|
||||
linkPreviewUrl: '/api/worker/link-preview',
|
||||
enablePreloading: true,
|
||||
enableNewSettingModal: true,
|
||||
enableNewSettingUnstableApi: false,
|
||||
enableSQLiteProvider: true,
|
||||
enableMoveDatabase: false,
|
||||
enableCloud: true,
|
||||
enableCaptcha: true,
|
||||
enableEnhanceShareMode: false,
|
||||
enablePayment: true,
|
||||
enablePageHistory: true,
|
||||
allowLocalWorkspace: false,
|
||||
serverUrlPrefix: 'https://affine.fail',
|
||||
appVersion: packageJson.version,
|
||||
editorVersion: packageJson.devDependencies['@blocksuite/presets'],
|
||||
appBuildType: 'canary',
|
||||
},
|
||||
};
|
||||
|
||||
const currentBuild = buildFlags.channel;
|
||||
|
||||
if (!(currentBuild in buildPreset)) {
|
||||
throw new Error(`BUILD_TYPE ${currentBuild} is not supported`);
|
||||
}
|
||||
|
||||
const currentBuildPreset = buildPreset[currentBuild];
|
||||
|
||||
const environmentPreset = {
|
||||
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,
|
||||
enableCloud: process.env.ENABLE_CLOUD
|
||||
? process.env.ENABLE_CLOUD === 'true'
|
||||
: currentBuildPreset.enableCloud,
|
||||
enableCaptcha: process.env.ENABLE_CAPTCHA
|
||||
? process.env.ENABLE_CAPTCHA === 'true'
|
||||
: buildFlags.mode === 'development'
|
||||
? false
|
||||
: currentBuildPreset.enableCaptcha,
|
||||
enableEnhanceShareMode: process.env.ENABLE_ENHANCE_SHARE_MODE
|
||||
? process.env.ENABLE_ENHANCE_SHARE_MODE === 'true'
|
||||
: currentBuildPreset.enableEnhanceShareMode,
|
||||
enableMoveDatabase: process.env.ENABLE_MOVE_DATABASE
|
||||
? process.env.ENABLE_MOVE_DATABASE === 'true'
|
||||
: currentBuildPreset.enableMoveDatabase,
|
||||
enablePayment: process.env.ENABLE_PAYMENT
|
||||
? process.env.ENABLE_PAYMENT !== 'false'
|
||||
: buildFlags.mode === 'development'
|
||||
? true
|
||||
: currentBuildPreset.enablePayment,
|
||||
enablePageHistory: process.env.ENABLE_PAGE_HISTORY
|
||||
? process.env.ENABLE_PAGE_HISTORY === 'true'
|
||||
: buildFlags.mode === 'development'
|
||||
? true
|
||||
: currentBuildPreset.enablePageHistory,
|
||||
allowLocalWorkspace: process.env.ALLOW_LOCAL_WORKSPACE
|
||||
? process.env.ALLOW_LOCAL_WORKSPACE === 'true'
|
||||
: buildFlags.mode === 'development'
|
||||
? true
|
||||
: currentBuildPreset.allowLocalWorkspace,
|
||||
isSelfHosted: process.env.SELF_HOSTED === 'true',
|
||||
};
|
||||
|
||||
const testEnvironmentPreset = {
|
||||
allowLocalWorkspace: true,
|
||||
};
|
||||
|
||||
if (buildFlags.mode === 'development') {
|
||||
currentBuildPreset.serverUrlPrefix = 'http://localhost:8080';
|
||||
}
|
||||
|
||||
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),
|
||||
|
||||
// test environment preset will overwrite current build preset
|
||||
// this environment variable is for github workflow e2e-test only
|
||||
...(process.env.IN_CI_TEST ? testEnvironmentPreset : {}),
|
||||
};
|
||||
}
|
||||
47
tools/cli/src/webpack/s3-plugin.ts
Normal file
47
tools/cli/src/webpack/s3-plugin.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import type { PutObjectCommandInput } from '@aws-sdk/client-s3';
|
||||
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||
import { lookup } from 'mime-types';
|
||||
import type { Compiler, WebpackPluginInstance } from 'webpack';
|
||||
|
||||
export const R2_BUCKET =
|
||||
process.env.R2_BUCKET ??
|
||||
(process.env.BUILD_TYPE === 'canary' ? 'assets-dev' : 'assets-prod');
|
||||
|
||||
export class WebpackS3Plugin implements WebpackPluginInstance {
|
||||
private readonly s3 = new S3Client({
|
||||
region: 'auto',
|
||||
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
|
||||
credentials: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
});
|
||||
|
||||
apply(compiler: Compiler) {
|
||||
compiler.hooks.assetEmitted.tapPromise(
|
||||
'WebpackS3Plugin',
|
||||
async (asset, { outputPath }) => {
|
||||
if (asset === 'index.html') {
|
||||
return;
|
||||
}
|
||||
const assetPath = join(outputPath, asset);
|
||||
const assetSource = await readFile(assetPath);
|
||||
const putObjectCommandOptions: PutObjectCommandInput = {
|
||||
Body: assetSource,
|
||||
Bucket: R2_BUCKET,
|
||||
Key: asset,
|
||||
};
|
||||
const contentType = lookup(asset);
|
||||
if (contentType) {
|
||||
putObjectCommandOptions.ContentType = contentType;
|
||||
}
|
||||
await this.s3.send(new PutObjectCommand(putObjectCommandOptions));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
43
tools/cli/src/webpack/template.html
Normal file
43
tools/cli/src/webpack/template.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<!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="/favicon-192.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="<%= DESCRIPTION %>" />
|
||||
<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" data-version="<%= GIT_SHORT_SHA %>"></div>
|
||||
</body>
|
||||
</html>
|
||||
55
tools/cli/src/webpack/webpack.config.ts
Normal file
55
tools/cli/src/webpack/webpack.config.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
import { join, resolve } from 'node:path';
|
||||
|
||||
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 { merge } from 'webpack-merge';
|
||||
|
||||
import { createConfiguration, rootPath, workspaceRoot } from './config.js';
|
||||
import { getRuntimeConfig } 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.`;
|
||||
|
||||
const gitShortHash = once(() => {
|
||||
const { GITHUB_SHA } = process.env;
|
||||
if (GITHUB_SHA) {
|
||||
return GITHUB_SHA.substring(0, 9);
|
||||
}
|
||||
const repo = new Repository(workspaceRoot);
|
||||
const shortSha = repo.head().target()?.substring(0, 9);
|
||||
if (shortSha) {
|
||||
return shortSha;
|
||||
}
|
||||
const sha = execSync(`git rev-parse --short HEAD`, {
|
||||
encoding: 'utf-8',
|
||||
}).trim();
|
||||
return sha;
|
||||
});
|
||||
|
||||
export function createWebpackConfig(cwd: string, flags: BuildFlags) {
|
||||
console.log('build flags', flags);
|
||||
const runtimeConfig = getRuntimeConfig(flags);
|
||||
console.log('runtime config', runtimeConfig);
|
||||
const config = createConfiguration(cwd, flags, runtimeConfig);
|
||||
return merge(config, {
|
||||
entry: {
|
||||
app: flags.entry ?? resolve(cwd, 'src/index.tsx'),
|
||||
},
|
||||
plugins: [
|
||||
new HTMLPlugin({
|
||||
template: join(rootPath, 'webpack', 'template.html'),
|
||||
inject: 'body',
|
||||
scriptLoading: 'module',
|
||||
minify: false,
|
||||
chunks: ['app'],
|
||||
filename: 'index.html',
|
||||
templateParameters: {
|
||||
GIT_SHORT_SHA: gitShortHash(),
|
||||
DESCRIPTION,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -6,5 +6,6 @@
|
||||
"moduleResolution": "Node",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": ["src", "package.json"],
|
||||
"references": [{ "path": "../../packages/common/env" }]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user