feat: cleanup webpack deps (#14530)

#### PR Dependency Tree


* **PR #14530** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Breaking Changes**
  * Webpack bundler support removed from the build system
* Bundler selection parameter removed from build and development
commands

* **Refactor**
  * Build configuration consolidated to a single bundler approach
* Webpack-specific build paths and workflows removed; development server
simplified

* **Chores**
  * Removed webpack-related dev dependencies and tooling
  * Updated package build scripts for a unified bundle command

* **Dependencies**
* Upgraded Sentry packages across frontend packages
(react/electron/esbuild plugin)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-02-28 00:24:08 +08:00
committed by GitHub
parent a4e2242b8d
commit 2cb171f553
30 changed files with 588 additions and 1929 deletions

View File

@@ -282,52 +282,6 @@ jobs:
path: ./test-results path: ./test-results
if-no-files-found: ignore if-no-files-found: ignore
bundler-matrix:
name: Bundler Matrix (${{ matrix.bundler }})
runs-on: ubuntu-24.04-arm
strategy:
fail-fast: false
matrix:
bundler: [webpack, rspack]
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
playwright-install: false
electron-install: false
full-cache: true
- name: Run frontend build matrix
env:
AFFINE_BUNDLER: ${{ matrix.bundler }}
run: |
set -euo pipefail
packages=(
"@affine/web"
"@affine/mobile"
"@affine/ios"
"@affine/android"
"@affine/admin"
"@affine/electron-renderer"
)
summary="test-results-bundler-${AFFINE_BUNDLER}.txt"
: > "$summary"
for pkg in "${packages[@]}"; do
start=$(date +%s)
yarn affine "$pkg" build
end=$(date +%s)
echo "${pkg},$((end-start))" >> "$summary"
done
- name: Upload bundler timing
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-bundler-${{ matrix.bundler }}
path: ./test-results-bundler-${{ matrix.bundler }}.txt
if-no-files-found: ignore
e2e-test: e2e-test:
name: E2E Test name: E2E Test
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm

View File

@@ -1,23 +0,0 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { build } from 'esbuild';
const result = await build({
entryPoints: ['./src/index.ts'],
bundle: true,
platform: 'node',
outdir: 'dist',
target: 'es2024',
sourcemap: true,
format: 'esm',
external: ['yjs'],
metafile: true,
});
if (process.env.METAFILE) {
await fs.writeFile(
path.resolve(`metafile-${Date.now()}.json`),
JSON.stringify(result.metafile, null, 2)
);
}

View File

@@ -10,8 +10,8 @@
"./dist": "./dist/index.js" "./dist": "./dist/index.js"
}, },
"scripts": { "scripts": {
"build": "yarn bundle", "build": "affine bundle -p @affine/reader",
"bundle": "node esbuild.config.js" "bundle": "affine bundle -p @affine/reader"
}, },
"dependencies": { "dependencies": {
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",

View File

@@ -37,7 +37,7 @@
"@radix-ui/react-toggle": "^1.1.1", "@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.1.1", "@radix-ui/react-toggle-group": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.5", "@radix-ui/react-tooltip": "^1.1.5",
"@sentry/react": "^9.47.1", "@sentry/react": "^10.40.0",
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",
"@toeverything/infra": "workspace:*", "@toeverything/infra": "workspace:*",
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",

View File

@@ -26,7 +26,7 @@
"@capacitor/keyboard": "^7.0.0", "@capacitor/keyboard": "^7.0.0",
"@capacitor/status-bar": "^7.0.0", "@capacitor/status-bar": "^7.0.0",
"@capgo/inappbrowser": "^8.0.0", "@capgo/inappbrowser": "^8.0.0",
"@sentry/react": "^9.47.1", "@sentry/react": "^10.40.0",
"@toeverything/infra": "workspace:*", "@toeverything/infra": "workspace:*",
"async-call-rpc": "^6.4.2", "async-call-rpc": "^6.4.2",
"idb": "^8.0.0", "idb": "^8.0.0",

View File

@@ -17,7 +17,7 @@
"@affine/track": "workspace:*", "@affine/track": "workspace:*",
"@blocksuite/affine": "workspace:*", "@blocksuite/affine": "workspace:*",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@sentry/react": "^9.47.1", "@sentry/react": "^10.40.0",
"@toeverything/infra": "workspace:*", "@toeverything/infra": "workspace:*",
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",
"@vanilla-extract/css": "^1.17.0", "@vanilla-extract/css": "^1.17.0",

View File

@@ -38,7 +38,6 @@
"@affine/nbstore": "workspace:*", "@affine/nbstore": "workspace:*",
"@electron-forge/cli": "^7.10.2", "@electron-forge/cli": "^7.10.2",
"@electron-forge/core": "^7.10.2", "@electron-forge/core": "^7.10.2",
"@electron-forge/core-utils": "^7.10.2",
"@electron-forge/maker-deb": "^7.10.2", "@electron-forge/maker-deb": "^7.10.2",
"@electron-forge/maker-dmg": "^7.10.2", "@electron-forge/maker-dmg": "^7.10.2",
"@electron-forge/maker-flatpak": "^7.10.2", "@electron-forge/maker-flatpak": "^7.10.2",
@@ -48,9 +47,9 @@
"@electron-forge/plugin-fuses": "^7.10.2", "@electron-forge/plugin-fuses": "^7.10.2",
"@electron-forge/shared-types": "^7.10.2", "@electron-forge/shared-types": "^7.10.2",
"@reforged/maker-appimage": "^5.2.0", "@reforged/maker-appimage": "^5.2.0",
"@sentry/electron": "^7.0.0", "@sentry/electron": "^7.9.0",
"@sentry/esbuild-plugin": "^4.0.0", "@sentry/esbuild-plugin": "^5.1.1",
"@sentry/react": "^9.47.1", "@sentry/react": "^10.40.0",
"@toeverything/infra": "workspace:*", "@toeverything/infra": "workspace:*",
"@types/set-cookie-parser": "^2.4.10", "@types/set-cookie-parser": "^2.4.10",
"@types/uuid": "^11.0.0", "@types/uuid": "^11.0.0",

View File

@@ -30,7 +30,7 @@
"@capacitor/haptics": "^7.0.0", "@capacitor/haptics": "^7.0.0",
"@capacitor/ios": "^7.0.0", "@capacitor/ios": "^7.0.0",
"@capacitor/keyboard": "^7.0.0", "@capacitor/keyboard": "^7.0.0",
"@sentry/react": "^9.47.1", "@sentry/react": "^10.40.0",
"@toeverything/infra": "workspace:^", "@toeverything/infra": "workspace:^",
"async-call-rpc": "^6.4.2", "async-call-rpc": "^6.4.2",
"capacitor-plugin-app-tracking-transparency": "^2.0.5", "capacitor-plugin-app-tracking-transparency": "^2.0.5",

View File

@@ -17,7 +17,7 @@
"@affine/track": "workspace:*", "@affine/track": "workspace:*",
"@blocksuite/affine": "workspace:*", "@blocksuite/affine": "workspace:*",
"@blocksuite/icons": "^2.2.17", "@blocksuite/icons": "^2.2.17",
"@sentry/react": "^9.47.1", "@sentry/react": "^10.40.0",
"@toeverything/infra": "workspace:*", "@toeverything/infra": "workspace:*",
"react": "^19.2.1", "react": "^19.2.1",
"react-dom": "^19.2.1", "react-dom": "^19.2.1",

View File

@@ -16,7 +16,7 @@
"@affine/nbstore": "workspace:*", "@affine/nbstore": "workspace:*",
"@affine/track": "workspace:*", "@affine/track": "workspace:*",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@sentry/react": "^9.47.1", "@sentry/react": "^10.40.0",
"@toeverything/infra": "workspace:*", "@toeverything/infra": "workspace:*",
"react": "^19.2.1", "react": "^19.2.1",
"react-dom": "^19.2.1", "react-dom": "^19.2.1",

View File

@@ -45,7 +45,7 @@
"@radix-ui/react-scroll-area": "^1.2.2", "@radix-ui/react-scroll-area": "^1.2.2",
"@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-toolbar": "^1.1.1", "@radix-ui/react-toolbar": "^1.1.1",
"@sentry/react": "^9.47.1", "@sentry/react": "^10.40.0",
"@toeverything/infra": "workspace:*", "@toeverything/infra": "workspace:*",
"@toeverything/pdf-viewer": "^0.1.1", "@toeverything/pdf-viewer": "^0.1.1",
"@toeverything/theme": "^1.1.23", "@toeverything/theme": "^1.1.23",

View File

@@ -1,4 +1,3 @@
/// <reference types="@webpack/env" />
/// <reference types="@rspack/core/module" /> /// <reference types="@rspack/core/module" />
declare module '*.md' { declare module '*.md' {

View File

@@ -8,7 +8,7 @@
}, },
"dependencies": { "dependencies": {
"@affine/debug": "workspace:*", "@affine/debug": "workspace:*",
"@sentry/react": "^9.47.1", "@sentry/react": "^10.40.0",
"nanoid": "^5.1.6", "nanoid": "^5.1.6",
"react-router-dom": "^6.30.3" "react-router-dom": "^6.30.3"
}, },

View File

@@ -9,8 +9,7 @@
"@affine-test/kit": "workspace:*", "@affine-test/kit": "workspace:*",
"@affine-tools/cli": "workspace:*", "@affine-tools/cli": "workspace:*",
"@affine-tools/utils": "workspace:*", "@affine-tools/utils": "workspace:*",
"@playwright/test": "=1.58.2", "@playwright/test": "=1.58.2"
"webpack": "^5.102.1"
}, },
"version": "0.26.3" "version": "0.26.3"
} }

