build: allow node package depends on workspace packages (#11963)

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

- **New Features**
  - Added a unified CLI entry point for server operations and introduced a new CLI executable alias.
  - Centralized and simplified server startup, allowing selection between CLI and server modes.
  - Added static migration module aggregation for easier migration management.

- **Bug Fixes**
  - Improved platform-specific native module loading for better compatibility and reliability.

- **Refactor**
  - Streamlined server build, startup, and artifact management processes.
  - Reorganized and simplified workflow and configuration files for backend services.
  - Transitioned export styles and import mechanisms for native modules to enhance maintainability.

- **Chores**
  - Removed unused dependencies and configuration files.
  - Updated test cases to reflect refined server flavor logic.
  - Adjusted package and build configurations for consistency and clarity.

- **Revert**
  - Removed legacy scripts and loaders no longer needed after refactor.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
forehalo
2025-04-24 10:36:51 +00:00
parent 4ffa37d1c3
commit f4ffdb9995
31 changed files with 331 additions and 332 deletions
+16 -3
View File
@@ -10,12 +10,16 @@ import WebpackDevServer, {
} from 'webpack-dev-server';
import { Option, PackageCommand } from './command';
import { createHTMLTargetConfig, createWorkerTargetConfig } from './webpack';
import {
createHTMLTargetConfig,
createNodeTargetConfig,
createWorkerTargetConfig,
} from './webpack';
function getBundleConfigs(pkg: Package) {
function getBaseWorkerConfigs(pkg: Package) {
const core = new Package('@affine/core');
const workerConfigs = [
return [
createWorkerTargetConfig(
pkg,
core.srcPath.join(
@@ -31,7 +35,9 @@ function getBundleConfigs(pkg: Package) {
core.srcPath.join('blocksuite/extensions/turbo-painter.worker.ts').value
),
];
}
function getBundleConfigs(pkg: Package) {
switch (pkg.name) {
case '@affine/admin': {
return [createHTMLTargetConfig(pkg, pkg.srcPath.join('index.tsx').value)];
@@ -40,6 +46,7 @@ function getBundleConfigs(pkg: Package) {
case '@affine/mobile':
case '@affine/ios':
case '@affine/android': {
const workerConfigs = getBaseWorkerConfigs(pkg);
workerConfigs.push(
createWorkerTargetConfig(
pkg,
@@ -58,6 +65,8 @@ function getBundleConfigs(pkg: Package) {
];
}
case '@affine/electron-renderer': {
const workerConfigs = getBaseWorkerConfigs(pkg);
return [
createHTMLTargetConfig(
pkg,
@@ -78,10 +87,14 @@ function getBundleConfigs(pkg: Package) {
...workerConfigs,
];
}
case '@affine/server': {
return [createNodeTargetConfig(pkg, pkg.srcPath.join('index.ts').value)];
}
}
throw new Error(`Unsupported package: ${pkg.name}`);
}
const IN_CI = !!process.env.CI;
const httpProxyMiddlewareLogLevel = IN_CI ? 'silent' : 'error';
+137 -83
View File
@@ -2,7 +2,7 @@ import { createRequire } from 'node:module';
import path from 'node:path';
import { getBuildConfig } from '@affine-tools/utils/build-config';
import { ProjectRoot } from '@affine-tools/utils/path';
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';
@@ -75,10 +75,7 @@ export function createHTMLTargetConfig(
},
entry,
output: {
environment: {
module: true,
dynamicImport: true,
},
environment: { module: true, dynamicImport: true },
filename: buildConfig.debug
? 'js/[name].js'
: 'js/[name].[contenthash:8].js',
@@ -127,12 +124,7 @@ export function createHTMLTargetConfig(
},
//#region rules
rules: [
{
test: /\.m?js?$/,
resolve: {
fullySpecified: false,
},
},
{ test: /\.m?js?$/, resolve: { fullySpecified: false } },
{
test: /\.js$/,
enforce: 'pre',
@@ -185,9 +177,7 @@ export function createHTMLTargetConfig(
target: 'es2022',
externalHelpers: false,
transform: {
react: {
runtime: 'automatic',
},
react: { runtime: 'automatic' },
useDefineForClassFields: false,
decoratorVersion: '2022-03',
},
@@ -200,18 +190,9 @@ export function createHTMLTargetConfig(
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: /\.(ttf|eot|woff|woff2)$/, type: 'asset/resource' },
{ test: /\.txt$/, type: 'asset/source' },
{ test: /\.inline\.svg$/, type: 'asset/inline' },
{
test: /\.css$/,
use: [
@@ -242,12 +223,7 @@ export function createHTMLTargetConfig(
]
: [
cssnano({
preset: [
'default',
{
convertValues: false,
},
],
preset: ['default', { convertValues: false }],
}),
],
},
@@ -298,9 +274,7 @@ export function createHTMLTargetConfig(
new WebpackS3Plugin(),
!buildConfig.debug &&
process.env.PERFSEE_TOKEN &&
new PerfseePlugin({
project: 'affine-toeverything',
}),
new PerfseePlugin({ project: 'affine-toeverything' }),
process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT &&
@@ -325,9 +299,7 @@ export function createHTMLTargetConfig(
]),
//#endregion
stats: {
errorDetails: true,
},
stats: { errorDetails: true },
//#region optimization
optimization: {
@@ -339,12 +311,8 @@ export function createHTMLTargetConfig(
extractComments: true,
terserOptions: {
ecma: 2020,
compress: {
unused: true,
},
mangle: {
keep_classnames: true,
},
compress: { unused: true },
mangle: { keep_classnames: true },
},
}),
],
@@ -353,9 +321,7 @@ export function createHTMLTargetConfig(
usedExports: true,
sideEffects: true,
removeAvailableModules: true,
runtimeChunk: {
name: 'runtime',
},
runtimeChunk: { name: 'runtime' },
splitChunks: {
chunks: 'all',
minSize: 1,
@@ -382,11 +348,7 @@ export function createHTMLTargetConfig(
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
default: { minChunks: 2, priority: -20, reuseExistingChunk: true },
styles: {
name: 'styles',
type: 'css/mini-extract',
@@ -416,9 +378,7 @@ export function createWorkerTargetConfig(
outputModule: false,
syncWebAssembly: true,
},
entry: {
[workerName]: entry,
},
entry: { [workerName]: entry },
output: {
filename: `js/${workerName}-${buildConfig.appVersion}.worker.js`,
path: pkg.distPath.value,
@@ -432,14 +392,9 @@ export function createWorkerTargetConfig(
devtool: buildConfig.debug ? 'cheap-module-source-map' : 'source-map',
resolve: {
symlinks: true,
extensionAlias: {
'.js': ['.js', '.ts'],
'.mjs': ['.mjs', '.mts'],
},
extensionAlias: { '.js': ['.js', '.ts'], '.mjs': ['.mjs', '.mts'] },
extensions: ['.js', '.ts'],
alias: {
yjs: ProjectRoot.join('node_modules', 'yjs').value,
},
alias: { yjs: ProjectRoot.join('node_modules', 'yjs').value },
},
module: {
@@ -454,12 +409,7 @@ export function createWorkerTargetConfig(
},
},
rules: [
{
test: /\.m?js?$/,
resolve: {
fullySpecified: false,
},
},
{ test: /\.m?js?$/, resolve: { fullySpecified: false } },
{
test: /\.js$/,
enforce: 'pre',
@@ -508,9 +458,7 @@ export function createWorkerTargetConfig(
{} as Record<string, string>
)
),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT &&
@@ -520,9 +468,7 @@ export function createWorkerTargetConfig(
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
]),
stats: {
errorDetails: true,
},
stats: { errorDetails: true },
optimization: {
minimize: !buildConfig.debug,
minimizer: [
@@ -532,12 +478,8 @@ export function createWorkerTargetConfig(
extractComments: true,
terserOptions: {
ecma: 2020,
compress: {
unused: true,
},
mangle: {
keep_classnames: true,
},
compress: { unused: true },
mangle: { keep_classnames: true },
},
}),
],
@@ -549,8 +491,120 @@ export function createWorkerTargetConfig(
runtimeChunk: false,
splitChunks: false,
},
performance: {
hints: false,
},
performance: { hints: false },
};
}
export function createNodeTargetConfig(
pkg: Package,
entry: string
): Omit<webpack.Configuration, 'name'> & { name: string } {
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: 'none',
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)
);
},
}),
]),
stats: { errorDetails: true },
optimization: { nodeEnv: false },
performance: { hints: false },
ignoreWarnings: [/^(?!CriticalDependenciesWarning$)/],
};
}
+18
View File
@@ -0,0 +1,18 @@
import { parse } from 'node:path';
export const raw = true;
/**
* @type {import('webpack').LoaderDefinitionFunction}
*/
export default function loader(content) {
const name = parse(this.resourcePath).base;
this.emitFile(name, content);
return `
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
const binding = require('./${name}')
export default binding
`;
}