View File

@@ -18,16 +18,14 @@
"@affine-tools/utils": "workspace:*", "@affine-tools/utils": "workspace:*",
"@affine/s3-compat": "workspace:*", "@affine/s3-compat": "workspace:*",
"@napi-rs/simple-git": "^0.1.22", "@napi-rs/simple-git": "^0.1.22",
"@perfsee/webpack": "^1.13.0",
"@rspack/core": "^1.7.6", "@rspack/core": "^1.7.6",
"@rspack/dev-server": "^1.1.3", "@rspack/dev-server": "^1.1.3",
"@sentry/webpack-plugin": "^4.0.0", "@sentry/webpack-plugin": "^5.1.1",
"@swc/core": "^1.10.1", "@swc/core": "^1.10.1",
"@tailwindcss/postcss": "^4.0.0", "@tailwindcss/postcss": "^4.0.0",
"@vanilla-extract/webpack-plugin": "^2.3.15", "@vanilla-extract/webpack-plugin": "^2.3.15",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"clipanion": "^3.2.1", "clipanion": "^3.2.1",
"copy-webpack-plugin": "^13.0.0",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"cssnano": "^7.0.6", "cssnano": "^7.0.6",
"html-webpack-plugin": "^5.6.3", "html-webpack-plugin": "^5.6.3",
@@ -35,7 +33,6 @@
"jsonc-parser": "^3.3.1", "jsonc-parser": "^3.3.1",
"lodash-es": "^4.17.23", "lodash-es": "^4.17.23",
"mime-types": "^3.0.0", "mime-types": "^3.0.0",
"mini-css-extract-plugin": "^2.9.2",
"node-loader": "^2.1.0", "node-loader": "^2.1.0",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"postcss-loader": "^8.1.1", "postcss-loader": "^8.1.1",
@@ -46,18 +43,13 @@
"style-loader": "^4.0.0", "style-loader": "^4.0.0",
"swc-loader": "^0.2.6", "swc-loader": "^0.2.6",
"tailwindcss": "^4.1.17", "tailwindcss": "^4.1.17",
"terser-webpack-plugin": "^5.3.10",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typanion": "^3.14.0", "typanion": "^3.14.0",
"typescript": "^5.9.3", "typescript": "^5.9.3"
"webpack": "^5.102.1",
"webpack-dev-server": "^5.2.0",
"webpack-merge": "^6.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/mime-types": "^3.0.0", "@types/mime-types": "^3.0.0",
"@types/node": "^22.0.0", "@types/node": "^22.0.0"
"@types/webpack-env": "^1.18.5"
} }
} }

View File

@@ -1,4 +1,4 @@
import type { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server'; import type { Configuration as RspackDevServerConfiguration } from '@rspack/dev-server';
export const RSPACK_SUPPORTED_PACKAGES = [ export const RSPACK_SUPPORTED_PACKAGES = [
'@affine/admin', '@affine/admin',
@@ -8,6 +8,7 @@ export const RSPACK_SUPPORTED_PACKAGES = [
'@affine/android', '@affine/android',
'@affine/electron-renderer', '@affine/electron-renderer',
'@affine/server', '@affine/server',
'@affine/reader',
] as const; ] as const;
const rspackSupportedPackageSet = new Set<string>(RSPACK_SUPPORTED_PACKAGES); const rspackSupportedPackageSet = new Set<string>(RSPACK_SUPPORTED_PACKAGES);
@@ -22,14 +23,14 @@ export function assertRspackSupportedPackageName(name: string) {
} }
throw new Error( throw new Error(
`AFFINE_BUNDLER=rspack currently supports: ${Array.from(RSPACK_SUPPORTED_PACKAGES).join(', ')}. Use AFFINE_BUNDLER=webpack for ${name}.` `Rspack bundling currently supports: ${Array.from(RSPACK_SUPPORTED_PACKAGES).join(', ')}. Unsupported package: ${name}.`
); );
} }
const IN_CI = !!process.env.CI; const IN_CI = !!process.env.CI;
const httpProxyMiddlewareLogLevel = IN_CI ? 'silent' : 'error'; const httpProxyMiddlewareLogLevel = IN_CI ? 'silent' : 'error';
export const DEFAULT_DEV_SERVER_CONFIG: WebpackDevServerConfiguration = { export const DEFAULT_DEV_SERVER_CONFIG: RspackDevServerConfiguration = {
host: '0.0.0.0', host: '0.0.0.0',
allowedHosts: 'all', allowedHosts: 'all',
hot: false, hot: false,

View File

@@ -9,32 +9,21 @@ import {
RspackDevServer, RspackDevServer,
} from '@rspack/dev-server'; } from '@rspack/dev-server';
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
import webpack from 'webpack';
import WebpackDevServer, {
type Configuration as WebpackDevServerConfiguration,
} from 'webpack-dev-server';
import { import {
assertRspackSupportedPackageName, assertRspackSupportedPackageName,
DEFAULT_DEV_SERVER_CONFIG, DEFAULT_DEV_SERVER_CONFIG,
isRspackSupportedPackageName,
} from './bundle-shared'; } from './bundle-shared';
import { type Bundler, getBundler } from './bundler';
import { Option, PackageCommand } from './command'; import { Option, PackageCommand } from './command';
import { import {
createHTMLTargetConfig as createRspackHTMLTargetConfig, createHTMLTargetConfig as createRspackHTMLTargetConfig,
createNodeTargetConfig as createRspackNodeTargetConfig, createNodeTargetConfig as createRspackNodeTargetConfig,
createWorkerTargetConfig as createRspackWorkerTargetConfig, createWorkerTargetConfig as createRspackWorkerTargetConfig,
} from './rspack'; } from './rspack';
import {
createHTMLTargetConfig as createWebpackHTMLTargetConfig,
createNodeTargetConfig as createWebpackNodeTargetConfig,
createWorkerTargetConfig as createWebpackWorkerTargetConfig,
} from './webpack';
import { import {
shouldUploadReleaseAssets, shouldUploadReleaseAssets,
uploadDistAssetsToS3, uploadDistAssetsToS3,
} from './webpack/s3-plugin.js'; } from './rspack-shared/s3-plugin.js';
type WorkerConfig = { name: string }; type WorkerConfig = { name: string };
type CreateWorkerTargetConfig = (pkg: Package, entry: string) => WorkerConfig; type CreateWorkerTargetConfig = (pkg: Package, entry: string) => WorkerConfig;
@@ -84,78 +73,6 @@ function getBaseWorkerConfigs(
]; ];
} }
function getWebpackBundleConfigs(pkg: Package): webpack.MultiConfiguration {
switch (pkg.name) {
case '@affine/admin': {
return [
createWebpackHTMLTargetConfig(
pkg,
pkg.srcPath.join('index.tsx').value,
{ selfhostPublicPath: '/admin/' }
),
] as webpack.MultiConfiguration;
}
case '@affine/web':
case '@affine/mobile':
case '@affine/ios':
case '@affine/android': {
const workerConfigs = getBaseWorkerConfigs(
pkg,
createWebpackWorkerTargetConfig
);
workerConfigs.push(
createWebpackWorkerTargetConfig(
pkg,
pkg.srcPath.join('nbstore.worker.ts').value
)
);
return [
createWebpackHTMLTargetConfig(
pkg,
pkg.srcPath.join('index.tsx').value,
{},
workerConfigs.map(config => config.name)
),
...workerConfigs,
] as webpack.MultiConfiguration;
}
case '@affine/electron-renderer': {
const workerConfigs = getBaseWorkerConfigs(
pkg,
createWebpackWorkerTargetConfig
);
return [
createWebpackHTMLTargetConfig(
pkg,
{
index: pkg.srcPath.join('app/index.tsx').value,
shell: pkg.srcPath.join('shell/index.tsx').value,
popup: pkg.srcPath.join('popup/index.tsx').value,
backgroundWorker: pkg.srcPath.join('background-worker/index.ts')
.value,
},
{
additionalEntryForSelfhost: false,
injectGlobalErrorHandler: false,
emitAssetsManifest: false,
},
workerConfigs.map(config => config.name)
),
...workerConfigs,
] as webpack.MultiConfiguration;
}
case '@affine/server': {
return [
createWebpackNodeTargetConfig(pkg, pkg.srcPath.join('index.ts').value),
] as webpack.MultiConfiguration;
}
}
throw new Error(`Unsupported package: ${pkg.name}`);
}
function getRspackBundleConfigs(pkg: Package): MultiRspackOptions { function getRspackBundleConfigs(pkg: Package): MultiRspackOptions {
assertRspackSupportedPackage(pkg); assertRspackSupportedPackage(pkg);
@@ -223,13 +140,24 @@ function getRspackBundleConfigs(pkg: Package): MultiRspackOptions {
createRspackNodeTargetConfig(pkg, pkg.srcPath.join('index.ts').value), createRspackNodeTargetConfig(pkg, pkg.srcPath.join('index.ts').value),
] as MultiRspackOptions; ] as MultiRspackOptions;
} }
case '@affine/reader': {
return [
createRspackNodeTargetConfig(pkg, pkg.srcPath.join('index.ts').value, {
outputFilename: 'index.js',
decoratorVersion: '2022-03',
libraryType: 'module',
bundleAllDependencies: true,
forceExternal: ['yjs'],
}),
] as MultiRspackOptions;
}
} }
throw new Error(`Unsupported package: ${pkg.name}`); throw new Error(`Unsupported package: ${pkg.name}`);
} }
export class BundleCommand extends PackageCommand { export class BundleCommand extends PackageCommand {
static override paths = [['bundle'], ['webpack'], ['pack'], ['bun']]; static override paths = [['bundle'], ['pack'], ['bun']];
// bundle is not able to run with deps // bundle is not able to run with deps
override _deps = false; override _deps = false;
@@ -241,123 +169,23 @@ export class BundleCommand extends PackageCommand {
async execute() { async execute() {
const pkg = this.workspace.getPackage(this.package); const pkg = this.workspace.getPackage(this.package);
const bundler = getBundler();
if (this.dev) { if (this.dev) {
await BundleCommand.dev(pkg, bundler); await BundleCommand.dev(pkg);
} else { } else {
await BundleCommand.build(pkg, bundler); await BundleCommand.build(pkg);
} }
} }
static async build(pkg: Package, bundler: Bundler = getBundler()) { static async build(pkg: Package) {
if (bundler === 'rspack' && !isRspackSupportedPackageName(pkg.name)) { return BundleCommand.buildWithRspack(pkg);
return BundleCommand.buildWithWebpack(pkg);
}
switch (bundler) {
case 'webpack':
return BundleCommand.buildWithWebpack(pkg);
case 'rspack':
return BundleCommand.buildWithRspack(pkg);
}
}
static async buildWithWebpack(pkg: Package) {
process.env.NODE_ENV = 'production';
const logger = new Logger('bundle');
logger.info(`Packing package ${pkg.name} with webpack...`);
logger.info('Cleaning old output...');
rmSync(pkg.distPath.value, { recursive: true, force: true });
const config = getWebpackBundleConfigs(pkg);
config.parallelism = cpus().length;
const compiler = webpack(config);
if (!compiler) {
throw new Error('Failed to create webpack compiler');
}
try {
const stats = await new Promise<webpack.Stats | webpack.MultiStats>(
(resolve, reject) => {
compiler.run((error, stats) => {
if (error) {
reject(error);
return;
}
if (!stats) {
reject(new Error('Failed to get webpack stats'));
return;
}
resolve(stats);
});
}
);
if (stats.hasErrors()) {
console.error(stats.toString('errors-only'));
process.exit(1);
return;
}
console.log(stats.toString('minimal'));
await uploadAssetsForPackage(pkg, logger);
} catch (error) {
console.error(error);
process.exit(1);
return;
}
} }
static async dev( static async dev(
pkg: Package, pkg: Package,
bundler: Bundler = getBundler(), devServerConfig?: RspackDevServerConfiguration
devServerConfig?:
| WebpackDevServerConfiguration
| RspackDevServerConfiguration
) { ) {
if (bundler === 'rspack' && !isRspackSupportedPackageName(pkg.name)) { return BundleCommand.devWithRspack(pkg, devServerConfig);
return BundleCommand.devWithWebpack(
pkg,
devServerConfig as WebpackDevServerConfiguration | undefined
);
}
switch (bundler) {
case 'webpack':
return BundleCommand.devWithWebpack(
pkg,
devServerConfig as WebpackDevServerConfiguration | undefined
);
case 'rspack':
return BundleCommand.devWithRspack(
pkg,
devServerConfig as RspackDevServerConfiguration | undefined
);
}
}
static async devWithWebpack(
pkg: Package,
devServerConfig?: WebpackDevServerConfiguration
) {
process.env.NODE_ENV = 'development';
const logger = new Logger('bundle');
logger.info(`Starting webpack dev server for ${pkg.name}...`);
const config = getWebpackBundleConfigs(pkg);
config.parallelism = cpus().length;
const compiler = webpack(config);
if (!compiler) {
throw new Error('Failed to create webpack compiler');
}
const devServer = new WebpackDevServer(
merge({}, DEFAULT_DEV_SERVER_CONFIG, devServerConfig),
compiler
);
await devServer.start();
} }
static async buildWithRspack(pkg: Package) { static async buildWithRspack(pkg: Package) {

View File

@@ -1,27 +0,0 @@
export const SUPPORTED_BUNDLERS = ['webpack', 'rspack'] as const;
export type Bundler = (typeof SUPPORTED_BUNDLERS)[number];
export const DEFAULT_BUNDLER: Bundler = 'rspack';
function isBundler(value: string): value is Bundler {
return SUPPORTED_BUNDLERS.includes(value as Bundler);
}
export function normalizeBundler(input: string | undefined | null): Bundler {
const value = input?.trim().toLowerCase();
if (!value) {
return DEFAULT_BUNDLER;
}
if (isBundler(value)) {
return value;
}
throw new Error(
`Unsupported AFFINE_BUNDLER: "${input}". Expected one of: ${SUPPORTED_BUNDLERS.join(', ')}.`
);
}
export function getBundler(env: NodeJS.ProcessEnv = process.env): Bundler {
return normalizeBundler(env.AFFINE_BUNDLER);
}

View File

@@ -1,5 +1,5 @@
(function () { (function () {
var errorEl = null; let errorEl = null;
function showGlobalErrorPage() { function showGlobalErrorPage() {
if (errorEl) { if (errorEl) {
return; return;
@@ -37,7 +37,7 @@
* @param event {PromiseRejectionEvent|ErrorEvent} * @param event {PromiseRejectionEvent|ErrorEvent}
*/ */
function handler(event) { function handler(event) {
var error; let error;
if ('error' in event) { if ('error' in event) {
error = error =
@@ -51,7 +51,7 @@
console.error('unhandled unrecoverable error', error); console.error('unhandled unrecoverable error', error);
var shouldCache = const shouldCache =
// syntax error // syntax error
error && error instanceof SyntaxError; error && error instanceof SyntaxError;
@@ -79,9 +79,9 @@
function unregisterRegisterGlobalErrorHandler(fn) { function unregisterRegisterGlobalErrorHandler(fn) {
if (typeof fn === 'function') { if (typeof fn === 'function') {
var app = document.getElementById('app'); const app = document.getElementById('app');
if (app) { if (app) {
var ob = new MutationObserver(function () { let ob = new MutationObserver(function () {
fn(); fn();
ob.disconnect(); ob.disconnect();
ob = null; ob = null;
@@ -93,7 +93,7 @@
} }
function ensureBasicEnvironment() { function ensureBasicEnvironment() {
var globals = [ const globals = [
'Promise', 'Promise',
'Map', 'Map',
'fetch', 'fetch',
@@ -102,7 +102,7 @@
]; ];
// eslint-disable-next-line @typescript-eslint/prefer-for-of // eslint-disable-next-line @typescript-eslint/prefer-for-of
for (var i = 0; i < globals.length; i++) { for (let i = 0; i < globals.length; i++) {
if (!(globals[i] in globalThis)) { if (!(globals[i] in globalThis)) {
showGlobalErrorPage(); showGlobalErrorPage();
return; return;
@@ -111,6 +111,6 @@
} }
ensureBasicEnvironment(); ensureBasicEnvironment();
var goodtogo = registerGlobalErrorHandler(); const goodtogo = registerGlobalErrorHandler();
unregisterRegisterGlobalErrorHandler(goodtogo); unregisterRegisterGlobalErrorHandler(goodtogo);
})(); })();

View File

@@ -5,7 +5,10 @@ import { Path, ProjectRoot } from '@affine-tools/utils/path';
import { Repository } from '@napi-rs/simple-git'; import { Repository } from '@napi-rs/simple-git';
import HTMLPlugin from 'html-webpack-plugin'; import HTMLPlugin from 'html-webpack-plugin';
import { once } from 'lodash-es'; import { once } from 'lodash-es';
import type { WebpackPluginInstance } from 'webpack';
type PluginLike = {
apply: (compiler: CompilerLike) => void;
};
type CompilerLike = { type CompilerLike = {
webpack?: { webpack?: {
@@ -204,12 +207,12 @@ const CorsPlugin = {
export function createHTMLPlugins( export function createHTMLPlugins(
BUILD_CONFIG: BUILD_CONFIG_TYPE, BUILD_CONFIG: BUILD_CONFIG_TYPE,
config: CreateHTMLPluginConfig config: CreateHTMLPluginConfig
): WebpackPluginInstance[] { ): (HTMLPlugin | PluginLike)[] {
const publicPath = getPublicPath(BUILD_CONFIG); const publicPath = getPublicPath(BUILD_CONFIG);
const htmlPluginOptions = getHTMLPluginOptions(BUILD_CONFIG); const htmlPluginOptions = getHTMLPluginOptions(BUILD_CONFIG);
const selfhostPublicPath = config.selfhostPublicPath ?? '/'; const selfhostPublicPath = config.selfhostPublicPath ?? '/';
const plugins: WebpackPluginInstance[] = []; const plugins: (HTMLPlugin | PluginLike)[] = [];
plugins.push( plugins.push(
new HTMLPlugin({ new HTMLPlugin({
...htmlPluginOptions, ...htmlPluginOptions,

View File

@@ -7,21 +7,51 @@ import { Package } from '@affine-tools/utils/workspace';
import rspack, { import rspack, {
type Configuration as RspackConfiguration, type Configuration as RspackConfiguration,
} from '@rspack/core'; } from '@rspack/core';
import { sentryWebpackPlugin } from '@sentry/webpack-plugin'; import type { sentryWebpackPlugin as SentryWebpackPluginFactory } from '@sentry/webpack-plugin';
import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin'; import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin';
import cssnano from 'cssnano'; import cssnano from 'cssnano';
import { compact, merge } from 'lodash-es'; import { compact, merge } from 'lodash-es';
import { queuedashScopePostcssPlugin } from '../postcss/queuedash-scope.js'; import { queuedashScopePostcssPlugin } from '../postcss/queuedash-scope.js';
import { productionCacheGroups } from '../webpack/cache-group.js'; import { productionCacheGroups } from '../rspack-shared/cache-group.js';
import { import {
type CreateHTMLPluginConfig, type CreateHTMLPluginConfig,
createHTMLPlugins as createWebpackCompatibleHTMLPlugins, createHTMLPlugins,
} from '../webpack/html-plugin.js'; } from '../rspack-shared/html-plugin.js';
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
const IN_CI = !!process.env.CI; const IN_CI = !!process.env.CI;
const hasSentryBuildEnvs = () =>
!!(
process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT
);
function createSentryPlugin() {
if (!hasSentryBuildEnvs()) {
return null;
}
try {
const { sentryWebpackPlugin } = require('@sentry/webpack-plugin') as {
sentryWebpackPlugin: typeof SentryWebpackPluginFactory;
};
return sentryWebpackPlugin({
org: process.env.SENTRY_ORG!,
project: process.env.SENTRY_PROJECT!,
authToken: process.env.SENTRY_AUTH_TOKEN!,
});
} catch (error) {
const reason =
error instanceof Error ? error.message : 'unknown load error';
throw new Error(
`Failed to load @sentry/webpack-plugin while SENTRY_* envs are set: ${reason}`
);
}
}
const availableChannels = ['canary', 'beta', 'stable', 'internal']; const availableChannels = ['canary', 'beta', 'stable', 'internal'];
function getBuildConfigFromEnv(pkg: Package) { function getBuildConfigFromEnv(pkg: Package) {
@@ -73,7 +103,7 @@ export function createHTMLTargetConfig(
console.log(`Config: ${JSON.stringify(buildConfig, null, 2)}`); console.log(`Config: ${JSON.stringify(buildConfig, null, 2)}`);
const config: RspackConfiguration = { const config: RspackConfiguration = {
//#region basic webpack config //#region basic bundler config
name: entry['index'], name: entry['index'],
dependencies: deps, dependencies: deps,
context: ProjectRoot.value, context: ProjectRoot.value,
@@ -253,7 +283,7 @@ export function createHTMLTargetConfig(
//#region plugins //#region plugins
plugins: compact([ plugins: compact([
!IN_CI && new rspack.ProgressPlugin(), !IN_CI && new rspack.ProgressPlugin(),
...createWebpackCompatibleHTMLPlugins(buildConfig, htmlConfig), ...createHTMLPlugins(buildConfig, htmlConfig),
new rspack.DefinePlugin({ new rspack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
...Object.entries(buildConfig).reduce( ...Object.entries(buildConfig).reduce(
@@ -280,14 +310,7 @@ export function createHTMLTargetConfig(
}, },
], ],
}), }),
process.env.SENTRY_AUTH_TOKEN && createSentryPlugin(),
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT &&
sentryWebpackPlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
// sourcemap url like # sourceMappingURL=76-6370cd185962bc89.js.map wont load in electron // sourcemap url like # sourceMappingURL=76-6370cd185962bc89.js.map wont load in electron
// this is because the default file:// protocol will be ignored by Chromium // this is because the default file:// protocol will be ignored by Chromium
// so we need to replace the sourceMappingURL to assets:// protocol // so we need to replace the sourceMappingURL to assets:// protocol
@@ -470,14 +493,7 @@ export function createWorkerTargetConfig(
) )
), ),
new rspack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }), new rspack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
process.env.SENTRY_AUTH_TOKEN && createSentryPlugin(),
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT &&
sentryWebpackPlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
]), ]),
stats: { errorDetails: true }, stats: { errorDetails: true },
optimization: { optimization: {
@@ -506,9 +522,18 @@ export function createWorkerTargetConfig(
export function createNodeTargetConfig( export function createNodeTargetConfig(
pkg: Package, pkg: Package,
entry: string entry: string,
options: {
outputFilename?: string;
decoratorVersion?: 'legacy' | '2022-03';
libraryType?: 'module' | 'commonjs2';
bundleAllDependencies?: boolean;
forceExternal?: string[];
} = {}
): Omit<RspackConfiguration, 'name'> & { name: string } { ): Omit<RspackConfiguration, 'name'> & { name: string } {
const dev = process.env.NODE_ENV === 'development'; const dev = process.env.NODE_ENV === 'development';
const useLegacyDecorator = options.decoratorVersion !== '2022-03';
const forceExternal = options.forceExternal ?? [];
return { return {
name: entry, name: entry,
context: ProjectRoot.value, context: ProjectRoot.value,
@@ -519,17 +544,28 @@ export function createNodeTargetConfig(
}, },
entry: { index: entry }, entry: { index: entry },
output: { output: {
filename: `main.js`, filename: options.outputFilename ?? 'main.js',
path: pkg.distPath.value, path: pkg.distPath.value,
clean: true, clean: true,
globalObject: 'globalThis', globalObject: 'globalThis',
...(options.libraryType
? { library: { type: options.libraryType } }
: {}),
}, },
target: ['node', 'es2022'], target: ['node', 'es2022'],
externals: ((data: any, callback: (err: null, value: boolean) => void) => { externals: ((data: any, callback: (err: null, value: boolean) => void) => {
if ( if (
data.request &&
forceExternal.some(
dep => data.request === dep || data.request.startsWith(`${dep}/`)
)
) {
callback(null, true);
} else if (
data.request && data.request &&
// import ... from 'module' // import ... from 'module'
/^[a-zA-Z@]/.test(data.request) && /^[a-zA-Z@]/.test(data.request) &&
!options.bundleAllDependencies &&
// not workspace deps // not workspace deps
!pkg.deps.some(dep => data.request!.startsWith(dep.name)) !pkg.deps.some(dep => data.request!.startsWith(dep.name))
) { ) {
@@ -561,8 +597,9 @@ export function createNodeTargetConfig(
}, },
{ {
test: /\.node$/, test: /\.node$/,
loader: Path.dir(import.meta.url).join('../webpack/node-loader.js') loader: Path.dir(import.meta.url).join(
.value, '../rspack-shared/node-loader.js'
).value,
}, },
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
@@ -582,8 +619,15 @@ export function createNodeTargetConfig(
target: 'es2022', target: 'es2022',
externalHelpers: false, externalHelpers: false,
transform: { transform: {
legacyDecorator: true, ...(useLegacyDecorator
decoratorMetadata: true, ? {
legacyDecorator: true,
decoratorMetadata: true,
}
: {
useDefineForClassFields: false,
decoratorVersion: '2022-03',
}),
react: { runtime: 'automatic' }, react: { runtime: 'automatic' },
}, },
}, },

View File

@@ -42,7 +42,7 @@ export class RunCommand extends PackageCommand {
\`affine init\` Generate the required files if there are any package added or removed \`affine init\` Generate the required files if there are any package added or removed
\`affine clean\` Clean the output files of ts, cargo, webpack, etc. \`affine clean\` Clean the output files of ts, cargo, bundler outputs, etc.
\`affine bundle\` Bundle the packages \`affine bundle\` Bundle the packages

View File

@@ -1,639 +0,0 @@
import { createRequire } from 'node:module';
import path from 'node:path';
import { getBuildConfig } from '@affine-tools/utils/build-config';
import { Path, ProjectRoot } from '@affine-tools/utils/path';
import { Package } from '@affine-tools/utils/workspace';
import { PerfseePlugin } from '@perfsee/webpack';
import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin';
import CopyPlugin from 'copy-webpack-plugin';
import { compact, merge } from 'lodash-es';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import TerserPlugin from 'terser-webpack-plugin';
import webpack from 'webpack';
import { queuedashScopePostcssPlugin } from '../postcss/queuedash-scope.js';
import { productionCacheGroups } from './cache-group.js';
import {
type CreateHTMLPluginConfig,
createHTMLPlugins,
} from './html-plugin.js';
const require = createRequire(import.meta.url);
const cssnano = require('cssnano');
const IN_CI = !!process.env.CI;
const availableChannels = ['canary', 'beta', 'stable', 'internal'];
function getBuildConfigFromEnv(pkg: Package) {
const channel = process.env.BUILD_TYPE ?? 'canary';
const dev = process.env.NODE_ENV === 'development';
if (!availableChannels.includes(channel)) {
throw new Error(
`BUILD_TYPE must be one of ${availableChannels.join(', ')}, received [${channel}]`
);
}
return getBuildConfig(pkg, {
// @ts-expect-error checked
channel,
mode: dev ? 'development' : 'production',
});
}
export function createHTMLTargetConfig(
pkg: Package,
entry: string | Record<string, string>,
htmlConfig: Partial<CreateHTMLPluginConfig> = {},
deps?: string[]
): webpack.Configuration {
entry = typeof entry === 'string' ? { index: entry } : entry;
htmlConfig = merge(
{},
{
filename: 'index.html',
additionalEntryForSelfhost: true,
injectGlobalErrorHandler: true,
emitAssetsManifest: true,
},
htmlConfig
);
const buildConfig = getBuildConfigFromEnv(pkg);
console.log(
`Building [${pkg.name}] for [${buildConfig.appBuildType}] channel in [${buildConfig.debug ? 'development' : 'production'}] mode.`
);
console.log(
`Entry points: ${Object.entries(entry)
.map(([name, path]) => `${name}: ${path}`)
.join(', ')}`
);
console.log(`Output path: ${pkg.distPath.value}`);
console.log(`Config: ${JSON.stringify(buildConfig, null, 2)}`);
const config: webpack.Configuration = {
//#region basic webpack config
name: entry['index'],
dependencies: deps,
context: ProjectRoot.value,
experiments: {
topLevelAwait: true,
outputModule: false,
syncWebAssembly: true,
},
entry,
output: {
environment: { module: true, dynamicImport: true },
filename: buildConfig.debug
? 'js/[name].js'
: 'js/[name].[contenthash:8].js',
assetModuleFilename: buildConfig.debug
? '[name].[contenthash:8][ext]'
: 'assets/[name].[contenthash:8][ext][query]',
path: pkg.distPath.value,
clean: false,
globalObject: 'globalThis',
// NOTE(@forehalo): always keep it '/'
publicPath: '/',
},
target: ['web', 'es2022'],
mode: buildConfig.debug ? 'development' : 'production',
devtool: buildConfig.debug ? 'cheap-module-source-map' : 'source-map',
resolve: {
symlinks: true,
extensionAlias: {
'.js': ['.js', '.tsx', '.ts'],
'.mjs': ['.mjs', '.mts'],
},
extensions: ['.js', '.ts', '.tsx'],
alias: {
yjs: ProjectRoot.join('node_modules', 'yjs').value,
lit: ProjectRoot.join('node_modules', 'lit').value,
'@preact/signals-core': ProjectRoot.join(
'node_modules',
'@preact',
'signals-core'
).value,
},
},
//#endregion
//#region module config
module: {
parser: {
javascript: {
// Do not mock Node.js globals
node: false,
requireJs: false,
import: true,
// Treat as missing export as error
strictExportPresence: true,
},
},
//#region rules
rules: [
{ test: /\.m?js?$/, resolve: { fullySpecified: false } },
{
test: /\.js$/,
enforce: 'pre',
include: /@blocksuite/,
use: ['source-map-loader'],
},
{
oneOf: [
{
test: /\.ts$/,
exclude: /node_modules/,
loader: 'swc-loader',
options: {
// https://swc.rs/docs/configuring-swc/
jsc: {
preserveAllComments: true,
parser: {
syntax: 'typescript',
dynamicImport: true,
topLevelAwait: false,
tsx: false,
decorators: true,
},
target: 'es2022',
externalHelpers: false,
transform: {
useDefineForClassFields: false,
decoratorVersion: '2022-03',
},
},
sourceMaps: true,
inlineSourcesContent: true,
},
},
{
test: /\.tsx$/,
exclude: /node_modules/,
loader: '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' },
useDefineForClassFields: false,
decoratorVersion: '2022-03',
},
},
sourceMaps: true,
inlineSourcesContent: true,
},
},
{
test: /\.(png|jpg|gif|svg|webp|mp4|zip)$/,
type: 'asset/resource',
},
{ test: /\.(ttf|eot|woff|woff2)$/, type: 'asset/resource' },
{ test: /\.txt$/, type: 'asset/source' },
{ test: /\.inline\.svg$/, type: 'asset/inline' },
{
test: /\.css$/,
use: [
buildConfig.debug
? 'style-loader'
: MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
url: true,
sourceMap: false,
modules: false,
import: true,
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: pkg.join('tailwind.config.js').exists()
? [
[
'@tailwindcss/postcss',
require(pkg.join('tailwind.config.js').value),
],
['autoprefixer'],
...(buildConfig.isAdmin
? [queuedashScopePostcssPlugin()]
: []),
]
: [
cssnano({
preset: ['default', { convertValues: false }],
}),
],
},
},
},
],
},
],
},
],
//#endregion
},
//#endregion
//#region plugins
plugins: compact([
!IN_CI && new webpack.ProgressPlugin({ percentBy: 'entries' }),
...createHTMLPlugins(buildConfig, htmlConfig),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
...Object.entries(buildConfig).reduce(
(def, [k, v]) => {
def[`BUILD_CONFIG.${k}`] = JSON.stringify(v);
return def;
},
{} as Record<string, string>
),
}),
!buildConfig.debug &&
// todo: support multiple entry points
new MiniCssExtractPlugin({
filename: `[name].[contenthash:8].css`,
ignoreOrder: true,
}),
new VanillaExtractPlugin(),
!buildConfig.isAdmin &&
new CopyPlugin({
patterns: [
{
// copy the shared public assets into dist
from: new Package('@affine/core').join('public').value,
},
],
}),
!buildConfig.debug &&
process.env.PERFSEE_TOKEN &&
new PerfseePlugin({ project: 'affine-toeverything' }),
process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT &&
sentryWebpackPlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
// sourcemap url like # sourceMappingURL=76-6370cd185962bc89.js.map wont load in electron
// this is because the default file:// protocol will be ignored by Chromium
// so we need to replace the sourceMappingURL to assets:// protocol
// for example:
// replace # sourceMappingURL=76-6370cd185962bc89.js.map
// to # sourceMappingURL=assets://./{dir}/76-6370cd185962bc89.js.map
buildConfig.isElectron &&
new webpack.SourceMapDevToolPlugin({
append: pathData => {
return `\n//# sourceMappingURL=assets://./${pathData.filename}.map`;
},
filename: '[file].map',
}),
]),
//#endregion
stats: { errorDetails: true },
//#region optimization
optimization: {
minimize: !buildConfig.debug,
minimizer: [
new TerserPlugin({
minify: TerserPlugin.swcMinify,
parallel: true,
extractComments: true,
terserOptions: {
ecma: 2020,
compress: { unused: true },
mangle: { keep_classnames: 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: productionCacheGroups,
},
},
//#endregion
};
if (buildConfig.debug && !IN_CI) {
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,
},
},
},
};
}
return config;
}
export function createWorkerTargetConfig(
pkg: Package,
entry: string
): Omit<webpack.Configuration, 'name'> & { name: string } {
const workerName = path.basename(entry).replace(/\.worker\.ts$/, '');
const buildConfig = getBuildConfigFromEnv(pkg);
return {
name: entry,
context: ProjectRoot.value,
experiments: {
topLevelAwait: true,
outputModule: false,
syncWebAssembly: true,
},
entry: { [workerName]: entry },
output: {
filename: `js/${workerName}-${buildConfig.appVersion}.worker.js`,
path: pkg.distPath.value,
clean: false,
globalObject: 'globalThis',
// NOTE(@forehalo): always keep it '/'
publicPath: '/',
},
target: ['webworker', 'es2022'],
mode: buildConfig.debug ? 'development' : 'production',
devtool: buildConfig.debug ? 'cheap-module-source-map' : 'source-map',
resolve: {
symlinks: true,
extensionAlias: { '.js': ['.js', '.ts'], '.mjs': ['.mjs', '.mts'] },
extensions: ['.js', '.ts'],
alias: { yjs: ProjectRoot.join('node_modules', 'yjs').value },
},
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: /\.ts$/,
exclude: /node_modules/,
loader: 'swc-loader',
options: {
// https://swc.rs/docs/configuring-swc/
jsc: {
preserveAllComments: true,
parser: {
syntax: 'typescript',
dynamicImport: true,
topLevelAwait: false,
tsx: false,
decorators: true,
},
target: 'es2022',
externalHelpers: false,
transform: {
useDefineForClassFields: false,
decoratorVersion: '2022-03',
},
},
sourceMaps: true,
inlineSourcesContent: true,
},
},
],
},
],
},
plugins: compact([
new webpack.DefinePlugin(
Object.entries(buildConfig).reduce(
(def, [k, v]) => {
def[`BUILD_CONFIG.${k}`] = JSON.stringify(v);
return def;
},
{} as Record<string, string>
)
),
new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT &&
sentryWebpackPlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
]),
stats: { errorDetails: true },
optimization: {
minimize: !buildConfig.debug,
minimizer: [
new TerserPlugin({
minify: TerserPlugin.swcMinify,
parallel: true,
extractComments: true,
terserOptions: {
ecma: 2020,
compress: { unused: true },
mangle: { keep_classnames: true },
},
}),
],
removeEmptyChunks: true,
providedExports: true,
usedExports: true,
sideEffects: true,
removeAvailableModules: true,
runtimeChunk: false,
splitChunks: false,
},
performance: { hints: false },
};
}
export function createNodeTargetConfig(
pkg: Package,
entry: string
): Omit<webpack.Configuration, 'name'> & { name: string } {
const dev = process.env.NODE_ENV === 'development';
return {
name: entry,
context: ProjectRoot.value,
experiments: {
topLevelAwait: true,
outputModule: pkg.packageJson.type === 'module',
syncWebAssembly: true,
},
entry: { index: entry },
output: {
filename: `main.js`,
path: pkg.distPath.value,
clean: true,
globalObject: 'globalThis',
},
target: ['node', 'es2022'],
externals: (data, callback) => {
if (
data.request &&
// import ... from 'module'
/^[a-zA-Z@]/.test(data.request) &&
// not workspace deps
!pkg.deps.some(dep => data.request!.startsWith(dep.name))
) {
callback(null, true);
} else {
callback(null, false);
}
},
externalsPresets: { node: true },
node: { __dirname: false, __filename: false },
mode: dev ? 'development' : 'production',
devtool: 'source-map',
resolve: {
symlinks: true,
extensionAlias: { '.js': ['.js', '.ts'], '.mjs': ['.mjs', '.mts'] },
extensions: ['.js', '.ts', '.tsx', '.node'],
alias: { yjs: ProjectRoot.join('node_modules', 'yjs').value },
},
module: {
parser: {
javascript: { url: false, importMeta: false, createRequire: false },
},
rules: [
{
test: /\.js$/,
enforce: 'pre',
include: /@blocksuite/,
use: ['source-map-loader'],
},
{
test: /\.node$/,
loader: Path.dir(import.meta.url).join('node-loader.js').value,
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
loader: 'swc-loader',
options: {
// https://swc.rs/docs/configuring-swc/
jsc: {
preserveAllComments: true,
parser: {
syntax: 'typescript',
dynamicImport: true,
topLevelAwait: true,
tsx: true,
decorators: true,
},
target: 'es2022',
externalHelpers: false,
transform: {
legacyDecorator: true,
decoratorMetadata: true,
react: { runtime: 'automatic' },
},
},
sourceMaps: true,
inlineSourcesContent: true,
},
},
],
},
plugins: compact([
new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
new webpack.IgnorePlugin({
checkResource(resource) {
const lazyImports = [
'@nestjs/microservices',
'@nestjs/websockets/socket-module',
'@apollo/subgraph',
'@apollo/gateway',
'@as-integrations/fastify',
'ts-morph',
'class-validator',
'class-transformer',
];
return lazyImports.some(lazyImport =>
resource.startsWith(lazyImport)
);
},
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"',
}),
]),
stats: { errorDetails: true },
optimization: {
nodeEnv: false,
minimize: !dev,
minimizer: [
new TerserPlugin({
minify: TerserPlugin.swcMinify,
parallel: true,
extractComments: true,
terserOptions: {
ecma: 2020,
compress: { unused: true },
mangle: { keep_classnames: true },
},
}),
],
},
performance: { hints: false },
ignoreWarnings: [/^(?!CriticalDependenciesWarning$)/],
};
}

View File

@@ -1,4 +0,0 @@
export interface BuildFlags {
mode: 'development' | 'production';
channel: 'stable' | 'beta' | 'canary' | 'internal';
}

View File

@@ -1,7 +1,7 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"types": ["build-config", "affine__env", "webpack-env"], "types": ["build-config", "affine__env"],
"lib": ["ESNext", "DOM", "DOM.Iterable"], "lib": ["ESNext", "DOM", "DOM.Iterable"],
"jsx": "react-jsx", "jsx": "react-jsx",
"composite": true "composite": true

1387
yarn.lock

File diff suppressed because it is too large Load Diff