From 4b4400068d5711cc1fc95cd860d702b08b4024cd Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Fri, 19 Jul 2024 16:35:38 +0800 Subject: [PATCH 001/369] chore: update newServer --- biome.json | 21 +- package.json | 2 + packages/core/package.json | 18 +- packages/core/src/newServer/hmr.ts | 46 ++ packages/core/src/newServer/http.ts | 139 ++++++ packages/core/src/newServer/index.ts | 133 ++++++ .../core/src/newServer/middlewares/cors.ts | 0 .../core/src/newServer/middlewares/error.ts | 0 .../core/src/newServer/middlewares/index.ts | 0 .../newServer/middlewares/lazy-compilation.ts | 0 .../src/newServer/middlewares/notFound.ts | 0 .../core/src/newServer/middlewares/proxy.ts | 15 + .../src/newServer/middlewares/resource.ts | 0 .../core/src/newServer/middlewares/static.ts | 0 packages/core/src/newServer/publicDir.ts | 35 ++ packages/core/src/newServer/type.ts | 100 +++++ packages/core/src/newServer/ws.ts | 196 +++++++++ packages/core/tsconfig.build.json | 5 +- pnpm-lock.yaml | 416 ++++++++++++------ 19 files changed, 976 insertions(+), 150 deletions(-) create mode 100644 packages/core/src/newServer/hmr.ts create mode 100644 packages/core/src/newServer/http.ts create mode 100644 packages/core/src/newServer/index.ts create mode 100644 packages/core/src/newServer/middlewares/cors.ts create mode 100644 packages/core/src/newServer/middlewares/error.ts create mode 100644 packages/core/src/newServer/middlewares/index.ts create mode 100644 packages/core/src/newServer/middlewares/lazy-compilation.ts create mode 100644 packages/core/src/newServer/middlewares/notFound.ts create mode 100644 packages/core/src/newServer/middlewares/proxy.ts create mode 100644 packages/core/src/newServer/middlewares/resource.ts create mode 100644 packages/core/src/newServer/middlewares/static.ts create mode 100644 packages/core/src/newServer/publicDir.ts create mode 100644 packages/core/src/newServer/type.ts create mode 100644 packages/core/src/newServer/ws.ts diff --git a/biome.json b/biome.json index a400607a47..158af3d1a4 100644 --- a/biome.json +++ b/biome.json @@ -30,28 +30,19 @@ "lineEnding": "lf", "lineWidth": 80, "attributePosition": "auto", - "include": [ - "**/*.ts" - ], - "ignore": [ - "node_modules", - "dist" - ] + "include": ["**/*.ts"], + "ignore": ["node_modules", "dist"] }, "organizeImports": { "enabled": true, - "include": [ - "./**/*.js", - "./**/*.ts", - "./**/*.mjs" - ] + "include": ["./**/*.js", "./**/*.ts", "./**/*.mjs"] }, "linter": { "enabled": true, "rules": { "recommended": false, "complexity": { - "noBannedTypes": "error", + "noBannedTypes": "off", "noExtraBooleanCast": "error", "noMultipleSpacesInRegularExpressionLiterals": "error", "noUselessCatch": "error", @@ -78,7 +69,7 @@ "noUnsafeFinally": "error", "noUnsafeOptionalChaining": "error", "noUnusedLabels": "error", - "noUnusedVariables": "warn", + "noUnusedVariables": "off", "useIsNan": "error", "useValidForDirection": "error", "useYield": "error" @@ -140,4 +131,4 @@ "attributePosition": "auto" } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 4b845d7bfd..4f609baf6e 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,8 @@ "prepare": "husky" }, "devDependencies": { + "@farmfe/core": "workspace:*", + "@farmfe/cli": "workspace:*", "@biomejs/biome": "1.7.2", "@changesets/cli": "^2.26.0", "@commitlint/cli": "^17.0.3", diff --git a/packages/core/package.json b/packages/core/package.json index 156bce1faa..820e2c7370 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -69,16 +69,25 @@ }, "devDependencies": { "@napi-rs/cli": "^2.18.4", + "@types/connect": "^3.4.38", "@types/figlet": "^1.5.5", "@types/fs-extra": "^11.0.1", + "@types/http-proxy": "^1.17.14", "@types/koa": "^2.13.5", "@types/koa-compress": "^4.0.3", "@types/koa-static": "^4.0.2", "@types/koa__cors": "^5.0.0", "@types/lodash.debounce": "^4.0.7", "@types/mime-types": "^2.1.2", - "@types/ws": "^8.5.4", - "react-refresh": "^0.14.0" + "@types/ws": "^8.5.8", + "connect": "^3.7.0", + "cors": "^2.8.5", + "debug": "^4.3.5", + "etag": "^1.8.1", + "http-proxy": "^1.18.1", + "react-refresh": "^0.14.0", + "sirv": "^2.0.3", + "ws": "^8.14.2" }, "dependencies": { "@farmfe/runtime": "workspace:0.12.1", @@ -91,7 +100,7 @@ "deepmerge": "^4.3.1", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", - "execa": "^7.1.1", + "execa": "9.3.0", "farm-browserslist-generator": "^1.0.0", "farm-plugin-replace-dirname": "0.2.1", "fast-glob": "^3.3.2", @@ -105,9 +114,8 @@ "lodash.debounce": "^4.0.8", "loglevel": "^1.8.1", "mime-types": "^2.1.35", - "open": "^9.1.0", + "open": "10.1.0", "slashes": "^3.0.12", - "ws": "^8.12.0", "zod": "^3.23.8", "zod-validation-error": "^3.3.0" }, diff --git a/packages/core/src/newServer/hmr.ts b/packages/core/src/newServer/hmr.ts new file mode 100644 index 0000000000..1a5eda65b3 --- /dev/null +++ b/packages/core/src/newServer/hmr.ts @@ -0,0 +1,46 @@ +import { + HMRBroadcasterClient, + HMRPayload, + InferCustomEventPayload +} from './type.js'; + +export interface HMRChannel { + /** + * Unique channel name + */ + name: string; + /** + * Broadcast events to all clients + */ + send(payload: HMRPayload): void; + /** + * Send custom event + */ + send(event: T, payload?: InferCustomEventPayload): void; + /** + * Handle custom event emitted by `import.meta.hot.send` + */ + on( + event: T, + listener: ( + data: InferCustomEventPayload, + client: HMRBroadcasterClient, + ...args: any[] + ) => void + ): void; + on(event: 'connection', listener: () => void): void; + /** + * Unregister event listener + */ + + // biome-ignore lint/complexity/noBannedTypes: + off(event: string, listener: Function): void; + /** + * Start listening for messages + */ + listen(): void; + /** + * Disconnect all clients, called when server is closed or restarted. + */ + close(): void; +} diff --git a/packages/core/src/newServer/http.ts b/packages/core/src/newServer/http.ts new file mode 100644 index 0000000000..a8f55ac02c --- /dev/null +++ b/packages/core/src/newServer/http.ts @@ -0,0 +1,139 @@ +/** + * The following is modified based on source found in + * https://github.com/vitejs/vite/blob/main/packages/vite/src/node/env.ts + * + * MIT License + * Copyright (c) 2019-present, Yuxi (Evan) + * https://github.com/vitejs/vite/blob/main/LICENSE + * + * Farm draws on the code of part of the vite server in order to better achieve the compatibility + * progress of the vite ecosystem and the integrity of vite's ecological development, + * which can reduce many unknown or known problems. + */ + +import type { OutgoingHttpHeaders as HttpServerHeaders } from 'node:http'; +import type { ServerOptions as HttpsServerOptions } from 'node:https'; +import path from 'node:path'; +import connect from 'connect'; +import fse from 'fs-extra'; +import { Logger } from '../utils/logger.js'; +import { HttpServer } from './index.js'; +import { ProxyOptions } from './middlewares/proxy.js'; + +export interface CommonServerOptions { + port?: number; + strictPort?: boolean; + host?: string | boolean; + https?: HttpsServerOptions; + open?: boolean | string; + proxy?: Record; + cors?: CorsOptions | boolean; + headers?: HttpServerHeaders; +} + +export type CorsOrigin = boolean | string | RegExp | (string | RegExp)[]; + +export interface CorsOptions { + origin?: + | CorsOrigin + | ((origin: string, cb: (err: Error, origins: CorsOrigin) => void) => void); + methods?: string | string[]; + allowedHeaders?: string | string[]; + exposedHeaders?: string | string[]; + credentials?: boolean; + maxAge?: number; + preflightContinue?: boolean; + optionsSuccessStatus?: number; +} + +// For the unencrypted tls protocol, we use http service. +// In other cases, https / http2 is used. +export async function resolveHttpServer( + { proxy }: CommonServerOptions, + app: connect.Server, + httpsOptions?: HttpsServerOptions +): Promise { + if (!httpsOptions) { + const { createServer } = await import('node:http'); + return createServer(app); + } + + // EXISTING PROBLEM: + // https://github.com/http-party/node-http-proxy/issues/1237 + + // MAYBE SOLUTION: + // https://github.com/nxtedition/node-http2-proxy + // https://github.com/fastify/fastify-http-proxy + if (proxy) { + const { createServer } = await import('node:https'); + return createServer(httpsOptions, app); + } else { + const { createSecureServer } = await import('node:http2'); + return createSecureServer( + { + maxSessionMemory: 1000, + ...httpsOptions, + allowHTTP1: true + }, + // @ts-ignore + app + ); + } +} + +export async function resolveHttpsConfig( + https: HttpsServerOptions | undefined +): Promise { + if (!https) return undefined; + + const [ca, cert, key, pfx] = await Promise.all([ + readFileIfExists(https.ca), + readFileIfExists(https.cert), + readFileIfExists(https.key), + readFileIfExists(https.pfx) + ]); + return { ...https, ca, cert, key, pfx }; +} + +async function readFileIfExists(value?: string | Buffer | any[]) { + if (typeof value === 'string') { + return fse.readFile(path.resolve(value)).catch(() => value); + } + return value; +} + +export async function httpServerStart( + httpServer: HttpServer, + serverOptions: { + port: number; + strictPort: boolean | undefined; + host: string | undefined; + logger: Logger; + } +): Promise { + let { port, strictPort, host, logger } = serverOptions; + + return new Promise((resolve, reject) => { + const onError = (e: Error & { code?: string }) => { + if (e.code === 'EADDRINUSE') { + if (strictPort) { + httpServer.removeListener('error', onError); + reject(new Error(`Port ${port} is already in use`)); + } else { + logger.info(`Port ${port} is in use, trying another one...`); + httpServer.listen(++port, host); + } + } else { + httpServer.removeListener('error', onError); + reject(e); + } + }; + + httpServer.on('error', onError); + + httpServer.listen(port, host, () => { + httpServer.removeListener('error', onError); + resolve(port); + }); + }); +} diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts new file mode 100644 index 0000000000..82bf2e29e5 --- /dev/null +++ b/packages/core/src/newServer/index.ts @@ -0,0 +1,133 @@ +import type * as http from 'node:http'; +import type { Server } from 'node:http'; +import type { OutgoingHttpHeaders as HttpServerHeaders } from 'node:http'; +import { type Http2SecureServer } from 'node:http2'; +import type { ServerOptions as HttpsServerOptions } from 'node:https'; +import path from 'node:path'; +import { WatchOptions } from 'chokidar'; +import connect from 'connect'; +import fse from 'fs-extra'; +import { Compiler } from '../compiler/index.js'; +import { normalizePublicPath } from '../config/normalize-config/normalize-output.js'; +import { NormalizedServerConfig, ResolvedUserConfig } from '../config/types.js'; +import { logger } from '../utils/logger.js'; +import { initPublicFiles } from '../utils/publicDir.js'; +import { FileWatcher } from '../watcher/index.js'; +import { HMRChannel } from './hmr.js'; +import { + CommonServerOptions, + resolveHttpServer, + resolveHttpsConfig +} from './http.js'; +import { WsServer } from './ws.js'; +export type HttpServer = http.Server | Http2SecureServer; + +type CompilerType = Compiler | null; + +export interface HmrOptions { + protocol?: string; + host?: string; + port?: number; + clientPort?: number; + path?: string; + timeout?: number; + overlay?: boolean; + server?: Server; + /** @internal */ + channels?: HMRChannel[]; +} + +export interface ServerOptions extends CommonServerOptions { + /** + * Configure HMR-specific options (port, host, path & protocol) + */ + hmr?: HmrOptions | boolean; + /** + * Do not start the websocket connection. + * @experimental + */ + ws?: false; + /** + * chokidar watch options or null to disable FS watching + * https://github.com/paulmillr/chokidar#api + */ + watchOptions?: WatchOptions | null; + /** + * Create dev server to be used as a middleware in an existing server + * @default false + */ + middlewareMode?: + | boolean + | { + /** + * Parent server instance to attach to + * + * This is needed to proxy WebSocket connections to the parent server. + */ + server: http.Server; + }; + origin?: string; +} +export class newServer { + private compiler: CompilerType; + + ws: WsServer; + config: ResolvedUserConfig; + serverConfig: CommonServerOptions & NormalizedServerConfig; + httpsOptions: HttpsServerOptions; + publicDir?: string; + publicPath?: string; + server?: HttpServer; + watcher: FileWatcher; + + constructor(compiler: CompilerType, config: ResolvedUserConfig) { + this.compiler = compiler; + this.config = config; + + if (!this.compiler) return; + + this.publicPath = + normalizePublicPath( + compiler.config.config.output.targetEnv, + compiler.config.config.output.publicPath, + logger, + false + ) || '/'; + } + + getCompiler(): CompilerType { + return this.compiler; + } + + async createServer() { + const initPublicFilesPromise = initPublicFiles(this.config); + const { root, server: serverConfig } = this.config; + this.httpsOptions = await resolveHttpsConfig(serverConfig.https); + const { middlewareMode } = serverConfig; + const middlewares = connect() as connect.Server; + this.server = middlewareMode + ? null + : await resolveHttpServer( + serverConfig as CommonServerOptions, + middlewares, + this.httpsOptions + ); + + const publicFiles = await initPublicFilesPromise; + const { publicDir } = this.config.compilation.assets; + this.createWebSocketServer(); + } + + public async createWebSocketServer() { + if (!this.server) { + throw new Error('Websocket requires a server.'); + } + const wsServer = new WsServer( + this.server, + this.config, + this.httpsOptions, + this.publicPath, + null + ); + } +} diff --git a/packages/core/src/newServer/middlewares/cors.ts b/packages/core/src/newServer/middlewares/cors.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/newServer/middlewares/error.ts b/packages/core/src/newServer/middlewares/error.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/newServer/middlewares/index.ts b/packages/core/src/newServer/middlewares/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/newServer/middlewares/lazy-compilation.ts b/packages/core/src/newServer/middlewares/lazy-compilation.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/newServer/middlewares/notFound.ts b/packages/core/src/newServer/middlewares/notFound.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/newServer/middlewares/proxy.ts b/packages/core/src/newServer/middlewares/proxy.ts new file mode 100644 index 0000000000..9c24cace87 --- /dev/null +++ b/packages/core/src/newServer/middlewares/proxy.ts @@ -0,0 +1,15 @@ +import type * as http from 'node:http'; +// import type * as net from 'node:net'; +// import connect from 'connect'; +import httpProxy from 'http-proxy'; + +export interface ProxyOptions extends httpProxy.ServerOptions { + rewrite?: (path: string) => string; + configure?: (proxy: httpProxy, options: ProxyOptions) => void; + bypass?: ( + req: http.IncomingMessage, + res: http.ServerResponse, + options: ProxyOptions + ) => void | null | undefined | false | string; + rewriteWsOrigin?: boolean | undefined; +} diff --git a/packages/core/src/newServer/middlewares/resource.ts b/packages/core/src/newServer/middlewares/resource.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/newServer/middlewares/static.ts b/packages/core/src/newServer/middlewares/static.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/newServer/publicDir.ts b/packages/core/src/newServer/publicDir.ts new file mode 100644 index 0000000000..9b6725ce51 --- /dev/null +++ b/packages/core/src/newServer/publicDir.ts @@ -0,0 +1,35 @@ +import { ResolvedUserConfig } from '../config/types.js'; +import { recursiveReaddir } from '../utils/index.js'; + +export const ERR_SYMLINK_IN_RECURSIVE_READDIR = + 'ERR_SYMLINK_IN_RECURSIVE_READDIR'; + +const publicFilesMap = new WeakMap>(); + +export async function initPublicFiles( + config: ResolvedUserConfig +): Promise | undefined> { + let fileNames: string[]; + const publicDir: string = config.compilation?.assets?.publicDir as string; + console.log(publicDir); + + try { + fileNames = await recursiveReaddir(publicDir); + } catch (e) { + if (e.code === ERR_SYMLINK_IN_RECURSIVE_READDIR) { + return; + } + throw e; + } + const publicFiles = new Set( + fileNames.map((fileName) => fileName.slice(publicDir.length)) + ); + publicFilesMap.set(config, publicFiles); + return publicFiles; +} + +export function getPublicFiles( + config: ResolvedUserConfig +): Set | undefined { + return publicFilesMap.get(config); +} diff --git a/packages/core/src/newServer/type.ts b/packages/core/src/newServer/type.ts new file mode 100644 index 0000000000..1fe2aaf024 --- /dev/null +++ b/packages/core/src/newServer/type.ts @@ -0,0 +1,100 @@ +export type HMRPayload = + | ConnectedPayload + | UpdatePayload + | FullReloadPayload + | CustomPayload + | ErrorPayload + | PrunePayload + +export interface ConnectedPayload { + type: 'connected' +} + +export interface UpdatePayload { + type: 'update' + updates: Update[] +} + +export interface Update { + type: 'js-update' | 'css-update' + path: string + acceptedPath: string + timestamp: number + /** @internal */ + explicitImportRequired?: boolean + /** @internal */ + isWithinCircularImport?: boolean + /** @internal */ + ssrInvalidates?: string[] +} + +export interface PrunePayload { + type: 'prune' + paths: string[] +} + +export interface FullReloadPayload { + type: 'full-reload' + path?: string + /** @internal */ + triggeredBy?: string +} + +export interface CustomPayload { + type: 'custom' + event: string + data?: any +} + +export interface ErrorPayload { + type: 'error' + err: { + [name: string]: any + message: string + stack: string + id?: string + frame?: string + plugin?: string + pluginCode?: string + loc?: { + file?: string + line: number + column: number + } + } +} + +export interface CustomEventMap { + 'vite:beforeUpdate': UpdatePayload + 'vite:afterUpdate': UpdatePayload + 'vite:beforePrune': PrunePayload + 'vite:beforeFullReload': FullReloadPayload + 'vite:error': ErrorPayload + 'vite:invalidate': InvalidatePayload + 'vite:ws:connect': WebSocketConnectionPayload + 'vite:ws:disconnect': WebSocketConnectionPayload +} + +export interface WebSocketConnectionPayload { + webSocket: WebSocket +} + +export interface InvalidatePayload { + path: string + message: string | undefined +} + +export type InferCustomEventPayload = + T extends keyof CustomEventMap ? CustomEventMap[T] : any + + +export interface HMRBroadcasterClient { + /** + * Send event to the client + */ + send(payload: HMRPayload): void + /** + * Send custom event + */ + send(event: string, payload?: CustomPayload['data']): void +} diff --git a/packages/core/src/newServer/ws.ts b/packages/core/src/newServer/ws.ts new file mode 100644 index 0000000000..5460a61313 --- /dev/null +++ b/packages/core/src/newServer/ws.ts @@ -0,0 +1,196 @@ +import type { IncomingMessage, Server } from 'node:http'; +import { STATUS_CODES, createServer as createHttpServer } from 'node:http'; +import type { ServerOptions as HttpsServerOptions } from 'node:https'; +import { createServer as createHttpsServer } from 'node:https'; +import type { Socket } from 'node:net'; +import path from 'node:path'; +import type { Duplex } from 'node:stream'; +import type { WebSocket as WebSocketRaw } from 'ws'; +import { WebSocketServer as WebSocketServerRaw_ } from 'ws'; +import { NormalizedServerConfig, ResolvedUserConfig } from '../config/types.js'; +import { HmrEngine } from '../server/hmr-engine.js'; +import { WebSocket as WebSocketTypes } from '../types/ws.js'; +import { ILogger, Logger } from '../utils/logger.js'; +import { isObject } from '../utils/share.js'; +import { HMRChannel } from './hmr.js'; +import { CommonServerOptions } from './http.js'; +import { HttpServer, ServerOptions } from './index.js'; +import { + CustomPayload, + ErrorPayload, + HMRPayload, + InferCustomEventPayload +} from './type.js'; + +export interface WebSocketServer extends HMRChannel { + /** + * Listen on port and host + */ + listen(): void; + /** + * Get all connected clients. + */ + clients: Set; + /** + * Disconnect all clients and terminate the server. + */ + close(): Promise; + /** + * Handle custom event emitted by `import.meta.hot.send` + */ + on: WebSocketTypes.Server['on'] & { + ( + event: T, + listener: WebSocketCustomListener> + ): void; + }; + /** + * Unregister event listener. + */ + off: WebSocketTypes.Server['off'] & { + // biome-ignore lint/complexity/noBannedTypes: + (event: string, listener: Function): void; + }; +} + +export interface WebSocketClient { + /** + * Send event to the client + */ + send(payload: HMRPayload): void; + /** + * Send custom event + */ + send(event: string, payload?: CustomPayload['data']): void; + /** + * The raw WebSocket instance + * @advanced + */ + socket: WebSocketTypes; +} + +const wsServerEvents = [ + 'connection', + 'error', + 'headers', + 'listening', + 'message' +]; + +function noop() { + // noop +} + +const HMR_HEADER = 'farm_hmr'; + +export type WebSocketCustomListener = ( + data: T, + client: WebSocketClient +) => void; + +const WebSocketServerRaw = process.versions.bun + ? // @ts-expect-error: Bun defines `import.meta.require` + import.meta.require('ws').WebSocketServer + : WebSocketServerRaw_; + +export class WsServer { + public wss: WebSocketRaw; + public customListeners = new Map>>(); + public clientsMap = new WeakMap(); + public bufferedError: ErrorPayload | null = null; + public logger: ILogger; + public wsServerOrHmrServer: Server; + + constructor( + private httpServer: HttpServer, + private config: ResolvedUserConfig, + private httpsOptions: HttpsServerOptions, + private publicPath: string, + private hmrEngine: HmrEngine, + logger?: ILogger + ) { + this.logger = logger ?? new Logger(); + this.createWebSocketServer(); + } + + createWebSocketServer() { + const serverConfig = this.config.server as ServerOptions; + if (serverConfig.ws === false) { + return { + name: 'ws', + get clients() { + return new Set(); + }, + async close() { + // noop + }, + on: noop as any as WebSocketServer['on'], + off: noop as any as WebSocketServer['off'], + listen: noop, + send: noop + }; + } + let wss: WebSocketServerRaw_; + let wsHttpServer: Server | undefined = undefined; + + const hmr = isObject(serverConfig.hmr) && serverConfig.hmr; + const hmrServer = hmr && hmr.server; + const hmrPort = hmr && hmr.port; + const portsAreCompatible = !hmrPort || hmrPort === serverConfig.port; + // @ts-ignore + this.wsServerOrHmrServer = + hmrServer || (portsAreCompatible && this.httpServer); + let hmrServerWsListener: ( + req: InstanceType, + socket: Duplex, + head: Buffer + ) => void; + const port = hmrPort || 9000; + const host = (hmr && hmr.host) || undefined; + + if (this.wsServerOrHmrServer) { + let hmrBase = this.publicPath; + const hmrPath = hmr ? hmr.path : undefined; + if (hmrPath) { + hmrBase = path.posix.join(hmrBase, hmrPath as string); + } + wss = new WebSocketServerRaw({ noServer: true }); + hmrServerWsListener = (req, socket, head) => { + if ( + req.headers['sec-websocket-protocol'] === HMR_HEADER && + req.url === hmrBase + ) { + wss.handleUpgrade(req, socket as Socket, head, (ws) => { + wss.emit('connection', ws, req); + }); + } + }; + this.wsServerOrHmrServer.on('upgrade', hmrServerWsListener); + } else { + // http server request handler keeps the same with + // https://github.com/websockets/ws/blob/45e17acea791d865df6b255a55182e9c42e5877a/lib/websocket-server.js#L88-L96 + const route = ((_, res) => { + const statusCode = 426; + const body = STATUS_CODES[statusCode]; + if (!body) + throw new Error( + `No body text found for the ${statusCode} status code` + ); + + res.writeHead(statusCode, { + 'Content-Length': body.length, + 'Content-Type': 'text/plain' + }); + res.end(body); + }) as Parameters[1]; + if (this.httpsOptions) { + wsHttpServer = createHttpsServer(this.httpsOptions, route); + } else { + wsHttpServer = createHttpServer(route); + } + // vite dev server in middleware mode + // need to call ws listen manually + wss = new WebSocketServerRaw({ server: wsHttpServer }); + } + } +} diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json index 3ea9330584..4f16e9c00d 100644 --- a/packages/core/tsconfig.build.json +++ b/packages/core/tsconfig.build.json @@ -3,10 +3,11 @@ "compilerOptions": { "rootDir": "src", "outDir": "dist", - "composite": true + "composite": true, + "noUnusedLocals": false }, "include": ["src/**/*.ts", "binding/**/*.d.ts"], - "exclude": ["src/**/*.spec.ts"], + "exclude": ["src/**/*.spec.ts", "src/newServer/**"], "references": [ { "path": "../utils/tsconfig.json" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0745fe1d41..7607c58402 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,6 +25,12 @@ importers: '@commitlint/config-conventional': specifier: ^17.0.3 version: 17.8.1 + '@farmfe/cli': + specifier: workspace:* + version: link:packages/cli + '@farmfe/core': + specifier: workspace:* + version: link:packages/core '@types/node': specifier: ^18.0.1 version: 18.18.8 @@ -2246,8 +2252,8 @@ importers: specifier: ^11.0.6 version: 11.0.6 execa: - specifier: ^7.1.1 - version: 7.2.0 + specifier: 9.3.0 + version: 9.3.0 farm-browserslist-generator: specifier: ^1.0.0 version: 1.0.0 @@ -2288,14 +2294,11 @@ importers: specifier: ^2.1.35 version: 2.1.35 open: - specifier: ^9.1.0 - version: 9.1.0 + specifier: 10.1.0 + version: 10.1.0 slashes: specifier: ^3.0.12 version: 3.0.12 - ws: - specifier: ^8.12.0 - version: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) zod: specifier: ^3.23.8 version: 3.23.8 @@ -2306,12 +2309,18 @@ importers: '@napi-rs/cli': specifier: ^2.18.4 version: 2.18.4 + '@types/connect': + specifier: ^3.4.38 + version: 3.4.38 '@types/figlet': specifier: ^1.5.5 version: 1.5.7 '@types/fs-extra': specifier: ^11.0.1 version: 11.0.3 + '@types/http-proxy': + specifier: ^1.17.14 + version: 1.17.14 '@types/koa': specifier: ^2.13.5 version: 2.13.10 @@ -2331,11 +2340,32 @@ importers: specifier: ^2.1.2 version: 2.1.3 '@types/ws': - specifier: ^8.5.4 + specifier: ^8.5.8 version: 8.5.8 + connect: + specifier: ^3.7.0 + version: 3.7.0 + cors: + specifier: ^2.8.5 + version: 2.8.5 + debug: + specifier: ^4.3.5 + version: 4.3.5 + etag: + specifier: ^1.8.1 + version: 1.8.1 + http-proxy: + specifier: ^1.18.1 + version: 1.18.1(debug@4.3.5) react-refresh: specifier: ^0.14.0 version: 0.14.0 + sirv: + specifier: ^2.0.3 + version: 2.0.3 + ws: + specifier: ^8.14.2 + version: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) packages/create-farm: devDependencies: @@ -4285,6 +4315,9 @@ packages: cpu: [x64] os: [win32] + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@simonwep/pickr@1.8.2': resolution: {integrity: sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==} @@ -4298,6 +4331,10 @@ packages: resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} @@ -4806,8 +4843,8 @@ packages: resolution: {integrity: sha512-VwVFUHlneOsWfv/GaaY7Kwk4XasDqkAlyFQtsHxnOw0yyBYWTrlEXtmb9RtC+VFBCdtuOeIXECmELNd5RrKp/g==} deprecated: This is a stub types definition. clipboard provides its own type definitions, so you do not need this installed. - '@types/connect@3.4.37': - resolution: {integrity: sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==} + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} '@types/content-disposition@0.5.7': resolution: {integrity: sha512-V9/5u21RHFR1zfdm3rQ6pJUKV+zSSVQt+yq16i1YhdivVzWgPEoKedc3GdT8aFjsqQbakdxuy3FnEdePUQOamQ==} @@ -5944,10 +5981,6 @@ packages: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} - big-integer@1.6.51: - resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} - engines: {node: '>=0.6'} - big.js@5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} @@ -5990,10 +6023,6 @@ packages: resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==} engines: {node: '>=14.16'} - bplist-parser@0.2.0: - resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} - engines: {node: '>= 5.10.0'} - brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -6055,9 +6084,9 @@ packages: builder-util@24.13.1: resolution: {integrity: sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==} - bundle-name@3.0.0: - resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} - engines: {node: '>=12'} + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} @@ -6457,6 +6486,10 @@ packages: resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==} engines: {node: '>=8'} + connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + consola@2.15.3: resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} @@ -7153,6 +7186,15 @@ packages: supports-color: optional: true + debug@4.3.5: + resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} @@ -7205,13 +7247,13 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - default-browser-id@3.0.0: - resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} - engines: {node: '>=12'} + default-browser-id@5.0.0: + resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} + engines: {node: '>=18'} - default-browser@4.0.0: - resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} - engines: {node: '>=14.16'} + default-browser@5.2.1: + resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} + engines: {node: '>=18'} defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -7695,6 +7737,10 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} + execa@9.3.0: + resolution: {integrity: sha512-l6JFbqnHEadBoVAVpN5dl2yCyfX28WoBAGaoQcNmLLSedOxTxcn2Qa83s8I/PA5i56vWru2OHOtrwF7Om2vqlg==} + engines: {node: ^18.19.0 || >=20.5.0} + exit-on-epipe@1.0.1: resolution: {integrity: sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==} engines: {node: '>=0.8'} @@ -7940,6 +7986,10 @@ packages: resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} engines: {node: '>=14'} + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -7967,6 +8017,10 @@ packages: resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} engines: {node: '>=0.10.0'} + finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} + finalhandler@1.2.0: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} engines: {node: '>= 0.8'} @@ -8200,6 +8254,10 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + get-symbol-description@1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} engines: {node: '>= 0.4'} @@ -8481,6 +8539,10 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + human-signals@7.0.0: + resolution: {integrity: sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==} + engines: {node: '>=18.18.0'} + humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} @@ -8745,6 +8807,10 @@ packages: resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} engines: {node: '>=10'} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + is-plain-object@2.0.4: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} @@ -8787,6 +8853,10 @@ packages: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -8818,6 +8888,10 @@ packages: resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} engines: {node: '>=12'} + is-unicode-supported@2.0.0: + resolution: {integrity: sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==} + engines: {node: '>=18'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -8844,6 +8918,10 @@ packages: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + isarray@0.0.1: resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} @@ -9858,6 +9936,10 @@ packages: resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npmlog@6.0.2: resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -9914,6 +9996,10 @@ packages: resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} engines: {node: '>=0.10.0'} + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -9940,9 +10026,9 @@ packages: only@0.0.2: resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} - open@9.1.0: - resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} - engines: {node: '>=14.16'} + open@10.1.0: + resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==} + engines: {node: '>=18'} opencollective-postinstall@2.0.3: resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==} @@ -10065,6 +10151,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + parse-node-version@1.0.1: resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} engines: {node: '>= 0.10'} @@ -10392,6 +10482,10 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-ms@9.0.0: + resolution: {integrity: sha512-E9e9HJ9R9NasGOgPaPE8VMeiPKAyWR5jcFpNnwIejslIhWqdqOrb2wShBsncMPUb+BcCd2OPYfh7p2W6oemTng==} + engines: {node: '>=18'} + primevue@3.52.0: resolution: {integrity: sha512-HLOVP5YI0ArFKUhIyfZsWmTNMaBYNCBWC/3DYvdd/Po4LY5/WXf7yIYvArE2q/3OuwSXJXvjlR8UNQeJYRSQog==} peerDependencies: @@ -11129,9 +11223,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - run-applescript@5.0.0: - resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} - engines: {node: '>=12'} + run-applescript@7.0.0: + resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} + engines: {node: '>=18'} run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} @@ -11790,6 +11884,10 @@ packages: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -12057,10 +12155,6 @@ packages: resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} engines: {node: '>=14.0.0'} - titleize@3.0.0: - resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} - engines: {node: '>=12'} - tmp-promise@3.0.3: resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} @@ -12430,10 +12524,6 @@ packages: resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} engines: {node: '>=0.10.0'} - untildify@4.0.0: - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} - engines: {node: '>=8'} - update-browserslist-db@1.0.13: resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true @@ -12984,6 +13074,10 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} + yoctocolors@2.1.1: + resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} + engines: {node: '>=18'} + zod-validation-error@3.3.0: resolution: {integrity: sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==} engines: {node: '>=18.0.0'} @@ -13331,7 +13425,7 @@ snapshots: '@babel/traverse': 7.24.1 '@babel/types': 7.24.0 convert-source-map: 2.0.0 - debug: 4.3.4 + debug: 4.3.5 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -13403,7 +13497,7 @@ snapshots: '@babel/core': 7.24.3 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-plugin-utils': 7.24.0 - debug: 4.3.4 + debug: 4.3.5 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -13772,7 +13866,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.1 '@babel/types': 7.24.0 - debug: 4.3.4 + debug: 4.3.5 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -13787,7 +13881,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.1 '@babel/types': 7.24.0 - debug: 4.3.4 + debug: 4.3.5 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -14453,7 +14547,7 @@ snapshots: '@electron/get': 3.0.0 chalk: 4.1.2 commander: 4.1.1 - debug: 4.3.4 + debug: 4.3.5 fs-extra: 10.1.0 listr2: 7.0.2 semver: 7.5.4 @@ -14468,7 +14562,7 @@ snapshots: '@electron/rebuild': 3.6.0 '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 - debug: 4.3.4 + debug: 4.3.5 find-up: 5.0.0 fs-extra: 10.1.0 log-symbols: 4.1.0 @@ -14496,7 +14590,7 @@ snapshots: '@electron/rebuild': 3.6.0 '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 - debug: 4.3.4 + debug: 4.3.5 fast-glob: 3.3.2 filenamify: 4.3.0 find-up: 5.0.0 @@ -14599,7 +14693,7 @@ snapshots: dependencies: '@electron-forge/shared-types': 7.4.0 '@malept/cross-spawn-promise': 2.0.0 - debug: 4.3.4 + debug: 4.3.5 fs-extra: 10.1.0 username: 5.1.0 transitivePeerDependencies: @@ -14654,7 +14748,7 @@ snapshots: '@electron/get@2.0.3': dependencies: - debug: 4.3.4 + debug: 4.3.5 env-paths: 2.2.1 fs-extra: 8.1.0 got: 11.8.6 @@ -14668,7 +14762,7 @@ snapshots: '@electron/get@3.0.0': dependencies: - debug: 4.3.4 + debug: 4.3.5 env-paths: 2.2.1 fs-extra: 8.1.0 got: 11.8.6 @@ -14682,7 +14776,7 @@ snapshots: '@electron/notarize@2.2.1': dependencies: - debug: 4.3.4 + debug: 4.3.5 fs-extra: 9.1.0 promise-retry: 2.0.1 transitivePeerDependencies: @@ -14690,7 +14784,7 @@ snapshots: '@electron/notarize@2.3.2': dependencies: - debug: 4.3.4 + debug: 4.3.5 fs-extra: 9.1.0 promise-retry: 2.0.1 transitivePeerDependencies: @@ -14699,7 +14793,7 @@ snapshots: '@electron/osx-sign@1.0.5': dependencies: compare-version: 0.1.2 - debug: 4.3.4 + debug: 4.3.5 fs-extra: 10.1.0 isbinaryfile: 4.0.10 minimist: 1.2.8 @@ -14710,7 +14804,7 @@ snapshots: '@electron/osx-sign@1.3.0': dependencies: compare-version: 0.1.2 - debug: 4.3.4 + debug: 4.3.5 fs-extra: 10.1.0 isbinaryfile: 4.0.10 minimist: 1.2.8 @@ -14726,7 +14820,7 @@ snapshots: '@electron/osx-sign': 1.3.0 '@electron/universal': 2.0.1 '@electron/windows-sign': 1.1.2 - debug: 4.3.4 + debug: 4.3.5 extract-zip: 2.0.1 filenamify: 4.3.0 fs-extra: 11.2.0 @@ -14746,7 +14840,7 @@ snapshots: dependencies: '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 - debug: 4.3.4 + debug: 4.3.5 detect-libc: 2.0.3 fs-extra: 10.1.0 got: 11.8.6 @@ -14766,7 +14860,7 @@ snapshots: dependencies: '@electron/asar': 3.2.10 '@malept/cross-spawn-promise': 1.1.1 - debug: 4.3.4 + debug: 4.3.5 dir-compare: 3.3.0 fs-extra: 9.1.0 minimatch: 3.1.2 @@ -14778,7 +14872,7 @@ snapshots: dependencies: '@electron/asar': 3.2.10 '@malept/cross-spawn-promise': 2.0.0 - debug: 4.3.4 + debug: 4.3.5 dir-compare: 4.2.0 fs-extra: 11.2.0 minimatch: 9.0.5 @@ -14789,7 +14883,7 @@ snapshots: '@electron/windows-sign@1.1.2': dependencies: cross-dirname: 0.1.0 - debug: 4.3.4 + debug: 4.3.5 fs-extra: 11.2.0 minimist: 1.2.8 postject: 1.0.0-alpha.6 @@ -14943,7 +15037,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.4 + debug: 4.3.5 espree: 9.6.1 globals: 13.24.0 ignore: 5.2.4 @@ -15035,7 +15129,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.4 + debug: 4.3.5 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -15306,7 +15400,7 @@ snapshots: '@malept/flatpak-bundler@0.4.0': dependencies: - debug: 4.3.4 + debug: 4.3.5 fs-extra: 9.1.0 lodash: 4.17.21 tmp-promise: 3.0.3 @@ -15605,6 +15699,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.14.1': optional: true + '@sec-ant/readable-stream@0.4.1': {} + '@simonwep/pickr@1.8.2': dependencies: core-js: 3.36.1 @@ -15616,6 +15712,8 @@ snapshots: '@sindresorhus/is@4.6.0': {} + '@sindresorhus/merge-streams@4.0.0': {} + '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 @@ -15631,7 +15729,7 @@ snapshots: '@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.1(svelte@4.0.0)(vite@5.2.8(@types/node@20.12.12)(less@4.2.0)(sass@1.74.1)(terser@5.31.1)))(svelte@4.0.0)(vite@5.2.8(@types/node@20.12.12)(less@4.2.0)(sass@1.74.1)(terser@5.31.1))': dependencies: '@sveltejs/vite-plugin-svelte': 3.0.1(svelte@4.0.0)(vite@5.2.8(@types/node@20.12.12)(less@4.2.0)(sass@1.74.1)(terser@5.31.1)) - debug: 4.3.4 + debug: 4.3.5 svelte: 4.0.0 vite: 5.2.8(@types/node@20.12.12)(less@4.2.0)(sass@1.74.1)(terser@5.31.1) transitivePeerDependencies: @@ -16629,7 +16727,7 @@ snapshots: '@types/body-parser@1.19.4': dependencies: - '@types/connect': 3.4.37 + '@types/connect': 3.4.38 '@types/node': 18.18.8 '@types/cacheable-request@6.0.3': @@ -16643,7 +16741,7 @@ snapshots: dependencies: clipboard: 2.0.11 - '@types/connect@3.4.37': + '@types/connect@3.4.38': dependencies: '@types/node': 18.18.8 @@ -16653,7 +16751,7 @@ snapshots: '@types/cookies@0.7.9': dependencies: - '@types/connect': 3.4.37 + '@types/connect': 3.4.38 '@types/express': 4.17.21 '@types/keygrip': 1.0.4 '@types/node': 18.18.8 @@ -17021,7 +17119,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.4.5) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.4.5) - debug: 4.3.4 + debug: 4.3.5 eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.4.5) optionalDependencies: @@ -17035,7 +17133,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.4 + debug: 4.3.5 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -17810,7 +17908,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.4 + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -18050,7 +18148,7 @@ snapshots: builder-util: 24.13.1 builder-util-runtime: 9.2.4 chromium-pickle-js: 0.2.0 - debug: 4.3.4 + debug: 4.3.5 dmg-builder: 24.13.3 ejs: 3.1.10 electron-publish: 24.13.1 @@ -18239,7 +18337,7 @@ snapshots: axios@1.6.5: dependencies: - follow-redirects: 1.15.4(debug@4.3.4) + follow-redirects: 1.15.4 form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -18247,7 +18345,7 @@ snapshots: axios@1.7.2: dependencies: - follow-redirects: 1.15.6 + follow-redirects: 1.15.6(debug@4.3.5) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -18393,8 +18491,6 @@ snapshots: dependencies: is-windows: 1.0.2 - big-integer@1.6.51: {} - big.js@5.2.2: {} binary-extensions@2.2.0: {} @@ -18484,10 +18580,6 @@ snapshots: widest-line: 4.0.1 wrap-ansi: 8.1.0 - bplist-parser@0.2.0: - dependencies: - big-integer: 1.6.51 - brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -18561,7 +18653,7 @@ snapshots: builder-util-runtime@9.2.4: dependencies: - debug: 4.3.4 + debug: 4.3.5 sax: 1.3.0 transitivePeerDependencies: - supports-color @@ -18575,7 +18667,7 @@ snapshots: builder-util-runtime: 9.2.4 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4 + debug: 4.3.5 fs-extra: 10.1.0 http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 @@ -18587,9 +18679,9 @@ snapshots: transitivePeerDependencies: - supports-color - bundle-name@3.0.0: + bundle-name@4.1.0: dependencies: - run-applescript: 5.0.0 + run-applescript: 7.0.0 busboy@1.6.0: dependencies: @@ -19033,6 +19125,15 @@ snapshots: write-file-atomic: 3.0.3 xdg-basedir: 4.0.0 + connect@3.7.0: + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + consola@2.15.3: {} console-control-strings@1.1.0: {} @@ -19452,6 +19553,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.5: + dependencies: + ms: 2.1.2 + decamelize-keys@1.1.1: dependencies: decamelize: 1.2.0 @@ -19513,17 +19618,12 @@ snapshots: deepmerge@4.3.1: {} - default-browser-id@3.0.0: - dependencies: - bplist-parser: 0.2.0 - untildify: 4.0.0 + default-browser-id@5.0.0: {} - default-browser@4.0.0: + default-browser@5.2.1: dependencies: - bundle-name: 3.0.0 - default-browser-id: 3.0.0 - execa: 7.2.0 - titleize: 3.0.0 + bundle-name: 4.1.0 + default-browser-id: 5.0.0 defaults@1.0.4: dependencies: @@ -19745,7 +19845,7 @@ snapshots: dependencies: '@malept/cross-spawn-promise': 1.1.1 asar: 3.2.0 - debug: 4.3.4 + debug: 4.3.5 fs-extra: 9.1.0 glob: 7.2.3 lodash: 4.17.21 @@ -19761,7 +19861,7 @@ snapshots: electron-installer-debian@3.2.0: dependencies: '@malept/cross-spawn-promise': 1.1.1 - debug: 4.3.4 + debug: 4.3.5 electron-installer-common: 0.10.3 fs-extra: 9.1.0 get-folder-size: 2.0.1 @@ -19775,7 +19875,7 @@ snapshots: electron-installer-redhat@3.4.0: dependencies: '@malept/cross-spawn-promise': 1.1.1 - debug: 4.3.4 + debug: 4.3.5 electron-installer-common: 0.10.3 fs-extra: 9.1.0 lodash: 4.17.21 @@ -19804,7 +19904,7 @@ snapshots: electron-winstaller@5.3.1: dependencies: '@electron/asar': 3.2.10 - debug: 4.3.4 + debug: 4.3.5 fs-extra: 7.0.1 lodash: 4.17.21 temp: 0.9.4 @@ -20175,6 +20275,21 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 + execa@9.3.0: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.3 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 7.0.0 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 5.3.0 + pretty-ms: 9.0.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.1 + exit-on-epipe@1.0.1: {} exit@0.1.2: {} @@ -20320,7 +20435,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.3.4 + debug: 4.3.5 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -20495,6 +20610,10 @@ snapshots: escape-string-regexp: 5.0.0 is-unicode-supported: 1.3.0 + figures@6.1.0: + dependencies: + is-unicode-supported: 2.0.0 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.1.1 @@ -20524,6 +20643,18 @@ snapshots: filter-obj@1.1.0: {} + finalhandler@1.1.2: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + finalhandler@1.2.0: dependencies: debug: 2.6.9 @@ -20567,7 +20698,7 @@ snapshots: flora-colossus@2.0.0: dependencies: - debug: 4.3.4 + debug: 4.3.5 fs-extra: 10.1.0 transitivePeerDependencies: - supports-color @@ -20586,11 +20717,11 @@ snapshots: follow-redirects@1.15.3: {} - follow-redirects@1.15.4(debug@4.3.4): - optionalDependencies: - debug: 4.3.4 + follow-redirects@1.15.4: {} - follow-redirects@1.15.6: {} + follow-redirects@1.15.6(debug@4.3.5): + optionalDependencies: + debug: 4.3.5 for-each@0.3.3: dependencies: @@ -20706,7 +20837,7 @@ snapshots: galactus@1.0.0: dependencies: - debug: 4.3.4 + debug: 4.3.5 flora-colossus: 2.0.0 fs-extra: 10.1.0 transitivePeerDependencies: @@ -20789,6 +20920,11 @@ snapshots: get-stream@8.0.1: {} + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + get-symbol-description@1.0.0: dependencies: call-bind: 1.0.7 @@ -21082,25 +21218,25 @@ snapshots: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.5 transitivePeerDependencies: - supports-color http-proxy-middleware@3.0.0: dependencies: '@types/http-proxy': 1.17.14 - debug: 4.3.4 - http-proxy: 1.18.1(debug@4.3.4) + debug: 4.3.5 + http-proxy: 1.18.1(debug@4.3.5) is-glob: 4.0.3 is-plain-obj: 3.0.0 micromatch: 4.0.5 transitivePeerDependencies: - supports-color - http-proxy@1.18.1(debug@4.3.4): + http-proxy@1.18.1(debug@4.3.5): dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.4(debug@4.3.4) + follow-redirects: 1.15.6(debug@4.3.5) requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -21113,7 +21249,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -21125,6 +21261,8 @@ snapshots: human-signals@5.0.0: {} + human-signals@7.0.0: {} + humanize-ms@1.2.1: dependencies: ms: 2.1.3 @@ -21393,6 +21531,8 @@ snapshots: is-plain-obj@3.0.0: {} + is-plain-obj@4.1.0: {} + is-plain-object@2.0.4: dependencies: isobject: 3.0.1 @@ -21424,6 +21564,8 @@ snapshots: is-stream@3.0.0: {} + is-stream@4.0.1: {} + is-string@1.0.7: dependencies: has-tostringtag: 1.0.0 @@ -21450,6 +21592,8 @@ snapshots: is-unicode-supported@1.3.0: {} + is-unicode-supported@2.0.0: {} + is-weakmap@2.0.2: {} is-weakref@1.0.2: @@ -21471,6 +21615,10 @@ snapshots: dependencies: is-docker: 2.2.1 + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + isarray@0.0.1: {} isarray@1.0.0: {} @@ -21521,7 +21669,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.4 + debug: 4.3.5 istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -22016,7 +22164,7 @@ snapshots: koa-send@5.0.1: dependencies: - debug: 4.3.4 + debug: 4.3.5 http-errors: 1.8.1 resolve-path: 1.4.0 transitivePeerDependencies: @@ -22751,6 +22899,10 @@ snapshots: dependencies: path-key: 4.0.0 + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + npmlog@6.0.2: dependencies: are-we-there-yet: 3.0.1 @@ -22804,6 +22956,10 @@ snapshots: dependencies: isobject: 3.0.1 + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -22828,12 +22984,12 @@ snapshots: only@0.0.2: {} - open@9.1.0: + open@10.1.0: dependencies: - default-browser: 4.0.0 + default-browser: 5.2.1 define-lazy-prop: 3.0.0 is-inside-container: 1.0.0 - is-wsl: 2.2.0 + is-wsl: 3.1.0 opencollective-postinstall@2.0.3: {} @@ -22955,6 +23111,8 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-ms@4.0.0: {} + parse-node-version@1.0.1: {} parse-passwd@1.0.0: {} @@ -23268,6 +23426,10 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.2.0 + pretty-ms@9.0.0: + dependencies: + parse-ms: 4.0.0 + primevue@3.52.0(vue@3.3.12(typescript@5.4.5)): dependencies: vue: 3.3.12(typescript@5.4.5) @@ -23875,7 +24037,7 @@ snapshots: read-binary-file-arch@1.0.6: dependencies: - debug: 4.3.4 + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -24149,9 +24311,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.14.1 fsevents: 2.3.3 - run-applescript@5.0.0: - dependencies: - execa: 5.1.1 + run-applescript@7.0.0: {} run-async@2.4.1: {} @@ -24570,7 +24730,7 @@ snapshots: socks-proxy-agent@7.0.0: dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.5 socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -24804,6 +24964,8 @@ snapshots: strip-final-newline@3.0.0: {} + strip-final-newline@4.0.0: {} + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -24842,7 +25004,7 @@ snapshots: sumchecker@3.0.1: dependencies: - debug: 4.3.4 + debug: 4.3.5 transitivePeerDependencies: - supports-color @@ -24850,7 +25012,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.3.4 + debug: 4.3.5 fast-safe-stringify: 2.1.1 form-data: 4.0.0 formidable: 2.1.2 @@ -25204,8 +25366,6 @@ snapshots: tinyspy@2.2.0: {} - titleize@3.0.0: {} - tmp-promise@3.0.3: dependencies: tmp: 0.2.3 @@ -25639,8 +25799,6 @@ snapshots: has-value: 0.3.1 isobject: 3.0.1 - untildify@4.0.0: {} - update-browserslist-db@1.0.13(browserslist@4.22.1): dependencies: browserslist: 4.22.1 @@ -25729,7 +25887,7 @@ snapshots: vite-node@1.4.0(@types/node@18.18.8)(less@4.2.0)(sass@1.74.1)(terser@5.31.1): dependencies: cac: 6.7.14 - debug: 4.3.4 + debug: 4.3.5 pathe: 1.1.1 picocolors: 1.0.0 vite: 5.2.8(@types/node@18.18.8)(less@4.2.0)(sass@1.74.1)(terser@5.31.1) @@ -25746,7 +25904,7 @@ snapshots: vite-node@1.4.0(@types/node@20.12.12)(less@4.2.0)(sass@1.74.1)(terser@5.31.1): dependencies: cac: 6.7.14 - debug: 4.3.4 + debug: 4.3.5 pathe: 1.1.1 picocolors: 1.0.0 vite: 5.2.8(@types/node@20.12.12)(less@4.2.0)(sass@1.74.1)(terser@5.31.1) @@ -26066,7 +26224,7 @@ snapshots: '@vitest/utils': 1.4.0 acorn-walk: 8.3.2 chai: 4.3.10 - debug: 4.3.4 + debug: 4.3.5 execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.5 @@ -26511,6 +26669,8 @@ snapshots: yocto-queue@1.0.0: {} + yoctocolors@2.1.1: {} + zod-validation-error@3.3.0(zod@3.23.8): dependencies: zod: 3.23.8 From 9e03be5984ef0ce4c0e2ca0774873efd49b360e4 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Fri, 19 Jul 2024 16:54:57 +0800 Subject: [PATCH 002/369] chore: update ws type --- packages/core/src/types/ws.ts | 559 ++++++++++++++++++++++++++++++++++ 1 file changed, 559 insertions(+) create mode 100644 packages/core/src/types/ws.ts diff --git a/packages/core/src/types/ws.ts b/packages/core/src/types/ws.ts new file mode 100644 index 0000000000..f03298cd9b --- /dev/null +++ b/packages/core/src/types/ws.ts @@ -0,0 +1,559 @@ +// Modified and inlined to avoid extra dependency +// Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/ws/index.d.ts + +// Type definitions for ws 8.5 +// Project: https://github.com/websockets/ws +// Definitions by: Paul Loyd +// Margus Lamp +// Philippe D'Alva +// reduckted +// teidesu +// Bartosz Wojtkowiak +// Kyle Hensel +// Samuel Skeen +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +/// + +import { EventEmitter } from 'node:events'; +import type { + Agent, + ClientRequest, + ClientRequestArgs, + Server as HTTPServer, + IncomingMessage, + OutgoingHttpHeaders +} from 'node:http'; +import type { Server as HTTPSServer } from 'node:https'; +import type { Duplex, DuplexOptions } from 'node:stream'; +import type { SecureContextOptions } from 'node:tls'; +import type { URL } from 'node:url'; +import type { ZlibOptions } from 'node:zlib'; + +// WebSocket socket. +declare class WebSocket extends EventEmitter { + /** The connection is not yet open. */ + static readonly CONNECTING: 0; + /** The connection is open and ready to communicate. */ + static readonly OPEN: 1; + /** The connection is in the process of closing. */ + static readonly CLOSING: 2; + /** The connection is closed. */ + static readonly CLOSED: 3; + + binaryType: 'nodebuffer' | 'arraybuffer' | 'fragments'; + readonly bufferedAmount: number; + readonly extensions: string; + /** Indicates whether the websocket is paused */ + readonly isPaused: boolean; + readonly protocol: string; + /** The current state of the connection */ + readonly readyState: + | typeof WebSocket.CONNECTING + | typeof WebSocket.OPEN + | typeof WebSocket.CLOSING + | typeof WebSocket.CLOSED; + readonly url: string; + + /** The connection is not yet open. */ + readonly CONNECTING: 0; + /** The connection is open and ready to communicate. */ + readonly OPEN: 1; + /** The connection is in the process of closing. */ + readonly CLOSING: 2; + /** The connection is closed. */ + readonly CLOSED: 3; + + onopen: ((event: WebSocket.Event) => void) | null; + onerror: ((event: WebSocket.ErrorEvent) => void) | null; + onclose: ((event: WebSocket.CloseEvent) => void) | null; + onmessage: ((event: WebSocket.MessageEvent) => void) | null; + + constructor(address: null); + constructor( + address: string | URL, + options?: WebSocket.ClientOptions | ClientRequestArgs + ); + constructor( + address: string | URL, + protocols?: string | string[], + options?: WebSocket.ClientOptions | ClientRequestArgs + ); + + close(code?: number, data?: string | Buffer): void; + ping(data?: any, mask?: boolean, cb?: (err: Error) => void): void; + pong(data?: any, mask?: boolean, cb?: (err: Error) => void): void; + send(data: any, cb?: (err?: Error) => void): void; + send( + data: any, + options: { + mask?: boolean | undefined; + binary?: boolean | undefined; + compress?: boolean | undefined; + fin?: boolean | undefined; + }, + cb?: (err?: Error) => void + ): void; + terminate(): void; + + /** + * Pause the websocket causing it to stop emitting events. Some events can still be + * emitted after this is called, until all buffered data is consumed. This method + * is a noop if the ready state is `CONNECTING` or `CLOSED`. + */ + pause(): void; + /** + * Make a paused socket resume emitting events. This method is a noop if the ready + * state is `CONNECTING` or `CLOSED`. + */ + resume(): void; + + // HTML5 WebSocket events + addEventListener( + method: 'message', + cb: (event: WebSocket.MessageEvent) => void, + options?: WebSocket.EventListenerOptions + ): void; + addEventListener( + method: 'close', + cb: (event: WebSocket.CloseEvent) => void, + options?: WebSocket.EventListenerOptions + ): void; + addEventListener( + method: 'error', + cb: (event: WebSocket.ErrorEvent) => void, + options?: WebSocket.EventListenerOptions + ): void; + addEventListener( + method: 'open', + cb: (event: WebSocket.Event) => void, + options?: WebSocket.EventListenerOptions + ): void; + + removeEventListener( + method: 'message', + cb: (event: WebSocket.MessageEvent) => void + ): void; + removeEventListener( + method: 'close', + cb: (event: WebSocket.CloseEvent) => void + ): void; + removeEventListener( + method: 'error', + cb: (event: WebSocket.ErrorEvent) => void + ): void; + removeEventListener( + method: 'open', + cb: (event: WebSocket.Event) => void + ): void; + + // Events + on( + event: 'close', + listener: (this: WebSocket, code: number, reason: Buffer) => void + ): this; + on(event: 'error', listener: (this: WebSocket, err: Error) => void): this; + on( + event: 'upgrade', + listener: (this: WebSocket, request: IncomingMessage) => void + ): this; + on( + event: 'message', + listener: ( + this: WebSocket, + data: WebSocket.RawData, + isBinary: boolean + ) => void + ): this; + on(event: 'open', listener: (this: WebSocket) => void): this; + on( + event: 'ping' | 'pong', + listener: (this: WebSocket, data: Buffer) => void + ): this; + on( + event: 'unexpected-response', + listener: ( + this: WebSocket, + request: ClientRequest, + response: IncomingMessage + ) => void + ): this; + on( + event: string | symbol, + listener: (this: WebSocket, ...args: any[]) => void + ): this; + + once( + event: 'close', + listener: (this: WebSocket, code: number, reason: Buffer) => void + ): this; + once(event: 'error', listener: (this: WebSocket, err: Error) => void): this; + once( + event: 'upgrade', + listener: (this: WebSocket, request: IncomingMessage) => void + ): this; + once( + event: 'message', + listener: ( + this: WebSocket, + data: WebSocket.RawData, + isBinary: boolean + ) => void + ): this; + once(event: 'open', listener: (this: WebSocket) => void): this; + once( + event: 'ping' | 'pong', + listener: (this: WebSocket, data: Buffer) => void + ): this; + once( + event: 'unexpected-response', + listener: ( + this: WebSocket, + request: ClientRequest, + response: IncomingMessage + ) => void + ): this; + once( + event: string | symbol, + listener: (this: WebSocket, ...args: any[]) => void + ): this; + + off( + event: 'close', + listener: (this: WebSocket, code: number, reason: Buffer) => void + ): this; + off(event: 'error', listener: (this: WebSocket, err: Error) => void): this; + off( + event: 'upgrade', + listener: (this: WebSocket, request: IncomingMessage) => void + ): this; + off( + event: 'message', + listener: ( + this: WebSocket, + data: WebSocket.RawData, + isBinary: boolean + ) => void + ): this; + off(event: 'open', listener: (this: WebSocket) => void): this; + off( + event: 'ping' | 'pong', + listener: (this: WebSocket, data: Buffer) => void + ): this; + off( + event: 'unexpected-response', + listener: ( + this: WebSocket, + request: ClientRequest, + response: IncomingMessage + ) => void + ): this; + off( + event: string | symbol, + listener: (this: WebSocket, ...args: any[]) => void + ): this; + + addListener( + event: 'close', + listener: (code: number, reason: Buffer) => void + ): this; + addListener(event: 'error', listener: (err: Error) => void): this; + addListener( + event: 'upgrade', + listener: (request: IncomingMessage) => void + ): this; + addListener( + event: 'message', + listener: (data: WebSocket.RawData, isBinary: boolean) => void + ): this; + addListener(event: 'open', listener: () => void): this; + addListener(event: 'ping' | 'pong', listener: (data: Buffer) => void): this; + addListener( + event: 'unexpected-response', + listener: (request: ClientRequest, response: IncomingMessage) => void + ): this; + addListener(event: string | symbol, listener: (...args: any[]) => void): this; + + removeListener( + event: 'close', + listener: (code: number, reason: Buffer) => void + ): this; + removeListener(event: 'error', listener: (err: Error) => void): this; + removeListener( + event: 'upgrade', + listener: (request: IncomingMessage) => void + ): this; + removeListener( + event: 'message', + listener: (data: WebSocket.RawData, isBinary: boolean) => void + ): this; + removeListener(event: 'open', listener: () => void): this; + removeListener( + event: 'ping' | 'pong', + listener: (data: Buffer) => void + ): this; + removeListener( + event: 'unexpected-response', + listener: (request: ClientRequest, response: IncomingMessage) => void + ): this; + removeListener( + event: string | symbol, + listener: (...args: any[]) => void + ): this; +} + +declare const WebSocketAlias: typeof WebSocket; +// @ts-ignore +interface WebSocketAlias extends WebSocket {} // tslint:disable-line no-empty-interface + +// @ts-ignore +// biome-ignore lint/style/noNamespace: +declare namespace WebSocket { + /** + * Data represents the raw message payload received over the WebSocket. + */ + type RawData = Buffer | ArrayBuffer | Buffer[]; + + /** + * Data represents the message payload received over the WebSocket. + */ + type Data = string | Buffer | ArrayBuffer | Buffer[]; + + /** + * CertMeta represents the accepted types for certificate & key data. + */ + type CertMeta = string | string[] | Buffer | Buffer[]; + + /** + * VerifyClientCallbackSync is a synchronous callback used to inspect the + * incoming message. The return value (boolean) of the function determines + * whether or not to accept the handshake. + */ + type VerifyClientCallbackSync = (info: { + origin: string; + secure: boolean; + req: IncomingMessage; + }) => boolean; + + /** + * VerifyClientCallbackAsync is an asynchronous callback used to inspect the + * incoming message. The return value (boolean) of the function determines + * whether or not to accept the handshake. + */ + type VerifyClientCallbackAsync = ( + info: { origin: string; secure: boolean; req: IncomingMessage }, + callback: ( + res: boolean, + code?: number, + message?: string, + headers?: OutgoingHttpHeaders + ) => void + ) => void; + + interface ClientOptions extends SecureContextOptions { + protocol?: string | undefined; + followRedirects?: boolean | undefined; + generateMask?(mask: Buffer): void; + handshakeTimeout?: number | undefined; + maxRedirects?: number | undefined; + perMessageDeflate?: boolean | PerMessageDeflateOptions | undefined; + localAddress?: string | undefined; + protocolVersion?: number | undefined; + headers?: { [key: string]: string } | undefined; + origin?: string | undefined; + agent?: Agent | undefined; + host?: string | undefined; + family?: number | undefined; + checkServerIdentity?(servername: string, cert: CertMeta): boolean; + rejectUnauthorized?: boolean | undefined; + maxPayload?: number | undefined; + skipUTF8Validation?: boolean | undefined; + } + + interface PerMessageDeflateOptions { + serverNoContextTakeover?: boolean | undefined; + clientNoContextTakeover?: boolean | undefined; + serverMaxWindowBits?: number | undefined; + clientMaxWindowBits?: number | undefined; + zlibDeflateOptions?: + | { + flush?: number | undefined; + finishFlush?: number | undefined; + chunkSize?: number | undefined; + windowBits?: number | undefined; + level?: number | undefined; + memLevel?: number | undefined; + strategy?: number | undefined; + dictionary?: Buffer | Buffer[] | DataView | undefined; + info?: boolean | undefined; + } + | undefined; + zlibInflateOptions?: ZlibOptions | undefined; + threshold?: number | undefined; + concurrencyLimit?: number | undefined; + } + + interface Event { + type: string; + target: WebSocket; + } + + interface ErrorEvent { + error: any; + message: string; + type: string; + target: WebSocket; + } + + interface CloseEvent { + wasClean: boolean; + code: number; + reason: string; + type: string; + target: WebSocket; + } + + interface MessageEvent { + data: Data; + type: string; + target: WebSocket; + } + + interface EventListenerOptions { + once?: boolean | undefined; + } + + interface ServerOptions { + host?: string | undefined; + port?: number | undefined; + backlog?: number | undefined; + server?: HTTPServer | HTTPSServer | undefined; + verifyClient?: + | VerifyClientCallbackAsync + | VerifyClientCallbackSync + | undefined; + handleProtocols?: ( + protocols: Set, + request: IncomingMessage + ) => string | false; + path?: string | undefined; + noServer?: boolean | undefined; + clientTracking?: boolean | undefined; + perMessageDeflate?: boolean | PerMessageDeflateOptions | undefined; + maxPayload?: number | undefined; + skipUTF8Validation?: boolean | undefined; + WebSocket?: typeof WebSocket.WebSocket | undefined; + } + + interface AddressInfo { + address: string; + family: string; + port: number; + } + + // WebSocket Server + class Server extends EventEmitter { + options: ServerOptions; + path: string; + clients: Set; + + constructor(options?: ServerOptions, callback?: () => void); + + address(): AddressInfo | string; + close(cb?: (err?: Error) => void): void; + handleUpgrade( + request: IncomingMessage, + socket: Duplex, + upgradeHead: Buffer, + callback: (client: T, request: IncomingMessage) => void + ): void; + shouldHandle(request: IncomingMessage): boolean | Promise; + + // Events + on( + event: 'connection', + cb: (this: Server, socket: T, request: IncomingMessage) => void + ): this; + on(event: 'error', cb: (this: Server, error: Error) => void): this; + on( + event: 'headers', + cb: (this: Server, headers: string[], request: IncomingMessage) => void + ): this; + on(event: 'close' | 'listening', cb: (this: Server) => void): this; + on( + event: string | symbol, + listener: (this: Server, ...args: any[]) => void + ): this; + + once( + event: 'connection', + cb: (this: Server, socket: T, request: IncomingMessage) => void + ): this; + once(event: 'error', cb: (this: Server, error: Error) => void): this; + once( + event: 'headers', + cb: (this: Server, headers: string[], request: IncomingMessage) => void + ): this; + once(event: 'close' | 'listening', cb: (this: Server) => void): this; + once( + event: string | symbol, + listener: (this: Server, ...args: any[]) => void + ): this; + + off( + event: 'connection', + cb: (this: Server, socket: T, request: IncomingMessage) => void + ): this; + off(event: 'error', cb: (this: Server, error: Error) => void): this; + off( + event: 'headers', + cb: (this: Server, headers: string[], request: IncomingMessage) => void + ): this; + off(event: 'close' | 'listening', cb: (this: Server) => void): this; + off( + event: string | symbol, + listener: (this: Server, ...args: any[]) => void + ): this; + + addListener( + event: 'connection', + cb: (client: T, request: IncomingMessage) => void + ): this; + addListener(event: 'error', cb: (err: Error) => void): this; + addListener( + event: 'headers', + cb: (headers: string[], request: IncomingMessage) => void + ): this; + addListener(event: 'close' | 'listening', cb: () => void): this; + addListener( + event: string | symbol, + listener: (...args: any[]) => void + ): this; + + removeListener(event: 'connection', cb: (client: T) => void): this; + removeListener(event: 'error', cb: (err: Error) => void): this; + removeListener( + event: 'headers', + cb: (headers: string[], request: IncomingMessage) => void + ): this; + removeListener(event: 'close' | 'listening', cb: () => void): this; + removeListener( + event: string | symbol, + listener: (...args: any[]) => void + ): this; + } + + const WebSocketServer: typeof Server; + interface WebSocketServer extends Server {} // tslint:disable-line no-empty-interface + const WebSocket: typeof WebSocketAlias; + interface WebSocket extends WebSocketAlias {} // tslint:disable-line no-empty-interface + + // WebSocket stream + function createWebSocketStream( + websocket: WebSocket, + options?: DuplexOptions + ): Duplex; +} + +// export = WebSocket +export { WebSocket, WebSocketAlias }; From cdd5494ab28a38559546aa7f56c9551b22084d04 Mon Sep 17 00:00:00 2001 From: ADNY <66500121+ErKeLost@users.noreply.github.com> Date: Mon, 22 Jul 2024 17:49:09 +0800 Subject: [PATCH 003/369] chore: refactor cli (#1649) * chore: update cli * chore: test cli example * chore: update config * chore: update config * chore: update config * chore: finish cli code * chore: finish cli options * chore: update cspell --- cspell.json | 16 +- examples/refactor-react/.gitignore | 22 + examples/refactor-react/README.md | 37 ++ examples/refactor-react/farm.config.ts | 10 + examples/refactor-react/index.html | 14 + examples/refactor-react/package.json | 24 ++ examples/refactor-react/public/favicon.ico | Bin 0 -> 4154 bytes examples/refactor-react/src/assets/logo.png | Bin 0 -> 16859 bytes examples/refactor-react/src/assets/react.svg | 1 + examples/refactor-react/src/index.css | 69 +++ examples/refactor-react/src/index.tsx | 11 + examples/refactor-react/src/main.css | 42 ++ examples/refactor-react/src/main.tsx | 32 ++ examples/refactor-react/src/typings.d.ts | 3 + examples/refactor-react/tsconfig.json | 25 ++ examples/refactor-react/tsconfig.node.json | 11 + js-plugins/electron/src/index.ts | 6 +- packages/cli/src/config.ts | 40 -- packages/cli/src/index.ts | 276 +++++++----- packages/cli/src/types.ts | 18 +- packages/cli/src/utils.ts | 115 +---- packages/core/src/config/index.ts | 423 ++++++++++--------- packages/core/src/config/mergeConfig.ts | 81 ++-- packages/core/src/config/schema.ts | 6 +- packages/core/src/config/types.ts | 20 +- packages/core/src/index.ts | 82 +++- packages/core/src/utils/share.ts | 2 +- packages/core/tests/common.ts | 5 +- packages/core/tsconfig.build.json | 2 +- pnpm-lock.yaml | 138 +++++- 30 files changed, 997 insertions(+), 534 deletions(-) create mode 100644 examples/refactor-react/.gitignore create mode 100644 examples/refactor-react/README.md create mode 100644 examples/refactor-react/farm.config.ts create mode 100644 examples/refactor-react/index.html create mode 100644 examples/refactor-react/package.json create mode 100644 examples/refactor-react/public/favicon.ico create mode 100644 examples/refactor-react/src/assets/logo.png create mode 100644 examples/refactor-react/src/assets/react.svg create mode 100644 examples/refactor-react/src/index.css create mode 100644 examples/refactor-react/src/index.tsx create mode 100644 examples/refactor-react/src/main.css create mode 100644 examples/refactor-react/src/main.tsx create mode 100644 examples/refactor-react/src/typings.d.ts create mode 100644 examples/refactor-react/tsconfig.json create mode 100644 examples/refactor-react/tsconfig.node.json delete mode 100644 packages/cli/src/config.ts diff --git a/cspell.json b/cspell.json index 24cc0c7c4d..ad1648aaad 100644 --- a/cspell.json +++ b/cspell.json @@ -16,6 +16,7 @@ "Architecure", "arraify", "Avenir", + "Bartosz", "Basepath", "bindgen", "bpos", @@ -69,6 +70,7 @@ "guolao", "hashbrown", "hasher", + "Hensel", "icns", "idents", "IHDR", @@ -76,6 +78,7 @@ "importee", "Inctive", "indicatif", + "instanceof", "Instantiator", "jfif", "JIDA", @@ -91,6 +94,7 @@ "loglevel", "lukastaegert", "mapref", + "Margus", "mdsvex", "Menlo", "Mergeable", @@ -104,6 +108,7 @@ "nanospinner", "napi", "NAPI", + "nodebuffer", "Nonoctal", "normpath", "npmlog", @@ -133,6 +138,7 @@ "querify", "querystring", "Raspopov", + "reduckted", "replacen", "repr", "rfind", @@ -148,6 +154,7 @@ "shulan", "shulandmimi", "sirv", + "Skeen", "srcset", "struct", "styl", @@ -156,6 +163,7 @@ "svgr", "tailwindcss", "Tauri", + "teidesu", "Tencent", "thiserror", "threadsafe", @@ -181,13 +189,9 @@ "wasix", "wasmer", "wechat", + "Wojtkowiak", "xlink", - "Yuxi", - "wasmer", - "primevue", - "shulan", - "globset", - "instanceof" + "Yuxi" ], "ignorePaths": [ "pnpm-lock.yaml", diff --git a/examples/refactor-react/.gitignore b/examples/refactor-react/.gitignore new file mode 100644 index 0000000000..a4cf82d2cd --- /dev/null +++ b/examples/refactor-react/.gitignore @@ -0,0 +1,22 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.sln +*.sw? diff --git a/examples/refactor-react/README.md b/examples/refactor-react/README.md new file mode 100644 index 0000000000..5802e857f7 --- /dev/null +++ b/examples/refactor-react/README.md @@ -0,0 +1,37 @@ +# Farm + React + +This template should help you start developing using React and TypeScript in Farm. + +## Setup + +Install the dependencies: + +```bash +pnpm install +``` + +## Get Started + +Start the dev server: + +```bash +pnpm start +``` + +Build the app for production: + +```bash +pnpm build +``` + +Preview the Production build product: + +```bash +pnpm preview +``` + +Clear persistent cache local files + +```bash +pnpm clean +``` diff --git a/examples/refactor-react/farm.config.ts b/examples/refactor-react/farm.config.ts new file mode 100644 index 0000000000..6ab6a340c1 --- /dev/null +++ b/examples/refactor-react/farm.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from '@farmfe/core'; + +export default defineConfig({ + plugins: ['@farmfe/plugin-react'], + compilation: { + presetEnv: false, + progress: false, + sourcemap: false + } +}); diff --git a/examples/refactor-react/index.html b/examples/refactor-react/index.html new file mode 100644 index 0000000000..56d5a1bdb6 --- /dev/null +++ b/examples/refactor-react/index.html @@ -0,0 +1,14 @@ + + + + + + + + Farm + React + TS + + +
+ + + \ No newline at end of file diff --git a/examples/refactor-react/package.json b/examples/refactor-react/package.json new file mode 100644 index 0000000000..87544a4faf --- /dev/null +++ b/examples/refactor-react/package.json @@ -0,0 +1,24 @@ +{ + "name": "react-template", + "version": "1.0.0", + "scripts": { + "dev": "farm start", + "start": "farm start", + "build": "farm build", + "preview": "farm preview", + "clean": "farm clean" + }, + "dependencies": { + "react": "18", + "react-dom": "18" + }, + "devDependencies": { + "@farmfe/cli": "workspace:*", + "@farmfe/core": "workspace:*", + "@farmfe/plugin-react": "^1.2.0", + "@types/react": "18", + "core-js": "^3.36.1", + "@types/react-dom": "18", + "react-refresh": "^0.14.0" + } +} diff --git a/examples/refactor-react/public/favicon.ico b/examples/refactor-react/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..80465dedc03cfd08b0e3b118db6e765f65e3adc3 GIT binary patch literal 4154 zcmb`~duWw)9LMqBoO)WB8>rdNSy794a8L80HKCYy;X53Ll~aB<(pZM`qE!25qV^T{4`B6-myS?o2hN82+<+U< zgU>Js#Y@ls0rgpHaWfVd>OhcuLiH?%JvX{-jp-L?TuqIfpde{Z+6RpMT(1M2a zNgW#BR8$vQhXMP8dvl>UUXQDxF|NSvPbf6_&zLFD zH5>=EtG%cFqj(pZ5A8>dbr{yJ+S~!fc|+tT()+LzipxT%okH!;)YStw?b>8VB6{p_in}7AeAdaJ^{r}^?eMB-Gk({ zrayu9w#~ow!{$co^?m3pP+TWG|G2Mpr`Uyk4031DEWT^Zs#|q!fzAf4HC z@HD383zV1%YP(h6O6-MVF$0><`LHpo%n?h&yyCS6;aV%P*?jSIU3mWM_tJK}3hkK- z(TTZGyGg9VBE;t=>{Gt7qs&mJ>d|=ip#xfr=c5gZ$yw07U$FsIX?|Ok>qC96J=cd; z@;YC2-m6XRg(lYaG*Z2nG~YT0)YowAdafLws6MUp<@g2%pfgBwk;0cy``Y{OLgf^v zvdn?TV0Do;U>(}g2+jRrsC}dJR{Q2sg!{9Maj?GBP`Bpc6{z<{_vLJy;6Olit;eS4G)6KtfV<)|&@?~GFW7k{s0_}^bcdli`x%y$}s)w9QNY*W`%sMACqBL=U`#(}{kZI}9O!ob|625;;!v7E?e72>_ YXKTD4qPpQwz4tCb{gqHVI7FV$f0MB}F8}}l literal 0 HcmV?d00001 diff --git a/examples/refactor-react/src/assets/logo.png b/examples/refactor-react/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0caeb4381267cff0c3adf3020077c55bac612a20 GIT binary patch literal 16859 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 literal 0 HcmV?d00001 diff --git a/examples/refactor-react/src/assets/react.svg b/examples/refactor-react/src/assets/react.svg new file mode 100644 index 0000000000..6c87de9bb3 --- /dev/null +++ b/examples/refactor-react/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/refactor-react/src/index.css b/examples/refactor-react/src/index.css new file mode 100644 index 0000000000..6cc4daf982 --- /dev/null +++ b/examples/refactor-react/src/index.css @@ -0,0 +1,69 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #9f1a8f; + text-decoration: inherit; +} +a:hover { + color: #9f1a8f; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #9f1a8f; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #9F1A8F; + } + button { + background-color: #f9f9f9; + } +} diff --git a/examples/refactor-react/src/index.tsx b/examples/refactor-react/src/index.tsx new file mode 100644 index 0000000000..39df64e842 --- /dev/null +++ b/examples/refactor-react/src/index.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { Main } from './main'; +import './index.css' + + +const container = document.querySelector('#root'); +// biome-ignore lint/style/noNonNullAssertion: +const root = createRoot(container!); + +root.render(
); diff --git a/examples/refactor-react/src/main.css b/examples/refactor-react/src/main.css new file mode 100644 index 0000000000..ff29d41287 --- /dev/null +++ b/examples/refactor-react/src/main.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #9F1A8Faa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/examples/refactor-react/src/main.tsx b/examples/refactor-react/src/main.tsx new file mode 100644 index 0000000000..419a29c9be --- /dev/null +++ b/examples/refactor-react/src/main.tsx @@ -0,0 +1,32 @@ +import React, { useState } from "react"; +import "./main.css"; +import reactLogo from "./assets/react.svg"; +import FarmLogo from "./assets/logo.png"; +export function Main() { + const [count, setCount] = useState(0); + + return ( + <> + +

Farm + React

+
+ +

+ Edit src/main.tsx and save to test HMR +

+
+

+ Click on the Farm and React logos to learn more +

+ + ); +} diff --git a/examples/refactor-react/src/typings.d.ts b/examples/refactor-react/src/typings.d.ts new file mode 100644 index 0000000000..fa0a2da548 --- /dev/null +++ b/examples/refactor-react/src/typings.d.ts @@ -0,0 +1,3 @@ +declare module '*.svg'; +declare module '*.png'; +declare module '*.css'; diff --git a/examples/refactor-react/tsconfig.json b/examples/refactor-react/tsconfig.json new file mode 100644 index 0000000000..7a7611e4a3 --- /dev/null +++ b/examples/refactor-react/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/examples/refactor-react/tsconfig.node.json b/examples/refactor-react/tsconfig.node.json new file mode 100644 index 0000000000..8d4232518e --- /dev/null +++ b/examples/refactor-react/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["farm.config.ts"] +} diff --git a/js-plugins/electron/src/index.ts b/js-plugins/electron/src/index.ts index 84d5c48d3b..46305ba716 100644 --- a/js-plugins/electron/src/index.ts +++ b/js-plugins/electron/src/index.ts @@ -3,7 +3,7 @@ import type { AddressInfo } from 'node:net'; import path from 'node:path'; // TODO: submit a PR to farm(export default farm) import { - type FarmCLIOptions, + type FarmCliOptions, type JsPlugin, type Server, type UserConfig, @@ -158,8 +158,8 @@ function resolveFarmConfig( }); } - // TODO: submit a PR to farm(Omit & UserConfig) - return opts.farm as FarmCLIOptions; + // TODO: submit a PR to farm(Omit & UserConfig) + return opts.farm as FarmCliOptions; } function resolveServerUrl(server: Server) { diff --git a/packages/cli/src/config.ts b/packages/cli/src/config.ts deleted file mode 100644 index 467019af8a..0000000000 --- a/packages/cli/src/config.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { FarmCLIOptions, UserConfig } from '@farmfe/core'; -import { FarmCLIBuildOptions, GlobalFarmCLIOptions } from './types.js'; - -export function getOptionFromBuildOption( - options: FarmCLIBuildOptions & GlobalFarmCLIOptions -): FarmCLIOptions & UserConfig { - const { - input, - outDir, - target, - format, - watch, - minify, - sourcemap, - treeShaking, - mode - } = options; - - const output: UserConfig['compilation']['output'] = { - ...(outDir && { path: outDir }), - ...(target && { targetEnv: target }), - ...(format && { format }) - }; - - const compilation: UserConfig['compilation'] = { - input: { ...(input && { index: input }) }, - output, - ...(watch && { watch }), - ...(minify && { minify }), - ...(sourcemap && { sourcemap }), - ...(treeShaking && { treeShaking }) - }; - - const defaultOptions: FarmCLIOptions & UserConfig = { - compilation, - ...(mode && { mode }) - }; - - return defaultOptions; -} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index e8409c5c3d..3e3891ced6 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,37 +1,43 @@ -import { readFileSync } from 'node:fs'; - import { cac } from 'cac'; -import { getOptionFromBuildOption } from './config.js'; + import { handleAsyncOperationErrors, - preventExperimentalWarning, resolveCliConfig, resolveCommandOptions, - resolveCore + resolveCore, + version } from './utils.js'; +import type { UserConfig } from '@farmfe/core'; import type { - FarmCLIBuildOptions, - FarmCLIPreviewOptions, - FarmCLIServerOptions, - GlobalFarmCLIOptions, - ICleanOptions + CleanOptions, + CliBuildOptions, + CliPreviewOptions, + CliServerOptions, + GlobalCliOptions } from './types.js'; -const { version } = JSON.parse( - readFileSync(new URL('../package.json', import.meta.url)).toString() -); - const cli = cac('farm'); // common command cli - .option('-c, --config ', 'use specified config file') - .option('-m, --mode ', 'set env mode') - .option('--base ', 'public base path') - .option('--clearScreen', 'allow/disable clear screen when logging', { - default: true - }); + .option( + '-c, --config ', + '[string] use specified config file (default: farm.config.js / farm.config.ts / farm.config.mjs / farm.config.cjs / farm.config.mts / farm.config.cts)' + ) + .option( + '-m, --mode ', + '[string] set env mode, when use with development (default: /)' + ) + .option('--base ', '[string] public base path') + .option('-d, --debug [feat]', `[string | boolean] show debug logs`) + .option( + '--clearScreen', + '[boolean] allow/disable clear screen when logging (default: true)', + { + default: true + } + ); // dev command cli @@ -41,33 +47,55 @@ cli ) .alias('start') .alias('dev') - .option('-l, --lazy', 'lazyCompilation') - .option('--host ', 'specify host') - .option('--port ', 'specify port') - .option('--open', 'open browser on server start') - .option('--hmr', 'enable hot module replacement') - .option('--cors', 'enable cors') - .option('--strictPort', 'specified port is already in use, exit with error') + .option('-l, --lazy', '[boolean] lazyCompilation (default: true)') + .option('--host ', '[string] specify host') + .option('--port ', '[string] specify port') + .option('--open', '[boolean] open browser on server start') + .option('--hmr', '[boolean] enable hot module replacement') + .option('--cors', '[boolean] enable cors') + .option( + '--strictPort', + '[boolean] specified port is already in use, exit with error (default: true)' + ) + .option('--target ', '[string] transpile targetEnv node, browser') + .option('--format ', '[string] transpile format esm, commonjs') + .option('--sourcemap', '[boolean] output source maps for build') + .option( + '--treeShaking', + '[boolean] Eliminate useless code without side effects' + ) + .option('--minify', '[boolean] code compression at build time') .action( async ( - rootPath: string, - options: FarmCLIServerOptions & GlobalFarmCLIOptions + root: string, + options: CliServerOptions & CliBuildOptions & GlobalCliOptions ) => { - const { root, configPath } = resolveCliConfig(rootPath, options); const resolveOptions = resolveCommandOptions(options); const defaultOptions = { root, - compilation: { - lazyCompilation: options.lazy - }, server: resolveOptions, clearScreen: options.clearScreen, - configPath, - mode: options.mode + configFile: options.config, + mode: options.mode, + compilation: { + lazyCompilation: options.lazy, + output: { + path: options?.outDir, + targetEnv: options?.target, + format: options?.format + }, + input: { + index: options?.input + }, + sourcemap: options.sourcemap, + minify: options.minify, + treeShaking: options.treeShaking + } }; const { start } = await resolveCore(); + handleAsyncOperationErrors( start(defaultOptions), 'Failed to start server' @@ -78,83 +106,113 @@ cli // build command cli .command('build [root]', 'compile the project in production mode') - .option('-o, --outDir ', 'output directory') - .option('-i, --input ', 'input file path') - .option('-w, --watch', 'watch file change') - .option('--target ', 'transpile targetEnv node, browser') - .option('--format ', 'transpile format esm, commonjs') - .option('--sourcemap', 'output source maps for build') - .option('--treeShaking', 'Eliminate useless code without side effects') - .option('--minify', 'code compression at build time') - .action( - async ( - rootPath: string, - options: FarmCLIBuildOptions & GlobalFarmCLIOptions - ) => { - const { root, configPath } = resolveCliConfig(rootPath, options); + .option('-o, --outDir ', '[string] output directory') + .option('-i, --input ', '[string] input file path') + .option('-w, --watch', '[boolean] watch file change') + .option('--target ', '[string] transpile targetEnv node, browser') + .option('--format ', '[string] transpile format esm, commonjs') + .option('--sourcemap', '[boolean] output source maps for build') + .option( + '--treeShaking', + '[boolean] Eliminate useless code without side effects' + ) + .option('--minify', '[boolean] code compression at build time') + .action(async (root: string, options: CliBuildOptions & GlobalCliOptions) => { + const defaultOptions = { + root, + configFile: options.configFile, + mode: options.mode, + watch: options.watch, + compilation: { + output: { + path: options?.outDir, + targetEnv: options?.target, + format: options?.format + }, + input: { + index: options?.input + }, + sourcemap: options.sourcemap, + minify: options.minify, + treeShaking: options.treeShaking + } + }; - const defaultOptions = { - root, - configPath, - ...getOptionFromBuildOption(options) - }; + const { build } = await resolveCore(); - const { build } = await resolveCore(); - handleAsyncOperationErrors(build(defaultOptions), 'error during build'); - } - ); + handleAsyncOperationErrors(build(defaultOptions), 'error during build'); + }); cli .command('watch [root]', 'watch file change') - .option('-o, --outDir ', 'output directory') - .option('-i, --input ', 'input file path') - .option('--target ', 'transpile targetEnv node, browser') - .option('--format ', 'transpile format esm, commonjs') - .option('--sourcemap', 'output source maps for build') - .option('--treeShaking', 'Eliminate useless code without side effects') - .option('--minify', 'code compression at build time') - .action( - async ( - rootPath: string, - options: FarmCLIBuildOptions & GlobalFarmCLIOptions - ) => { - const { root, configPath } = resolveCliConfig(rootPath, options); - - const defaultOptions = { - root, - configPath, - ...getOptionFromBuildOption(options) - }; - - const { watch } = await resolveCore(); - handleAsyncOperationErrors( - watch(defaultOptions), - 'error during watch project' - ); - } - ); + .option('-o, --outDir ', '[string] output directory') + .option('-i, --input ', '[string] input file path') + .option('--target ', '[string] transpile targetEnv node, browser') + .option('--format ', '[string] transpile format esm, commonjs') + .option('--sourcemap', '[boolean] output source maps for build') + .option( + '--treeShaking', + '[boolean] Eliminate useless code without side effects' + ) + .option('--minify', '[boolean] code compression at build time') + .action(async (root: string, options: CliBuildOptions & GlobalCliOptions) => { + const defaultOptions = { + root, + configFile: options.configFile, + mode: options.mode, + compilation: { + watch: options.watch, + output: { + path: options?.outDir, + targetEnv: options?.target, + format: options?.format + }, + input: { + index: options?.input + }, + sourcemap: options.sourcemap, + minify: options.minify, + treeShaking: options.treeShaking + } + }; + + const { watch } = await resolveCore(); + + handleAsyncOperationErrors( + watch(defaultOptions), + 'error during watch project' + ); + }); cli .command('preview [root]', 'compile the project in watch mode') - .option('--port ', 'specify port') - .option('--open', 'open browser on server preview start') + .option('--host [host]', `[string] specify hostname`) + .option('--port ', `[number] specify port`) + .option('--open', '[boolean] open browser on server preview start') + .option('--outDir ', `[string] output directory (default: dist)`) + .option('--strictPort', `[boolean] exit if specified port is already in use`) .action( - async ( - rootPath: string, - options: FarmCLIPreviewOptions & GlobalFarmCLIOptions - ) => { - const { root, configPath } = resolveCliConfig(rootPath, options); - - const resolveOptions = resolveCommandOptions(options); + async (root: string, options: CliPreviewOptions & GlobalCliOptions) => { const defaultOptions = { root, mode: options.mode, - server: resolveOptions, - configPath, - port: options.port + preview: { + port: options.port, + strictPort: options?.strictPort, + host: options.host, + open: options.open + }, + configPath: options.configPath, + port: options.port, + compilation: { + output: { + path: options.outDir + } + } }; const { preview } = await resolveCore(); + handleAsyncOperationErrors( preview(defaultOptions), 'Failed to start preview server' @@ -168,18 +226,14 @@ cli '--recursive', 'Recursively search for node_modules directories and clean them' ) - .action(async (rootPath: string, options: ICleanOptions) => { + .action(async (rootPath: string, options: CleanOptions) => { const { root } = resolveCliConfig(rootPath, options); - const { clean } = await resolveCore(); - try { - await clean(root, options?.recursive); - } catch (e) { - const { Logger } = await import('@farmfe/core'); - const logger = new Logger(); - logger.error(`Failed to clean cache: \n ${e.stack}`); - process.exit(1); - } + const { clean } = await resolveCore(); + handleAsyncOperationErrors( + clean(root, options?.recursive), + 'Failed to clean cache' + ); }); // Listening for unknown command @@ -187,16 +241,10 @@ cli.on('command:*', async () => { const { Logger } = await import('@farmfe/core'); const logger = new Logger(); logger.error( - 'Unknown command place Run "farm --help" to see available commands' + `Unknown command place Run "farm --help" to see available commands` ); }); -// warning::: use mdn browser compatibility data with experimental warning in terminal so prevent experimental warning -// we don't use it in `@farmfe/core` package because -// we need to prevent it in cli package but we don't prevent it in core package -// We only keep the original code environment. -preventExperimentalWarning(); - cli.help(); cli.version(version); diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index afdd44364f..37f668636a 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -1,11 +1,11 @@ -export interface GlobalFarmCLIOptions { +export interface GlobalCliOptions { '--'?: string[]; c?: boolean | string; config?: string; configPath?: string; m?: string; base?: string; - mode?: 'development' | 'production'; + mode?: 'development' | 'production' | string; w?: boolean; watch?: boolean; watchPath?: string; @@ -15,12 +15,12 @@ export interface GlobalFarmCLIOptions { clearScreen?: boolean; } -export interface ICleanOptions { +export interface CleanOptions { path?: string; recursive?: boolean; } -export interface FarmCLIServerOptions { +export interface CliServerOptions { port?: string; open?: boolean; https?: boolean; @@ -28,7 +28,8 @@ export interface FarmCLIServerOptions { strictPort?: boolean; } -export interface FarmCLIBuildOptions { +export interface CliBuildOptions { + configFile?: string | undefined; input?: string; outDir?: string; sourcemap?: boolean; @@ -47,7 +48,10 @@ export interface FarmCLIBuildOptions { | 'browser-esnext'; } -export interface FarmCLIPreviewOptions { - open?: boolean; +export interface CliPreviewOptions { + host?: string | boolean; port?: number; + open?: boolean | string; + strictPort?: boolean; + outDir?: string; } diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 18717f8cb8..f3840e35e8 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -1,25 +1,11 @@ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { readFileSync } from 'node:fs'; import path from 'node:path'; -import readline from 'node:readline'; -import { fileURLToPath } from 'node:url'; import type { build, clean, preview, start, watch } from '@farmfe/core'; import { Logger } from '@farmfe/core'; -import spawn from 'cross-spawn'; -import walkdir from 'walkdir'; -import type { GlobalFarmCLIOptions, ICleanOptions } from './types.js'; +import type { CleanOptions, GlobalCliOptions } from './types.js'; const logger = new Logger(); -interface installProps { - cwd: string; - package: string; -} - -export const TEMPLATES_DIR = path.join( - path.dirname(fileURLToPath(import.meta.url)), - '..', - 'templates' -); export async function resolveCore(): Promise<{ start: typeof start; @@ -38,69 +24,6 @@ export async function resolveCore(): Promise<{ } } -export function copyFiles( - source: string, - dest: string, - callback?: (content: string) => string -): void { - walkdir(source, { sync: true }, (p, stat) => { - if (stat.isFile()) { - const content = readFileSync(p).toString(); - const newContent = callback?.(content) ?? content; - - const relativePath = path.relative(source, p); - const destPath = path.join(dest, relativePath); - - if (!existsSync(path.dirname(destPath))) { - mkdirSync(path.dirname(destPath), { recursive: true }); - } - - writeFileSync(destPath, newContent); - } - }); - - if (!existsSync(path.join(dest, '.gitignore'))) { - writeFileSync( - path.join(dest, '.gitignore'), - ` -node_modules -*.farm` - ); - } -} - -export async function install(options: installProps): Promise { - const cwd = options.cwd; - return new Promise((resolve, reject) => { - const command = options.package; - const args = ['install']; - - const child = spawn(command, args, { - cwd, - stdio: 'inherit' - }); - - child.once('close', (code: number) => { - if (code !== 0) { - reject({ - command: `${command} ${args.join(' ')}` - }); - return; - } - resolve(); - }); - child.once('error', reject); - }); -} -/** - * 用于规范化目标路径 - * @param {string |undefined} targetDir - * @returns - */ -export function formatTargetDir(targetDir: string | undefined) { - return targetDir?.trim()?.replace(/\/+$/g, ''); -} - /** * filter duplicate item in options */ @@ -112,18 +35,7 @@ export function filterDuplicateOptions(options: T) { } } -/** - * clear command screen - */ -export function clearScreen() { - const repeatCount = process.stdout.rows - 2; - const blank = repeatCount > 0 ? '\n'.repeat(repeatCount) : ''; - console.log(blank); - readline.cursorTo(process.stdout, 0, 0); - readline.clearScreenDown(process.stdout); -} - -export function cleanOptions(options: GlobalFarmCLIOptions) { +export function cleanOptions(options: GlobalCliOptions) { const resolveOptions = { ...options }; delete resolveOptions['--']; @@ -141,8 +53,8 @@ export function cleanOptions(options: GlobalFarmCLIOptions) { } export function resolveCommandOptions( - options: GlobalFarmCLIOptions -): GlobalFarmCLIOptions { + options: GlobalCliOptions +): GlobalCliOptions { const resolveOptions = { ...options }; filterDuplicateOptions(resolveOptions); return cleanOptions(resolveOptions); @@ -164,17 +76,6 @@ export async function handleAsyncOperationErrors( } } -// prevent node experimental warning -export function preventExperimentalWarning() { - const defaultEmit = process.emit; - process.emit = function (...args: any[]) { - if (args[1].name === 'ExperimentalWarning') { - return undefined; - } - return defaultEmit.call(this, ...args); - }; -} - export function resolveRootPath(rootPath = '') { return rootPath && path.isAbsolute(rootPath) ? rootPath @@ -183,7 +84,7 @@ export function resolveRootPath(rootPath = '') { export function resolveCliConfig( root: string, - options: GlobalFarmCLIOptions & ICleanOptions + options: GlobalCliOptions & CleanOptions ) { root = resolveRootPath(root); const configPath = getConfigPath(root, options.config); @@ -192,3 +93,7 @@ export function resolveCliConfig( configPath }; } + +export const { version } = JSON.parse( + readFileSync(new URL('../package.json', import.meta.url)).toString() +); diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index bcb9a51de8..15ed769b07 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -3,6 +3,7 @@ import fs from 'node:fs'; import module from 'node:module'; import path, { isAbsolute, join } from 'node:path'; import { pathToFileURL } from 'node:url'; +import fse from 'fs-extra'; import { bindingPath } from '../../binding/index.js'; import { OutputConfig } from '../types/binding.js'; @@ -14,7 +15,8 @@ import { resolveAsyncPlugins, resolveConfigHook, resolveConfigResolvedHook, - resolveFarmPlugins + resolveFarmPlugins, + rustPluginResolver } from '../plugin/index.js'; import { Server } from '../server/index.js'; import { @@ -59,7 +61,8 @@ import { mergeConfig, mergeFarmCliConfig } from './mergeConfig.js'; import { normalizeExternal } from './normalize-config/normalize-external.js'; import type { Alias, - FarmCLIOptions, + ConfigEnv, + FarmCliOptions, NormalizedServerConfig, ResolvedCompilation, ResolvedUserConfig, @@ -84,40 +87,7 @@ export function defineFarmConfig(config: UserConfigExport): UserConfigExport { return config; } -async function getDefaultConfig( - config: UserConfig, - inlineOptions: FarmCLIOptions, - mode?: CompilationMode, - logger?: Logger -) { - logger = logger ?? new Logger(); - const resolvedUserConfig = await resolveMergedUserConfig( - config, - undefined, - inlineOptions.mode ?? mode, - logger - ); - - resolvedUserConfig.server = normalizeDevServerConfig( - inlineOptions.server, - mode - ); - - resolvedUserConfig.compilation = await normalizeUserCompilationConfig( - resolvedUserConfig, - config, - logger, - mode, - true - ); - resolvedUserConfig.root = resolvedUserConfig.compilation.root; - resolvedUserConfig.jsPlugins = []; - resolvedUserConfig.rustPlugins = []; - - return resolvedUserConfig; -} - -async function handleServerPortConflict( +export async function handleServerPortConflict( resolvedUserConfig: ResolvedUserConfig, logger: Logger, mode?: CompilationMode @@ -136,45 +106,46 @@ async function handleServerPortConflict( * @param configPath */ export async function resolveConfig( - inlineOptions: FarmCLIOptions & UserConfig = {}, - mode?: CompilationMode, - logger?: Logger, - isHandleServerPortConflict = true + inlineOptions: FarmCliOptions & UserConfig, + command: 'start' | 'build' | 'preview', + defaultMode: CompilationMode = 'development', + defaultNodeEnv: CompilationMode = 'development', + isPreview = false, + logger?: Logger ): Promise { - // Clear the console according to the cli command - - checkClearScreen(inlineOptions); logger = logger ?? new Logger(); + + let mode = defaultMode; + const envMode = inlineOptions.mode || defaultMode; + const isNodeEnvSet = !!process.env.NODE_ENV; inlineOptions.mode = inlineOptions.mode ?? mode; + if (!isNodeEnvSet) { + setProcessEnv(defaultNodeEnv); + } + + const configEnv: ConfigEnv = { + mode: envMode, + command, + isPreview + }; + // configPath may be file or directory let { configPath } = inlineOptions; - let rawConfig: UserConfig = mergeFarmCliConfig(inlineOptions, {}); - - // if the config file can not found, just merge cli options and return default - if (configPath) { - if (!path.isAbsolute(configPath)) { - throw new Error('configPath must be an absolute path'); - } + const { configFile } = inlineOptions; + const loadedUserConfig: any = await loadConfigFile( + configFile, + inlineOptions, + configEnv + ); - const loadedUserConfig = await loadConfigFile( - configPath, - inlineOptions, - logger - ); + let rawConfig: UserConfig = mergeFarmCliConfig(inlineOptions, {}); - if (loadedUserConfig) { - configPath = loadedUserConfig.configFilePath; - rawConfig = mergeConfig(rawConfig, loadedUserConfig.config); - } - rawConfig.compilation.mode = - loadedUserConfig?.config?.compilation?.mode ?? mode; - } else { - mergeConfig( - rawConfig, - await getDefaultConfig(rawConfig, inlineOptions, mode, logger) - ); + if (loadedUserConfig) { + configPath = loadedUserConfig.configFilePath; + rawConfig = mergeConfig(rawConfig, loadedUserConfig.config); } - + rawConfig.compilation.mode = + loadedUserConfig?.config?.compilation?.mode ?? mode; const { config: userConfig, configFilePath } = { configFilePath: configPath, config: rawConfig @@ -193,38 +164,33 @@ export async function resolveConfig( const mergedUserConfig = mergeFarmCliConfig(inlineOptions, config); - const resolvedUserConfig = await resolveMergedUserConfig( + const resolvedUserConfig = await resolveUserConfig( mergedUserConfig, configFilePath, - inlineOptions.mode ?? mode, + mode, logger ); - // normalize server config first cause it may be used in normalizeUserCompilationConfig + // normalize server config first cause it may be used in normalizeUserCompilationFnConfig resolvedUserConfig.server = normalizeDevServerConfig( resolvedUserConfig.server, mode ); - if (isHandleServerPortConflict) { - await handleServerPortConflict(resolvedUserConfig, logger, mode); - } + // if (isHandleServerPortConflict) { + // await handleServerPortConflict(resolvedUserConfig, logger, mode); + // } resolvedUserConfig.compilation = await normalizeUserCompilationConfig( resolvedUserConfig, - mergedUserConfig, - logger, - mode + 'development' ); - // normalize root path - resolvedUserConfig.root = normalizeBasePath( - resolvedUserConfig.compilation.root - ); + resolvedUserConfig.root = resolvedUserConfig.compilation.root; resolvedUserConfig.jsPlugins = sortFarmJsPlugins; resolvedUserConfig.rustPlugins = rustPlugins; - // Temporarily dealing with alias objects and arrays in js will be unified in rust in the future.] + // // Temporarily dealing with alias objects and arrays in js will be unified in rust in the future.] if (vitePlugins.length) { resolvedUserConfig.compilation.resolve.alias = getAliasEntries( resolvedUserConfig.compilation.resolve.alias @@ -233,7 +199,7 @@ export async function resolveConfig( await resolveConfigResolvedHook(resolvedUserConfig, sortFarmJsPlugins); // Fix: Await the Promise and pass the resolved value to the function. - // TODO Temporarily solve the problem of alias adaptation to vite + // // TODO Temporarily solve the problem of alias adaptation to vite if (resolvedUserConfig.compilation?.resolve?.alias && vitePlugins.length) { resolvedUserConfig.compilation.resolve.alias = transformAliasWithVite( resolvedUserConfig.compilation.resolve.alias as unknown as Array @@ -258,26 +224,22 @@ export async function resolveConfig( */ export async function normalizeUserCompilationConfig( resolvedUserConfig: ResolvedUserConfig, - userConfig: UserConfig, - logger: Logger, mode: CompilationMode = 'development', - isDefault = false + logger: Logger = new Logger() ): Promise { - const { compilation, root = process.cwd(), clearScreen } = resolvedUserConfig; + const { compilation, root, clearScreen } = resolvedUserConfig; // resolve root path + const resolvedRootPath = normalizePath(root); resolvedUserConfig.root = resolvedRootPath; - if (!userConfig.compilation) { - userConfig.compilation = {}; - } - // if normalize default config, skip check input option - const inputIndexConfig = !isDefault - ? checkCompilationInputValue(userConfig, logger) - : {}; + const inputIndexConfig = checkCompilationInputValue( + resolvedUserConfig, + logger + ); const resolvedCompilation: ResolvedCompilation = merge( {}, @@ -299,7 +261,7 @@ export async function normalizeUserCompilationConfig( resolvedCompilation.coreLibPath = bindingPath; normalizeOutput(resolvedCompilation, isProduction, logger); - normalizeExternal(userConfig, resolvedCompilation); + normalizeExternal(resolvedUserConfig, resolvedCompilation); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore do not check type for this internal option @@ -387,9 +349,9 @@ export async function normalizeUserCompilationConfig( if (!resolvedCompilation.runtime.namespace) { // read package.json name field const packageJsonPath = path.resolve(resolvedRootPath, 'package.json'); - const packageJsonExists = fs.existsSync(packageJsonPath); + const packageJsonExists = fse.existsSync(packageJsonPath); const namespaceName = packageJsonExists - ? JSON.parse(fs.readFileSync(packageJsonPath, { encoding: 'utf-8' })) + ? JSON.parse(fse.readFileSync(packageJsonPath, { encoding: 'utf-8' })) ?.name ?? FARM_DEFAULT_NAMESPACE : FARM_DEFAULT_NAMESPACE; @@ -413,12 +375,14 @@ export async function normalizeUserCompilationConfig( resolvedCompilation.mode = mode; } setProcessEnv(resolvedCompilation.mode); + // TODO add targetEnv `lib-browser` and `lib-node` support const is_entry_html = Object.keys(resolvedCompilation.input).length === 0 || - Object.values(resolvedCompilation.input).some((value) => - value.endsWith('.html') - ); + Object.values(resolvedCompilation.input) + .filter(Boolean) + .some((value) => value.endsWith('.html')); + if ( resolvedCompilation.output.targetEnv !== 'node' && isArray(resolvedCompilation.runtime.plugins) && @@ -546,10 +510,6 @@ export async function normalizeUserCompilationConfig( }; if (resolvedCompilation.script.parser.tsConfig !== undefined) resolvedCompilation.script.parser.tsConfig.decorators = true; - else - userConfig.compilation.script.parser.tsConfig = { - decorators: true - }; } // normalize persistent cache at last @@ -563,7 +523,7 @@ export async function normalizeUserCompilationConfig( } export const DEFAULT_HMR_OPTIONS: Required = { - host: true, + host: 'localhost', port: (process.env.FARM_DEFAULT_HMR_PORT && Number(process.env.FARM_DEFAULT_HMR_PORT)) ?? @@ -571,7 +531,11 @@ export const DEFAULT_HMR_OPTIONS: Required = { path: '/__hmr', overlay: true, protocol: 'ws', - watchOptions: {} + watchOptions: {}, + clientPort: 9000, + timeout: 0, + server: null, + channels: [] }; export const DEFAULT_DEV_SERVER_OPTIONS: NormalizedServerConfig = { @@ -586,6 +550,7 @@ export const DEFAULT_DEV_SERVER_OPTIONS: NormalizedServerConfig = { host: true, proxy: {}, hmr: DEFAULT_HMR_OPTIONS, + middlewareMode: false, open: false, strictPort: false, cors: false, @@ -673,12 +638,13 @@ const formatToExt: Record = { esm: 'mjs' }; -async function readConfigFile( - inlineOptions: FarmCLIOptions, +export async function readConfigFile( + inlineOptions: FarmCliOptions, configFilePath: string, + configEnv: any, logger: Logger ): Promise { - if (fs.existsSync(configFilePath)) { + if (fse.existsSync(configFilePath)) { !__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ && logger.info(`Using config file at ${bold(green(configFilePath))}`); const format: Format = process.env.FARM_CONFIG_FORMAT @@ -686,8 +652,10 @@ async function readConfigFile( ? 'cjs' : 'esm' : formatFromExt[path.extname(configFilePath).slice(1)] ?? 'esm'; + // we need transform all type farm.config with __dirname and __filename const Compiler = (await import('../compiler/index.js')).Compiler; + const outputPath = path.join( path.dirname(configFilePath), 'node_modules', @@ -699,67 +667,25 @@ async function readConfigFile( .split('.') .join('')}.${formatToExt[format]}`; - const tsDefaultUserConfig: UserConfig = { - root: inlineOptions.root, - compilation: { - input: { - [fileName]: configFilePath - }, - output: { - entryFilename: '[entryName]', - path: outputPath, - format, - targetEnv: 'node' - }, - external: [ - ...(process.env.FARM_CONFIG_FULL_BUNDLE - ? [] - : ['!^(\\./|\\.\\./|[A-Za-z]:\\\\|/).*']), - '^@farmfe/core$' - ], - partialBundling: { - enforceResources: [ - { - name: fileName, - test: ['.+'] - } - ] - }, - watch: false, - sourcemap: false, - treeShaking: false, - minify: false, - presetEnv: false, - lazyCompilation: false, - persistentCache: false, - progress: false - } - }; - - const tsDefaultResolvedUserConfig: ResolvedUserConfig = - await resolveMergedUserConfig( - tsDefaultUserConfig, - undefined, - 'development', - logger - ); - - const normalizedConfig = await normalizeUserCompilationConfig( - tsDefaultResolvedUserConfig, - tsDefaultUserConfig, - logger, - 'development' + const normalizedConfig = await resolveDefaultUserConfig({ + inlineOptions, + configFilePath, + format, + outputPath, + fileName + }); + + const replaceDirnamePlugin = await rustPluginResolver( + 'farm-plugin-replace-dirname', + // normalizedConfig.root!, + process.cwd() ); - const replaceDirnamePlugin = await import( - 'farm-plugin-replace-dirname' - ).then((mod) => mod.default); - const compiler = new Compiler( { config: normalizedConfig, jsPlugins: [], - rustPlugins: [[replaceDirnamePlugin, '{}']] + rustPlugins: [replaceDirnamePlugin] }, logger ); @@ -784,17 +710,11 @@ async function readConfigFile( // Change to vm.module of node or loaders as far as it is stable const userConfig = (await import(filePath as string)).default; try { - fs.unlink(filePath, () => void 0); - // remove parent dir if empty - const isEmpty = fs.readdirSync(outputPath).length === 0; - if (isEmpty) { - fs.rmSync(outputPath); - } + fse.unlink(filePath, () => void 0); } catch { /** do nothing */ } - const configEnv = { mode: inlineOptions.mode ?? process.env.NODE_ENV }; const config = await (typeof userConfig === 'function' ? userConfig(configEnv) : userConfig); @@ -819,7 +739,7 @@ export function normalizePublicDir(root: string, userPublicDir?: string) { } export function checkClearScreen( - inlineConfig: FarmCLIOptions | ResolvedUserConfig + inlineConfig: FarmCliOptions | ResolvedUserConfig ) { if ( inlineConfig?.clearScreen && @@ -891,26 +811,31 @@ export async function resolveMergedUserConfig( * @returns loaded config and config file path */ export async function loadConfigFile( - configPath: string, - inlineOptions: FarmCLIOptions, + configFile: string, + inlineOptions: any, + configEnv: any, logger: Logger = new Logger() -): Promise<{ config: UserConfig; configFilePath: string } | undefined> { - // if configPath points to a directory, try to find a config file in it using default config +): Promise<{ config: any; configFilePath: string } | undefined> { + const { root = '.' } = inlineOptions; + const configRootPath = path.resolve(root); + let resolvedPath: string | undefined; try { - const configFilePath = await getConfigFilePath(configPath); - - if (configFilePath) { - const config = await readConfigFile( - inlineOptions, - configFilePath, - logger - ); - - return { - config: config && parseUserConfig(config), - configFilePath: configFilePath - }; + if (configFile) { + resolvedPath = path.resolve(root, configFile); + } else { + resolvedPath = await getConfigFilePath(configRootPath); } + + const config = await readConfigFile( + inlineOptions, + resolvedPath, + configEnv, + logger + ); + return { + config: config && parseUserConfig(config), + configFilePath: resolvedPath + }; } catch (error) { // In this place, the original use of throw caused emit to the outermost catch // callback, causing the code not to execute. If the internal catch compiler's own @@ -919,7 +844,6 @@ export async function loadConfigFile( const errorMessage = convertErrorMessage(error); const stackTrace = error.code === 'GenericFailure' ? '' : `\n${error.stack}`; - if (inlineOptions.mode === 'production') { logger.error( `Failed to load config file: ${errorMessage} \n${stackTrace}`, @@ -928,10 +852,8 @@ export async function loadConfigFile( } ); } - const potentialSolution = 'Potential solutions: \n1. Try set `FARM_CONFIG_FORMAT=cjs`(default to esm)\n2. Try set `FARM_CONFIG_FULL_BUNDLE=1`'; - throw new Error( `Failed to load farm config file: ${errorMessage}. \n ${potentialSolution} \n ${error.stack}` ); @@ -941,13 +863,14 @@ export async function loadConfigFile( function checkCompilationInputValue(userConfig: UserConfig, logger: Logger) { const { compilation } = userConfig; const targetEnv = compilation?.output?.targetEnv; + const inputValue = Object.values(compilation?.input).filter(Boolean); const isTargetNode = targetEnv === 'node'; const defaultHtmlPath = './index.html'; let inputIndexConfig: { index?: string } = { index: '' }; let errorMessage = ''; // Check if input is specified - if (!isEmptyObject(compilation?.input)) { + if (!isEmptyObject(compilation?.input) && inputValue.length) { inputIndexConfig = compilation?.input; } else { if (isTargetNode) { @@ -989,22 +912,19 @@ function checkCompilationInputValue(userConfig: UserConfig, logger: Logger) { } export async function getConfigFilePath( - configPath: string + configRootPath: string ): Promise { - if (fs.statSync(configPath).isDirectory()) { + if (fse.statSync(configRootPath).isDirectory()) { for (const name of DEFAULT_CONFIG_NAMES) { - const resolvedPath = path.join(configPath, name); + const resolvedPath = path.join(configRootPath, name); const isFile = - fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isFile(); + fse.existsSync(resolvedPath) && fse.statSync(resolvedPath).isFile(); if (isFile) { return resolvedPath; } } - } else if (fs.statSync(configPath).isFile()) { - return configPath; } - return undefined; } @@ -1037,3 +957,108 @@ export async function resolvePlugins( vitePluginAdapters }; } + +export async function resolveDefaultUserConfig(options: any) { + const { inlineOptions, format, outputPath, fileName, configFilePath } = + options; + const defaultConfig: UserConfig = { + root: path.resolve(inlineOptions.root ?? '.'), + compilation: { + input: { + [fileName]: configFilePath + }, + output: { + entryFilename: '[entryName]', + path: outputPath, + format, + targetEnv: 'node' + }, + external: [ + ...(process.env.FARM_CONFIG_FULL_BUNDLE + ? [] + : ['!^(\\./|\\.\\./|[A-Za-z]:\\\\|/).*']), + '^@farmfe/core$' + ], + partialBundling: { + enforceResources: [ + { + name: fileName, + test: ['.+'] + } + ] + }, + watch: false, + sourcemap: false, + treeShaking: false, + minify: false, + presetEnv: false, + lazyCompilation: false, + persistentCache: false, + progress: false + } + }; + + const resolvedUserConfig: ResolvedUserConfig = await resolveUserConfig( + defaultConfig, + undefined, + 'development' + ); + + const normalizedConfig = await normalizeUserCompilationConfig( + resolvedUserConfig, + 'development' + ); + + return normalizedConfig; +} + +export async function resolveUserConfig( + userConfig: UserConfig, + configFilePath: string | undefined, + mode: 'development' | 'production' | string, + logger: Logger = new Logger() +): Promise { + const resolvedUserConfig = { + ...userConfig + } as ResolvedUserConfig; + + // set internal config + resolvedUserConfig.envMode = mode; + + if (configFilePath) { + const dependencies = await traceDependencies(configFilePath, logger); + dependencies.sort(); + resolvedUserConfig.configFileDependencies = dependencies; + resolvedUserConfig.configFilePath = configFilePath; + } + + const resolvedRootPath = resolvedUserConfig.root ?? process.cwd(); + const resolvedEnvPath = resolvedUserConfig.envDir + ? resolvedUserConfig.envDir + : resolvedRootPath; + + const userEnv = loadEnv( + resolvedUserConfig.envMode ?? mode, + resolvedEnvPath, + resolvedUserConfig.envPrefix + ); + const existsEnvFiles = getExistsEnvFiles( + resolvedUserConfig.envMode ?? mode, + resolvedEnvPath + ); + + resolvedUserConfig.envFiles = [ + ...(Array.isArray(resolvedUserConfig.envFiles) + ? resolvedUserConfig.envFiles + : []), + ...existsEnvFiles + ]; + + resolvedUserConfig.env = { + ...userEnv, + NODE_ENV: userConfig.compilation.mode ?? mode, + mode: mode + }; + + return resolvedUserConfig; +} diff --git a/packages/core/src/config/mergeConfig.ts b/packages/core/src/config/mergeConfig.ts index f58578b625..d7cfc9f18d 100644 --- a/packages/core/src/config/mergeConfig.ts +++ b/packages/core/src/config/mergeConfig.ts @@ -1,7 +1,7 @@ import path, { isAbsolute } from 'node:path'; import { isString } from '../plugin/js/utils.js'; import { isArray, isObject } from '../utils/share.js'; -import { FarmCLIOptions, UserConfig } from './types.js'; +import { FarmCliOptions, UserConfig } from './types.js'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function mergeConfig>( @@ -46,10 +46,11 @@ export function mergeConfig>( } export function mergeFarmCliConfig( - cliOption: FarmCLIOptions & UserConfig, + cliOption: FarmCliOptions & UserConfig, target: UserConfig ): UserConfig { let left: UserConfig = {}; + const options = initialCliOptions(cliOption); ( [ @@ -62,10 +63,10 @@ export function mergeFarmCliConfig( 'server', 'vitePlugins' ] satisfies (keyof UserConfig)[] - ).forEach((key) => { - const value = cliOption[key]; + ).forEach((key: keyof (FarmCliOptions & UserConfig)) => { + const value = options[key]; if (value || typeof value === 'boolean') { - left = mergeConfig(left, { [key]: cliOption[key] }); + left = mergeConfig(left, { [key]: options[key] }); } }); @@ -77,12 +78,12 @@ export function mergeFarmCliConfig( const cliRoot = cliOption.root; if (!isAbsolute(cliRoot)) { - target.root = path.resolve(process.cwd(), cliRoot); + target.root = path.resolve(cliRoot); } else { target.root = cliRoot; } } else { - target.root = process.cwd(); + target.root = path.resolve('.'); } if (configRootPath) { @@ -95,55 +96,87 @@ export function mergeFarmCliConfig( } } - if (isString(cliOption.host) || typeof cliOption.host === 'boolean') { - left = mergeConfig(left, { server: { host: cliOption.host } }); + if (isString(options.host) || typeof options.host === 'boolean') { + left = mergeConfig(left, { server: { host: options.host } }); } - if (typeof cliOption.minify === 'boolean') { - left = mergeConfig(left, { compilation: { minify: cliOption.minify } }); + if (typeof options.minify === 'boolean') { + left = mergeConfig(left, { compilation: { minify: options.minify } }); } - if (cliOption.outDir) { + if (options.outDir) { left = mergeConfig(left, { - compilation: { output: { path: cliOption.outDir } } + compilation: { output: { path: options.outDir } } }); } - if (cliOption.port) { + if (options.port) { left = mergeConfig(left, { server: { - port: cliOption.port + port: options.port } }); } - if (cliOption.mode) { + if (options.mode) { left = mergeConfig(left, { compilation: { - mode: cliOption.mode as UserConfig['compilation']['mode'] + mode: options.mode as UserConfig['compilation']['mode'] } }); } - if (cliOption.https) { + if (options.https) { left = mergeConfig(left, { server: { - https: cliOption.https + https: options.https } }); } - if (cliOption.sourcemap) { + if (options.sourcemap) { left = mergeConfig(left, { - compilation: { sourcemap: cliOption.sourcemap } + compilation: { sourcemap: options.sourcemap } }); } return mergeConfig(left, target); } -export function initialCliOptions(options: FarmCLIOptions): FarmCLIOptions { - return { - ...options +export function initialCliOptions(options: any): any { + const { + input, + outDir, + target, + format, + watch, + minify, + sourcemap, + treeShaking, + mode + } = options; + + const output: UserConfig['compilation']['output'] = { + ...(outDir && { path: outDir }), + ...(target && { targetEnv: target }), + ...(format && { format }) }; + + const compilation: UserConfig['compilation'] = { + input: { ...(input && { index: input }) }, + output, + ...(watch && { watch }), + ...(minify && { minify }), + ...(sourcemap && { sourcemap }), + ...(treeShaking && { treeShaking }) + }; + + const defaultOptions: any = { + compilation, + root: options.root, + configFile: options.configFile, + ...(mode && { mode }) + }; + + return defaultOptions; } diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index 6e47e2e991..433056ba07 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -44,8 +44,7 @@ const compilationConfigSchema = z 'browser-legacy', 'browser-esnext', 'browser-es2015', - 'browser-es2017', - 'library' + 'browser-es2017' ]) .optional(), format: z.enum(['cjs', 'esm']).optional() @@ -217,8 +216,7 @@ const compilationConfigSchema = z z.literal('minify-module'), z.literal('minify-resource-pot') ]) - .optional(), - moduleDecls: z.boolean().optional() + .optional() }) ]) .optional(), diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index d736ff7616..96e454f1ab 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -10,9 +10,12 @@ import type { RustPlugin } from '../plugin/rust/index.js'; import type { JsPlugin } from '../plugin/type.js'; import type { Options } from 'http-proxy-middleware'; import type { Logger } from '../utils/index.js'; +import { HmrOptions } from '../newServer/index.js'; export interface ConfigEnv { mode: string; + command: string; + isPreview: boolean; } export type UserConfigFnPromise = (env: ConfigEnv) => Promise; @@ -42,6 +45,7 @@ export interface UserServerConfig { // whether to serve static assets in spa mode, default to true spa?: boolean; middlewares?: DevServerMiddleware[]; + middlewareMode?: boolean | string; writeToDisk?: boolean; } @@ -56,7 +60,7 @@ export interface UserPreviewServerConfig { export type NormalizedServerConfig = Required< Omit & { - hmr?: Required; + hmr?: UserHmrConfig; } >; @@ -65,12 +69,7 @@ export interface NormalizedConfig { serverConfig?: NormalizedServerConfig; } -export interface UserHmrConfig { - host?: string | boolean; - port?: number; - path?: string; - overlay?: boolean; - protocol?: string; +export interface UserHmrConfig extends HmrOptions { watchOptions?: WatchOptions; } @@ -125,7 +124,7 @@ export interface ResolvedUserConfig extends UserConfig { rustPlugins?: [string, string][]; } -export interface GlobalFarmCLIOptions { +export interface GlobalCliOptions { '--'?: string[]; c?: boolean | string; config?: string; @@ -155,11 +154,12 @@ export interface FarmCLIPreviewOptions { host?: string | boolean; } -export interface FarmCLIOptions +export interface FarmCliOptions extends FarmCLIBuildOptions, - FarmCLIPreviewOptions { + FarmCLIPreviewOptions { logger?: Logger; config?: string; + configFile?: string; configPath?: string; compilation?: Config['config']; mode?: string; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 3fac0c1c2a..ddfe375bbe 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -46,7 +46,7 @@ import { FileWatcher } from './watcher/index.js'; import { __FARM_GLOBAL__ } from './config/_global.js'; import type { - FarmCLIOptions, + FarmCliOptions, ResolvedUserConfig, UserPreviewServerConfig } from './config/types.js'; @@ -58,7 +58,7 @@ import { ConfigWatcher } from './watcher/config-watcher.js'; import type { JsPlugin } from './plugin/type.js'; export async function start( - inlineConfig?: FarmCLIOptions & UserConfig + inlineConfig?: FarmCliOptions & UserConfig ): Promise { inlineConfig = inlineConfig ?? {}; const logger = inlineConfig.logger ?? new Logger(); @@ -67,10 +67,42 @@ export async function start( try { const resolvedUserConfig = await resolveConfig( inlineConfig, + 'start', 'development', + 'development', + false + ); + + const compiler = await createCompiler(resolvedUserConfig, logger); + + const devServer = await createDevServer( + compiler, + resolvedUserConfig, logger ); + await devServer.listen(); + } catch (error) { + logger.error('Failed to start the server', { exit: true, error }); + } +} + +export async function startRefactorCli( + inlineConfig?: FarmCliOptions & UserConfig +): Promise { + inlineConfig = inlineConfig ?? {}; + const logger = inlineConfig.logger ?? new Logger(); + setProcessEnv('development'); + + try { + const resolvedUserConfig = await resolveConfig( + inlineConfig, + 'start', + 'development', + 'development', + false + ); + const compiler = await createCompiler(resolvedUserConfig, logger); const devServer = await createDevServer( @@ -85,8 +117,32 @@ export async function start( } } +export async function buildRefactorCli( + inlineConfig?: FarmCliOptions & UserConfig +): Promise { + inlineConfig = inlineConfig ?? {}; + const logger = inlineConfig.logger ?? new Logger(); + setProcessEnv('production'); + + try { + const resolvedUserConfig = await resolveConfig( + inlineConfig, + 'build', + 'production', + 'production', + false + ); + + await createBundleHandler(resolvedUserConfig, logger); + // copy resources under publicDir to output.path + await copyPublicDirectory(resolvedUserConfig, logger); + } catch (err) { + logger.error(`Failed to build: ${err}`, { exit: true }); + } +} + export async function build( - inlineConfig?: FarmCLIOptions & UserConfig + inlineConfig?: FarmCliOptions & UserConfig ): Promise { inlineConfig = inlineConfig ?? {}; const logger = inlineConfig.logger ?? new Logger(); @@ -94,8 +150,9 @@ export async function build( const resolvedUserConfig = await resolveConfig( inlineConfig, + 'build', + 'production', 'production', - logger, false ); @@ -108,13 +165,15 @@ export async function build( } } -export async function preview(inlineConfig?: FarmCLIOptions): Promise { +export async function preview(inlineConfig?: FarmCliOptions): Promise { inlineConfig = inlineConfig ?? {}; const logger = inlineConfig.logger ?? new Logger(); const resolvedUserConfig = await resolveConfig( inlineConfig, + 'preview', 'production', - logger + 'production', + true ); const { root, output } = resolvedUserConfig.compilation; @@ -154,7 +213,7 @@ export async function preview(inlineConfig?: FarmCLIOptions): Promise { } export async function watch( - inlineConfig?: FarmCLIOptions & UserConfig + inlineConfig?: FarmCliOptions & UserConfig ): Promise { inlineConfig = inlineConfig ?? {}; const logger = inlineConfig.logger ?? new Logger(); @@ -162,9 +221,10 @@ export async function watch( const resolvedUserConfig = await resolveConfig( inlineConfig, - 'development', - logger, - true + 'build', + 'production', + 'production', + false ); const hostname = await resolveHostname(resolvedUserConfig.server.host); @@ -428,7 +488,7 @@ export async function createFileWatcher( await devServer.close(); __FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ = true; - await start(resolvedUserConfig as FarmCLIOptions & UserConfig); + await start(resolvedUserConfig as FarmCliOptions & UserConfig); }); }); return fileWatcher; diff --git a/packages/core/src/utils/share.ts b/packages/core/src/utils/share.ts index a086b26c94..5a6316f87d 100644 --- a/packages/core/src/utils/share.ts +++ b/packages/core/src/utils/share.ts @@ -71,7 +71,7 @@ export const version = JSON.parse( ).version; export function normalizePath(id: string): string { - return path.posix.normalize(id); + return path.posix.normalize(isWindows ? id.replace(/\\/g, '/') : id); } export function normalizeBasePath(basePath: string): string { diff --git a/packages/core/tests/common.ts b/packages/core/tests/common.ts index ef997a5a74..72346e06c0 100644 --- a/packages/core/tests/common.ts +++ b/packages/core/tests/common.ts @@ -49,9 +49,8 @@ export async function getCompiler( const compilationConfig = await normalizeUserCompilationConfig( resolvedUserConfig, - userConfig, - new Logger(), - 'production' + 'production', + new Logger() ); return new Compiler({ diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json index 4f16e9c00d..0646acb6ca 100644 --- a/packages/core/tsconfig.build.json +++ b/packages/core/tsconfig.build.json @@ -7,7 +7,7 @@ "noUnusedLocals": false }, "include": ["src/**/*.ts", "binding/**/*.d.ts"], - "exclude": ["src/**/*.spec.ts", "src/newServer/**"], + "exclude": ["src/**/*.spec.ts"], "references": [ { "path": "../utils/tsconfig.json" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4983d91ea0..78c3036726 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1104,6 +1104,37 @@ importers: specifier: ^0.14.0 version: 0.14.0 + examples/refactor-react: + dependencies: + react: + specifier: '18' + version: 18.2.0 + react-dom: + specifier: '18' + version: 18.2.0(react@18.2.0) + devDependencies: + '@farmfe/cli': + specifier: workspace:* + version: link:../../packages/cli + '@farmfe/core': + specifier: workspace:* + version: link:../../packages/core + '@farmfe/plugin-react': + specifier: ^1.2.0 + version: 1.2.0 + '@types/react': + specifier: '18' + version: 18.2.35 + '@types/react-dom': + specifier: '18' + version: 18.2.14 + core-js: + specifier: ^3.36.1 + version: 3.36.1 + react-refresh: + specifier: ^0.14.0 + version: 0.14.0 + examples/resolve-module-graph: devDependencies: '@farmfe/cli': @@ -3826,6 +3857,63 @@ packages: engines: {node: '>= 16'} hasBin: true + '@farmfe/plugin-react-darwin-arm64@1.2.0': + resolution: {integrity: sha512-9a8wp6lg8NytO+kU8hu2gCFer+PL4TJ92SkU/5v9xdcsioElWpnMDGlwcoI8bXqi60/WR8RyExsDIBubCgjbXQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@farmfe/plugin-react-darwin-x64@1.2.0': + resolution: {integrity: sha512-JXkdg3zmevlf+kbdd05+6x+L/l2IYY7Vm4hqkymbxlVdaFd2ydHmyMk9ekcmtkOijlUtEYoD3a9whstzvJ+FkA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@farmfe/plugin-react-linux-arm64-gnu@1.2.0': + resolution: {integrity: sha512-B98ldEqeJn6Uesnxr13Y/nFfIP4Qr8Svcd3mJqaOFcaOF9OZvRYFvQha1DRAoBrp8VhntghijqoWJeC1qKUhKw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@farmfe/plugin-react-linux-arm64-musl@1.2.0': + resolution: {integrity: sha512-o49P/vCWlqAkFeIVtZqy1OyyIlGHr2w+O9ty5ojwMGXGXHOrvBi1IL2ItlFqxUawweli6mNspDO0bJSJZ51gOw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@farmfe/plugin-react-linux-x64-gnu@1.2.0': + resolution: {integrity: sha512-Z1hX52mHllxXn6GolTpuN3sqmz8yku6N/rs0NHbjezgyRPWFWOMS7fnD6SMf/TPvRPGeRX1bj49rr9GMqsQEgQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@farmfe/plugin-react-linux-x64-musl@1.2.0': + resolution: {integrity: sha512-eZzEE9eCeteIpsQr1u4dnFzEEisYuuUIVhbNZX8mPCBYQ9ZB6RXMZYj3lmHgl3qNGagxH26msqcpr7t3U8qPuQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@farmfe/plugin-react-win32-arm64-msvc@1.2.0': + resolution: {integrity: sha512-JluDXSQFs3s5txZghCbeqdOjtocSW4vaoQWgcQQ88zpFrTlqqwg4xnrXdeC3CqgeNcVq5ZMJtx2VwsJqITvPxg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@farmfe/plugin-react-win32-ia32-msvc@1.2.0': + resolution: {integrity: sha512-b6I+qSG8+a59YE0d2J+QLWDi5qxQUY1C/TeYvGUBeoOs7/pCKdznvd2eQJ5N9Yvafzn6zlN9//oz1A/VLvqSBg==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@farmfe/plugin-react-win32-x64-msvc@1.2.0': + resolution: {integrity: sha512-9GWEdbvGUB+ovdAAQhHF7l4v0MaTXjOIoQZd4g6+rGDQtMIx4d1M6EOPx4D1Yn9/+dI1157UWWt9PK9Lod2h+w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@farmfe/plugin-react@1.2.0': + resolution: {integrity: sha512-S5kU7NgqiyyhnDsZ7DEvszQIE6sCA0CNp7oTbdDcPxotPNBoyOcBHviSP3P5jvtIv6mmlF8Me2C1aLWJQRw9PA==} + '@farmfe/plugin-sass-darwin-arm64@1.0.5': resolution: {integrity: sha512-0uRiWqEjLUjEQbaJBXFKCdp4P++00vHE0WegUBgA7KpnQ8USWBJEJ+AQklBKuZitjpqlkJqCsq09bEmkwzEVYA==} engines: {node: '>= 10'} @@ -6208,12 +6296,12 @@ packages: builder-util@24.13.1: resolution: {integrity: sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==} - bundle-name@4.1.0: - resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} - engines: {node: '>=18'} bundle-n-require@1.1.1: resolution: {integrity: sha512-EB2wFjXF106LQLe/CYnKCMCdLeTW47AtcEtUfiqAOgr2a08k0+YgRklur2aLfEYHlhz6baMskZ8L2U92Hh0vyA==} + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} @@ -15383,6 +15471,45 @@ snapshots: inquirer: 9.2.11 walkdir: 0.4.1 + '@farmfe/plugin-react-darwin-arm64@1.2.0': + optional: true + + '@farmfe/plugin-react-darwin-x64@1.2.0': + optional: true + + '@farmfe/plugin-react-linux-arm64-gnu@1.2.0': + optional: true + + '@farmfe/plugin-react-linux-arm64-musl@1.2.0': + optional: true + + '@farmfe/plugin-react-linux-x64-gnu@1.2.0': + optional: true + + '@farmfe/plugin-react-linux-x64-musl@1.2.0': + optional: true + + '@farmfe/plugin-react-win32-arm64-msvc@1.2.0': + optional: true + + '@farmfe/plugin-react-win32-ia32-msvc@1.2.0': + optional: true + + '@farmfe/plugin-react-win32-x64-msvc@1.2.0': + optional: true + + '@farmfe/plugin-react@1.2.0': + optionalDependencies: + '@farmfe/plugin-react-darwin-arm64': 1.2.0 + '@farmfe/plugin-react-darwin-x64': 1.2.0 + '@farmfe/plugin-react-linux-arm64-gnu': 1.2.0 + '@farmfe/plugin-react-linux-arm64-musl': 1.2.0 + '@farmfe/plugin-react-linux-x64-gnu': 1.2.0 + '@farmfe/plugin-react-linux-x64-musl': 1.2.0 + '@farmfe/plugin-react-win32-arm64-msvc': 1.2.0 + '@farmfe/plugin-react-win32-ia32-msvc': 1.2.0 + '@farmfe/plugin-react-win32-x64-msvc': 1.2.0 + '@farmfe/plugin-sass-darwin-arm64@1.0.5': optional: true @@ -16214,9 +16341,8 @@ snapshots: '@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.1(svelte@4.0.0)(vite@5.2.8(@types/node@20.12.12)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1)))(svelte@4.0.0)(vite@5.2.8(@types/node@20.12.12)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.0.1(svelte@4.0.0)(vite@5.2.8(@types/node@20.12.12)(less@4.2.0)(sass@1.74.1)(terser@5.31.1)) + '@sveltejs/vite-plugin-svelte': 3.0.1(svelte@4.0.0)(vite@5.2.8(@types/node@20.12.12)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1)) debug: 4.3.5 - svelte: 4.0.0 vite: 5.2.8(@types/node@20.12.12)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1) transitivePeerDependencies: @@ -19218,7 +19344,7 @@ snapshots: dependencies: esbuild: 0.20.2 node-eval: 2.0.0 - + bundle-name@4.1.0: dependencies: run-applescript: 7.0.0 From fa0dfcdc6712dede7cee7aa3306b0e89298eaf69 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Tue, 23 Jul 2024 15:54:34 +0800 Subject: [PATCH 004/369] chore: optimize code --- packages/core/src/server/middlewares/cors.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/server/middlewares/cors.ts b/packages/core/src/server/middlewares/cors.ts index d3c26bf2c9..b85b182d6a 100644 --- a/packages/core/src/server/middlewares/cors.ts +++ b/packages/core/src/server/middlewares/cors.ts @@ -7,3 +7,5 @@ export function cors(devSeverContext: Server): Middleware { if (!config.cors) return; return koaCors(typeof config.cors === 'boolean' ? {} : config.cors); } + +export const corsMiddleware = cors; From 72f88b891569d620f222f595f1fd4c425915d742 Mon Sep 17 00:00:00 2001 From: ADNY <66500121+ErKeLost@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:03:56 +0800 Subject: [PATCH 005/369] chore: optimize core logic (#1657) * chore: optimize core package logic * chore: optimize config * chore: optimize code --- examples/refactor-react/farm.config.ts | 5 +- packages/core/src/config/index.ts | 555 +++++++++++------------- packages/core/src/config/mergeConfig.ts | 5 +- 3 files changed, 266 insertions(+), 299 deletions(-) diff --git a/examples/refactor-react/farm.config.ts b/examples/refactor-react/farm.config.ts index 6ab6a340c1..5e9d17aa52 100644 --- a/examples/refactor-react/farm.config.ts +++ b/examples/refactor-react/farm.config.ts @@ -5,6 +5,9 @@ export default defineConfig({ compilation: { presetEnv: false, progress: false, - sourcemap: false + sourcemap: false, + runtime: { + isolate: true + } } }); diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index 15ed769b07..783d91af29 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -87,20 +87,6 @@ export function defineFarmConfig(config: UserConfigExport): UserConfigExport { return config; } -export async function handleServerPortConflict( - resolvedUserConfig: ResolvedUserConfig, - logger: Logger, - mode?: CompilationMode -) { - // check port availability: auto increment the port if a conflict occurs - - try { - mode !== 'production' && - (await Server.resolvePortConflict(resolvedUserConfig.server, logger)); - // eslint-disable-next-line no-empty - } catch {} -} - /** * Resolve and load user config from the specified path * @param configPath @@ -115,44 +101,48 @@ export async function resolveConfig( ): Promise { logger = logger ?? new Logger(); - let mode = defaultMode; - const envMode = inlineOptions.mode || defaultMode; + const compileMode = defaultMode; + const mode = inlineOptions.mode || defaultMode; const isNodeEnvSet = !!process.env.NODE_ENV; - inlineOptions.mode = inlineOptions.mode ?? mode; + inlineOptions.mode = mode; + if (!isNodeEnvSet) { setProcessEnv(defaultNodeEnv); } const configEnv: ConfigEnv = { - mode: envMode, + mode, command, isPreview }; // configPath may be file or directory - let { configPath } = inlineOptions; - const { configFile } = inlineOptions; + const { configFile, configPath: initialConfigPath } = inlineOptions; const loadedUserConfig: any = await loadConfigFile( configFile, inlineOptions, configEnv ); - let rawConfig: UserConfig = mergeFarmCliConfig(inlineOptions, {}); + let rawConfig: UserConfig = mergeFarmCliConfig( + inlineOptions, + {}, + compileMode + ); + let configPath = initialConfigPath; if (loadedUserConfig) { configPath = loadedUserConfig.configFilePath; rawConfig = mergeConfig(rawConfig, loadedUserConfig.config); } - rawConfig.compilation.mode = - loadedUserConfig?.config?.compilation?.mode ?? mode; + const { config: userConfig, configFilePath } = { configFilePath: configPath, config: rawConfig }; const { jsPlugins, vitePlugins, rustPlugins, vitePluginAdapters } = - await resolvePlugins(userConfig, logger, mode); + await resolvePlugins(userConfig, compileMode, logger); const sortFarmJsPlugins = getSortedPlugins([ ...jsPlugins, @@ -162,19 +152,17 @@ export async function resolveConfig( const config = await resolveConfigHook(userConfig, sortFarmJsPlugins); - const mergedUserConfig = mergeFarmCliConfig(inlineOptions, config); - const resolvedUserConfig = await resolveUserConfig( - mergedUserConfig, + config, configFilePath, - mode, + compileMode, logger ); // normalize server config first cause it may be used in normalizeUserCompilationFnConfig resolvedUserConfig.server = normalizeDevServerConfig( resolvedUserConfig.server, - mode + compileMode ); // if (isHandleServerPortConflict) { @@ -190,7 +178,7 @@ export async function resolveConfig( resolvedUserConfig.jsPlugins = sortFarmJsPlugins; resolvedUserConfig.rustPlugins = rustPlugins; - // // Temporarily dealing with alias objects and arrays in js will be unified in rust in the future.] + // Temporarily dealing with alias objects and arrays in js will be unified in rust in the future.] if (vitePlugins.length) { resolvedUserConfig.compilation.resolve.alias = getAliasEntries( resolvedUserConfig.compilation.resolve.alias @@ -199,7 +187,7 @@ export async function resolveConfig( await resolveConfigResolvedHook(resolvedUserConfig, sortFarmJsPlugins); // Fix: Await the Promise and pass the resolved value to the function. - // // TODO Temporarily solve the problem of alias adaptation to vite + //TODO Temporarily solve the problem of alias adaptation to vite if (resolvedUserConfig.compilation?.resolve?.alias && vitePlugins.length) { resolvedUserConfig.compilation.resolve.alias = transformAliasWithVite( resolvedUserConfig.compilation.resolve.alias as unknown as Array @@ -236,7 +224,7 @@ export async function normalizeUserCompilationConfig( resolvedUserConfig.root = resolvedRootPath; // if normalize default config, skip check input option - const inputIndexConfig = checkCompilationInputValue( + const inputIndexConfig = await checkCompilationInputValue( resolvedUserConfig, logger ); @@ -266,9 +254,7 @@ export async function normalizeUserCompilationConfig( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore do not check type for this internal option if (!resolvedCompilation.assets?.publicDir) { - if (!resolvedCompilation.assets) { - resolvedCompilation.assets = {}; - } + resolvedCompilation.assets ??= {}; const userPublicDir = resolvedUserConfig.publicDir ? resolvedUserConfig.publicDir @@ -330,20 +316,15 @@ export async function normalizeUserCompilationConfig( if (!resolvedCompilation.runtime.plugins) { resolvedCompilation.runtime.plugins = []; } else { + const resolvePluginPath = (plugin: any) => { + if (path.isAbsolute(plugin)) return plugin; + return plugin.startsWith('.') + ? path.resolve(resolvedRootPath, plugin) + : require.resolve(plugin); + }; // make sure all plugin paths are absolute resolvedCompilation.runtime.plugins = - resolvedCompilation.runtime.plugins.map((plugin) => { - if (!path.isAbsolute(plugin)) { - if (!plugin.startsWith('.')) { - // resolve plugin from node_modules - return require.resolve(plugin); - } else { - return path.resolve(resolvedRootPath, plugin); - } - } - - return plugin; - }); + resolvedCompilation.runtime.plugins.map(resolvePluginPath); } // set namespace to package.json name field's hash if (!resolvedCompilation.runtime.namespace) { @@ -364,24 +345,19 @@ export async function normalizeUserCompilationConfig( if (isProduction) { resolvedCompilation.lazyCompilation = false; } else if (resolvedCompilation.lazyCompilation === undefined) { - if (isDevelopment) { - resolvedCompilation.lazyCompilation = true; - } else { - resolvedCompilation.lazyCompilation = false; - } + resolvedCompilation.lazyCompilation ??= isDevelopment; } - if (resolvedCompilation.mode === undefined) { - resolvedCompilation.mode = mode; - } + resolvedCompilation.mode ??= mode; + setProcessEnv(resolvedCompilation.mode); // TODO add targetEnv `lib-browser` and `lib-node` support const is_entry_html = - Object.keys(resolvedCompilation.input).length === 0 || - Object.values(resolvedCompilation.input) - .filter(Boolean) - .some((value) => value.endsWith('.html')); + !resolvedCompilation.input || + Object.values(resolvedCompilation.input).some( + (value) => value && value.endsWith('.html') + ); if ( resolvedCompilation.output.targetEnv !== 'node' && @@ -439,11 +415,7 @@ export async function normalizeUserCompilationConfig( } if (resolvedCompilation.treeShaking === undefined) { - if (isProduction) { - resolvedCompilation.treeShaking = true; - } else { - resolvedCompilation.treeShaking = false; - } + resolvedCompilation.treeShaking ??= isProduction; } if (resolvedCompilation.script?.plugins?.length) { @@ -469,19 +441,11 @@ export async function normalizeUserCompilationConfig( } if (resolvedCompilation.minify === undefined) { - if (isProduction) { - resolvedCompilation.minify = true; - } else { - resolvedCompilation.minify = false; - } + resolvedCompilation.minify ??= isProduction; } if (resolvedCompilation.presetEnv === undefined) { - if (isProduction) { - resolvedCompilation.presetEnv = true; - } else { - resolvedCompilation.presetEnv = false; - } + resolvedCompilation.presetEnv ??= isProduction; } // setting the custom configuration @@ -584,12 +548,20 @@ export const DEFAULT_COMPILATION_OPTIONS: Partial = { }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -function tryAsFileRead(value?: any): string | Buffer { - if (typeof value === 'string' && fs.existsSync(value)) { - return fs.readFileSync(path.resolve(value.toString())); + +function tryHttpsAsFileRead(value: unknown): string | Buffer | unknown { + if (typeof value === 'string') { + try { + const resolvedPath = path.resolve(value); + const stats = fs.statSync(resolvedPath); + + if (stats.isFile()) { + return fs.readFileSync(resolvedPath); + } + } catch {} } - return value; + return Buffer.isBuffer(value) ? value : value; } export function normalizeDevServerConfig( @@ -616,10 +588,10 @@ export function normalizeDevServerConfig( https: https ? { ...https, - ca: tryAsFileRead(options.https.ca), - cert: tryAsFileRead(options.https.cert), - key: tryAsFileRead(options.https.key), - pfx: tryAsFileRead(options.https.pfx) + ca: tryHttpsAsFileRead(options.https.ca), + cert: tryHttpsAsFileRead(options.https.cert), + key: tryHttpsAsFileRead(options.https.key), + pfx: tryHttpsAsFileRead(options.https.pfx) } : undefined }) as NormalizedServerConfig; @@ -644,57 +616,56 @@ export async function readConfigFile( configEnv: any, logger: Logger ): Promise { - if (fse.existsSync(configFilePath)) { - !__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ && - logger.info(`Using config file at ${bold(green(configFilePath))}`); - const format: Format = process.env.FARM_CONFIG_FORMAT - ? process.env.FARM_CONFIG_FORMAT === 'cjs' - ? 'cjs' - : 'esm' - : formatFromExt[path.extname(configFilePath).slice(1)] ?? 'esm'; + if (!fse.existsSync(configFilePath)) return; + if (!__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__) { + logger.info(`Using config file at ${bold(green(configFilePath))}`); + } - // we need transform all type farm.config with __dirname and __filename - const Compiler = (await import('../compiler/index.js')).Compiler; + const format = getFormat(configFilePath); - const outputPath = path.join( - path.dirname(configFilePath), - 'node_modules', - '.farm' - ); + // we need transform all type farm.config with __dirname and __filename + const Compiler = (await import('../compiler/index.js')).Compiler; - const fileName = `farm.config.bundle-${Date.now()}-${Math.random() - .toString(16) - .split('.') - .join('')}.${formatToExt[format]}`; + const outputPath = path.join( + path.dirname(configFilePath), + 'node_modules', + '.farm' + ); - const normalizedConfig = await resolveDefaultUserConfig({ - inlineOptions, - configFilePath, - format, - outputPath, - fileName - }); - - const replaceDirnamePlugin = await rustPluginResolver( - 'farm-plugin-replace-dirname', - // normalizedConfig.root!, - process.cwd() - ); + const fileName = `farm.config.bundle-${Date.now()}-${Math.random() + .toString(16) + .split('.') + .join('')}.${formatToExt[format]}`; - const compiler = new Compiler( - { - config: normalizedConfig, - jsPlugins: [], - rustPlugins: [replaceDirnamePlugin] - }, - logger - ); + const normalizedConfig = await resolveDefaultUserConfig({ + inlineOptions, + configFilePath, + format, + outputPath, + fileName + }); + + const replaceDirnamePlugin = await rustPluginResolver( + 'farm-plugin-replace-dirname', + normalizedConfig.root + ); - const FARM_PROFILE = process.env.FARM_PROFILE; - // disable FARM_PROFILE in farm_config - if (FARM_PROFILE) { - process.env.FARM_PROFILE = ''; - } + const compiler = new Compiler( + { + config: normalizedConfig, + jsPlugins: [], + rustPlugins: [replaceDirnamePlugin] + }, + logger + ); + + const FARM_PROFILE = process.env.FARM_PROFILE; + // disable FARM_PROFILE in farm_config + if (FARM_PROFILE) { + process.env.FARM_PROFILE = ''; + } + + try { await compiler.compile(); if (FARM_PROFILE) { @@ -703,107 +674,33 @@ export async function readConfigFile( compiler.writeResourcesToDisk(); - const filePath = isWindows - ? pathToFileURL(path.join(outputPath, fileName)) - : path.join(outputPath, fileName); - + const filePath = getFilePath(outputPath, fileName); // Change to vm.module of node or loaders as far as it is stable - const userConfig = (await import(filePath as string)).default; - try { - fse.unlink(filePath, () => void 0); - } catch { - /** do nothing */ - } + const { default: userConfig } = await import(filePath); const config = await (typeof userConfig === 'function' ? userConfig(configEnv) : userConfig); - if (!config.root) { - config.root = inlineOptions.root; - } - if (!isObject(config)) { throw new Error(`config must export or return an object.`); } + + config.root ??= inlineOptions.root; + return config; + } finally { + fse.unlink(getFilePath(outputPath, fileName)).catch(() => {}); } } -export function normalizePublicDir(root: string, userPublicDir?: string) { - const publicDir = userPublicDir ?? 'public'; +export function normalizePublicDir(root: string, publicDir = 'public') { const absPublicDirPath = path.isAbsolute(publicDir) ? publicDir - : path.join(root, publicDir); + : path.resolve(root, publicDir); return absPublicDirPath; } -export function checkClearScreen( - inlineConfig: FarmCliOptions | ResolvedUserConfig -) { - if ( - inlineConfig?.clearScreen && - !__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ - ) { - clearScreen(); - } -} - -export async function resolveMergedUserConfig( - mergedUserConfig: UserConfig, - configFilePath: string | undefined, - mode: 'development' | 'production' | string, - logger: Logger = new Logger() -): Promise { - const resolvedUserConfig = { - ...mergedUserConfig, - compilation: { - ...mergedUserConfig.compilation, - external: [] - } - } as ResolvedUserConfig; - - // set internal config - resolvedUserConfig.envMode = mode; - - if (configFilePath) { - const dependencies = await traceDependencies(configFilePath, logger); - dependencies.sort(); - resolvedUserConfig.configFileDependencies = dependencies; - resolvedUserConfig.configFilePath = configFilePath; - } - - const resolvedRootPath = resolvedUserConfig.root ?? process.cwd(); - const resolvedEnvPath = resolvedUserConfig.envDir - ? resolvedUserConfig.envDir - : resolvedRootPath; - - const userEnv = loadEnv( - resolvedUserConfig.envMode ?? mode, - resolvedEnvPath, - resolvedUserConfig.envPrefix - ); - const existsEnvFiles = getExistsEnvFiles( - resolvedUserConfig.envMode ?? mode, - resolvedEnvPath - ); - - resolvedUserConfig.envFiles = [ - ...(Array.isArray(resolvedUserConfig.envFiles) - ? resolvedUserConfig.envFiles - : []), - ...existsEnvFiles - ]; - - resolvedUserConfig.env = { - ...userEnv, - NODE_ENV: mergedUserConfig.compilation.mode ?? mode, - mode: mode - }; - - return resolvedUserConfig; -} - /** * Load config file from the specified path and return the config and config file path * @param configPath the config path, could be a directory or a file @@ -820,11 +717,11 @@ export async function loadConfigFile( const configRootPath = path.resolve(root); let resolvedPath: string | undefined; try { - if (configFile) { - resolvedPath = path.resolve(root, configFile); - } else { - resolvedPath = await getConfigFilePath(configRootPath); - } + resolvedPath = await resolveConfigFilePath( + configFile, + root, + configRootPath + ); const config = await readConfigFile( inlineOptions, @@ -860,7 +757,10 @@ export async function loadConfigFile( } } -function checkCompilationInputValue(userConfig: UserConfig, logger: Logger) { +export async function checkCompilationInputValue( + userConfig: UserConfig, + logger: Logger +) { const { compilation } = userConfig; const targetEnv = compilation?.output?.targetEnv; const inputValue = Object.values(compilation?.input).filter(Boolean); @@ -873,13 +773,15 @@ function checkCompilationInputValue(userConfig: UserConfig, logger: Logger) { if (!isEmptyObject(compilation?.input) && inputValue.length) { inputIndexConfig = compilation?.input; } else { + const rootPath = userConfig?.root ?? '.'; if (isTargetNode) { // If input is not specified, try to find index.js or index.ts const entryFiles = ['./index.js', './index.ts']; for (const entryFile of entryFiles) { try { - if (fs.statSync(path.resolve(userConfig?.root, entryFile))) { + const resolvedPath = path.resolve(rootPath, entryFile); + if (await checkFileExists(resolvedPath)) { inputIndexConfig = { index: entryFile }; break; } @@ -889,7 +791,8 @@ function checkCompilationInputValue(userConfig: UserConfig, logger: Logger) { } } else { try { - if (fs.statSync(path.resolve(userConfig?.root, defaultHtmlPath))) { + const resolvedHtmlPath = path.resolve(rootPath, defaultHtmlPath); + if (await checkFileExists(resolvedHtmlPath)) { inputIndexConfig = { index: defaultHtmlPath }; } } catch (error) { @@ -914,15 +817,16 @@ function checkCompilationInputValue(userConfig: UserConfig, logger: Logger) { export async function getConfigFilePath( configRootPath: string ): Promise { - if (fse.statSync(configRootPath).isDirectory()) { - for (const name of DEFAULT_CONFIG_NAMES) { - const resolvedPath = path.join(configRootPath, name); - const isFile = - fse.existsSync(resolvedPath) && fse.statSync(resolvedPath).isFile(); - - if (isFile) { - return resolvedPath; - } + const stat = await fse.stat(configRootPath); + if (!stat.isDirectory()) { + return undefined; + } + + for (const name of DEFAULT_CONFIG_NAMES) { + const resolvedPath = path.join(configRootPath, name); + const fileStat = await fse.stat(resolvedPath); + if (fileStat.isFile()) { + return resolvedPath; } } return undefined; @@ -930,28 +834,21 @@ export async function getConfigFilePath( export async function resolvePlugins( userConfig: UserConfig, - logger: Logger, - mode: CompilationMode + mode: CompilationMode, + logger: Logger ) { - const { jsPlugins, rustPlugins } = await resolveFarmPlugins(userConfig); - const rawJsPlugins = (await resolveAsyncPlugins(jsPlugins || [])).filter( - Boolean - ); + const { jsPlugins: rawJsPlugins, rustPlugins } = + await resolveFarmPlugins(userConfig); + const jsPlugins = await resolveAndFilterAsyncPlugins(rawJsPlugins); - let vitePluginAdapters: JsPlugin[] = []; const vitePlugins = (userConfig?.vitePlugins ?? []).filter(Boolean); - if (vitePlugins.length) { - vitePluginAdapters = await handleVitePlugins( - vitePlugins, - userConfig, - logger, - mode - ); - } + const vitePluginAdapters = vitePlugins.length + ? await handleVitePlugins(vitePlugins, userConfig, logger, mode) + : []; return { - jsPlugins: rawJsPlugins, + jsPlugins, vitePlugins, rustPlugins, vitePluginAdapters @@ -959,44 +856,7 @@ export async function resolvePlugins( } export async function resolveDefaultUserConfig(options: any) { - const { inlineOptions, format, outputPath, fileName, configFilePath } = - options; - const defaultConfig: UserConfig = { - root: path.resolve(inlineOptions.root ?? '.'), - compilation: { - input: { - [fileName]: configFilePath - }, - output: { - entryFilename: '[entryName]', - path: outputPath, - format, - targetEnv: 'node' - }, - external: [ - ...(process.env.FARM_CONFIG_FULL_BUNDLE - ? [] - : ['!^(\\./|\\.\\./|[A-Za-z]:\\\\|/).*']), - '^@farmfe/core$' - ], - partialBundling: { - enforceResources: [ - { - name: fileName, - test: ['.+'] - } - ] - }, - watch: false, - sourcemap: false, - treeShaking: false, - minify: false, - presetEnv: false, - lazyCompilation: false, - persistentCache: false, - progress: false - } - }; + const defaultConfig: UserConfig = createDefaultConfig(options); const resolvedUserConfig: ResolvedUserConfig = await resolveUserConfig( defaultConfig, @@ -1014,36 +874,32 @@ export async function resolveDefaultUserConfig(options: any) { export async function resolveUserConfig( userConfig: UserConfig, - configFilePath: string | undefined, - mode: 'development' | 'production' | string, + configFilePath?: string | undefined, + mode: 'development' | 'production' | string = 'development', logger: Logger = new Logger() ): Promise { const resolvedUserConfig = { - ...userConfig + ...userConfig, + envMode: mode } as ResolvedUserConfig; // set internal config - resolvedUserConfig.envMode = mode; - if (configFilePath) { const dependencies = await traceDependencies(configFilePath, logger); - dependencies.sort(); - resolvedUserConfig.configFileDependencies = dependencies; + resolvedUserConfig.configFileDependencies = dependencies.sort(); resolvedUserConfig.configFilePath = configFilePath; } - const resolvedRootPath = resolvedUserConfig.root ?? process.cwd(); - const resolvedEnvPath = resolvedUserConfig.envDir - ? resolvedUserConfig.envDir - : resolvedRootPath; + const resolvedRootPath = resolvedUserConfig.root; + const resolvedEnvPath = resolvedUserConfig.envDir ?? resolvedRootPath; const userEnv = loadEnv( - resolvedUserConfig.envMode ?? mode, + resolvedUserConfig.envMode, resolvedEnvPath, resolvedUserConfig.envPrefix ); const existsEnvFiles = getExistsEnvFiles( - resolvedUserConfig.envMode ?? mode, + resolvedUserConfig.envMode, resolvedEnvPath ); @@ -1056,9 +912,116 @@ export async function resolveUserConfig( resolvedUserConfig.env = { ...userEnv, - NODE_ENV: userConfig.compilation.mode ?? mode, - mode: mode + NODE_ENV: userConfig.compilation.mode, + mode }; return resolvedUserConfig; } + +export function createDefaultConfig(options: any): UserConfig { + const { inlineOptions, format, outputPath, fileName, configFilePath } = + options; + return { + root: path.resolve(inlineOptions.root ?? '.'), + compilation: { + input: { + [fileName]: configFilePath + }, + output: { + entryFilename: '[entryName]', + path: outputPath, + format, + targetEnv: 'node' + }, + external: [ + ...(process.env.FARM_CONFIG_FULL_BUNDLE + ? [] + : ['!^(\\./|\\.\\./|[A-Za-z]:\\\\|/).*']), + '^@farmfe/core$' + ], + partialBundling: { + enforceResources: [ + { + name: fileName, + test: ['.+'] + } + ] + }, + watch: false, + sourcemap: false, + treeShaking: false, + minify: false, + presetEnv: false, + lazyCompilation: false, + persistentCache: false, + progress: false + } + }; +} + +export async function resolveAndFilterAsyncPlugins( + plugins: JsPlugin[] = [] +): Promise { + return (await resolveAsyncPlugins(plugins)).filter(Boolean); +} + +export async function checkFileExists(filePath: string): Promise { + try { + await fse.stat(filePath); + return true; + } catch { + return false; + } +} + +export async function resolveConfigFilePath( + configFile: string, + root: string, + configRootPath: string +): Promise { + if (configFile) { + return path.resolve(root, configFile); + } else { + return await getConfigFilePath(configRootPath); + } +} + +export async function handleServerPortConflict( + resolvedUserConfig: ResolvedUserConfig, + logger: Logger, + mode?: CompilationMode +) { + // check port availability: auto increment the port if a conflict occurs + + try { + mode !== 'production' && + (await Server.resolvePortConflict(resolvedUserConfig.server, logger)); + // eslint-disable-next-line no-empty + } catch {} +} + +export function checkClearScreen( + inlineConfig: FarmCliOptions | ResolvedUserConfig +) { + if ( + inlineConfig?.clearScreen && + !__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ + ) { + clearScreen(); + } +} + +export function getFormat(configFilePath: string): Format { + return process.env.FARM_CONFIG_FORMAT === 'cjs' + ? 'cjs' + : process.env.FARM_CONFIG_FORMAT === 'esm' + ? 'esm' + : formatFromExt[path.extname(configFilePath).slice(1)] ?? 'esm'; +} + +export function getFilePath(outputPath: string, fileName: string): string { + return isWindows + ? pathToFileURL(path.join(outputPath, fileName)).toString() + : path.join(outputPath, fileName); +} diff --git a/packages/core/src/config/mergeConfig.ts b/packages/core/src/config/mergeConfig.ts index d7cfc9f18d..be05e78f59 100644 --- a/packages/core/src/config/mergeConfig.ts +++ b/packages/core/src/config/mergeConfig.ts @@ -47,7 +47,8 @@ export function mergeConfig>( export function mergeFarmCliConfig( cliOption: FarmCliOptions & UserConfig, - target: UserConfig + target: UserConfig, + mode?: 'development' | 'production' ): UserConfig { let left: UserConfig = {}; const options = initialCliOptions(cliOption); @@ -121,7 +122,7 @@ export function mergeFarmCliConfig( if (options.mode) { left = mergeConfig(left, { compilation: { - mode: options.mode as UserConfig['compilation']['mode'] + mode: mode ?? (options.mode as UserConfig['compilation']['mode']) } }); } From 95a5fbad0401fac176a632b590c3c51c7cbc2120 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Tue, 23 Jul 2024 16:59:47 +0800 Subject: [PATCH 006/369] chore: update http server --- packages/core/src/index.ts | 70 ++++------------------------ packages/core/src/newServer/index.ts | 8 ++-- 2 files changed, 14 insertions(+), 64 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ddfe375bbe..fa534cde13 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -38,6 +38,7 @@ import { normalizePublicDir, resolveConfig } from './config/index.js'; +import { newServer } from './newServer/index.js'; import { Server } from './server/index.js'; import { compilerHandler } from './utils/build.js'; import { colors } from './utils/color.js'; @@ -74,73 +75,22 @@ export async function start( ); const compiler = await createCompiler(resolvedUserConfig, logger); + const server = new newServer(compiler, resolvedUserConfig); + await server.createServer(); + console.log(server.httpServer); - const devServer = await createDevServer( - compiler, - resolvedUserConfig, - logger - ); + // const devServer = await createDevServer( + // compiler, + // resolvedUserConfig, + // logger + // ); - await devServer.listen(); + // await devServer.listen(); } catch (error) { logger.error('Failed to start the server', { exit: true, error }); } } -export async function startRefactorCli( - inlineConfig?: FarmCliOptions & UserConfig -): Promise { - inlineConfig = inlineConfig ?? {}; - const logger = inlineConfig.logger ?? new Logger(); - setProcessEnv('development'); - - try { - const resolvedUserConfig = await resolveConfig( - inlineConfig, - 'start', - 'development', - 'development', - false - ); - - const compiler = await createCompiler(resolvedUserConfig, logger); - - const devServer = await createDevServer( - compiler, - resolvedUserConfig, - logger - ); - - await devServer.listen(); - } catch (error) { - logger.error('Failed to start the server', { exit: true, error }); - } -} - -export async function buildRefactorCli( - inlineConfig?: FarmCliOptions & UserConfig -): Promise { - inlineConfig = inlineConfig ?? {}; - const logger = inlineConfig.logger ?? new Logger(); - setProcessEnv('production'); - - try { - const resolvedUserConfig = await resolveConfig( - inlineConfig, - 'build', - 'production', - 'production', - false - ); - - await createBundleHandler(resolvedUserConfig, logger); - // copy resources under publicDir to output.path - await copyPublicDirectory(resolvedUserConfig, logger); - } catch (err) { - logger.error(`Failed to build: ${err}`, { exit: true }); - } -} - export async function build( inlineConfig?: FarmCliOptions & UserConfig ): Promise { diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index 82bf2e29e5..a1501bf3e7 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -77,7 +77,7 @@ export class newServer { httpsOptions: HttpsServerOptions; publicDir?: string; publicPath?: string; - server?: HttpServer; + httpServer?: HttpServer; watcher: FileWatcher; constructor(compiler: CompilerType, config: ResolvedUserConfig) { @@ -105,7 +105,7 @@ export class newServer { this.httpsOptions = await resolveHttpsConfig(serverConfig.https); const { middlewareMode } = serverConfig; const middlewares = connect() as connect.Server; - this.server = middlewareMode + this.httpServer = middlewareMode ? null : await resolveHttpServer( serverConfig as CommonServerOptions, @@ -119,11 +119,11 @@ export class newServer { } public async createWebSocketServer() { - if (!this.server) { + if (!this.httpServer) { throw new Error('Websocket requires a server.'); } const wsServer = new WsServer( - this.server, + this.httpServer, this.config, this.httpsOptions, this.publicPath, From 5be9275c9e983a05d8482a1b59c20ba50a66ad7e Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Tue, 23 Jul 2024 17:53:04 +0800 Subject: [PATCH 007/369] chore: update code --- packages/core/src/index.ts | 1 - packages/core/src/newServer/index.ts | 31 +++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index fa534cde13..3385a9bdea 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -77,7 +77,6 @@ export async function start( const compiler = await createCompiler(resolvedUserConfig, logger); const server = new newServer(compiler, resolvedUserConfig); await server.createServer(); - console.log(server.httpServer); // const devServer = await createDevServer( // compiler, diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index a1501bf3e7..d91c113a1e 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -7,11 +7,13 @@ import path from 'node:path'; import { WatchOptions } from 'chokidar'; import connect from 'connect'; import fse from 'fs-extra'; +import { WebSocketServer as WebSocketServerRaw_ } from 'ws'; import { Compiler } from '../compiler/index.js'; import { normalizePublicPath } from '../config/normalize-config/normalize-output.js'; import { NormalizedServerConfig, ResolvedUserConfig } from '../config/types.js'; import { logger } from '../utils/logger.js'; import { initPublicFiles } from '../utils/publicDir.js'; +import { isObject } from '../utils/share.js'; import { FileWatcher } from '../watcher/index.js'; import { HMRChannel } from './hmr.js'; import { @@ -19,7 +21,7 @@ import { resolveHttpServer, resolveHttpsConfig } from './http.js'; -import { WsServer } from './ws.js'; +import { WebSocketClient, WebSocketServer, WsServer } from './ws.js'; export type HttpServer = http.Server | Http2SecureServer; type CompilerType = Compiler | null; @@ -68,6 +70,11 @@ export interface ServerOptions extends CommonServerOptions { }; origin?: string; } + +function noop() { + // noop +} + export class newServer { private compiler: CompilerType; @@ -119,9 +126,31 @@ export class newServer { } public async createWebSocketServer() { + // @ts-ignore + if (this.config.server.ws === false) { + return { + name: 'ws', + get clients() { + return new Set(); + }, + async close() { + // noop + }, + on: noop as any as WebSocketServer['on'], + off: noop as any as WebSocketServer['off'], + listen: noop, + send: noop + }; + } if (!this.httpServer) { throw new Error('Websocket requires a server.'); } + + let wss: WebSocketServerRaw_; + let wsHttpServer: Server | undefined = undefined; + const hmr = isObject(this.config.server.hmr) && this.config.server.hmr; + const hmrServer = hmr && hmr.server; + const hmrPort = hmr && hmr.port; const wsServer = new WsServer( this.httpServer, this.config, From b0458a0aa576e2534b45b567702d963393dfc161 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Wed, 24 Jul 2024 09:33:30 +0800 Subject: [PATCH 008/369] chore: update comment --- packages/core/src/config/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index 783d91af29..e10f47c363 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -187,7 +187,7 @@ export async function resolveConfig( await resolveConfigResolvedHook(resolvedUserConfig, sortFarmJsPlugins); // Fix: Await the Promise and pass the resolved value to the function. - //TODO Temporarily solve the problem of alias adaptation to vite + //TODO Temporarily solve the problem of alias adaptation to vite we should resolve this in rust side if (resolvedUserConfig.compilation?.resolve?.alias && vitePlugins.length) { resolvedUserConfig.compilation.resolve.alias = transformAliasWithVite( resolvedUserConfig.compilation.resolve.alias as unknown as Array From 5683a29119ee76fb4f58314e11fa2b20fdffa5e7 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Wed, 24 Jul 2024 16:08:29 +0800 Subject: [PATCH 009/369] chore: update http test --- examples/refactor-react/farm.config.ts | 3 + packages/cli/src/index.ts | 6 +- packages/core/src/index.ts | 46 +++++++-- packages/core/src/newServer/index.ts | 81 ++++++++++++---- pnpm-lock.yaml | 124 ++----------------------- 5 files changed, 113 insertions(+), 147 deletions(-) diff --git a/examples/refactor-react/farm.config.ts b/examples/refactor-react/farm.config.ts index 5e9d17aa52..9ecee06b93 100644 --- a/examples/refactor-react/farm.config.ts +++ b/examples/refactor-react/farm.config.ts @@ -9,5 +9,8 @@ export default defineConfig({ runtime: { isolate: true } + }, + server: { + writeToDisk: true, } }); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 3e3891ced6..f758492f02 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -94,10 +94,12 @@ cli } }; - const { start } = await resolveCore(); + const { start2 }: any = await resolveCore(); + // const { start }: any = await resolveCore(); handleAsyncOperationErrors( - start(defaultOptions), + start2(defaultOptions), + // start(defaultOptions), 'Failed to start server' ); } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index dea2c1b976..21716b75b0 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -75,16 +75,14 @@ export async function start( ); const compiler = await createCompiler(resolvedUserConfig, logger); - const server = new newServer(compiler, resolvedUserConfig); - await server.createServer(); - // const devServer = await createDevServer( - // compiler, - // resolvedUserConfig, - // logger - // ); + const devServer = await createDevServer( + compiler, + resolvedUserConfig, + logger + ); - // await devServer.listen(); + await devServer.listen(); } catch (error) { logger.error('Failed to start the server', { exit: true, error }); } @@ -458,3 +456,35 @@ export function logFileChanges(files: string[], root: string, logger: Logger) { export { defineFarmConfig as defineConfig } from './config/index.js'; export { loadEnv }; + +export async function start2( + inlineConfig?: FarmCliOptions & UserConfig +): Promise { + inlineConfig = inlineConfig ?? {}; + const logger = inlineConfig.logger ?? new Logger(); + setProcessEnv('development'); + + try { + const resolvedUserConfig = await resolveConfig( + inlineConfig, + 'start', + 'development', + 'development', + false + ); + + const compiler = await createCompiler(resolvedUserConfig, logger); + const server = new newServer(compiler, resolvedUserConfig); + await server.createServer(); + await server.listen(); + // const devServer = await createDevServer( + // compiler, + // resolvedUserConfig, + // logger + // ); + + // await devServer.listen(); + } catch (error) { + logger.error('Failed to start the server', { exit: true, error }); + } +} diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index d91c113a1e..3262540ea4 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -11,6 +11,7 @@ import { WebSocketServer as WebSocketServerRaw_ } from 'ws'; import { Compiler } from '../compiler/index.js'; import { normalizePublicPath } from '../config/normalize-config/normalize-output.js'; import { NormalizedServerConfig, ResolvedUserConfig } from '../config/types.js'; +import { logError } from '../server/error.js'; import { logger } from '../utils/logger.js'; import { initPublicFiles } from '../utils/publicDir.js'; import { isObject } from '../utils/share.js'; @@ -127,30 +128,10 @@ export class newServer { public async createWebSocketServer() { // @ts-ignore - if (this.config.server.ws === false) { - return { - name: 'ws', - get clients() { - return new Set(); - }, - async close() { - // noop - }, - on: noop as any as WebSocketServer['on'], - off: noop as any as WebSocketServer['off'], - listen: noop, - send: noop - }; - } if (!this.httpServer) { throw new Error('Websocket requires a server.'); } - let wss: WebSocketServerRaw_; - let wsHttpServer: Server | undefined = undefined; - const hmr = isObject(this.config.server.hmr) && this.config.server.hmr; - const hmrServer = hmr && hmr.server; - const hmrPort = hmr && hmr.port; const wsServer = new WsServer( this.httpServer, this.config, @@ -159,4 +140,64 @@ export class newServer { null ); } + + public async listen(): Promise { + if (!this.httpServer) { + // this.logger.error('HTTP server is not created yet'); + return; + } + const { port, open, protocol, hostname } = this.config.server; + + await this.compile(); + const { createServer } = await import('node:http'); + + // this.httpServer = createServer((req, res) => { + // if (req.url === '/') { + // // res.writeHead(200, { 'Content-Type': 'text/plain' }); + // // res.end('Hello, World!'); + // } else if (req.url === '/about') { + // res.writeHead(200, { 'Content-Type': 'text/plain' }); + // res.end('About page'); + // } else { + // res.writeHead(404, { 'Content-Type': 'text/plain' }); + // res.end('404 Not Found'); + // } + // }); + + this.httpServer.on('request', (req, res) => { + // 设置响应头 + // res.writeHead(200, { 'Content-Type': 'application/json' }); + + // 创建响应体对象 + const responseBody = { + message: "这是使用 on('request') 方法的响应", + timestamp: new Date().toISOString(), + path: req.url + }; + + // 将对象转换为 JSON 字符串 + const jsonResponse = JSON.stringify(responseBody); + res.setHeader('Content-Type', 'text/plain; charset=utf-8'); + // 发送响应 + res.end('我是默认请求头'); + }); + + this.httpServer.listen(port, hostname.name, () => { + console.log(`Server running at ${protocol}://${hostname.name}:${port}/`); + }); + } + + private async compile(): Promise { + try { + await this.compiler.compile(); + } catch (err) { + throw new Error(logError(err) as unknown as string); + } + + if (this.config.server.writeToDisk) { + this.compiler.writeResourcesToDisk(); + } else { + this.compiler.callWriteResourcesHook(); + } + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af23a0204b..8d0ca5cc2e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3852,11 +3852,6 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@farmfe/cli@1.0.2': - resolution: {integrity: sha512-ZZRmXOSLkA7kWzmj6IVSH5U4NimNzFSa9hI0rRlPbgNwsfBIooUfthMjMZPnMwnFD9SIxLurlMJkwKyb4wpDKQ==} - engines: {node: '>= 16'} - hasBin: true - '@farmfe/plugin-react-darwin-arm64@1.2.0': resolution: {integrity: sha512-9a8wp6lg8NytO+kU8hu2gCFer+PL4TJ92SkU/5v9xdcsioElWpnMDGlwcoI8bXqi60/WR8RyExsDIBubCgjbXQ==} engines: {node: '>= 10'} @@ -3914,64 +3909,6 @@ packages: '@farmfe/plugin-react@1.2.0': resolution: {integrity: sha512-S5kU7NgqiyyhnDsZ7DEvszQIE6sCA0CNp7oTbdDcPxotPNBoyOcBHviSP3P5jvtIv6mmlF8Me2C1aLWJQRw9PA==} - '@farmfe/plugin-sass-darwin-arm64@1.0.5': - resolution: {integrity: sha512-0uRiWqEjLUjEQbaJBXFKCdp4P++00vHE0WegUBgA7KpnQ8USWBJEJ+AQklBKuZitjpqlkJqCsq09bEmkwzEVYA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@farmfe/plugin-sass-darwin-x64@1.0.5': - resolution: {integrity: sha512-lVogirOJiRMtDUkeEksYpt+40yRn7/syWGPCqBOn489tyKSvr1WMvIICAntqO4+FryVTQU3tgck11XYUd87cMA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@farmfe/plugin-sass-linux-arm64-gnu@1.0.5': - resolution: {integrity: sha512-0Iv6bzrTRqaGZ4QKt+aabJrvMRHPa8Vv9kSgGbqhs40Spm1cqUIMoHBfS76L8PX+BG6327C/5iqv4oOERmWoNg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@farmfe/plugin-sass-linux-arm64-musl@1.0.5': - resolution: {integrity: sha512-uOjnGp9fxMaIbRo3CPBtH2EivpSHhB1nje+f40JFcbiklPhcEs41/W62ZgSg6NOq2ocNbUbv1sdp1/6eBt0v7Q==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@farmfe/plugin-sass-linux-x64-gnu@1.0.5': - resolution: {integrity: sha512-29uzHgjLt0ZdWwthr+VmcWXAj1M9SGMbrt9j4UdPxJcnTdfSKNhrIPkfaQOWOHNvpy4IF2HRb3bdLVC3Nm3qyw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@farmfe/plugin-sass-linux-x64-musl@1.0.5': - resolution: {integrity: sha512-FDLnfXk6oGjVbsA4/9ddgpSlAOfC6b+iK5ZlfiN83q4D2twviTQ+/iFLXOlz+U5Wdz8oa250O694iRYEV8qwBA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@farmfe/plugin-sass-win32-arm64-msvc@1.0.5': - resolution: {integrity: sha512-PW3XEK+SO3ygIP7mfCn/hM6x5VS70C7CNU50ijLGjm9Fp4S1HKePyGlE0sk5JAqgTpfcPTv8fqgwj2PKFd/byw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@farmfe/plugin-sass-win32-ia32-msvc@1.0.5': - resolution: {integrity: sha512-nPuwI7N5ug/niQ+W1IMmGBqAHDi8M47da6OSkpK00HEvSYiqelf5x2dfJNA3ICcVck3tSxp1yrjfpO9Z8TJOuw==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@farmfe/plugin-sass-win32-x64-msvc@1.0.5': - resolution: {integrity: sha512-C7kCrWBdHo+EZsMM5sq3IyVLGwCThu3eWA6jAv5PFJoXhQ4jKWq8/PoanIhz5fJU1joJT7tNYVulRzkCAE0pdg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@farmfe/plugin-sass@1.0.5': - resolution: {integrity: sha512-uTcXGS3ONqP0aqqFMIrnK9S2fP7IsRlwX9DKUUfHTq5NAi7MkbJ/nz8fnKL7xzKxPzuoxUaWgirmqbtqNlf3vA==} - engines: {node: '>=16'} - '@farmfe/utils@0.0.1': resolution: {integrity: sha512-QLbgNrojcvxfumXA/H329XAXhoCahmeSH3JmaiwwJEGS2QAmWfgAJMegjwlt6OmArGVO4gSbJ7Xbmm1idZZs+g==} @@ -15475,13 +15412,6 @@ snapshots: '@eslint/js@8.57.0': {} - '@farmfe/cli@1.0.2': - dependencies: - cac: 6.7.14 - cross-spawn: 7.0.3 - inquirer: 9.2.11 - walkdir: 0.4.1 - '@farmfe/plugin-react-darwin-arm64@1.2.0': optional: true @@ -15521,52 +15451,6 @@ snapshots: '@farmfe/plugin-react-win32-ia32-msvc': 1.2.0 '@farmfe/plugin-react-win32-x64-msvc': 1.2.0 - '@farmfe/plugin-sass-darwin-arm64@1.0.5': - optional: true - - '@farmfe/plugin-sass-darwin-x64@1.0.5': - optional: true - - '@farmfe/plugin-sass-linux-arm64-gnu@1.0.5': - optional: true - - '@farmfe/plugin-sass-linux-arm64-musl@1.0.5': - optional: true - - '@farmfe/plugin-sass-linux-x64-gnu@1.0.5': - optional: true - - '@farmfe/plugin-sass-linux-x64-musl@1.0.5': - optional: true - - '@farmfe/plugin-sass-win32-arm64-msvc@1.0.5': - optional: true - - '@farmfe/plugin-sass-win32-ia32-msvc@1.0.5': - optional: true - - '@farmfe/plugin-sass-win32-x64-msvc@1.0.5': - optional: true - - '@farmfe/plugin-sass@1.0.5': - optionalDependencies: - '@farmfe/plugin-sass-darwin-arm64': 1.0.5 - '@farmfe/plugin-sass-darwin-x64': 1.0.5 - '@farmfe/plugin-sass-linux-arm64-gnu': 1.0.5 - '@farmfe/plugin-sass-linux-arm64-musl': 1.0.5 - '@farmfe/plugin-sass-linux-x64-gnu': 1.0.5 - '@farmfe/plugin-sass-linux-x64-musl': 1.0.5 - '@farmfe/plugin-sass-win32-arm64-msvc': 1.0.5 - '@farmfe/plugin-sass-win32-ia32-msvc': 1.0.5 - '@farmfe/plugin-sass-win32-x64-msvc': 1.0.5 - sass-embedded-darwin-arm64: 1.62.0 - sass-embedded-darwin-x64: 1.62.0 - sass-embedded-linux-arm64: 1.62.0 - sass-embedded-linux-ia32: 1.62.0 - sass-embedded-linux-x64: 1.62.0 - sass-embedded-win32-ia32: 1.62.0 - sass-embedded-win32-x64: 1.62.0 - '@farmfe/utils@0.0.1': {} '@floating-ui/core@1.5.0': @@ -18447,7 +18331,7 @@ snapshots: '@vueuse/shared@9.13.0(@vue/composition-api@1.7.2(vue@3.3.7(typescript@5.4.5)))(vue@3.3.7(typescript@5.4.5))': dependencies: - vue-demi: 0.14.8(@vue/composition-api@1.7.2(vue@3.3.7(typescript@5.4.5)))(vue@3.3.7(typescript@5.4.5)) + vue-demi: 0.14.9(@vue/composition-api@1.7.2(vue@3.3.7(typescript@5.4.5)))(vue@3.3.7(typescript@5.4.5)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -27084,6 +26968,12 @@ snapshots: optionalDependencies: '@vue/composition-api': 1.7.2(vue@3.3.7(typescript@5.4.5)) + vue-demi@0.14.9(@vue/composition-api@1.7.2(vue@3.3.7(typescript@5.4.5)))(vue@3.3.7(typescript@5.4.5)): + dependencies: + vue: 3.3.7(typescript@5.4.5) + optionalDependencies: + '@vue/composition-api': 1.7.2(vue@3.3.7(typescript@5.4.5)) + vue-demi@0.14.9(@vue/composition-api@1.7.2(vue@3.4.15(typescript@5.4.5)))(vue@3.4.15(typescript@5.4.5)): dependencies: vue: 3.4.15(typescript@5.4.5) From b0dc639d27bcf6ea7398f42435b76479acbb485c Mon Sep 17 00:00:00 2001 From: smz8023 <31838004+smz8023@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:04:51 +0800 Subject: [PATCH 010/369] refactor: Define and modify field types (#1664) * refactor: Define and modify field types * refactor: Define and modify field types * chore: change the type and add jsdoc --------- Co-authored-by: shaomingzhen.smz --- packages/cli/src/types.ts | 23 ++++++++-------- packages/cli/src/utils.ts | 55 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index 37f668636a..dc3074b3b4 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -3,39 +3,40 @@ export interface GlobalCliOptions { c?: boolean | string; config?: string; configPath?: string; - m?: string; base?: string; + m?: string; mode?: 'development' | 'production' | string; - w?: boolean; - watch?: boolean; - watchPath?: string; - port?: number; - lazy?: boolean; l?: boolean; + lazy?: boolean; + port?: number; clearScreen?: boolean; } export interface CleanOptions { - path?: string; recursive?: boolean; } export interface CliServerOptions { - port?: string; + port?: number; open?: boolean; https?: boolean; hmr?: boolean; + cors?: boolean; strictPort?: boolean; } export interface CliBuildOptions { - configFile?: string | undefined; - input?: string; + o?: string; outDir?: string; + i?: string; + input?: string; + w?: boolean; + watch?: boolean; sourcemap?: boolean; minify?: boolean; treeShaking?: boolean; format?: 'cjs' | 'esm'; + configFile?: string | undefined; target?: | 'browser' | 'node' @@ -52,6 +53,6 @@ export interface CliPreviewOptions { host?: string | boolean; port?: number; open?: boolean | string; - strictPort?: boolean; outDir?: string; + strictPort?: boolean; } diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index f3840e35e8..ec9c7a0c98 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -3,10 +3,25 @@ import path from 'node:path'; import type { build, clean, preview, start, watch } from '@farmfe/core'; import { Logger } from '@farmfe/core'; -import type { CleanOptions, GlobalCliOptions } from './types.js'; +import type { + CleanOptions, + CliBuildOptions, + CliServerOptions, + GlobalCliOptions +} from './types.js'; const logger = new Logger(); +/** + * + * @returns {Promise<{ start: typeof start, build: typeof build, watch: typeof watch, preview: typeof preview, clean: typeof clean }>} + * A promise that resolves to an object containing the core functionalities: + * - `start`: Compile the project in dev mode and serve it with farm dev server'. + * - `build`: compile the project in production mode'. + * - `watch`: watch file change'. + * - `preview`: compile the project in watch mode'. + * - `clean`: Clean up the cache built incrementally'. + */ export async function resolveCore(): Promise<{ start: typeof start; build: typeof build; @@ -34,8 +49,13 @@ export function filterDuplicateOptions(options: T) { } } } - -export function cleanOptions(options: GlobalCliOptions) { +/** + * @param options The cli passes parameters + * @returns Remove parameters that are not required + */ +export function cleanOptions( + options: GlobalCliOptions & CliServerOptions & CliBuildOptions +) { const resolveOptions = { ...options }; delete resolveOptions['--']; @@ -52,6 +72,11 @@ export function cleanOptions(options: GlobalCliOptions) { return resolveOptions; } +/** + * + * @param options cli parameters + * @returns resolve command options + */ export function resolveCommandOptions( options: GlobalCliOptions ): GlobalCliOptions { @@ -60,10 +85,21 @@ export function resolveCommandOptions( return cleanOptions(resolveOptions); } +/** + * + * @param root root path + * @param configPath config path + * @returns config path absolute path + */ export function getConfigPath(root: string, configPath: string) { return path.resolve(root, configPath ?? ''); } +/** + * + * @param asyncOperation The asynchronous operation to be executed. + * @param errorMessage The error message to log if the operation fails. + */ export async function handleAsyncOperationErrors( asyncOperation: Promise, errorMessage: string @@ -76,12 +112,25 @@ export async function handleAsyncOperationErrors( } } +/** + * + * @param rootPath root path + * @returns absolute path + */ export function resolveRootPath(rootPath = '') { return rootPath && path.isAbsolute(rootPath) ? rootPath : path.resolve(process.cwd(), rootPath); } +/** + * + * @param root root path + * @param options cli parameters + * @returns + * - root root path + * - configPath + */ export function resolveCliConfig( root: string, options: GlobalCliOptions & CleanOptions From 71cf8faca784ec46122f602fbdbd425e2054e010 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Fri, 26 Jul 2024 20:53:19 +0800 Subject: [PATCH 011/369] chore: finish generateResourceMiddleware --- packages/core/package.json | 1 + packages/core/src/newServer/index.ts | 66 ++++-- .../src/newServer/middlewares/htmlFallback.ts | 1 + .../src/newServer/middlewares/resource.ts | 105 ++++++++++ .../core/src/server/middlewares/resources.ts | 144 ++++++------- packages/core/tsconfig.build.json | 3 +- pnpm-lock.yaml | 191 +++++------------- 7 files changed, 275 insertions(+), 236 deletions(-) create mode 100644 packages/core/src/newServer/middlewares/htmlFallback.ts diff --git a/packages/core/package.json b/packages/core/package.json index c58b0bac61..2b756916af 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -113,6 +113,7 @@ "koa-static": "^5.0.0", "lodash.debounce": "^4.0.8", "loglevel": "^1.8.1", + "mime": "^4.0.4", "mime-types": "^2.1.35", "open": "10.1.0", "slashes": "^3.0.12", diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index 3262540ea4..29f05e34ca 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -22,6 +22,7 @@ import { resolveHttpServer, resolveHttpsConfig } from './http.js'; +import { resourceMiddleware } from './middlewares/resource.js'; import { WebSocketClient, WebSocketServer, WsServer } from './ws.js'; export type HttpServer = http.Server | Http2SecureServer; @@ -124,6 +125,34 @@ export class newServer { const publicFiles = await initPublicFilesPromise; const { publicDir } = this.config.compilation.assets; this.createWebSocketServer(); + + // middleware + + middlewares.use( + resourceMiddleware(this.httpServer, this.compiler, this.publicPath) + ); + + middlewares.use((req, _res, next) => { + console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`); + next(); + }); + + // 定义一个响应中间件 + // middlewares.use((req, res, next) => { + // if (req.url === '/') { + // res.setHeader('Content-Type', 'text/plain; charset=utf-8'); + // res.end('你好,这是 Connect 中间件!'); + // } else { + // next(); + // } + // }); + + // // 定义一个 404 处理中间件 + // middlewares.use((_req, res) => { + // res.statusCode = 404; + // res.setHeader('Content-Type', 'text/plain;charset=utf-8'); + // res.end('404 - 页面未找到'); + // }); } public async createWebSocketServer() { @@ -149,7 +178,7 @@ export class newServer { const { port, open, protocol, hostname } = this.config.server; await this.compile(); - const { createServer } = await import('node:http'); + // const { createServer } = await import('node:http'); // this.httpServer = createServer((req, res) => { // if (req.url === '/') { @@ -164,23 +193,24 @@ export class newServer { // } // }); - this.httpServer.on('request', (req, res) => { - // 设置响应头 - // res.writeHead(200, { 'Content-Type': 'application/json' }); - - // 创建响应体对象 - const responseBody = { - message: "这是使用 on('request') 方法的响应", - timestamp: new Date().toISOString(), - path: req.url - }; - - // 将对象转换为 JSON 字符串 - const jsonResponse = JSON.stringify(responseBody); - res.setHeader('Content-Type', 'text/plain; charset=utf-8'); - // 发送响应 - res.end('我是默认请求头'); - }); + // this.httpServer.on('request', (req, res) => { + // // 设置响应头 + // // res.writeHead(200, { 'Content-Type': 'application/json' }); + // res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); + + // // 创建响应体对象 + // const responseBody = { + // message: "这是使用 on('request') 方法的响应", + // timestamp: new Date().toISOString(), + // path: req.url + // }; + + // // 将对象转换为 JSON 字符串 + // const jsonResponse = JSON.stringify(responseBody); + // // res.setHeader('Content-Type', 'text/plain; charset=utf-8'); + // // 发送响应 + // res.end(jsonResponse); + // }); this.httpServer.listen(port, hostname.name, () => { console.log(`Server running at ${protocol}://${hostname.name}:${port}/`); diff --git a/packages/core/src/newServer/middlewares/htmlFallback.ts b/packages/core/src/newServer/middlewares/htmlFallback.ts new file mode 100644 index 0000000000..ff8b4c5632 --- /dev/null +++ b/packages/core/src/newServer/middlewares/htmlFallback.ts @@ -0,0 +1 @@ +export default {}; diff --git a/packages/core/src/newServer/middlewares/resource.ts b/packages/core/src/newServer/middlewares/resource.ts index e69de29bb2..dab8230a5b 100644 --- a/packages/core/src/newServer/middlewares/resource.ts +++ b/packages/core/src/newServer/middlewares/resource.ts @@ -0,0 +1,105 @@ +import mime from 'mime'; +import { extname } from 'path/posix'; +import { Compiler } from '../../compiler/index.js'; +import { + generateFileTree, + generateFileTreeHtml, + stripQueryAndHash +} from '../../utils/index.js'; +import { cleanUrl } from '../../utils/url.js'; +import { HttpServer } from '../index.js'; +interface RealResourcePath { + resourcePath: string; + rawPath: string; + resource: Buffer; +} + +export function resourceMiddleware( + server: HttpServer, + compiler: Compiler, + publicPath: string +) { + return async function generateResourceMiddleware( + req: any, + res: any, + next: () => void + ) { + if (res.writableEnded) { + return next(); + } + + // const url = req.url && cleanUrl(req.url); + const url = req.url?.slice(1) || 'index.html'; + + if (compiler.compiling) { + await new Promise((resolve) => { + compiler.onUpdateFinish(() => resolve(undefined)); + }); + } + + let stripQueryAndHashUrl = stripQueryAndHash(url); + const resourceResult: any = findResource( + [url, stripQueryAndHashUrl], + compiler, + res, + publicPath + ); + + if (resourceResult === true) { + next(); + } + + if (resourceResult) { + res.setHeader('Content-Type', mime.getType(extname(url || 'index.html'))); + res.end(resourceResult.resource); + return; + } + + next(); + }; +} + +function findResource( + paths: string[], + compiler: Compiler, + res: any, + publicPath: string +): true | undefined | RealResourcePath { + for (const resourcePath of new Set(paths)) { + // output_files + if (resourcePath === '_output_files') { + const files = Object.keys(compiler.resources()).sort(); + const fileTree = generateFileTree(files); + res.type = '.html'; + res.body = generateFileTreeHtml(fileTree); + return true; + } + + const { resourceWithoutPublicPath } = normalizePathByPublicPath( + publicPath, + resourcePath + ); + + const resource = compiler.resource(resourceWithoutPublicPath); + + if (resource) { + return { + resource, + resourcePath: resourceWithoutPublicPath, + rawPath: resourcePath + }; + } + } +} + +function normalizePathByPublicPath(publicPath: string, resourcePath: string) { + const base = publicPath.match(/^https?:\/\//) ? '' : publicPath; + let resourceWithoutPublicPath = resourcePath; + + if (base && resourcePath.startsWith(base)) { + resourcePath = resourcePath.replace(new RegExp(`([^/]+)${base}`), '$1/'); + resourceWithoutPublicPath = resourcePath.slice(base.length); + } + + return { resourceWithoutPublicPath, fullPath: resourcePath }; +} diff --git a/packages/core/src/server/middlewares/resources.ts b/packages/core/src/server/middlewares/resources.ts index fbc02364d9..c1bf2a77cb 100644 --- a/packages/core/src/server/middlewares/resources.ts +++ b/packages/core/src/server/middlewares/resources.ts @@ -111,78 +111,78 @@ export function resourcesMiddleware(compiler: Compiler, serverContext: Server) { return; } - const { fullPath, resourceWithoutPublicPath } = normalizePathByPublicPath( - publicPath, - stripQueryAndHashUrl - ); - - // if resource is image or font, try it in local file system to be compatible with vue - { - // try local file system - const absPath = path.join( - compiler.config.config.root, - resourceWithoutPublicPath - ); - // const mimeStr = mime.lookup(absPath); - - if ( - existsSync(absPath) && - statSync(absPath).isFile() - // mimeStr && - // (mimeStr.startsWith('image') || mimeStr.startsWith('font')) - ) { - ctx.type = extname(fullPath); - ctx.body = readFileSync(absPath); - return; - } - - // try local file system with publicDir - const absPathPublicDir = path.resolve( - compiler.config.config.root, - compiler.config.config.assets.publicDir, - resourceWithoutPublicPath - ); - - if (existsSync(absPathPublicDir) && statSync(absPathPublicDir).isFile()) { - ctx.type = extname(fullPath); - ctx.body = readFileSync(absPathPublicDir); - return; - } - } - - // if resource is not found and spa is not disabled, find the closest index.html from resourcePath - { - // if request mime is not html, return 404 - if (!ctx.accepts('html')) { - ctx.status = 404; - } else if (config.spa !== false) { - const pathComps = resourceWithoutPublicPath.split('/'); - - while (pathComps.length > 0) { - const pathStr = pathComps.join('/') + '.html'; - const resource = compiler.resources()[pathStr]; - - if (resource) { - ctx.type = '.html'; - ctx.body = resource; - return; - } - - pathComps.pop(); - } - - const indexHtml = compiler.resources()['index.html']; - - if (indexHtml) { - ctx.type = '.html'; - ctx.body = indexHtml; - return; - } - } else { - // cannot find index.html, return 404 - ctx.status = 404; - } - } + // const { fullPath, resourceWithoutPublicPath } = normalizePathByPublicPath( + // publicPath, + // stripQueryAndHashUrl + // ); + + // // if resource is image or font, try it in local file system to be compatible with vue + // { + // // try local file system + // const absPath = path.join( + // compiler.config.config.root, + // resourceWithoutPublicPath + // ); + // // const mimeStr = mime.lookup(absPath); + + // if ( + // existsSync(absPath) && + // statSync(absPath).isFile() + // // mimeStr && + // // (mimeStr.startsWith('image') || mimeStr.startsWith('font')) + // ) { + // ctx.type = extname(fullPath); + // ctx.body = readFileSync(absPath); + // return; + // } + + // // try local file system with publicDir + // const absPathPublicDir = path.resolve( + // compiler.config.config.root, + // compiler.config.config.assets.publicDir, + // resourceWithoutPublicPath + // ); + + // if (existsSync(absPathPublicDir) && statSync(absPathPublicDir).isFile()) { + // ctx.type = extname(fullPath); + // ctx.body = readFileSync(absPathPublicDir); + // return; + // } + // } + + // // if resource is not found and spa is not disabled, find the closest index.html from resourcePath + // { + // // if request mime is not html, return 404 + // if (!ctx.accepts('html')) { + // ctx.status = 404; + // } else if (config.spa !== false) { + // const pathComps = resourceWithoutPublicPath.split('/'); + + // while (pathComps.length > 0) { + // const pathStr = pathComps.join('/') + '.html'; + // const resource = compiler.resources()[pathStr]; + + // if (resource) { + // ctx.type = '.html'; + // ctx.body = resource; + // return; + // } + + // pathComps.pop(); + // } + + // const indexHtml = compiler.resources()['index.html']; + + // if (indexHtml) { + // ctx.type = '.html'; + // ctx.body = indexHtml; + // return; + // } + // } else { + // // cannot find index.html, return 404 + // ctx.status = 404; + // } + // } }; } diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json index 0646acb6ca..14d5897b4f 100644 --- a/packages/core/tsconfig.build.json +++ b/packages/core/tsconfig.build.json @@ -4,7 +4,8 @@ "rootDir": "src", "outDir": "dist", "composite": true, - "noUnusedLocals": false + "noUnusedLocals": false, + "noUnusedParameters": false, }, "include": ["src/**/*.ts", "binding/**/*.d.ts"], "exclude": ["src/**/*.spec.ts"], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 197b92498f..ec61a6b296 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -655,7 +655,7 @@ importers: version: 5.1.3(@types/eslint@8.56.10)(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.3.2) farmup: specifier: latest - version: 0.0.12 + version: 0.1.0 jest: specifier: ^29.5.0 version: 29.7.0(@types/node@20.12.12)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@20.12.12)(typescript@5.4.5)) @@ -1121,7 +1121,7 @@ importers: version: link:../../packages/core '@farmfe/plugin-react': specifier: ^1.2.0 - version: 1.2.0 + version: link:../../rust-plugins/react '@types/react': specifier: '18' version: 18.2.35 @@ -1760,10 +1760,10 @@ importers: dependencies: tdesign-icons-vue: specifier: latest - version: 0.2.2(vue@2.6.14) + version: 0.2.4(vue@2.6.14) tdesign-vue: specifier: latest - version: 1.9.3(vue@2.6.14) + version: 1.9.8(vue@2.6.14) vite-plugin-vue2-svg: specifier: ^0.4.0 version: 0.4.0(ejs@3.1.10)(lodash@4.17.21)(vue-template-compiler@2.6.14(vue@2.6.14)) @@ -1794,10 +1794,10 @@ importers: dependencies: tdesign-icons-vue: specifier: latest - version: 0.2.2(vue@2.7.16) + version: 0.2.4(vue@2.7.16) tdesign-vue: specifier: latest - version: 1.9.3(vue@2.7.16) + version: 1.9.8(vue@2.7.16) vite-plugin-vue2-svg: specifier: ^0.4.0 version: 0.4.0(ejs@3.1.10)(lodash@4.17.21)(vue-template-compiler@2.7.16(vue@2.7.16)) @@ -2361,6 +2361,9 @@ importers: loglevel: specifier: ^1.8.1 version: 1.8.1 + mime: + specifier: ^4.0.4 + version: 4.0.4 mime-types: specifier: ^2.1.35 version: 2.1.35 @@ -3852,63 +3855,6 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@farmfe/plugin-react-darwin-arm64@1.2.0': - resolution: {integrity: sha512-9a8wp6lg8NytO+kU8hu2gCFer+PL4TJ92SkU/5v9xdcsioElWpnMDGlwcoI8bXqi60/WR8RyExsDIBubCgjbXQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@farmfe/plugin-react-darwin-x64@1.2.0': - resolution: {integrity: sha512-JXkdg3zmevlf+kbdd05+6x+L/l2IYY7Vm4hqkymbxlVdaFd2ydHmyMk9ekcmtkOijlUtEYoD3a9whstzvJ+FkA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@farmfe/plugin-react-linux-arm64-gnu@1.2.0': - resolution: {integrity: sha512-B98ldEqeJn6Uesnxr13Y/nFfIP4Qr8Svcd3mJqaOFcaOF9OZvRYFvQha1DRAoBrp8VhntghijqoWJeC1qKUhKw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@farmfe/plugin-react-linux-arm64-musl@1.2.0': - resolution: {integrity: sha512-o49P/vCWlqAkFeIVtZqy1OyyIlGHr2w+O9ty5ojwMGXGXHOrvBi1IL2ItlFqxUawweli6mNspDO0bJSJZ51gOw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@farmfe/plugin-react-linux-x64-gnu@1.2.0': - resolution: {integrity: sha512-Z1hX52mHllxXn6GolTpuN3sqmz8yku6N/rs0NHbjezgyRPWFWOMS7fnD6SMf/TPvRPGeRX1bj49rr9GMqsQEgQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@farmfe/plugin-react-linux-x64-musl@1.2.0': - resolution: {integrity: sha512-eZzEE9eCeteIpsQr1u4dnFzEEisYuuUIVhbNZX8mPCBYQ9ZB6RXMZYj3lmHgl3qNGagxH26msqcpr7t3U8qPuQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@farmfe/plugin-react-win32-arm64-msvc@1.2.0': - resolution: {integrity: sha512-JluDXSQFs3s5txZghCbeqdOjtocSW4vaoQWgcQQ88zpFrTlqqwg4xnrXdeC3CqgeNcVq5ZMJtx2VwsJqITvPxg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@farmfe/plugin-react-win32-ia32-msvc@1.2.0': - resolution: {integrity: sha512-b6I+qSG8+a59YE0d2J+QLWDi5qxQUY1C/TeYvGUBeoOs7/pCKdznvd2eQJ5N9Yvafzn6zlN9//oz1A/VLvqSBg==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@farmfe/plugin-react-win32-x64-msvc@1.2.0': - resolution: {integrity: sha512-9GWEdbvGUB+ovdAAQhHF7l4v0MaTXjOIoQZd4g6+rGDQtMIx4d1M6EOPx4D1Yn9/+dI1157UWWt9PK9Lod2h+w==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@farmfe/plugin-react@1.2.0': - resolution: {integrity: sha512-S5kU7NgqiyyhnDsZ7DEvszQIE6sCA0CNp7oTbdDcPxotPNBoyOcBHviSP3P5jvtIv6mmlF8Me2C1aLWJQRw9PA==} - '@farmfe/utils@0.0.1': resolution: {integrity: sha512-QLbgNrojcvxfumXA/H329XAXhoCahmeSH3JmaiwwJEGS2QAmWfgAJMegjwlt6OmArGVO4gSbJ7Xbmm1idZZs+g==} @@ -8106,8 +8052,8 @@ packages: farm-plugin-replace-dirname@0.2.1: resolution: {integrity: sha512-aJ4euQzxoq0sVu4AwXrNQflHJrSZdrdApGEyVRtN6KiCop3CHXnTg9ydlyCNXN2unQB283aNjojvCd5E/32KgA==} - farmup@0.0.12: - resolution: {integrity: sha512-B8RMixABXp/ILgadg2wQ1BAXwcdOqKdF1K4aq6rYur+7MsrRRlj6g/Wvifil0Xs1zvAUKBGEdCGIcu9TL29Brg==} + farmup@0.1.0: + resolution: {integrity: sha512-+xQuuQFGmHZsy9p/vDDwKOepdD7id4/THKorHmzvX3AeescqFQGJenksyhZab3cMJMYSqfJ8vbr1RkLIKgs7mg==} hasBin: true fast-deep-equal@3.1.3: @@ -8488,6 +8434,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} @@ -9887,6 +9834,11 @@ packages: engines: {node: '>=4.0.0'} hasBin: true + mime@4.0.4: + resolution: {integrity: sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==} + engines: {node: '>=16'} + hasBin: true + mimic-fn@1.2.0: resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} engines: {node: '>=4'} @@ -11496,14 +11448,17 @@ packages: rimraf@2.6.3: resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true roarr@2.15.4: @@ -12362,13 +12317,13 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} - tdesign-icons-vue@0.2.2: - resolution: {integrity: sha512-aAHrd5K2O9pQSUZb09A58qIH9b6fOP9pUapvxzvByuc5Ri3YLLgoz+6/XZcRu5S8insx7OzfhH951qjKxfzLtg==} + tdesign-icons-vue@0.2.4: + resolution: {integrity: sha512-rgBVBW/Tes+ZFXtmkN0crmQuIszLeL+MqkmDme41dxwObxjFQ+SBkPWn8m+IUrTZ9CX/uNGjFG+QTaC8qcm90g==} peerDependencies: vue: ^2.6.12 - tdesign-vue@1.9.3: - resolution: {integrity: sha512-PoKSvQsrL3kiu8+geSFWamu7m2TubeU7/MtsRxLkqOwQJK/LDIsKtLLrxi5fBKOEZpN5NOGPz9Q1YXJQpd241Q==} + tdesign-vue@1.9.8: + resolution: {integrity: sha512-8kdsbb/AChhpvopfMs9lAK9Uxy8jvxu+ZQk35MjRFuQlZ5PW2CdfctEnsnz7yUGlFBGOJB4+08KxhPi6xDkH6A==} peerDependencies: vue: ~2.6.10 @@ -13065,19 +13020,8 @@ packages: vscode-uri@3.0.8: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} - vue-demi@0.14.7: - resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} - engines: {node: '>=12'} - hasBin: true - peerDependencies: - '@vue/composition-api': ^1.0.0-rc.1 - vue: ^3.0.0-0 || ^2.6.0 - peerDependenciesMeta: - '@vue/composition-api': - optional: true - - vue-demi@0.14.8: - resolution: {integrity: sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==} + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} engines: {node: '>=12'} hasBin: true peerDependencies: @@ -13087,8 +13031,8 @@ packages: '@vue/composition-api': optional: true - vue-demi@0.14.9: - resolution: {integrity: sha512-dC1TJMODGM8lxhP6wLToncaDPPNB3biVxxRDuNCYpuXwi70ou7NsGd97KVTJ2omepGId429JZt8oaZKeXbqxwg==} + vue-demi@0.14.7: + resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} engines: {node: '>=12'} hasBin: true peerDependencies: @@ -15412,45 +15356,6 @@ snapshots: '@eslint/js@8.57.0': {} - '@farmfe/plugin-react-darwin-arm64@1.2.0': - optional: true - - '@farmfe/plugin-react-darwin-x64@1.2.0': - optional: true - - '@farmfe/plugin-react-linux-arm64-gnu@1.2.0': - optional: true - - '@farmfe/plugin-react-linux-arm64-musl@1.2.0': - optional: true - - '@farmfe/plugin-react-linux-x64-gnu@1.2.0': - optional: true - - '@farmfe/plugin-react-linux-x64-musl@1.2.0': - optional: true - - '@farmfe/plugin-react-win32-arm64-msvc@1.2.0': - optional: true - - '@farmfe/plugin-react-win32-ia32-msvc@1.2.0': - optional: true - - '@farmfe/plugin-react-win32-x64-msvc@1.2.0': - optional: true - - '@farmfe/plugin-react@1.2.0': - optionalDependencies: - '@farmfe/plugin-react-darwin-arm64': 1.2.0 - '@farmfe/plugin-react-darwin-x64': 1.2.0 - '@farmfe/plugin-react-linux-arm64-gnu': 1.2.0 - '@farmfe/plugin-react-linux-arm64-musl': 1.2.0 - '@farmfe/plugin-react-linux-x64-gnu': 1.2.0 - '@farmfe/plugin-react-linux-x64-musl': 1.2.0 - '@farmfe/plugin-react-win32-arm64-msvc': 1.2.0 - '@farmfe/plugin-react-win32-ia32-msvc': 1.2.0 - '@farmfe/plugin-react-win32-x64-msvc': 1.2.0 - '@farmfe/utils@0.0.1': {} '@floating-ui/core@1.5.0': @@ -15470,7 +15375,7 @@ snapshots: dependencies: '@monaco-editor/loader': 1.4.0 vue: 3.4.15(typescript@5.4.5) - vue-demi: 0.14.9(@vue/composition-api@1.7.2(vue@3.4.15(typescript@5.4.5)))(vue@3.4.15(typescript@5.4.5)) + vue-demi: 0.14.10(@vue/composition-api@1.7.2(vue@3.4.15(typescript@5.4.5)))(vue@3.4.15(typescript@5.4.5)) optionalDependencies: '@vue/composition-api': 1.7.2(vue@3.4.15(typescript@5.4.5)) @@ -18322,7 +18227,7 @@ snapshots: '@types/web-bluetooth': 0.0.16 '@vueuse/metadata': 9.13.0 '@vueuse/shared': 9.13.0(@vue/composition-api@1.7.2(vue@3.3.7(typescript@5.4.5)))(vue@3.3.7(typescript@5.4.5)) - vue-demi: 0.14.8(@vue/composition-api@1.7.2(vue@3.3.7(typescript@5.4.5)))(vue@3.3.7(typescript@5.4.5)) + vue-demi: 0.14.10(@vue/composition-api@1.7.2(vue@3.3.7(typescript@5.4.5)))(vue@3.3.7(typescript@5.4.5)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -18331,7 +18236,7 @@ snapshots: '@vueuse/shared@9.13.0(@vue/composition-api@1.7.2(vue@3.3.7(typescript@5.4.5)))(vue@3.3.7(typescript@5.4.5))': dependencies: - vue-demi: 0.14.9(@vue/composition-api@1.7.2(vue@3.3.7(typescript@5.4.5)))(vue@3.3.7(typescript@5.4.5)) + vue-demi: 0.14.10(@vue/composition-api@1.7.2(vue@3.3.7(typescript@5.4.5)))(vue@3.3.7(typescript@5.4.5)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -20473,7 +20378,7 @@ snapshots: chalk: 4.1.2 fs-extra: 10.1.0 lazy-val: 1.0.5 - mime: 2.5.2 + mime: 2.6.0 transitivePeerDependencies: - supports-color @@ -21129,7 +21034,7 @@ snapshots: farm-plugin-replace-dirname-win32-ia32-msvc: 0.2.1 farm-plugin-replace-dirname-win32-x64-msvc: 0.2.1 - farmup@0.0.12: + farmup@0.1.0: dependencies: '@farmfe/core': link:packages/core @@ -23235,6 +23140,8 @@ snapshots: mime@2.6.0: {} + mime@4.0.4: {} + mimic-fn@1.2.0: {} mimic-fn@2.1.0: {} @@ -25908,19 +25815,19 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 - tdesign-icons-vue@0.2.2(vue@2.6.14): + tdesign-icons-vue@0.2.4(vue@2.6.14): dependencies: '@babel/runtime': 7.23.2 classnames: 2.3.2 vue: 2.6.14 - tdesign-icons-vue@0.2.2(vue@2.7.16): + tdesign-icons-vue@0.2.4(vue@2.7.16): dependencies: '@babel/runtime': 7.23.2 classnames: 2.3.2 vue: 2.7.16 - tdesign-vue@1.9.3(vue@2.6.14): + tdesign-vue@1.9.8(vue@2.6.14): dependencies: '@babel/runtime': 7.23.2 '@popperjs/core': 2.11.8 @@ -25937,12 +25844,12 @@ snapshots: mitt: 3.0.1 raf: 3.4.1 sortablejs: 1.15.2 - tdesign-icons-vue: 0.2.2(vue@2.6.14) + tdesign-icons-vue: 0.2.4(vue@2.6.14) tinycolor2: 1.6.0 validator: 13.11.0 vue: 2.6.14 - tdesign-vue@1.9.3(vue@2.7.16): + tdesign-vue@1.9.8(vue@2.7.16): dependencies: '@babel/runtime': 7.23.2 '@popperjs/core': 2.11.8 @@ -25959,7 +25866,7 @@ snapshots: mitt: 3.0.1 raf: 3.4.1 sortablejs: 1.15.2 - tdesign-icons-vue: 0.2.2(vue@2.7.16) + tdesign-icons-vue: 0.2.4(vue@2.7.16) tinycolor2: 1.6.0 validator: 13.11.0 vue: 2.7.16 @@ -26956,25 +26863,19 @@ snapshots: vscode-uri@3.0.8: {} - vue-demi@0.14.7(@vue/composition-api@1.7.2(vue@3.4.15(typescript@5.4.5)))(vue@3.4.15(typescript@5.4.5)): - dependencies: - vue: 3.4.15(typescript@5.4.5) - optionalDependencies: - '@vue/composition-api': 1.7.2(vue@3.4.15(typescript@5.4.5)) - - vue-demi@0.14.8(@vue/composition-api@1.7.2(vue@3.3.7(typescript@5.4.5)))(vue@3.3.7(typescript@5.4.5)): + vue-demi@0.14.10(@vue/composition-api@1.7.2(vue@3.3.7(typescript@5.4.5)))(vue@3.3.7(typescript@5.4.5)): dependencies: vue: 3.3.7(typescript@5.4.5) optionalDependencies: '@vue/composition-api': 1.7.2(vue@3.3.7(typescript@5.4.5)) - vue-demi@0.14.9(@vue/composition-api@1.7.2(vue@3.3.7(typescript@5.4.5)))(vue@3.3.7(typescript@5.4.5)): + vue-demi@0.14.10(@vue/composition-api@1.7.2(vue@3.4.15(typescript@5.4.5)))(vue@3.4.15(typescript@5.4.5)): dependencies: - vue: 3.3.7(typescript@5.4.5) + vue: 3.4.15(typescript@5.4.5) optionalDependencies: - '@vue/composition-api': 1.7.2(vue@3.3.7(typescript@5.4.5)) + '@vue/composition-api': 1.7.2(vue@3.4.15(typescript@5.4.5)) - vue-demi@0.14.9(@vue/composition-api@1.7.2(vue@3.4.15(typescript@5.4.5)))(vue@3.4.15(typescript@5.4.5)): + vue-demi@0.14.7(@vue/composition-api@1.7.2(vue@3.4.15(typescript@5.4.5)))(vue@3.4.15(typescript@5.4.5)): dependencies: vue: 3.4.15(typescript@5.4.5) optionalDependencies: From aa7cc382b8e45fbcad78fa1cf6c79af38f997b4e Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Sat, 27 Jul 2024 12:37:53 +0800 Subject: [PATCH 012/369] chore: progress with htmlFallback middleware --- examples/refactor-react/about.html | 11 ++ packages/core/package.json | 2 + packages/core/src/newServer/http.ts | 4 + packages/core/src/newServer/index.ts | 19 ++- .../src/newServer/middlewares/htmlFallback.ts | 66 ++++++++- .../src/newServer/middlewares/resource.ts | 84 ++++++----- packages/core/src/utils/fsUtils.ts | 131 ++++++++++++++++++ pnpm-lock.yaml | 13 ++ 8 files changed, 293 insertions(+), 37 deletions(-) create mode 100644 examples/refactor-react/about.html create mode 100644 packages/core/src/utils/fsUtils.ts diff --git a/examples/refactor-react/about.html b/examples/refactor-react/about.html new file mode 100644 index 0000000000..5b65d32cf5 --- /dev/null +++ b/examples/refactor-react/about.html @@ -0,0 +1,11 @@ + + + + + + Document + + + im about page + + diff --git a/packages/core/package.json b/packages/core/package.json index 2b756916af..473e87d109 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -69,6 +69,7 @@ }, "devDependencies": { "@napi-rs/cli": "^2.18.4", + "@types/compression": "^1.7.5", "@types/connect": "^3.4.38", "@types/figlet": "^1.5.5", "@types/fs-extra": "^11.0.1", @@ -97,6 +98,7 @@ "@koa/cors": "^5.0.0", "@swc/helpers": "^0.5.0", "chokidar": "^3.5.3", + "compression": "^1.7.4", "deepmerge": "^4.3.1", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", diff --git a/packages/core/src/newServer/http.ts b/packages/core/src/newServer/http.ts index a8f55ac02c..56dcb26985 100644 --- a/packages/core/src/newServer/http.ts +++ b/packages/core/src/newServer/http.ts @@ -65,9 +65,13 @@ export async function resolveHttpServer( // https://github.com/nxtedition/node-http2-proxy // https://github.com/fastify/fastify-http-proxy if (proxy) { + console.log('走的是哪里'); + const { createServer } = await import('node:https'); return createServer(httpsOptions, app); } else { + console.log('我现在用的就是 http2'); + const { createSecureServer } = await import('node:http2'); return createSecureServer( { diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index 29f05e34ca..bdb7cc920a 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -5,6 +5,7 @@ import { type Http2SecureServer } from 'node:http2'; import type { ServerOptions as HttpsServerOptions } from 'node:https'; import path from 'node:path'; import { WatchOptions } from 'chokidar'; +import compression from 'compression'; import connect from 'connect'; import fse from 'fs-extra'; import { WebSocketServer as WebSocketServerRaw_ } from 'ws'; @@ -22,6 +23,7 @@ import { resolveHttpServer, resolveHttpsConfig } from './http.js'; +import { htmlFallbackMiddleware } from './middlewares/htmlFallback.js'; import { resourceMiddleware } from './middlewares/resource.js'; import { WebSocketClient, WebSocketServer, WsServer } from './ws.js'; export type HttpServer = http.Server | Http2SecureServer; @@ -127,15 +129,24 @@ export class newServer { this.createWebSocketServer(); // middleware + // middlewares.use(compression()); + // TODO todo add appType + middlewares.use( + htmlFallbackMiddleware( + this.httpServer, + this.compiler, + this.publicPath, + this.config + ) + ); middlewares.use( resourceMiddleware(this.httpServer, this.compiler, this.publicPath) ); - middlewares.use((req, _res, next) => { - console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`); - next(); - }); + // middlewares.use((req, _res, next) => { + // next(); + // }); // 定义一个响应中间件 // middlewares.use((req, res, next) => { diff --git a/packages/core/src/newServer/middlewares/htmlFallback.ts b/packages/core/src/newServer/middlewares/htmlFallback.ts index ff8b4c5632..672b4e9329 100644 --- a/packages/core/src/newServer/middlewares/htmlFallback.ts +++ b/packages/core/src/newServer/middlewares/htmlFallback.ts @@ -1 +1,65 @@ -export default {}; +import path from 'path'; +import { Compiler } from '../../compiler/index.js'; +import { ResolvedUserConfig } from '../../config/types.js'; +import { commonFsUtils } from '../../utils/fsUtils.js'; +import { cleanUrl } from '../../utils/url.js'; +import { HttpServer } from '../index.js'; +export function htmlFallbackMiddleware( + server: HttpServer, + compiler: Compiler, + publicPath: string, + config: ResolvedUserConfig +) { + return async function htmlFallbackMiddleware( + req: any, + res: any, + next: () => void + ) { + if ( + // Only accept GET or HEAD + (req.method !== 'GET' && req.method !== 'HEAD') || + // Exclude default favicon requests + req.url === '/favicon.ico' || + // Require Accept: text/html or */* + !( + req.headers.accept === undefined || // equivalent to `Accept: */*` + req.headers.accept === '' || // equivalent to `Accept: */*` + req.headers.accept.includes('text/html') || + req.headers.accept.includes('*/*') + ) + ) { + return next(); + } + const url = cleanUrl(req.url); + const pathname = decodeURIComponent(url); + + if (pathname.endsWith('.html')) { + const filePath = path.join(config.root, pathname); + if (commonFsUtils.existsSync(filePath)) { + req.url = url; + return next(); + } + } else if (pathname[pathname.length - 1] === '/') { + const filePath = path.join(config.root, pathname, 'index.html'); + if (commonFsUtils.existsSync(filePath)) { + const newUrl = url + 'index.html'; + req.url = newUrl; + return next(); + } + } else { + //TODO mpa not compatible 如果是纯 html 的结果 html 需要可能判断一下 mpa 适配 + const filePath = path.join(config.root, pathname + '.html'); + if (commonFsUtils.existsSync(filePath)) { + const newUrl = url + '.html'; + req.url = newUrl; + return next(); + } + } + + // TODO htmlFallBack when spa + // if (config.appType === 'spa') { + // req.url = '/index.html'; + // } + next(); + }; +} diff --git a/packages/core/src/newServer/middlewares/resource.ts b/packages/core/src/newServer/middlewares/resource.ts index dab8230a5b..328c673d4d 100644 --- a/packages/core/src/newServer/middlewares/resource.ts +++ b/packages/core/src/newServer/middlewares/resource.ts @@ -1,3 +1,4 @@ +import { Readable } from 'node:stream'; import mime from 'mime'; import { extname } from 'path/posix'; import { Compiler } from '../../compiler/index.js'; @@ -28,8 +29,13 @@ export function resourceMiddleware( return next(); } - // const url = req.url && cleanUrl(req.url); - const url = req.url?.slice(1) || 'index.html'; + const url = req.url && cleanUrl(req.url); + console.log('url:', url); + + // TODO resolve html but not input file html + // htmlFallbackMiddleware appends '.html' to URLs + // if (url?.endsWith('.html') && req.headers['sec-fetch-dest'] !== 'script') { + // } if (compiler.compiling) { await new Promise((resolve) => { @@ -37,21 +43,37 @@ export function resourceMiddleware( }); } - let stripQueryAndHashUrl = stripQueryAndHash(url); - const resourceResult: any = findResource( - [url, stripQueryAndHashUrl], - compiler, - res, - publicPath - ); + const resourceResult: any = findResource(url, compiler, res, publicPath); if (resourceResult === true) { next(); } + // if (resourceResult) { + // res.setHeader('Content-Type', mime.getType(extname(url || 'index.html'))); + // res.end(resourceResult.resource); + // return; + // } + if (resourceResult) { + if (resourceResult.etag) { + const ifNoneMatch = req.headers['if-none-match']; + if (ifNoneMatch === resourceResult.etag) { + res.statusCode = 304; + res.end(); + return; + } + res.setHeader('ETag', resourceResult.etag); + } + + res.setHeader('Cache-Control', 'max-age=31536000,immutable'); res.setHeader('Content-Type', mime.getType(extname(url || 'index.html'))); - res.end(resourceResult.resource); + + if (resourceResult.resource.length > 1024 * 1024) { + Readable.from(resourceResult.resource).pipe(res); + } else { + res.end(resourceResult.resource); + } return; } @@ -60,35 +82,33 @@ export function resourceMiddleware( } function findResource( - paths: string[], + paths: string, compiler: Compiler, res: any, publicPath: string ): true | undefined | RealResourcePath { - for (const resourcePath of new Set(paths)) { - // output_files - if (resourcePath === '_output_files') { - const files = Object.keys(compiler.resources()).sort(); - const fileTree = generateFileTree(files); - res.type = '.html'; - res.body = generateFileTreeHtml(fileTree); - return true; - } + // output_files + if (paths === '_output_files') { + const files = Object.keys(compiler.resources()).sort(); + const fileTree = generateFileTree(files); + res.type = '.html'; + res.body = generateFileTreeHtml(fileTree); + return true; + } - const { resourceWithoutPublicPath } = normalizePathByPublicPath( - publicPath, - resourcePath - ); + const { resourceWithoutPublicPath } = normalizePathByPublicPath( + publicPath, + paths + ); - const resource = compiler.resource(resourceWithoutPublicPath); + const resource = compiler.resource(resourceWithoutPublicPath); - if (resource) { - return { - resource, - resourcePath: resourceWithoutPublicPath, - rawPath: resourcePath - }; - } + if (resource) { + return { + resource, + resourcePath: resourceWithoutPublicPath, + rawPath: paths + }; } } diff --git a/packages/core/src/utils/fsUtils.ts b/packages/core/src/utils/fsUtils.ts new file mode 100644 index 0000000000..65b43a3f28 --- /dev/null +++ b/packages/core/src/utils/fsUtils.ts @@ -0,0 +1,131 @@ +import { exec } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import { isWindows, normalizePath } from './share.js'; + +export function tryStatSync(file: string): fs.Stats | undefined { + try { + // The "throwIfNoEntry" is a performance optimization for cases where the file does not exist + return fs.statSync(file, { throwIfNoEntry: false }); + } catch { + // Ignore errors + } +} + +function isDirectory(path: string): boolean { + const stat = tryStatSync(path); + return stat?.isDirectory() ?? false; +} + +function tryResolveRealFile( + file: string, + preserveSymlinks?: boolean +): string | undefined { + const stat = tryStatSync(file); + if (stat?.isFile()) return getRealPath(file, preserveSymlinks); +} + +function tryResolveRealFileWithExtensions( + filePath: string, + extensions: string[], + preserveSymlinks?: boolean +): string | undefined { + for (const ext of extensions) { + const res = tryResolveRealFile(filePath + ext, preserveSymlinks); + if (res) return res; + } +} + +function tryResolveRealFileOrType( + file: string, + preserveSymlinks?: boolean +): { path?: string; type: 'directory' | 'file' } | undefined { + const fileStat = tryStatSync(file); + if (fileStat?.isFile()) { + return { path: getRealPath(file, preserveSymlinks), type: 'file' }; + } + if (fileStat?.isDirectory()) { + return { type: 'directory' }; + } + return; +} +const windowsNetworkMap = new Map(); + +function windowsMappedRealpathSync(path: string) { + const realPath = fs.realpathSync.native(path); + if (realPath.startsWith('\\\\')) { + for (const [network, volume] of windowsNetworkMap) { + if (realPath.startsWith(network)) + return realPath.replace(network, volume); + } + } + return realPath; +} +function optimizeSafeRealPathSync() { + // Skip if using Node <18.10 due to MAX_PATH issue: https://github.com/vitejs/vite/issues/12931 + const nodeVersion = process.versions.node.split('.').map(Number); + if (nodeVersion[0] < 18 || (nodeVersion[0] === 18 && nodeVersion[1] < 10)) { + safeRealpathSync = fs.realpathSync; + return; + } + // Check the availability `fs.realpathSync.native` + // in Windows virtual and RAM disks that bypass the Volume Mount Manager, in programs such as imDisk + // get the error EISDIR: illegal operation on a directory + try { + fs.realpathSync.native(path.resolve('./')); + } catch (error) { + if (error.message.includes('EISDIR: illegal operation on a directory')) { + safeRealpathSync = fs.realpathSync; + return; + } + } + exec('net use', (error, stdout) => { + if (error) return; + const lines = stdout.split('\n'); + // OK Y: \\NETWORKA\Foo Microsoft Windows Network + // OK Z: \\NETWORKA\Bar Microsoft Windows Network + for (const line of lines) { + const m = line.match(parseNetUseRE); + if (m) windowsNetworkMap.set(m[3], m[2]); + } + if (windowsNetworkMap.size === 0) { + safeRealpathSync = fs.realpathSync.native; + } else { + safeRealpathSync = windowsMappedRealpathSync; + } + }); +} + +const parseNetUseRE = /^(\w+)? +(\w:) +([^ ]+)\s/; +let firstSafeRealPathSyncRun = false; + +function windowsSafeRealPathSync(path: string): string { + if (!firstSafeRealPathSyncRun) { + optimizeSafeRealPathSync(); + firstSafeRealPathSyncRun = true; + } + return fs.realpathSync(path); +} + +// `fs.realpathSync.native` resolves differently in Windows network drive, +// causing file read errors. skip for now. +// https://github.com/nodejs/node/issues/37737 +export let safeRealpathSync = isWindows + ? windowsSafeRealPathSync + : fs.realpathSync.native; + +function getRealPath(resolved: string, preserveSymlinks?: boolean): string { + if (!preserveSymlinks) { + resolved = safeRealpathSync(resolved); + } + return normalizePath(resolved); +} + +export const commonFsUtils = { + existsSync: fs.existsSync, + isDirectory, + + tryResolveRealFile, + tryResolveRealFileWithExtensions, + tryResolveRealFileOrType +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec61a6b296..2ea0fa85f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2313,6 +2313,9 @@ importers: chokidar: specifier: ^3.5.3 version: 3.5.3 + compression: + specifier: ^1.7.4 + version: 1.7.4 deepmerge: specifier: ^4.3.1 version: 4.3.1 @@ -2383,6 +2386,9 @@ importers: '@napi-rs/cli': specifier: ^2.18.4 version: 2.18.4 + '@types/compression': + specifier: ^1.7.5 + version: 1.7.5 '@types/connect': specifier: ^3.4.38 version: 3.4.38 @@ -4915,6 +4921,9 @@ packages: resolution: {integrity: sha512-VwVFUHlneOsWfv/GaaY7Kwk4XasDqkAlyFQtsHxnOw0yyBYWTrlEXtmb9RtC+VFBCdtuOeIXECmELNd5RrKp/g==} deprecated: This is a stub types definition. clipboard provides its own type definitions, so you do not need this installed. + '@types/compression@1.7.5': + resolution: {integrity: sha512-AAQvK5pxMpaT+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDg==} + '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} @@ -17161,6 +17170,10 @@ snapshots: dependencies: clipboard: 2.0.11 + '@types/compression@1.7.5': + dependencies: + '@types/express': 4.17.21 + '@types/connect@3.4.38': dependencies: '@types/node': 18.18.8 From 3c6b54df6a4623a1caaec4ec79c951190caaa830 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Sat, 27 Jul 2024 12:44:58 +0800 Subject: [PATCH 013/369] chore: prepare publicMiddleware --- packages/core/src/newServer/http.ts | 2 +- packages/core/src/newServer/index.ts | 16 ++++++++++++++-- .../middlewares/{cors.ts => corsMiddleware.ts} | 0 .../middlewares/{error.ts => errorMiddleware.ts} | 0 ...htmlFallback.ts => htmlFallbackMiddleware.ts} | 0 .../{lazy-compilation.ts => lazyCompilation.ts} | 0 .../{notFound.ts => notFoundMiddleware.ts} | 0 .../middlewares/{proxy.ts => proxyMiddleware.ts} | 0 .../newServer/middlewares/publicMiddleware.ts | 16 ++++++++++++++++ .../{resource.ts => resourceMiddleware.ts} | 0 .../{static.ts => staticMiddleware.ts} | 0 11 files changed, 31 insertions(+), 3 deletions(-) rename packages/core/src/newServer/middlewares/{cors.ts => corsMiddleware.ts} (100%) rename packages/core/src/newServer/middlewares/{error.ts => errorMiddleware.ts} (100%) rename packages/core/src/newServer/middlewares/{htmlFallback.ts => htmlFallbackMiddleware.ts} (100%) rename packages/core/src/newServer/middlewares/{lazy-compilation.ts => lazyCompilation.ts} (100%) rename packages/core/src/newServer/middlewares/{notFound.ts => notFoundMiddleware.ts} (100%) rename packages/core/src/newServer/middlewares/{proxy.ts => proxyMiddleware.ts} (100%) create mode 100644 packages/core/src/newServer/middlewares/publicMiddleware.ts rename packages/core/src/newServer/middlewares/{resource.ts => resourceMiddleware.ts} (100%) rename packages/core/src/newServer/middlewares/{static.ts => staticMiddleware.ts} (100%) diff --git a/packages/core/src/newServer/http.ts b/packages/core/src/newServer/http.ts index 56dcb26985..d7689679c4 100644 --- a/packages/core/src/newServer/http.ts +++ b/packages/core/src/newServer/http.ts @@ -18,7 +18,7 @@ import connect from 'connect'; import fse from 'fs-extra'; import { Logger } from '../utils/logger.js'; import { HttpServer } from './index.js'; -import { ProxyOptions } from './middlewares/proxy.js'; +import { ProxyOptions } from './middlewares/proxyMiddleware.js'; export interface CommonServerOptions { port?: number; diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index bdb7cc920a..412251332b 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -23,8 +23,9 @@ import { resolveHttpServer, resolveHttpsConfig } from './http.js'; -import { htmlFallbackMiddleware } from './middlewares/htmlFallback.js'; -import { resourceMiddleware } from './middlewares/resource.js'; +import { htmlFallbackMiddleware } from './middlewares/htmlFallbackMiddleware.js'; +import { publicMiddleware } from './middlewares/publicMiddleware.js'; +import { resourceMiddleware } from './middlewares/resourceMiddleware.js'; import { WebSocketClient, WebSocketServer, WsServer } from './ws.js'; export type HttpServer = http.Server | Http2SecureServer; @@ -130,6 +131,17 @@ export class newServer { // middleware // middlewares.use(compression()); + + if (this.publicDir) { + middlewares.use( + publicMiddleware( + this.httpServer, + this.compiler, + this.publicPath, + this.config + ) + ); + } // TODO todo add appType middlewares.use( htmlFallbackMiddleware( diff --git a/packages/core/src/newServer/middlewares/cors.ts b/packages/core/src/newServer/middlewares/corsMiddleware.ts similarity index 100% rename from packages/core/src/newServer/middlewares/cors.ts rename to packages/core/src/newServer/middlewares/corsMiddleware.ts diff --git a/packages/core/src/newServer/middlewares/error.ts b/packages/core/src/newServer/middlewares/errorMiddleware.ts similarity index 100% rename from packages/core/src/newServer/middlewares/error.ts rename to packages/core/src/newServer/middlewares/errorMiddleware.ts diff --git a/packages/core/src/newServer/middlewares/htmlFallback.ts b/packages/core/src/newServer/middlewares/htmlFallbackMiddleware.ts similarity index 100% rename from packages/core/src/newServer/middlewares/htmlFallback.ts rename to packages/core/src/newServer/middlewares/htmlFallbackMiddleware.ts diff --git a/packages/core/src/newServer/middlewares/lazy-compilation.ts b/packages/core/src/newServer/middlewares/lazyCompilation.ts similarity index 100% rename from packages/core/src/newServer/middlewares/lazy-compilation.ts rename to packages/core/src/newServer/middlewares/lazyCompilation.ts diff --git a/packages/core/src/newServer/middlewares/notFound.ts b/packages/core/src/newServer/middlewares/notFoundMiddleware.ts similarity index 100% rename from packages/core/src/newServer/middlewares/notFound.ts rename to packages/core/src/newServer/middlewares/notFoundMiddleware.ts diff --git a/packages/core/src/newServer/middlewares/proxy.ts b/packages/core/src/newServer/middlewares/proxyMiddleware.ts similarity index 100% rename from packages/core/src/newServer/middlewares/proxy.ts rename to packages/core/src/newServer/middlewares/proxyMiddleware.ts diff --git a/packages/core/src/newServer/middlewares/publicMiddleware.ts b/packages/core/src/newServer/middlewares/publicMiddleware.ts new file mode 100644 index 0000000000..f7e50de23b --- /dev/null +++ b/packages/core/src/newServer/middlewares/publicMiddleware.ts @@ -0,0 +1,16 @@ +import { Compiler } from '../../compiler/index.js'; +import { ResolvedUserConfig } from '../../config/types.js'; +import { HttpServer } from '../index.js'; + +export function publicMiddleware( + server: HttpServer, + compiler: Compiler, + publicPath: string, + config: ResolvedUserConfig +) { + return async function handlerPublicMiddleware( + req: any, + res: any, + next: () => void + ) {}; +} diff --git a/packages/core/src/newServer/middlewares/resource.ts b/packages/core/src/newServer/middlewares/resourceMiddleware.ts similarity index 100% rename from packages/core/src/newServer/middlewares/resource.ts rename to packages/core/src/newServer/middlewares/resourceMiddleware.ts diff --git a/packages/core/src/newServer/middlewares/static.ts b/packages/core/src/newServer/middlewares/staticMiddleware.ts similarity index 100% rename from packages/core/src/newServer/middlewares/static.ts rename to packages/core/src/newServer/middlewares/staticMiddleware.ts From 7fc0c1e59dc53e8f8fafb50e7a7ac4b6bd46d2c7 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Sun, 28 Jul 2024 11:22:45 +0800 Subject: [PATCH 014/369] chore: set send req res --- examples/refactor-react/index.html | 5 +- packages/core/package.json | 1 + packages/core/src/newServer/index.ts | 7 ++- .../middlewares/resourceMiddleware.ts | 35 ++++---------- packages/core/src/newServer/send.ts | 48 +++++++++++++++++++ pnpm-lock.yaml | 10 ++++ 6 files changed, 77 insertions(+), 29 deletions(-) create mode 100644 packages/core/src/newServer/send.ts diff --git a/examples/refactor-react/index.html b/examples/refactor-react/index.html index 56d5a1bdb6..e7974db029 100644 --- a/examples/refactor-react/index.html +++ b/examples/refactor-react/index.html @@ -4,11 +4,12 @@ - + + Farm + React + TS
- \ No newline at end of file + diff --git a/packages/core/package.json b/packages/core/package.json index 473e87d109..6b9cf5826f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -71,6 +71,7 @@ "@napi-rs/cli": "^2.18.4", "@types/compression": "^1.7.5", "@types/connect": "^3.4.38", + "@types/etag": "^1.8.3", "@types/figlet": "^1.5.5", "@types/fs-extra": "^11.0.1", "@types/http-proxy": "^1.17.14", diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index 412251332b..20cbeba2ba 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -153,7 +153,12 @@ export class newServer { ); middlewares.use( - resourceMiddleware(this.httpServer, this.compiler, this.publicPath) + resourceMiddleware( + this.httpServer, + this.compiler, + this.publicPath, + this.config + ) ); // middlewares.use((req, _res, next) => { diff --git a/packages/core/src/newServer/middlewares/resourceMiddleware.ts b/packages/core/src/newServer/middlewares/resourceMiddleware.ts index 328c673d4d..57d406e1d8 100644 --- a/packages/core/src/newServer/middlewares/resourceMiddleware.ts +++ b/packages/core/src/newServer/middlewares/resourceMiddleware.ts @@ -2,6 +2,7 @@ import { Readable } from 'node:stream'; import mime from 'mime'; import { extname } from 'path/posix'; import { Compiler } from '../../compiler/index.js'; +import { ResolvedUserConfig } from '../../config/types.js'; import { generateFileTree, generateFileTreeHtml, @@ -9,6 +10,7 @@ import { } from '../../utils/index.js'; import { cleanUrl } from '../../utils/url.js'; import { HttpServer } from '../index.js'; +import { send } from '../send.js'; interface RealResourcePath { resourcePath: string; rawPath: string; @@ -18,7 +20,8 @@ interface RealResourcePath { export function resourceMiddleware( server: HttpServer, compiler: Compiler, - publicPath: string + publicPath: string, + config: ResolvedUserConfig ) { return async function generateResourceMiddleware( req: any, @@ -30,7 +33,6 @@ export function resourceMiddleware( } const url = req.url && cleanUrl(req.url); - console.log('url:', url); // TODO resolve html but not input file html // htmlFallbackMiddleware appends '.html' to URLs @@ -49,31 +51,12 @@ export function resourceMiddleware( next(); } - // if (resourceResult) { - // res.setHeader('Content-Type', mime.getType(extname(url || 'index.html'))); - // res.end(resourceResult.resource); - // return; - // } - if (resourceResult) { - if (resourceResult.etag) { - const ifNoneMatch = req.headers['if-none-match']; - if (ifNoneMatch === resourceResult.etag) { - res.statusCode = 304; - res.end(); - return; - } - res.setHeader('ETag', resourceResult.etag); - } - - res.setHeader('Cache-Control', 'max-age=31536000,immutable'); - res.setHeader('Content-Type', mime.getType(extname(url || 'index.html'))); - - if (resourceResult.resource.length > 1024 * 1024) { - Readable.from(resourceResult.resource).pipe(res); - } else { - res.end(resourceResult.resource); - } + console.log(url); + + // need judge if resource is a deps node_modules set cache-control to 1 year + const headers = config.server.headers || {}; + send(req, res, resourceResult.resource, url, { headers }); return; } diff --git a/packages/core/src/newServer/send.ts b/packages/core/src/newServer/send.ts new file mode 100644 index 0000000000..32d26731e1 --- /dev/null +++ b/packages/core/src/newServer/send.ts @@ -0,0 +1,48 @@ +import { IncomingMessage, OutgoingHttpHeaders, ServerResponse } from 'http'; +import getEtag from 'etag'; +import mime from 'mime'; +import { extname } from 'path/posix'; + +export interface SendOptions { + etag?: string; + cacheControl?: string; + headers?: OutgoingHttpHeaders; +} + +export function send( + req: IncomingMessage, + res: ServerResponse, + content: string | Buffer, + url: string, + options: SendOptions +): void { + const { + etag = getEtag(content, { weak: true }), + cacheControl = 'no-cache', + headers + } = options; + + if (res.writableEnded) { + return; + } + + if (req.headers['if-none-match'] === etag) { + res.statusCode = 304; + res.end(); + return; + } + + res.setHeader('Content-Type', mime.getType(extname(url))); + res.setHeader('Cache-Control', cacheControl); + res.setHeader('Etag', etag); + + if (headers) { + for (const name in headers) { + res.setHeader(name, headers[name]); + } + } + + res.statusCode = 200; + res.end(content); + return; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ea0fa85f0..9d74128bf6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2392,6 +2392,9 @@ importers: '@types/connect': specifier: ^3.4.38 version: 3.4.38 + '@types/etag': + specifier: ^1.8.3 + version: 1.8.3 '@types/figlet': specifier: ^1.5.5 version: 1.5.7 @@ -4960,6 +4963,9 @@ packages: '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/etag@1.8.3': + resolution: {integrity: sha512-QYHv9Yeh1ZYSMPQOoxY4XC4F1r+xRUiAriB303F4G6uBsT3KKX60DjiogvVv+2VISVDuJhcIzMdbjT+Bm938QQ==} + '@types/express-serve-static-core@4.17.39': resolution: {integrity: sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==} @@ -17215,6 +17221,10 @@ snapshots: '@types/estree@1.0.5': {} + '@types/etag@1.8.3': + dependencies: + '@types/node': 18.18.8 + '@types/express-serve-static-core@4.17.39': dependencies: '@types/node': 18.18.8 From f748067d2cc0f796eb1092142077ce8a8eb95a93 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Sun, 28 Jul 2024 11:25:12 +0800 Subject: [PATCH 015/369] chore: set send req res --- packages/core/src/newServer/middlewares/resourceMiddleware.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/src/newServer/middlewares/resourceMiddleware.ts b/packages/core/src/newServer/middlewares/resourceMiddleware.ts index 57d406e1d8..03dfb8c68a 100644 --- a/packages/core/src/newServer/middlewares/resourceMiddleware.ts +++ b/packages/core/src/newServer/middlewares/resourceMiddleware.ts @@ -52,8 +52,6 @@ export function resourceMiddleware( } if (resourceResult) { - console.log(url); - // need judge if resource is a deps node_modules set cache-control to 1 year const headers = config.server.headers || {}; send(req, res, resourceResult.resource, url, { headers }); From 1a810ddbe162bf40c343683a310b918db4277bb7 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Sun, 28 Jul 2024 12:13:03 +0800 Subject: [PATCH 016/369] chore: set send req res --- examples/react/public/vite.svg | 1 + examples/react/src/vite.svg | 1 + examples/refactor-react/index.html | 2 +- examples/refactor-react/src/assets/base/react.svg | 1 + examples/refactor-react/src/main.tsx | 8 ++++---- 5 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 examples/react/public/vite.svg create mode 100644 examples/react/src/vite.svg create mode 100644 examples/refactor-react/src/assets/base/react.svg diff --git a/examples/react/public/vite.svg b/examples/react/public/vite.svg new file mode 100644 index 0000000000..e7b8dfb1b2 --- /dev/null +++ b/examples/react/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/react/src/vite.svg b/examples/react/src/vite.svg new file mode 100644 index 0000000000..e7b8dfb1b2 --- /dev/null +++ b/examples/react/src/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/refactor-react/index.html b/examples/refactor-react/index.html index e7974db029..b054de685b 100644 --- a/examples/refactor-react/index.html +++ b/examples/refactor-react/index.html @@ -5,7 +5,7 @@ - + Farm + React + TS diff --git a/examples/refactor-react/src/assets/base/react.svg b/examples/refactor-react/src/assets/base/react.svg new file mode 100644 index 0000000000..6c87de9bb3 --- /dev/null +++ b/examples/refactor-react/src/assets/base/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/refactor-react/src/main.tsx b/examples/refactor-react/src/main.tsx index 419a29c9be..6457c710a9 100644 --- a/examples/refactor-react/src/main.tsx +++ b/examples/refactor-react/src/main.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import "./main.css"; -import reactLogo from "./assets/react.svg"; -import FarmLogo from "./assets/logo.png"; +// import reactLogo from "./assets/react.svg"; +// import FarmLogo from "./assets/logo.png"; export function Main() { const [count, setCount] = useState(0); @@ -9,10 +9,10 @@ export function Main() { <>

Farm + React

From da459090144777f17baf11b83609efde55740a25 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Sun, 28 Jul 2024 14:08:59 +0800 Subject: [PATCH 017/369] chore: prepare websocket and hmr system --- examples/refactor-react/src/main.tsx | 8 ++--- packages/core/src/newServer/index.ts | 37 +++++------------------- packages/runtime-plugin-hmr/src/index.ts | 6 ++-- 3 files changed, 15 insertions(+), 36 deletions(-) diff --git a/examples/refactor-react/src/main.tsx b/examples/refactor-react/src/main.tsx index 6457c710a9..419a29c9be 100644 --- a/examples/refactor-react/src/main.tsx +++ b/examples/refactor-react/src/main.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import "./main.css"; -// import reactLogo from "./assets/react.svg"; -// import FarmLogo from "./assets/logo.png"; +import reactLogo from "./assets/react.svg"; +import FarmLogo from "./assets/logo.png"; export function Main() { const [count, setCount] = useState(0); @@ -9,10 +9,10 @@ export function Main() { <>

Farm + React

diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index 20cbeba2ba..a9b41de753 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -127,7 +127,7 @@ export class newServer { const publicFiles = await initPublicFilesPromise; const { publicDir } = this.config.compilation.assets; - this.createWebSocketServer(); + // this.createWebSocketServer(); // middleware // middlewares.use(compression()); @@ -160,27 +160,6 @@ export class newServer { this.config ) ); - - // middlewares.use((req, _res, next) => { - // next(); - // }); - - // 定义一个响应中间件 - // middlewares.use((req, res, next) => { - // if (req.url === '/') { - // res.setHeader('Content-Type', 'text/plain; charset=utf-8'); - // res.end('你好,这是 Connect 中间件!'); - // } else { - // next(); - // } - // }); - - // // 定义一个 404 处理中间件 - // middlewares.use((_req, res) => { - // res.statusCode = 404; - // res.setHeader('Content-Type', 'text/plain;charset=utf-8'); - // res.end('404 - 页面未找到'); - // }); } public async createWebSocketServer() { @@ -189,13 +168,13 @@ export class newServer { throw new Error('Websocket requires a server.'); } - const wsServer = new WsServer( - this.httpServer, - this.config, - this.httpsOptions, - this.publicPath, - null - ); + // const wsServer = new WsServer( + // this.httpServer, + // this.config, + // this.httpsOptions, + // this.publicPath, + // null + // ); } public async listen(): Promise { diff --git a/packages/runtime-plugin-hmr/src/index.ts b/packages/runtime-plugin-hmr/src/index.ts index dade759596..fa3d95474f 100644 --- a/packages/runtime-plugin-hmr/src/index.ts +++ b/packages/runtime-plugin-hmr/src/index.ts @@ -10,11 +10,11 @@ let hmrClient: HmrClient; export default ({ name: 'farm-runtime-hmr-client-plugin', bootstrap(moduleSystem) { - hmrClient = new HmrClient(moduleSystem); - hmrClient.connect(); + // hmrClient = new HmrClient(moduleSystem); + // hmrClient.connect(); }, moduleCreated(module) { // create a hot context for each module - module.meta.hot = createHotContext(module.id, hmrClient); + // module.meta.hot = createHotContext(module.id, hmrClient); } }); From b2ccc837c8a211b495b0c09966f04f57b7e917a2 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Mon, 29 Jul 2024 16:22:11 +0800 Subject: [PATCH 018/369] chore: update ci --- .github/workflows/codspeed.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml index dc53e363f2..a7dfdf45fc 100644 --- a/.github/workflows/codspeed.yml +++ b/.github/workflows/codspeed.yml @@ -5,6 +5,8 @@ on: branches: - "main" pull_request: + branches: + - main # `workflow_dispatch` allows CodSpeed to trigger backtest # performance analysis in order to generate initial data. workflow_dispatch: From aa6639004c0a77143095a996d96ec27329cecce1 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Mon, 29 Jul 2024 17:28:21 +0800 Subject: [PATCH 019/369] chore: update core logic --- examples/refactor-react/farm.config.ts | 7 +- packages/core/src/config/index.ts | 46 +++++----- pnpm-lock.yaml | 118 +------------------------ 3 files changed, 28 insertions(+), 143 deletions(-) diff --git a/examples/refactor-react/farm.config.ts b/examples/refactor-react/farm.config.ts index 5e9d17aa52..b3c87f3518 100644 --- a/examples/refactor-react/farm.config.ts +++ b/examples/refactor-react/farm.config.ts @@ -3,9 +3,10 @@ import { defineConfig } from '@farmfe/core'; export default defineConfig({ plugins: ['@farmfe/plugin-react'], compilation: { - presetEnv: false, - progress: false, - sourcemap: false, + // presetEnv: false, + // progress: false, + // sourcemap: false, + persistentCache: false, runtime: { isolate: true } diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index e10f47c363..ce500cfcea 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -1,7 +1,6 @@ -import crypto from 'node:crypto'; -import fs from 'node:fs'; -import module from 'node:module'; -import path, { isAbsolute, join } from 'node:path'; +import { createHash } from 'node:crypto'; +import { createRequire } from 'node:module'; +import path from 'node:path'; import { pathToFileURL } from 'node:url'; import fse from 'fs-extra'; @@ -258,16 +257,16 @@ export async function normalizeUserCompilationConfig( const userPublicDir = resolvedUserConfig.publicDir ? resolvedUserConfig.publicDir - : join(resolvedCompilation.root, 'public'); + : path.join(resolvedCompilation.root, 'public'); - if (isAbsolute(userPublicDir)) { + if (path.isAbsolute(userPublicDir)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore do not check type for this internal option resolvedCompilation.assets.publicDir = userPublicDir; } else { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore do not check type for this internal option - resolvedCompilation.assets.publicDir = join( + resolvedCompilation.assets.publicDir = path.join( resolvedCompilation.root, userPublicDir ); @@ -290,7 +289,7 @@ export async function normalizeUserCompilationConfig( }, {}) ); - const require = module.createRequire(import.meta.url); + const require = createRequire(import.meta.url); const hmrClientPluginPath = require.resolve('@farmfe/runtime-plugin-hmr'); const ImportMetaPluginPath = require.resolve( '@farmfe/runtime-plugin-import-meta' @@ -332,12 +331,11 @@ export async function normalizeUserCompilationConfig( const packageJsonPath = path.resolve(resolvedRootPath, 'package.json'); const packageJsonExists = fse.existsSync(packageJsonPath); const namespaceName = packageJsonExists - ? JSON.parse(fse.readFileSync(packageJsonPath, { encoding: 'utf-8' })) - ?.name ?? FARM_DEFAULT_NAMESPACE + ? JSON.parse(fse.readFileSync(packageJsonPath, 'utf-8')).name || + FARM_DEFAULT_NAMESPACE : FARM_DEFAULT_NAMESPACE; - resolvedCompilation.runtime.namespace = crypto - .createHash('md5') + resolvedCompilation.runtime.namespace = createHash('md5') .update(namespaceName) .digest('hex'); } @@ -553,10 +551,10 @@ function tryHttpsAsFileRead(value: unknown): string | Buffer | unknown { if (typeof value === 'string') { try { const resolvedPath = path.resolve(value); - const stats = fs.statSync(resolvedPath); + const stats = fse.statSync(resolvedPath); if (stats.isFile()) { - return fs.readFileSync(resolvedPath); + return fse.readFileSync(resolvedPath); } } catch {} } @@ -742,11 +740,8 @@ export async function loadConfigFile( const stackTrace = error.code === 'GenericFailure' ? '' : `\n${error.stack}`; if (inlineOptions.mode === 'production') { - logger.error( - `Failed to load config file: ${errorMessage} \n${stackTrace}`, - { - exit: true - } + throw new Error( + `Failed to load farm config file: ${errorMessage} \n${stackTrace}` ); } const potentialSolution = @@ -824,11 +819,14 @@ export async function getConfigFilePath( for (const name of DEFAULT_CONFIG_NAMES) { const resolvedPath = path.join(configRootPath, name); - const fileStat = await fse.stat(resolvedPath); - if (fileStat.isFile()) { - return resolvedPath; - } + try { + const fileStat = await fse.stat(resolvedPath); + if (fileStat.isFile()) { + return resolvedPath; + } + } catch {} } + return undefined; } @@ -976,7 +974,7 @@ export async function checkFileExists(filePath: string): Promise { } export async function resolveConfigFilePath( - configFile: string, + configFile: string | undefined, root: string, configRootPath: string ): Promise { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 609f7af1c8..20a0f0717c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3874,11 +3874,6 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@farmfe/cli@1.0.2': - resolution: {integrity: sha512-ZZRmXOSLkA7kWzmj6IVSH5U4NimNzFSa9hI0rRlPbgNwsfBIooUfthMjMZPnMwnFD9SIxLurlMJkwKyb4wpDKQ==} - engines: {node: '>= 16'} - hasBin: true - '@farmfe/plugin-react-darwin-arm64@1.2.0': resolution: {integrity: sha512-9a8wp6lg8NytO+kU8hu2gCFer+PL4TJ92SkU/5v9xdcsioElWpnMDGlwcoI8bXqi60/WR8RyExsDIBubCgjbXQ==} engines: {node: '>= 10'} @@ -3936,64 +3931,6 @@ packages: '@farmfe/plugin-react@1.2.0': resolution: {integrity: sha512-S5kU7NgqiyyhnDsZ7DEvszQIE6sCA0CNp7oTbdDcPxotPNBoyOcBHviSP3P5jvtIv6mmlF8Me2C1aLWJQRw9PA==} - '@farmfe/plugin-sass-darwin-arm64@1.0.5': - resolution: {integrity: sha512-0uRiWqEjLUjEQbaJBXFKCdp4P++00vHE0WegUBgA7KpnQ8USWBJEJ+AQklBKuZitjpqlkJqCsq09bEmkwzEVYA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@farmfe/plugin-sass-darwin-x64@1.0.5': - resolution: {integrity: sha512-lVogirOJiRMtDUkeEksYpt+40yRn7/syWGPCqBOn489tyKSvr1WMvIICAntqO4+FryVTQU3tgck11XYUd87cMA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@farmfe/plugin-sass-linux-arm64-gnu@1.0.5': - resolution: {integrity: sha512-0Iv6bzrTRqaGZ4QKt+aabJrvMRHPa8Vv9kSgGbqhs40Spm1cqUIMoHBfS76L8PX+BG6327C/5iqv4oOERmWoNg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@farmfe/plugin-sass-linux-arm64-musl@1.0.5': - resolution: {integrity: sha512-uOjnGp9fxMaIbRo3CPBtH2EivpSHhB1nje+f40JFcbiklPhcEs41/W62ZgSg6NOq2ocNbUbv1sdp1/6eBt0v7Q==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@farmfe/plugin-sass-linux-x64-gnu@1.0.5': - resolution: {integrity: sha512-29uzHgjLt0ZdWwthr+VmcWXAj1M9SGMbrt9j4UdPxJcnTdfSKNhrIPkfaQOWOHNvpy4IF2HRb3bdLVC3Nm3qyw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@farmfe/plugin-sass-linux-x64-musl@1.0.5': - resolution: {integrity: sha512-FDLnfXk6oGjVbsA4/9ddgpSlAOfC6b+iK5ZlfiN83q4D2twviTQ+/iFLXOlz+U5Wdz8oa250O694iRYEV8qwBA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@farmfe/plugin-sass-win32-arm64-msvc@1.0.5': - resolution: {integrity: sha512-PW3XEK+SO3ygIP7mfCn/hM6x5VS70C7CNU50ijLGjm9Fp4S1HKePyGlE0sk5JAqgTpfcPTv8fqgwj2PKFd/byw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@farmfe/plugin-sass-win32-ia32-msvc@1.0.5': - resolution: {integrity: sha512-nPuwI7N5ug/niQ+W1IMmGBqAHDi8M47da6OSkpK00HEvSYiqelf5x2dfJNA3ICcVck3tSxp1yrjfpO9Z8TJOuw==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@farmfe/plugin-sass-win32-x64-msvc@1.0.5': - resolution: {integrity: sha512-C7kCrWBdHo+EZsMM5sq3IyVLGwCThu3eWA6jAv5PFJoXhQ4jKWq8/PoanIhz5fJU1joJT7tNYVulRzkCAE0pdg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@farmfe/plugin-sass@1.0.5': - resolution: {integrity: sha512-uTcXGS3ONqP0aqqFMIrnK9S2fP7IsRlwX9DKUUfHTq5NAi7MkbJ/nz8fnKL7xzKxPzuoxUaWgirmqbtqNlf3vA==} - engines: {node: '>=16'} - '@farmfe/utils@0.0.1': resolution: {integrity: sha512-QLbgNrojcvxfumXA/H329XAXhoCahmeSH3JmaiwwJEGS2QAmWfgAJMegjwlt6OmArGVO4gSbJ7Xbmm1idZZs+g==} @@ -15559,13 +15496,6 @@ snapshots: '@eslint/js@8.57.0': {} - '@farmfe/cli@1.0.2': - dependencies: - cac: 6.7.14 - cross-spawn: 7.0.3 - inquirer: 9.2.11 - walkdir: 0.4.1 - '@farmfe/plugin-react-darwin-arm64@1.2.0': optional: true @@ -15605,52 +15535,6 @@ snapshots: '@farmfe/plugin-react-win32-ia32-msvc': 1.2.0 '@farmfe/plugin-react-win32-x64-msvc': 1.2.0 - '@farmfe/plugin-sass-darwin-arm64@1.0.5': - optional: true - - '@farmfe/plugin-sass-darwin-x64@1.0.5': - optional: true - - '@farmfe/plugin-sass-linux-arm64-gnu@1.0.5': - optional: true - - '@farmfe/plugin-sass-linux-arm64-musl@1.0.5': - optional: true - - '@farmfe/plugin-sass-linux-x64-gnu@1.0.5': - optional: true - - '@farmfe/plugin-sass-linux-x64-musl@1.0.5': - optional: true - - '@farmfe/plugin-sass-win32-arm64-msvc@1.0.5': - optional: true - - '@farmfe/plugin-sass-win32-ia32-msvc@1.0.5': - optional: true - - '@farmfe/plugin-sass-win32-x64-msvc@1.0.5': - optional: true - - '@farmfe/plugin-sass@1.0.5': - optionalDependencies: - '@farmfe/plugin-sass-darwin-arm64': 1.0.5 - '@farmfe/plugin-sass-darwin-x64': 1.0.5 - '@farmfe/plugin-sass-linux-arm64-gnu': 1.0.5 - '@farmfe/plugin-sass-linux-arm64-musl': 1.0.5 - '@farmfe/plugin-sass-linux-x64-gnu': 1.0.5 - '@farmfe/plugin-sass-linux-x64-musl': 1.0.5 - '@farmfe/plugin-sass-win32-arm64-msvc': 1.0.5 - '@farmfe/plugin-sass-win32-ia32-msvc': 1.0.5 - '@farmfe/plugin-sass-win32-x64-msvc': 1.0.5 - sass-embedded-darwin-arm64: 1.62.0 - sass-embedded-darwin-x64: 1.62.0 - sass-embedded-linux-arm64: 1.62.0 - sass-embedded-linux-ia32: 1.62.0 - sass-embedded-linux-x64: 1.62.0 - sass-embedded-win32-ia32: 1.62.0 - sass-embedded-win32-x64: 1.62.0 - '@farmfe/utils@0.0.1': {} '@floating-ui/core@1.5.0': @@ -27164,6 +27048,8 @@ snapshots: vitefu@0.2.5(vite@5.2.8(@types/node@20.14.12)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1)): optionalDependencies: + vite: 5.2.8(@types/node@20.14.12)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1) + vitest@2.0.4(@types/node@20.14.12)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1): dependencies: '@ampproject/remapping': 2.3.0 From 7dc316443febfde69003154e63b4684cb5015714 Mon Sep 17 00:00:00 2001 From: ADNY <66500121+ErKeLost@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:28:55 +0800 Subject: [PATCH 020/369] chore: Completed all refactoring of @farmfe/cli (#1678) * chore: Completed all refactoring of @farmfe/cli * chore: Completed all refactoring of @farmfe/cli --- examples/refactor-react/package.json | 2 +- packages/cli/README.md | 14 +++-- packages/cli/package.json | 16 ++--- packages/cli/src/index.ts | 23 +++---- packages/cli/src/types.ts | 13 ++-- packages/cli/src/utils.ts | 8 ++- packages/core/src/config/constants.ts | 8 +++ packages/core/src/config/index.ts | 1 + pnpm-lock.yaml | 87 --------------------------- 9 files changed, 42 insertions(+), 130 deletions(-) diff --git a/examples/refactor-react/package.json b/examples/refactor-react/package.json index 87544a4faf..0746d21050 100644 --- a/examples/refactor-react/package.json +++ b/examples/refactor-react/package.json @@ -2,7 +2,7 @@ "name": "react-template", "version": "1.0.0", "scripts": { - "dev": "farm start", + "dev": "farm", "start": "farm start", "build": "farm build", "preview": "farm preview", diff --git a/packages/cli/README.md b/packages/cli/README.md index 7c6c7bcb77..16546f99f1 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -8,20 +8,26 @@ pnpm add @farmfe/cli -D ``` -start your farm project: +start your farm project in development mode: ```sh -farm start +farm dev ``` -build: +build your farm project in production mode: ```sh farm build ``` -preview: +preview your farm project in production mode: ```sh farm preview ``` + +clean your farm persistent cache: + +```sh +farm clean +``` diff --git a/packages/cli/package.json b/packages/cli/package.json index 645e414fa0..4a4884d536 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -28,28 +28,20 @@ }, "files": [ "dist", - "bin", - "templates" + "bin" ], "scripts": { "start": "rimraf dist && tsc -b -w", "build": "tsc -b", - "type-check": "tsc --noEmit", "prepublishOnly": "npm run build" }, "engines": { "node": ">= 16" }, "dependencies": { - "cac": "^6.7.14", - "cross-spawn": "^7.0.3", - "inquirer": "9.2.12", - "walkdir": "^0.4.1" + "cac": "^6.7.14" }, - "devDependencies": { - "@farmfe/cli": "workspace:*", - "@farmfe/core": "workspace:*", - "@types/cross-spawn": "^6.0.2", - "@types/inquirer": "^9.0.3" + "peerDependencies": { + "@farmfe/core": "workspace:*" } } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 3e3891ced6..17881076fa 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,11 +1,11 @@ +import { VERSION as CORE_VERSION } from '@farmfe/core'; import { cac } from 'cac'; - import { + VERSION, handleAsyncOperationErrors, resolveCliConfig, resolveCommandOptions, - resolveCore, - version + resolveCore } from './utils.js'; import type { UserConfig } from '@farmfe/core'; @@ -120,7 +120,7 @@ cli .action(async (root: string, options: CliBuildOptions & GlobalCliOptions) => { const defaultOptions = { root, - configFile: options.configFile, + configFile: options.config, mode: options.mode, watch: options.watch, compilation: { @@ -158,7 +158,7 @@ cli .action(async (root: string, options: CliBuildOptions & GlobalCliOptions) => { const defaultOptions = { root, - configFile: options.configFile, + configFile: options.config, mode: options.mode, compilation: { watch: options.watch, @@ -202,7 +202,7 @@ cli host: options.host, open: options.open }, - configPath: options.configPath, + configFile: options.config, port: options.port, compilation: { output: { @@ -236,17 +236,8 @@ cli ); }); -// Listening for unknown command -cli.on('command:*', async () => { - const { Logger } = await import('@farmfe/core'); - const logger = new Logger(); - logger.error( - `Unknown command place Run "farm --help" to see available commands` - ); -}); - cli.help(); -cli.version(version); +cli.version(`@farmfe/cli ${VERSION} @farmfe/core ${CORE_VERSION}`); cli.parse(); diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index dc3074b3b4..c005567c95 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -2,13 +2,9 @@ export interface GlobalCliOptions { '--'?: string[]; c?: boolean | string; config?: string; - configPath?: string; base?: string; m?: string; mode?: 'development' | 'production' | string; - l?: boolean; - lazy?: boolean; - port?: number; clearScreen?: boolean; } @@ -19,7 +15,6 @@ export interface CleanOptions { export interface CliServerOptions { port?: number; open?: boolean; - https?: boolean; hmr?: boolean; cors?: boolean; strictPort?: boolean; @@ -32,11 +27,12 @@ export interface CliBuildOptions { input?: string; w?: boolean; watch?: boolean; + l?: boolean; + lazy?: boolean; sourcemap?: boolean; minify?: boolean; treeShaking?: boolean; format?: 'cjs' | 'esm'; - configFile?: string | undefined; target?: | 'browser' | 'node' @@ -46,7 +42,10 @@ export interface CliBuildOptions { | 'browser-legacy' | 'browser-es2015' | 'browser-es2017' - | 'browser-esnext'; + | 'browser-esnext' + | 'library' + | 'library-browser' + | 'library-node'; } export interface CliPreviewOptions { diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index ec9c7a0c98..eb91de7648 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -78,8 +78,8 @@ export function cleanOptions( * @returns resolve command options */ export function resolveCommandOptions( - options: GlobalCliOptions -): GlobalCliOptions { + options: GlobalCliOptions & CliServerOptions +): GlobalCliOptions & CliServerOptions { const resolveOptions = { ...options }; filterDuplicateOptions(resolveOptions); return cleanOptions(resolveOptions); @@ -143,6 +143,8 @@ export function resolveCliConfig( }; } -export const { version } = JSON.parse( +const { version } = JSON.parse( readFileSync(new URL('../package.json', import.meta.url)).toString() ); + +export const VERSION = version; diff --git a/packages/core/src/config/constants.ts b/packages/core/src/config/constants.ts index af8f50e909..dbad9016eb 100644 --- a/packages/core/src/config/constants.ts +++ b/packages/core/src/config/constants.ts @@ -1,3 +1,5 @@ +import { readFileSync } from 'node:fs'; + export const DEFAULT_CONFIG_NAMES = [ 'farm.config.ts', 'farm.config.js', @@ -15,3 +17,9 @@ export const CUSTOM_KEYS = { }; export const FARM_RUST_PLUGIN_FUNCTION_ENTRY = 'func.js'; + +const { version } = JSON.parse( + readFileSync(new URL('../../package.json', import.meta.url)).toString() +); + +export const VERSION = version; diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index ce500cfcea..49934d101e 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -73,6 +73,7 @@ import type { } from './types.js'; export * from './types.js'; +export * from './constants.js'; export function defineFarmConfig(config: UserConfig): UserConfig; export function defineFarmConfig( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20a0f0717c..fefca51b85 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2276,28 +2276,6 @@ importers: cac: specifier: ^6.7.14 version: 6.7.14 - cross-spawn: - specifier: ^7.0.3 - version: 7.0.3 - inquirer: - specifier: 9.2.12 - version: 9.2.12 - walkdir: - specifier: ^0.4.1 - version: 0.4.1 - devDependencies: - '@farmfe/cli': - specifier: workspace:* - version: 'link:' - '@farmfe/core': - specifier: workspace:* - version: link:../core - '@types/cross-spawn': - specifier: ^6.0.2 - version: 6.0.4 - '@types/inquirer': - specifier: ^9.0.3 - version: 9.0.6 packages/core: dependencies: @@ -5003,9 +4981,6 @@ packages: '@types/cookies@0.7.9': resolution: {integrity: sha512-SrGYvhKohd/WSOII0WpflC73RgdJhQoqpwq9q+n/qugNGiDSGYXfHy3QvB4+X+J/gYe27j2fSRnK4B+1A3nvsw==} - '@types/cross-spawn@6.0.4': - resolution: {integrity: sha512-GGLpeThc2Bu8FBGmVn76ZU3lix17qZensEI4/MPty0aZpm2CHfgEMis31pf5X5EiudYKcPAsWciAsCALoPo5dw==} - '@types/d3-timer@2.0.2': resolution: {integrity: sha512-Dz39VLKZhWWeqSqbgYKF5BDJDUiPITo9M2cev/+HQBvXs+biES2d3LndnopuJ5YwaKK1h56CPWqDB+ghUXhm9A==} @@ -5066,9 +5041,6 @@ packages: '@types/http-proxy@1.17.14': resolution: {integrity: sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==} - '@types/inquirer@9.0.6': - resolution: {integrity: sha512-1Go1AAP/yOy3Pth5Xf1DC3nfZ03cJLCPx6E2YnSN/5I3w1jHBVH4170DkZ+JxfmA7c9kL9+bf9z3FRGa4kNAqg==} - '@types/is-ci@3.0.3': resolution: {integrity: sha512-FdHbjLiN2e8fk9QYQyVYZrK8svUDJpxSaSWLUga8EZS1RGAvvrqM9zbVARBtQuYPeLgnJxM2xloOswPwj1o2cQ==} @@ -5252,9 +5224,6 @@ packages: '@types/supertest@6.0.2': resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==} - '@types/through@0.0.32': - resolution: {integrity: sha512-7XsfXIsjdfJM2wFDRAtEWp3zb2aVPk5QeyZxGlVK57q4u26DczMHhJmlhr0Jqv0THwxam/L8REXkj8M2I/lcvw==} - '@types/tinycolor2@1.4.5': resolution: {integrity: sha512-uLJijDHN5E6j5n1qefF9oaeplgszXglWXWTviMoFr/YxgvbyrkFil20yDT7ljhCiTQ/BfCYtxfJS81LdTro5DQ==} @@ -8192,10 +8161,6 @@ packages: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} - figures@5.0.0: - resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} - engines: {node: '>=14'} - figures@6.1.0: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} @@ -8853,10 +8818,6 @@ packages: resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} engines: {node: '>=12.0.0'} - inquirer@9.2.12: - resolution: {integrity: sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==} - engines: {node: '>=14.18.0'} - inquirer@9.2.15: resolution: {integrity: sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==} engines: {node: '>=18'} @@ -9109,10 +9070,6 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - is-unicode-supported@2.0.0: resolution: {integrity: sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==} engines: {node: '>=18'} @@ -13235,10 +13192,6 @@ packages: typescript: optional: true - walkdir@0.4.1: - resolution: {integrity: sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==} - engines: {node: '>=6.0.0'} - walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -17355,10 +17308,6 @@ snapshots: '@types/keygrip': 1.0.4 '@types/node': 18.18.8 - '@types/cross-spawn@6.0.4': - dependencies: - '@types/node': 18.18.8 - '@types/d3-timer@2.0.2': {} '@types/debug@4.1.12': @@ -17433,11 +17382,6 @@ snapshots: dependencies: '@types/node': 18.18.8 - '@types/inquirer@9.0.6': - dependencies: - '@types/through': 0.0.32 - rxjs: 7.8.1 - '@types/is-ci@3.0.3': dependencies: ci-info: 3.9.0 @@ -17650,10 +17594,6 @@ snapshots: '@types/methods': 1.1.4 '@types/superagent': 8.1.7 - '@types/through@0.0.32': - dependencies: - '@types/node': 18.18.8 - '@types/tinycolor2@1.4.5': {} '@types/ua-parser-js@0.7.39': {} @@ -21276,11 +21216,6 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 - figures@5.0.0: - dependencies: - escape-string-regexp: 5.0.0 - is-unicode-supported: 1.3.0 - figures@6.1.0: dependencies: is-unicode-supported: 2.0.0 @@ -22040,24 +21975,6 @@ snapshots: through: 2.3.8 wrap-ansi: 6.2.0 - inquirer@9.2.12: - dependencies: - '@ljharb/through': 2.3.13 - ansi-escapes: 4.3.2 - chalk: 5.3.0 - cli-cursor: 3.1.0 - cli-width: 4.1.0 - external-editor: 3.1.0 - figures: 5.0.0 - lodash: 4.17.21 - mute-stream: 1.0.0 - ora: 5.4.1 - run-async: 3.0.0 - rxjs: 7.8.1 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - inquirer@9.2.15: dependencies: '@ljharb/through': 2.3.13 @@ -22272,8 +22189,6 @@ snapshots: is-unicode-supported@0.1.0: {} - is-unicode-supported@1.3.0: {} - is-unicode-supported@2.0.0: {} is-weakmap@2.0.2: {} @@ -27213,8 +27128,6 @@ snapshots: optionalDependencies: typescript: 5.4.5 - walkdir@0.4.1: {} - walker@1.0.8: dependencies: makeerror: 1.0.12 From 01762d27a5ea9670b60b5391850e3c48ca252904 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Mon, 29 Jul 2024 17:55:07 +0800 Subject: [PATCH 021/369] chore: test binary performance --- Cargo.toml | 5 +++++ packages/core/package.json | 1 + scripts/build.mjs | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8704946b2e..6eac56d0e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,8 @@ opt-level = 0 debug = 2 lto = false codegen-units = 16 + +[profile.dev] +codegen-units = 1 +lto = "fat" +opt-level = 3 diff --git a/packages/core/package.json b/packages/core/package.json index 984a1043f3..a8e9ba415d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,6 +58,7 @@ "build:cjs": "node scripts/build-cjs.mjs", "build": "tsc -p tsconfig.build.json && npm run build:cjs", "build:rs": "npm run build:rs:debug -- --release", + "build:rs-dev": "npm run build:rs:debug -- --dev", "build:rs:publish": "npm run build:rs:debug -- --release", "build:rs:debug": "napi build --platform --cargo-name farmfe_node -p farmfe_node --cargo-cwd ../../ binding --js binding.cjs --dts binding.d.ts", "build:rs:profile": "cross-env FARM_PROFILE=1 npm run build:rs -- --features profile", diff --git a/scripts/build.mjs b/scripts/build.mjs index d2af534a5e..c93fb142b1 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -89,7 +89,7 @@ export const installLinuxProtobuf = async () => { // build core command export const buildCore = () => - execa(DEFAULT_PACKAGE_MANAGER, ['build:rs'], { + execa(DEFAULT_PACKAGE_MANAGER, ['build:rs-dev'], { cwd: PKG_CORE }).then(buildCoreCjs); From 3ffa686fc8331b2ca11d7235aba15cca3bd9f349 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Tue, 30 Jul 2024 10:53:22 +0800 Subject: [PATCH 022/369] chore: update code --- Cargo.toml | 5 ----- packages/core/package.json | 1 - scripts/build.mjs | 2 +- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6eac56d0e3..8704946b2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,3 @@ opt-level = 0 debug = 2 lto = false codegen-units = 16 - -[profile.dev] -codegen-units = 1 -lto = "fat" -opt-level = 3 diff --git a/packages/core/package.json b/packages/core/package.json index a8e9ba415d..984a1043f3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,7 +58,6 @@ "build:cjs": "node scripts/build-cjs.mjs", "build": "tsc -p tsconfig.build.json && npm run build:cjs", "build:rs": "npm run build:rs:debug -- --release", - "build:rs-dev": "npm run build:rs:debug -- --dev", "build:rs:publish": "npm run build:rs:debug -- --release", "build:rs:debug": "napi build --platform --cargo-name farmfe_node -p farmfe_node --cargo-cwd ../../ binding --js binding.cjs --dts binding.d.ts", "build:rs:profile": "cross-env FARM_PROFILE=1 npm run build:rs -- --features profile", diff --git a/scripts/build.mjs b/scripts/build.mjs index c93fb142b1..d2af534a5e 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -89,7 +89,7 @@ export const installLinuxProtobuf = async () => { // build core command export const buildCore = () => - execa(DEFAULT_PACKAGE_MANAGER, ['build:rs-dev'], { + execa(DEFAULT_PACKAGE_MANAGER, ['build:rs'], { cwd: PKG_CORE }).then(buildCoreCjs); From 69391da7e88b98a0326dddc1ec91464842b525b3 Mon Sep 17 00:00:00 2001 From: Nirvana <1357711537@qq.com> Date: Tue, 30 Jul 2024 22:44:43 +0800 Subject: [PATCH 023/369] feat: add the pulicMiddleware (#1673) * feat: add the pulicMiddleware * feat: add the logger for the publicMiddleware --------- Co-authored-by: zengwenjie.paq Co-authored-by: ADNY <66500121+ErKeLost@users.noreply.github.com> --- examples/public-dir/src/main.tsx | 5 +- packages/core/package.json | 2 +- packages/core/src/config/index.ts | 7 ++ packages/core/src/config/types.ts | 1 + packages/core/src/index.ts | 2 +- packages/core/src/newServer/index.ts | 22 ++-- .../middlewares/htmlFallbackMiddleware.ts | 1 + .../newServer/middlewares/publicMiddleware.ts | 107 ++++++++++++++++-- packages/core/src/newServer/publicDir.ts | 3 +- packages/core/src/utils/path.ts | 8 ++ packages/core/src/utils/url.ts | 13 +++ pnpm-lock.yaml | 46 +++++++- 12 files changed, 189 insertions(+), 28 deletions(-) diff --git a/examples/public-dir/src/main.tsx b/examples/public-dir/src/main.tsx index c2abe8bc93..18dcdaa0a0 100644 --- a/examples/public-dir/src/main.tsx +++ b/examples/public-dir/src/main.tsx @@ -1,7 +1,8 @@ import React, { useState } from "react"; import "./main.css"; import reactLogo from "./assets/react.svg"; -// import FarmLogo from "../public/logo.png"; +import FarmLogo from "/new-logo.png"; +// import FarmLogo from "../newPublic/new-logo.png"; export function Main() { const [count, setCount] = useState(0); @@ -9,7 +10,7 @@ export function Main() { <>
- {/* Farm logo */} + Farm logo React logo diff --git a/packages/core/package.json b/packages/core/package.json index 984a1043f3..4c48b95230 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -88,7 +88,7 @@ "etag": "^1.8.1", "http-proxy": "^1.18.1", "react-refresh": "^0.14.0", - "sirv": "^2.0.3", + "sirv": "^2.0.4", "ws": "^8.14.2" }, "dependencies": { diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index 49934d101e..73309b351e 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -118,6 +118,7 @@ export async function resolveConfig( // configPath may be file or directory const { configFile, configPath: initialConfigPath } = inlineOptions; + const loadedUserConfig: any = await loadConfigFile( configFile, inlineOptions, @@ -697,6 +698,7 @@ export function normalizePublicDir(root: string, publicDir = 'public') { const absPublicDirPath = path.isAbsolute(publicDir) ? publicDir : path.resolve(root, publicDir); + return absPublicDirPath; } @@ -915,6 +917,11 @@ export async function resolveUserConfig( mode }; + resolvedUserConfig.publicDir = normalizePublicDir( + resolvedRootPath, + userConfig.publicDir + ); + return resolvedUserConfig; } diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index 96e454f1ab..71eea65fea 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -160,6 +160,7 @@ export interface FarmCliOptions logger?: Logger; config?: string; configFile?: string; + // todo: check to delete configPath?: string; compilation?: Config['config']; mode?: string; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 21716b75b0..80fdc69798 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -474,7 +474,7 @@ export async function start2( ); const compiler = await createCompiler(resolvedUserConfig, logger); - const server = new newServer(compiler, resolvedUserConfig); + const server = new newServer(compiler, resolvedUserConfig, logger); await server.createServer(); await server.listen(); // const devServer = await createDevServer( diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index a9b41de753..f00c97ac75 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -13,7 +13,7 @@ import { Compiler } from '../compiler/index.js'; import { normalizePublicPath } from '../config/normalize-config/normalize-output.js'; import { NormalizedServerConfig, ResolvedUserConfig } from '../config/types.js'; import { logError } from '../server/error.js'; -import { logger } from '../utils/logger.js'; +import { Logger, logger } from '../utils/logger.js'; import { initPublicFiles } from '../utils/publicDir.js'; import { isObject } from '../utils/share.js'; import { FileWatcher } from '../watcher/index.js'; @@ -91,11 +91,16 @@ export class newServer { publicPath?: string; httpServer?: HttpServer; watcher: FileWatcher; + logger: Logger; - constructor(compiler: CompilerType, config: ResolvedUserConfig) { + constructor( + compiler: CompilerType, + config: ResolvedUserConfig, + logger: Logger + ) { this.compiler = compiler; this.config = config; - + this.logger = logger; if (!this.compiler) return; this.publicPath = @@ -132,15 +137,8 @@ export class newServer { // middleware // middlewares.use(compression()); - if (this.publicDir) { - middlewares.use( - publicMiddleware( - this.httpServer, - this.compiler, - this.publicPath, - this.config - ) - ); + if (publicDir) { + middlewares.use(publicMiddleware(this.logger, this.config, publicFiles)); } // TODO todo add appType middlewares.use( diff --git a/packages/core/src/newServer/middlewares/htmlFallbackMiddleware.ts b/packages/core/src/newServer/middlewares/htmlFallbackMiddleware.ts index 672b4e9329..91e0d0064b 100644 --- a/packages/core/src/newServer/middlewares/htmlFallbackMiddleware.ts +++ b/packages/core/src/newServer/middlewares/htmlFallbackMiddleware.ts @@ -4,6 +4,7 @@ import { ResolvedUserConfig } from '../../config/types.js'; import { commonFsUtils } from '../../utils/fsUtils.js'; import { cleanUrl } from '../../utils/url.js'; import { HttpServer } from '../index.js'; + export function htmlFallbackMiddleware( server: HttpServer, compiler: Compiler, diff --git a/packages/core/src/newServer/middlewares/publicMiddleware.ts b/packages/core/src/newServer/middlewares/publicMiddleware.ts index f7e50de23b..f5ea6ff29e 100644 --- a/packages/core/src/newServer/middlewares/publicMiddleware.ts +++ b/packages/core/src/newServer/middlewares/publicMiddleware.ts @@ -1,16 +1,107 @@ -import { Compiler } from '../../compiler/index.js'; +import sirv from 'sirv'; import { ResolvedUserConfig } from '../../config/types.js'; -import { HttpServer } from '../index.js'; +import { colors } from '../../utils/color.js'; +import { Logger } from '../../utils/logger.js'; +import { removeHashFromPath, withTrailingSlash } from '../../utils/path.js'; +import { normalizePath } from '../../utils/share.js'; +import { + cleanUrl, + isImportRequest, + knownJavascriptExtensionRE, + removeImportQuery, + urlRE +} from '../../utils/url.js'; + +function warnAboutPublicDir(url: string, publicPath: string) { + let warning: string; + if (isImportRequest(url)) { + const rawUrl = removeImportQuery(url); + if (urlRE.test(url)) { + warning = + `Assets in the public directory are directly accessible at the root path.\n` + + `Use ${colors.brandColor( + rawUrl.replace(publicPath, '/') + )} instead of the explicit ${colors.brandColor(rawUrl)}.`; + } else { + warning = + 'Assets in the public directory should not be imported directly in JavaScript.\n' + + `To import an asset, place it inside the src directory. Use ${colors.brandColor( + rawUrl.replace(publicPath, '/src/') + )} instead of ${colors.cyan(rawUrl)}.\n` + + `For referencing the asset's path, use ${colors.brandColor( + rawUrl.replace(publicPath, '/') + )}.`; + } + } else { + warning = + `Public directory files are accessible directly at the root path.\n` + + `Use ${colors.brandColor( + url.replace(publicPath, '/') + )} directly, rather than ${colors.brandColor(`${publicPath}${url}`)}.`; + } + + return warning; +} export function publicMiddleware( - server: HttpServer, - compiler: Compiler, - publicPath: string, - config: ResolvedUserConfig + logger: Logger, + config: ResolvedUserConfig, + publicFiles?: Set ) { - return async function handlerPublicMiddleware( + const { publicDir, root } = config; + const publicPath = `${publicDir.slice(root.length)}`; + const headers = config.server.headers; + const serve = sirv(publicDir, { + dev: true, + etag: true, + extensions: [], + setHeaders: (res, path) => { + if (knownJavascriptExtensionRE.test(path)) { + res.setHeader('Content-Type', 'text/javascript'); + } + if (headers) { + for (const name in headers) { + res.setHeader(name, headers[name]!); + } + } + } + }); + const toFilePath = (url: string) => { + let filePath = cleanUrl(url); + if (filePath.indexOf('%') !== -1) { + try { + filePath = decodeURI(filePath); + } catch (err) { + // ignore + } + } + return normalizePath(filePath); + }; + + return async function farmHandlerPublicMiddleware( req: any, res: any, next: () => void - ) {}; + ) { + const url = removeHashFromPath(req.url!); + const filePath = toFilePath(url); + + // If it is not equal, it means that it is recognized as a module + if ( + publicDir.startsWith(withTrailingSlash(root)) && + publicFiles.has(url) && + req.url !== url + ) { + const publicDirWarning = warnAboutPublicDir(url, publicPath); + if (publicDirWarning) { + logger.warn(publicDirWarning); + } + } + + if (publicFiles && !publicFiles.has(filePath)) { + return next(); + } + + serve(req, res, next); + }; } diff --git a/packages/core/src/newServer/publicDir.ts b/packages/core/src/newServer/publicDir.ts index 9b6725ce51..71ff3e2326 100644 --- a/packages/core/src/newServer/publicDir.ts +++ b/packages/core/src/newServer/publicDir.ts @@ -10,8 +10,7 @@ export async function initPublicFiles( config: ResolvedUserConfig ): Promise | undefined> { let fileNames: string[]; - const publicDir: string = config.compilation?.assets?.publicDir as string; - console.log(publicDir); + const publicDir: string = config.publicDir; try { fileNames = await recursiveReaddir(publicDir); diff --git a/packages/core/src/utils/path.ts b/packages/core/src/utils/path.ts index 7a8519f3fc..f6ac7c7df1 100644 --- a/packages/core/src/utils/path.ts +++ b/packages/core/src/utils/path.ts @@ -14,3 +14,11 @@ const postfixRE = /[?#].*$/; export function stripQueryAndHash(path: string): string { return path.replace(postfixRE, ''); } + +export function removeHashFromPath(url: string): string { + const hashPattern = /(_[a-zA-Z\d]{4,8})\./; + + const newURL = url.replace(hashPattern, '.'); + + return newURL; +} diff --git a/packages/core/src/utils/url.ts b/packages/core/src/utils/url.ts index edb507354c..fa6d78c428 100644 --- a/packages/core/src/utils/url.ts +++ b/packages/core/src/utils/url.ts @@ -2,3 +2,16 @@ const postfixRE = /[?#].*$/; export function cleanUrl(url: string): string { return url.replace(postfixRE, ''); } + +const importQueryRE = /(\?|&)import=?(?:&|$)/; +export const isImportRequest = (url: string): boolean => + importQueryRE.test(url); + +const trailingSeparatorRE = /[?&]$/; +export function removeImportQuery(url: string): string { + return url.replace(importQueryRE, '$1').replace(trailingSeparatorRE, ''); +} + +export const knownJavascriptExtensionRE = /\.[tj]sx?$/; + +export const urlRE = /(\?|&)url(?:&|$)/; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cbe8ddfa1a..2c1998cd7e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2431,8 +2431,8 @@ importers: specifier: ^0.14.0 version: 0.14.0 sirv: - specifier: ^2.0.3 - version: 2.0.3 + specifier: ^2.0.4 + version: 2.0.4 ws: specifier: ^8.14.2 version: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) @@ -3115,24 +3115,28 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64@1.7.2': resolution: {integrity: sha512-Z1CSGQE6fHz55gkiFHv9E8wEAaSUd7dHSRaxSCBa7utonHqpIeMbvj3Evm1w0WfGLFDtRXLV1fTfEdM0FMTOhA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64-musl@1.7.2': resolution: {integrity: sha512-x10LpGMepDrLS+h2TZ6/T7egpHjGKtiI4GuShNylmBQJWfTotbFf9eseHggrqJ4WZf9yrGoVYrtbxXftuB95sQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64@1.7.2': resolution: {integrity: sha512-vXXyox8/CQijBxAu0+r8FfSO7JlC4tob3PbaFda8gPJFRz2uFJw39HtxVUwbTV1EcU6wSPh4SiRu5sZfP1VHrQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-win32-arm64@1.7.2': resolution: {integrity: sha512-kRXdlKzcU7INf6/ldu0nVmkOgt7bKqmyXRRCUqqaJfA32+9InTbkD8tGrHZEVYIWr+eTuKcg16qZVDsPSDFZ8g==} @@ -4287,6 +4291,9 @@ packages: '@polka/url@1.0.0-next.23': resolution: {integrity: sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==} + '@polka/url@1.0.0-next.25': + resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} + '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -4404,36 +4411,43 @@ packages: resolution: {integrity: sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.14.1': resolution: {integrity: sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-powerpc64le-gnu@4.14.1': resolution: {integrity: sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==} cpu: [ppc64le] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.14.1': resolution: {integrity: sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.14.1': resolution: {integrity: sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.14.1': resolution: {integrity: sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.14.1': resolution: {integrity: sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.14.1': resolution: {integrity: sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==} @@ -8026,18 +8040,21 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] farm-plugin-remove-console-linux-arm64-musl@0.1.8: resolution: {integrity: sha512-vT1fy3hBdIqLNMI+hXi7I+Wseep6v3pUY99BDOnrkkHFkIjVuDzHCrQEYGLJOLchZM9fJnqidkhGTi0K+LZd6g==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] farm-plugin-remove-console-linux-x64-gnu@0.1.8: resolution: {integrity: sha512-MJkZn+OeWig78DkAb5ur/lxXYYlZwAO05WvQHJOgQLSjxyOAVTDPYn2j+mCGgPrZdDwyghODM9UdVlxe1lMb7w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] farm-plugin-remove-console-linux-x64-musl@0.1.8: resolution: {integrity: sha512-ZObFlj+/ulEqUjr1Ggpm03FRcZQ5BZepK24kWf36pxrB6cn/dSv40GsXzzKELfPrh12Q15NJZVSW/0AT4SLnTw==} @@ -8083,18 +8100,21 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] farm-plugin-replace-dirname-linux-arm64-musl@0.2.1: resolution: {integrity: sha512-m3gH8ggczbRYTHZSNp3LjIQIcqhvDO4O78bxXc8O1ozKD8M47/YfQLyQV06M7H4rZ8s6XV3Bb1kAcRAASp3M5A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] farm-plugin-replace-dirname-linux-x64-gnu@0.2.1: resolution: {integrity: sha512-MehKkoM2RFw3sCnEu9nCbXKjxtC3hfTad0h/dC+Z8iEBcLEReVLoNzHWWUa6BxkxqDtB82/BWO/ObSUj/VUnwQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] farm-plugin-replace-dirname-linux-x64-musl@0.2.1: resolution: {integrity: sha512-o1qPZi16N/sHOteZYJVv6UmZFK3QKpVQrywk/4spJI0mPH9A9Y+G6iBE2Tqjb3d+1Hb6phr++EBJHZ2x1ajtGQ==} @@ -9540,24 +9560,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.25.1: resolution: {integrity: sha512-IhxVFJoTW8wq6yLvxdPvyHv4NjzcpN1B7gjxrY3uaykQNXPHNIpChLB52+wfH+yS58zm1PL4LemUp8u9Cfp6Bw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.25.1: resolution: {integrity: sha512-RXIaru79KrREPEd6WLXfKfIp4QzoppZvD3x7vuTKkDA64PwTzKJ2jaC43RZHRt8BmyIkRRlmywNhTRMbmkPYpA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.25.1: resolution: {integrity: sha512-TdcNqFsAENEEFr8fJWg0Y4fZ/nwuqTRsIr7W7t2wmDUlA8eSXVepeeONYcb+gtTj1RaXn/WgNLB45SFkz+XBZA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-x64-msvc@1.25.1: resolution: {integrity: sha512-9KZZkmmy9oGDSrnyHuxP6iMhbsgChUiu/NSgOx+U1I/wTngBStDf2i2aGRCHvFqj19HqqBEI4WuGVQBa2V6e0A==} @@ -10066,6 +10090,10 @@ packages: resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} engines: {node: '>=10'} + mrmime@2.0.0: + resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} + engines: {node: '>=10'} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -11949,6 +11977,10 @@ packages: resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==} engines: {node: '>= 10'} + sirv@2.0.4: + resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} + engines: {node: '>= 10'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -16128,6 +16160,8 @@ snapshots: '@polka/url@1.0.0-next.23': {} + '@polka/url@1.0.0-next.25': {} + '@popperjs/core@2.11.8': {} '@rc-component/color-picker@1.4.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': @@ -23428,6 +23462,8 @@ snapshots: mrmime@1.0.1: {} + mrmime@2.0.0: {} + ms@2.0.0: {} ms@2.1.2: {} @@ -25414,6 +25450,12 @@ snapshots: mrmime: 1.0.1 totalist: 3.0.1 + sirv@2.0.4: + dependencies: + '@polka/url': 1.0.0-next.25 + mrmime: 2.0.0 + totalist: 3.0.1 + sisteransi@1.0.5: {} size-sensor@1.0.2: {} From 2c5d802ed7106fa66aafe764bf88f4cbf612b05a Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Wed, 31 Jul 2024 10:00:47 +0800 Subject: [PATCH 024/369] chore: test public middleware --- examples/refactor-react/index.html | 2 +- examples/refactor-react/public/react.svg | 1 + packages/core/src/config/schema.ts | 238 +++++++++--------- packages/core/src/newServer/http.ts | 2 +- packages/core/src/newServer/index.ts | 6 +- .../{corsMiddleware.ts => cors.ts} | 0 .../{errorMiddleware.ts => error.ts} | 0 ...lFallbackMiddleware.ts => htmlFallback.ts} | 0 .../{notFoundMiddleware.ts => notFound.ts} | 0 .../{proxyMiddleware.ts => proxy.ts} | 0 .../{publicMiddleware.ts => public.ts} | 0 .../{resourceMiddleware.ts => resource.ts} | 0 .../{staticMiddleware.ts => static.ts} | 0 13 files changed, 123 insertions(+), 126 deletions(-) create mode 100644 examples/refactor-react/public/react.svg rename packages/core/src/newServer/middlewares/{corsMiddleware.ts => cors.ts} (100%) rename packages/core/src/newServer/middlewares/{errorMiddleware.ts => error.ts} (100%) rename packages/core/src/newServer/middlewares/{htmlFallbackMiddleware.ts => htmlFallback.ts} (100%) rename packages/core/src/newServer/middlewares/{notFoundMiddleware.ts => notFound.ts} (100%) rename packages/core/src/newServer/middlewares/{proxyMiddleware.ts => proxy.ts} (100%) rename packages/core/src/newServer/middlewares/{publicMiddleware.ts => public.ts} (100%) rename packages/core/src/newServer/middlewares/{resourceMiddleware.ts => resource.ts} (100%) rename packages/core/src/newServer/middlewares/{staticMiddleware.ts => static.ts} (100%) diff --git a/examples/refactor-react/index.html b/examples/refactor-react/index.html index b054de685b..816b922910 100644 --- a/examples/refactor-react/index.html +++ b/examples/refactor-react/index.html @@ -5,7 +5,7 @@ - + Farm + React + TS diff --git a/examples/refactor-react/public/react.svg b/examples/refactor-react/public/react.svg new file mode 100644 index 0000000000..6c87de9bb3 --- /dev/null +++ b/examples/refactor-react/public/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index c91d4a6d56..80c439de63 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -1,17 +1,36 @@ -import http from 'http'; +import http from 'node:http'; import { SecureServerOptions } from 'node:http2'; import { z } from 'zod'; import { fromZodError } from 'zod-validation-error'; import type { UserConfig } from './types.js'; -const stringRewriteSchema = z.record(z.string(), z.string()); +const TARGET_ENV = { + BROWSER: 'browser', + NODE: 'node', + NODE_LEGACY: 'node-legacy', + NODE_NEXT: 'node-next', + NODE16: 'node16', + BROWSER_LEGACY: 'browser-legacy', + BROWSER_ESNEXT: 'browser-esnext', + BROWSER_ES2015: 'browser-es2015', + BROWSER_ES2017: 'browser-es2017', + LIBRARY: 'library', + LIBRARY_BROWSER: 'library-browser', + LIBRARY_NODE: 'library-node' +} as const; -const functionRewriteSchema = z.union([ - z.function().args(z.string(), z.any()).returns(z.string()), - z.function().args(z.string(), z.any()).returns(z.promise(z.string())) +const baseRewriteSchema = z.union([ + z.record(z.string(), z.string()), + z + .function() + .args(z.string(), z.any()) + .returns(z.union([z.string(), z.promise(z.string())])) ]); +const stringRewriteSchema = baseRewriteSchema; +const functionRewriteSchema = baseRewriteSchema; + const pathFilterSchema = z.union([ z.string(), z.array(z.string()), @@ -21,39 +40,102 @@ const pathFilterSchema = z.union([ .returns(z.boolean()) ]); -const pathRewriteSchema = z.union([stringRewriteSchema, functionRewriteSchema]); +const pathRewriteSchema = baseRewriteSchema; + +const outputSchema = z + .object({ + entryFilename: z.string().optional(), + filename: z.string().optional(), + path: z.string().optional(), + publicPath: z.string().optional(), + assetsFilename: z.string().optional(), + targetEnv: z + .enum(Object.values(TARGET_ENV) as [string, ...string[]]) + .optional(), + format: z.enum(['cjs', 'esm']).optional() + }) + .strict() + .optional(); + +const serverSchema = z + .object({ + headers: z.record(z.string()).optional(), + port: z.number().positive().int().optional(), + host: z + .union([ + z.string().regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/), + z.literal('localhost'), + z.boolean() + ]) + .optional(), + open: z.boolean().optional(), + https: z.custom(), + cors: z.boolean().optional(), + proxy: z + .record( + z + .object({ + target: z.string(), + changeOrigin: z.boolean().optional(), + agent: z.any().optional(), + secure: z.boolean().optional(), + logs: z.any().optional(), + pathRewrite: pathRewriteSchema.optional(), + pathFilter: pathFilterSchema.optional(), + headers: z.record(z.string()).optional(), + on: z + .object({ + proxyReq: z + .function() + .args(z.any(), z.any(), z.any()) + .returns(z.void()) + .optional(), + proxyRes: z + .function() + .args(z.any(), z.any(), z.any()) + .returns(z.void()) + .optional(), + error: z + .function() + .args(z.instanceof(Error), z.any(), z.any()) + .returns(z.void()) + .optional() + }) + .optional() + }) + .passthrough() + ) + .optional(), + strictPort: z.boolean().optional(), + hmr: z + .union([ + z.boolean(), + z + .object({ + protocol: z.string().optional(), + host: z.union([z.string().min(1), z.boolean()]).optional(), + port: z.number().positive().int().optional(), + path: z.string().optional(), + watchOptions: z + .object({ + awaitWriteFinish: z.number().positive().int().optional() + }) + .optional(), + overlay: z.boolean().optional() + }) + .strict() + ]) + .optional(), + middlewares: z.array(z.any()).optional(), + writeToDisk: z.boolean().optional() + }) + .strict(); const compilationConfigSchema = z .object({ root: z.string().optional(), input: z.record(z.string()).optional(), - output: z - .object({ - entryFilename: z.string().optional(), - filename: z.string().optional(), - path: z.string().optional(), - publicPath: z.string().optional(), - assetsFilename: z.string().optional(), - targetEnv: z - .enum([ - 'browser', - 'node', - 'node-legacy', - 'node-next', - 'node16', - 'browser-legacy', - 'browser-esnext', - 'browser-es2015', - 'browser-es2017', - 'library', - 'library-browser', - 'library-node' - ]) - .optional(), - format: z.enum(['cjs', 'esm']).optional() - }) - .strict() - .optional(), + output: outputSchema, resolve: z .object({ extensions: z.array(z.string()).optional(), @@ -305,103 +387,17 @@ const FarmConfigSchema = z vitePlugins: z.array(z.any()).optional(), compilation: compilationConfigSchema.optional(), mode: z.string().optional(), - server: z - .object({ - headers: z.record(z.string()).optional(), - port: z.number().positive().int().optional(), - host: z - .union([ - z.string().regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/), - z.literal('localhost'), - z.boolean() - ]) - .optional(), - open: z.boolean().optional(), - https: z.custom(), - cors: z.boolean().optional(), - proxy: z - .record( - z - .object({ - target: z.string(), - changeOrigin: z.boolean().optional(), - agent: z.any().optional(), - secure: z.boolean().optional(), - logs: z.any().optional(), - pathRewrite: pathRewriteSchema.optional(), - pathFilter: pathFilterSchema.optional(), - headers: z.record(z.string()).optional(), - on: z - .object({ - proxyReq: z - .function() - .args( - z.instanceof(Object), - z.instanceof(Object), - z.instanceof(Object) - ) - .returns(z.void()) - .optional(), - proxyRes: z - .function() - .args( - z.instanceof(Object), - z.instanceof(Object), - z.instanceof(Object) - ) - .returns(z.void()) - .optional(), - error: z - .function() - .args( - z.instanceof(Error), - z.instanceof(Object), - z.instanceof(Object) - ) - .returns(z.void()) - .optional() - }) - .optional() - }) - .passthrough() - ) - .optional(), - strictPort: z.boolean().optional(), - hmr: z - .union([ - z.boolean(), - z - .object({ - protocol: z.string().optional(), - host: z.union([z.string().min(1), z.boolean()]).optional(), - port: z.number().positive().int().optional(), - path: z.string().optional(), - watchOptions: z - .object({ - awaitWriteFinish: z.number().positive().int().optional() - }) - .optional(), - overlay: z.boolean().optional() - }) - .strict() - ]) - .optional(), - middlewares: z.array(z.any()).optional(), - writeToDisk: z.boolean().optional() - }) - .strict() - .optional() + server: serverSchema.optional() }) .strict(); export function parseUserConfig(config: UserConfig): UserConfig { try { const parsed = FarmConfigSchema.parse(config); + // TODO type not need `as UserConfig` return parsed as UserConfig; - // return config as UserConfig; } catch (err) { const validationError = fromZodError(err); - // the error now is readable by the user throw new Error( `${validationError.toString()}. \n Please check your configuration file or command line configuration.` ); diff --git a/packages/core/src/newServer/http.ts b/packages/core/src/newServer/http.ts index d7689679c4..56dcb26985 100644 --- a/packages/core/src/newServer/http.ts +++ b/packages/core/src/newServer/http.ts @@ -18,7 +18,7 @@ import connect from 'connect'; import fse from 'fs-extra'; import { Logger } from '../utils/logger.js'; import { HttpServer } from './index.js'; -import { ProxyOptions } from './middlewares/proxyMiddleware.js'; +import { ProxyOptions } from './middlewares/proxy.js'; export interface CommonServerOptions { port?: number; diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index f00c97ac75..95ee85e9d2 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -23,9 +23,9 @@ import { resolveHttpServer, resolveHttpsConfig } from './http.js'; -import { htmlFallbackMiddleware } from './middlewares/htmlFallbackMiddleware.js'; -import { publicMiddleware } from './middlewares/publicMiddleware.js'; -import { resourceMiddleware } from './middlewares/resourceMiddleware.js'; +import { htmlFallbackMiddleware } from './middlewares/htmlFallback.js'; +import { publicMiddleware } from './middlewares/public.js'; +import { resourceMiddleware } from './middlewares/resource.js'; import { WebSocketClient, WebSocketServer, WsServer } from './ws.js'; export type HttpServer = http.Server | Http2SecureServer; diff --git a/packages/core/src/newServer/middlewares/corsMiddleware.ts b/packages/core/src/newServer/middlewares/cors.ts similarity index 100% rename from packages/core/src/newServer/middlewares/corsMiddleware.ts rename to packages/core/src/newServer/middlewares/cors.ts diff --git a/packages/core/src/newServer/middlewares/errorMiddleware.ts b/packages/core/src/newServer/middlewares/error.ts similarity index 100% rename from packages/core/src/newServer/middlewares/errorMiddleware.ts rename to packages/core/src/newServer/middlewares/error.ts diff --git a/packages/core/src/newServer/middlewares/htmlFallbackMiddleware.ts b/packages/core/src/newServer/middlewares/htmlFallback.ts similarity index 100% rename from packages/core/src/newServer/middlewares/htmlFallbackMiddleware.ts rename to packages/core/src/newServer/middlewares/htmlFallback.ts diff --git a/packages/core/src/newServer/middlewares/notFoundMiddleware.ts b/packages/core/src/newServer/middlewares/notFound.ts similarity index 100% rename from packages/core/src/newServer/middlewares/notFoundMiddleware.ts rename to packages/core/src/newServer/middlewares/notFound.ts diff --git a/packages/core/src/newServer/middlewares/proxyMiddleware.ts b/packages/core/src/newServer/middlewares/proxy.ts similarity index 100% rename from packages/core/src/newServer/middlewares/proxyMiddleware.ts rename to packages/core/src/newServer/middlewares/proxy.ts diff --git a/packages/core/src/newServer/middlewares/publicMiddleware.ts b/packages/core/src/newServer/middlewares/public.ts similarity index 100% rename from packages/core/src/newServer/middlewares/publicMiddleware.ts rename to packages/core/src/newServer/middlewares/public.ts diff --git a/packages/core/src/newServer/middlewares/resourceMiddleware.ts b/packages/core/src/newServer/middlewares/resource.ts similarity index 100% rename from packages/core/src/newServer/middlewares/resourceMiddleware.ts rename to packages/core/src/newServer/middlewares/resource.ts diff --git a/packages/core/src/newServer/middlewares/staticMiddleware.ts b/packages/core/src/newServer/middlewares/static.ts similarity index 100% rename from packages/core/src/newServer/middlewares/staticMiddleware.ts rename to packages/core/src/newServer/middlewares/static.ts From 7e81dfa92c77ef4b2a790196d8d12e8e6d7bfac5 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Wed, 31 Jul 2024 16:32:04 +0800 Subject: [PATCH 025/369] chore: update hmr --- .vscode/settings.json | 4 +- examples/refactor-react/farm.config.ts | 2 +- examples/refactor-react/src/main.tsx | 2 +- packages/core/src/config/types.ts | 1 + packages/core/src/index.ts | 54 ++++- packages/core/src/newServer/error.ts | 95 ++++++++ packages/core/src/newServer/hmr-engine.ts | 203 ++++++++++++++++++ packages/core/src/newServer/http.ts | 4 - packages/core/src/newServer/index.ts | 74 +++---- .../core/src/newServer/middlewares/public.ts | 8 +- packages/core/src/newServer/ws.ts | 192 ++++++++++++++++- packages/core/src/server/ws.ts | 1 + packages/core/src/watcher/index.ts | 11 +- packages/runtime-plugin-hmr/src/hmr-client.ts | 2 + packages/runtime-plugin-hmr/src/index.ts | 6 +- packages/utils/src/color.ts | 25 ++- 16 files changed, 608 insertions(+), 76 deletions(-) create mode 100644 packages/core/src/newServer/error.ts create mode 100644 packages/core/src/newServer/hmr-engine.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index fc7d12d02e..db18a0b276 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,10 +4,10 @@ "biome.enabled": true, "editor.defaultFormatter": "biomejs.biome", "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[javascript]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer" diff --git a/examples/refactor-react/farm.config.ts b/examples/refactor-react/farm.config.ts index 1587dd5127..d2298b49bb 100644 --- a/examples/refactor-react/farm.config.ts +++ b/examples/refactor-react/farm.config.ts @@ -6,7 +6,7 @@ export default defineConfig({ // presetEnv: false, // progress: false, // sourcemap: false, - persistentCache: false, + // persistentCache: false, runtime: { isolate: true } diff --git a/examples/refactor-react/src/main.tsx b/examples/refactor-react/src/main.tsx index 419a29c9be..e28f192115 100644 --- a/examples/refactor-react/src/main.tsx +++ b/examples/refactor-react/src/main.tsx @@ -7,7 +7,7 @@ export function Main() { return ( <> -
+
2 Farm logo diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index 71eea65fea..7e6151f08e 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -71,6 +71,7 @@ export interface NormalizedConfig { export interface UserHmrConfig extends HmrOptions { watchOptions?: WatchOptions; + overlay?: boolean; } type InternalConfig = Config['config'] extends undefined diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 80fdc69798..5885dfdf14 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -38,7 +38,7 @@ import { normalizePublicDir, resolveConfig } from './config/index.js'; -import { newServer } from './newServer/index.js'; +import { HttpServer, newServer } from './newServer/index.js'; import { Server } from './server/index.js'; import { compilerHandler } from './utils/build.js'; import { colors } from './utils/color.js'; @@ -444,6 +444,52 @@ export async function createFileWatcher( return fileWatcher; } +export async function createFileWatcher2( + devServer: HttpServer, + resolvedUserConfig: ResolvedUserConfig, + logger: Logger = new Logger() +) { + if ( + resolvedUserConfig.server.hmr && + resolvedUserConfig.compilation.mode === 'production' + ) { + logger.error('HMR cannot be enabled in production mode.'); + return; + } + + if (!resolvedUserConfig.server.hmr) { + return; + } + + // if (resolvedUserConfig.server.watcher) { + // return; + // } + + // @ts-ignore + const fileWatcher = new FileWatcher(devServer, resolvedUserConfig, logger); + // devServer.watcher = fileWatcher; + await fileWatcher.watch(); + + const configFilePath = await getConfigFilePath(resolvedUserConfig.root); + const farmWatcher = new ConfigWatcher({ + ...resolvedUserConfig, + configFilePath + }); + farmWatcher.watch(async (files: string[]) => { + checkClearScreen(resolvedUserConfig); + + // devServer.restart(async () => { + // logFileChanges(files, resolvedUserConfig.root, logger); + // farmWatcher?.close(); + + // await devServer.close(); + // __FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ = true; + // await start(resolvedUserConfig as FarmCliOptions & UserConfig); + // }); + }); + return fileWatcher; +} + export function logFileChanges(files: string[], root: string, logger: Logger) { const changedFiles = files .map((file) => path.relative(root, file)) @@ -476,6 +522,12 @@ export async function start2( const compiler = await createCompiler(resolvedUserConfig, logger); const server = new newServer(compiler, resolvedUserConfig, logger); await server.createServer(); + // @ts-ignore + await createFileWatcher2(server, resolvedUserConfig, logger); + // call configureDevServer hook after both server and watcher are ready + // resolvedUserConfig.jsPlugins.forEach((plugin: JsPlugin) => + // plugin.configureDevServer?.(server) + // ); await server.listen(); // const devServer = await createDevServer( // compiler, diff --git a/packages/core/src/newServer/error.ts b/packages/core/src/newServer/error.ts new file mode 100644 index 0000000000..467f338eca --- /dev/null +++ b/packages/core/src/newServer/error.ts @@ -0,0 +1,95 @@ +import type { RollupError } from 'rollup'; +import { colors } from '../utils/color.js'; +import { pad } from '../utils/share.js'; +// import { DevServer } from './index.js'; + +export function prepareError(err: Error & { potentialSolution?: string }) { + return { + message: stripAnsi(err.message), + stack: stripAnsi(cleanStack(err.stack || '')), + id: (err as RollupError).id, + frame: stripAnsi((err as RollupError).frame || ''), + plugin: (err as RollupError).plugin, + pluginCode: (err as RollupError).pluginCode?.toString(), + loc: (err as RollupError).loc, + potential: err.potentialSolution || '' + }; +} + +export function stripAnsi(str: string) { + // eslint-disable-next-line no-control-regex + return str.replace(/\x1b\[[0-9;]*m/g, ''); +} + +export function cleanStack(stack: string) { + return stack + .split(/\n/g) + .filter((l) => /^\s*at/.test(l)) + .join('\n'); +} + +export function buildErrorMessage( + err: RollupError & { source: string }, + args: string[] = [], + includeStack = true +): string { + if (err.plugin) args.push(` Plugin: ${colors.magenta(err.plugin)}`); + const loc = err.loc ? `:${err.loc.line}:${err.loc.column}` : ''; + if (err.id) args.push(` File: ${colors.cyan(err.id)}${loc}`); + if (err.frame) args.push(colors.yellow(pad(err.frame))); + else if (err.source) args.push(colors.yellow(err.source)); + if (includeStack && err.stack) args.push(pad(cleanStack(err.stack))); + return args.join('\n'); +} + +export function logError(err: Error, throwErrorFlag = true) { + let errorMessages: string[] = []; + try { + errorMessages = JSON.parse(err.message); + } catch (_) { + throw new Error(err.message); + } + + if (!Array.isArray(errorMessages) || errorMessages.length === 0) { + if (throwErrorFlag) { + throw new Error(err.message); + } + return err.message; + } + + const formattedErrorMessages = errorMessages.map((errorMsg: string) => { + try { + const parsedErrorMsg = JSON.parse(errorMsg); + if ( + parsedErrorMsg && + typeof parsedErrorMsg === 'object' && + (parsedErrorMsg.message || parsedErrorMsg.reason) + ) { + return `${buildErrorMessage(parsedErrorMsg, [ + colors.red( + `Internal server error: ${ + parsedErrorMsg.message || parsedErrorMsg.reason + }` + ) + ])}`; + } else { + return colors.red(errorMsg); + } + } catch { + return colors.red(errorMsg); + } + }); + const errorMessage = formattedErrorMessages.join('\n'); + if (throwErrorFlag) { + throw new Error(errorMessage); + } + return errorMessage; +} + +// TODO server logger e.g: DevServer.logger.error(msg); + +// server.ws.send({ +// type: 'error', +// err: prepareError(err) +// }); +// } diff --git a/packages/core/src/newServer/hmr-engine.ts b/packages/core/src/newServer/hmr-engine.ts new file mode 100644 index 0000000000..173d877304 --- /dev/null +++ b/packages/core/src/newServer/hmr-engine.ts @@ -0,0 +1,203 @@ +import fse from 'fs-extra'; +// queue all updates and compile them one by one + +import { stat } from 'node:fs/promises'; +import { isAbsolute, relative } from 'node:path'; + +import type { Resource } from '@farmfe/runtime/src/resource-loader.js'; +import { Compiler } from '../compiler/index.js'; +import { + UserConfig, + UserHmrConfig, + checkClearScreen +} from '../config/index.js'; +import { HttpServer } from '../newServer/index.js'; +import type { JsUpdateResult } from '../types/binding.js'; +import { Logger, bold, cyan, green } from '../utils/index.js'; +import { logError } from './error.js'; +import { WebSocketClient, WebSocketServer } from './ws.js'; + +export class HmrEngine { + private _updateQueue: string[] = []; + // private _updateResults: Map = + + private _compiler: Compiler; + private _devServer: HttpServer; + private _onUpdates: ((result: JsUpdateResult) => void)[]; + + private _lastModifiedTimestamp: Map; + + public config: UserConfig; + public ws: WebSocketServer; + constructor( + compiler: Compiler, + devServer: HttpServer, + config: UserConfig, + ws: WebSocketServer, + private _logger: Logger + ) { + this._compiler = compiler; + this._devServer = devServer; + // this._lastAttemptWasError = false; + this._lastModifiedTimestamp = new Map(); + this.ws = ws; + } + + callUpdates(result: JsUpdateResult) { + this._onUpdates?.forEach((cb) => cb(result)); + } + + onUpdateFinish(cb: (result: JsUpdateResult) => void) { + if (!this._onUpdates) { + this._onUpdates = []; + } + this._onUpdates.push(cb); + } + + recompileAndSendResult = async (): Promise => { + const queue = [...this._updateQueue]; + + if (queue.length === 0) { + return; + } + + let updatedFilesStr = queue + .map((item) => { + if (isAbsolute(item)) { + return relative(this._compiler.config.config.root, item); + } else { + const resolvedPath = this._compiler.transformModulePath( + this._compiler.config.config.root, + item + ); + return relative(this._compiler.config.config.root, resolvedPath); + } + }) + .join(', '); + if (updatedFilesStr.length >= 100) { + updatedFilesStr = + updatedFilesStr.slice(0, 100) + `...(${queue.length} files)`; + } + + try { + // we must add callback before update + this._compiler.onUpdateFinish(async () => { + // if there are more updates, recompile again + if (this._updateQueue.length > 0) { + await this.recompileAndSendResult(); + } + if (this.config?.server.writeToDisk) { + this._compiler.writeResourcesToDisk(); + } + }); + + checkClearScreen(this._compiler.config.config); + const start = Date.now(); + const result = await this._compiler.update(queue); + this._logger.info( + `${bold(cyan(updatedFilesStr))} updated in ${bold( + green(`${Date.now() - start}ms`) + )}` + ); + + // clear update queue after update finished + this._updateQueue = this._updateQueue.filter( + (item) => !queue.includes(item) + ); + + let dynamicResourcesMap: Record = null; + + if (result.dynamicResourcesMap) { + for (const [key, value] of Object.entries(result.dynamicResourcesMap)) { + if (!dynamicResourcesMap) { + dynamicResourcesMap = {} as Record; + } + dynamicResourcesMap[key] = value.map((r) => ({ + path: r[0], + type: r[1] as 'script' | 'link' + })); + } + } + const { + added, + changed, + removed, + immutableModules, + mutableModules, + boundaries + } = result; + const resultStr = `{ + added: [${formatHmrResult(added)}], + changed: [${formatHmrResult(changed)}], + removed: [${formatHmrResult(removed)}], + immutableModules: ${JSON.stringify(immutableModules.trim())}, + mutableModules: ${JSON.stringify(mutableModules.trim())}, + boundaries: ${JSON.stringify(boundaries)}, + dynamicResourcesMap: ${JSON.stringify(dynamicResourcesMap)} + }`; + + this.callUpdates(result); + + this.ws.clients.forEach((client: WebSocketClient) => { + // @ts-ignore + client.rawSend(` + { + type: 'farm-update', + result: ${resultStr} + } + `); + }); + } catch (err) { + checkClearScreen(this._compiler.config.config); + console.log(err); + // throw new Error(logError(err) as unknown as string); + } + }; + + async hmrUpdate(absPath: string | string[], force = false) { + const paths = Array.isArray(absPath) ? absPath : [absPath]; + + for (const path of paths) { + if (this._compiler.hasModule(path) && !this._updateQueue.includes(path)) { + if (fse.existsSync(path)) { + const lastModifiedTimestamp = this._lastModifiedTimestamp.get(path); + const currentTimestamp = (await stat(path)).mtime.toISOString(); + // only update the file if the timestamp changed since last update + if (!force && lastModifiedTimestamp === currentTimestamp) { + continue; + } + this._lastModifiedTimestamp.set(path, currentTimestamp); + } + // push the path into the queue + this._updateQueue.push(path); + } + } + + if (!this._compiler.compiling && this._updateQueue.length > 0) { + try { + await this.recompileAndSendResult(); + } catch (e) { + // eslint-disable-next-line no-control-regex + const serialization = e.message.replace(/\x1b\[[0-9;]*m/g, ''); + const errorStr = `${JSON.stringify({ + message: serialization + })}`; + this.ws.clients.forEach((client: WebSocketClient) => { + // @ts-ignore + client.rawSend(` + { + type: 'error', + err: ${errorStr}, + overlay: ${(this.config.server.hmr as UserHmrConfig).overlay} + } + `); + }); + this._logger.error(e); + } + } + } +} + +function formatHmrResult(array: string[]) { + return array.map((item) => `'${item.replaceAll('\\', '\\\\')}'`).join(', '); +} diff --git a/packages/core/src/newServer/http.ts b/packages/core/src/newServer/http.ts index 56dcb26985..a8f55ac02c 100644 --- a/packages/core/src/newServer/http.ts +++ b/packages/core/src/newServer/http.ts @@ -65,13 +65,9 @@ export async function resolveHttpServer( // https://github.com/nxtedition/node-http2-proxy // https://github.com/fastify/fastify-http-proxy if (proxy) { - console.log('走的是哪里'); - const { createServer } = await import('node:https'); return createServer(httpsOptions, app); } else { - console.log('我现在用的就是 http2'); - const { createSecureServer } = await import('node:http2'); return createSecureServer( { diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index 95ee85e9d2..882880923d 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -13,10 +13,11 @@ import { Compiler } from '../compiler/index.js'; import { normalizePublicPath } from '../config/normalize-config/normalize-output.js'; import { NormalizedServerConfig, ResolvedUserConfig } from '../config/types.js'; import { logError } from '../server/error.js'; -import { Logger, logger } from '../utils/logger.js'; +import { Logger, bootstrap, logger } from '../utils/logger.js'; import { initPublicFiles } from '../utils/publicDir.js'; import { isObject } from '../utils/share.js'; import { FileWatcher } from '../watcher/index.js'; +import { HmrEngine } from './hmr-engine.js'; import { HMRChannel } from './hmr.js'; import { CommonServerOptions, @@ -76,14 +77,14 @@ export interface ServerOptions extends CommonServerOptions { origin?: string; } -function noop() { +export function noop() { // noop } export class newServer { private compiler: CompilerType; - ws: WsServer; + ws: any; config: ResolvedUserConfig; serverConfig: CommonServerOptions & NormalizedServerConfig; httpsOptions: HttpsServerOptions; @@ -91,6 +92,7 @@ export class newServer { publicPath?: string; httpServer?: HttpServer; watcher: FileWatcher; + hmrEngine?: HmrEngine; logger: Logger; constructor( @@ -132,10 +134,18 @@ export class newServer { const publicFiles = await initPublicFilesPromise; const { publicDir } = this.config.compilation.assets; - // this.createWebSocketServer(); + + this.createWebSocketServer(); + this.hmrEngine = new HmrEngine( + this.compiler, + this.httpServer, + this.config, + this.ws, + this.logger + ); // middleware - // middlewares.use(compression()); + middlewares.use(compression()); if (publicDir) { middlewares.use(publicMiddleware(this.logger, this.config, publicFiles)); @@ -166,56 +176,28 @@ export class newServer { throw new Error('Websocket requires a server.'); } - // const wsServer = new WsServer( - // this.httpServer, - // this.config, - // this.httpsOptions, - // this.publicPath, - // null - // ); + const wsServer = new WsServer( + this.httpServer, + this.config, + this.httpsOptions, + this.publicPath, + null + ); + + const ws = wsServer.createWebSocketServer(); + this.ws = ws; } public async listen(): Promise { if (!this.httpServer) { - // this.logger.error('HTTP server is not created yet'); + this.logger.warn('HTTP server is not created yet'); return; } const { port, open, protocol, hostname } = this.config.server; + const start = Date.now(); await this.compile(); - // const { createServer } = await import('node:http'); - - // this.httpServer = createServer((req, res) => { - // if (req.url === '/') { - // // res.writeHead(200, { 'Content-Type': 'text/plain' }); - // // res.end('Hello, World!'); - // } else if (req.url === '/about') { - // res.writeHead(200, { 'Content-Type': 'text/plain' }); - // res.end('About page'); - // } else { - // res.writeHead(404, { 'Content-Type': 'text/plain' }); - // res.end('404 Not Found'); - // } - // }); - - // this.httpServer.on('request', (req, res) => { - // // 设置响应头 - // // res.writeHead(200, { 'Content-Type': 'application/json' }); - // res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); - - // // 创建响应体对象 - // const responseBody = { - // message: "这是使用 on('request') 方法的响应", - // timestamp: new Date().toISOString(), - // path: req.url - // }; - - // // 将对象转换为 JSON 字符串 - // const jsonResponse = JSON.stringify(responseBody); - // // res.setHeader('Content-Type', 'text/plain; charset=utf-8'); - // // 发送响应 - // res.end(jsonResponse); - // }); + bootstrap(Date.now() - start, this.compiler.config); this.httpServer.listen(port, hostname.name, () => { console.log(`Server running at ${protocol}://${hostname.name}:${port}/`); diff --git a/packages/core/src/newServer/middlewares/public.ts b/packages/core/src/newServer/middlewares/public.ts index f5ea6ff29e..96c2fb2950 100644 --- a/packages/core/src/newServer/middlewares/public.ts +++ b/packages/core/src/newServer/middlewares/public.ts @@ -1,5 +1,5 @@ import sirv from 'sirv'; -import { ResolvedUserConfig } from '../../config/types.js'; + import { colors } from '../../utils/color.js'; import { Logger } from '../../utils/logger.js'; import { removeHashFromPath, withTrailingSlash } from '../../utils/path.js'; @@ -12,6 +12,8 @@ import { urlRE } from '../../utils/url.js'; +import type { ResolvedUserConfig } from '../../config/types.js'; + function warnAboutPublicDir(url: string, publicPath: string) { let warning: string; if (isImportRequest(url)) { @@ -61,7 +63,7 @@ export function publicMiddleware( } if (headers) { for (const name in headers) { - res.setHeader(name, headers[name]!); + res.setHeader(name, headers[name]); } } } @@ -83,7 +85,7 @@ export function publicMiddleware( res: any, next: () => void ) { - const url = removeHashFromPath(req.url!); + const url = removeHashFromPath(req.url); const filePath = toFilePath(url); // If it is not equal, it means that it is recognized as a module diff --git a/packages/core/src/newServer/ws.ts b/packages/core/src/newServer/ws.ts index 5460a61313..ae4a3de1b6 100644 --- a/packages/core/src/newServer/ws.ts +++ b/packages/core/src/newServer/ws.ts @@ -99,7 +99,7 @@ export class WsServer { public clientsMap = new WeakMap(); public bufferedError: ErrorPayload | null = null; public logger: ILogger; - public wsServerOrHmrServer: Server; + public wsServer: Server; constructor( private httpServer: HttpServer, @@ -110,7 +110,7 @@ export class WsServer { logger?: ILogger ) { this.logger = logger ?? new Logger(); - this.createWebSocketServer(); + // this.createWebSocketServer(); } createWebSocketServer() { @@ -138,8 +138,7 @@ export class WsServer { const hmrPort = hmr && hmr.port; const portsAreCompatible = !hmrPort || hmrPort === serverConfig.port; // @ts-ignore - this.wsServerOrHmrServer = - hmrServer || (portsAreCompatible && this.httpServer); + this.wsServer = hmrServer || (portsAreCompatible && this.httpServer); let hmrServerWsListener: ( req: InstanceType, socket: Duplex, @@ -148,7 +147,7 @@ export class WsServer { const port = hmrPort || 9000; const host = (hmr && hmr.host) || undefined; - if (this.wsServerOrHmrServer) { + if (this.wsServer) { let hmrBase = this.publicPath; const hmrPath = hmr ? hmr.path : undefined; if (hmrPath) { @@ -165,7 +164,7 @@ export class WsServer { }); } }; - this.wsServerOrHmrServer.on('upgrade', hmrServerWsListener); + this.wsServer.on('upgrade', hmrServerWsListener); } else { // http server request handler keeps the same with // https://github.com/websockets/ws/blob/45e17acea791d865df6b255a55182e9c42e5877a/lib/websocket-server.js#L88-L96 @@ -192,5 +191,186 @@ export class WsServer { // need to call ws listen manually wss = new WebSocketServerRaw({ server: wsHttpServer }); } + + wss.on('connection', (socket) => { + socket.on('message', (raw) => { + if (!this.customListeners.size) return; + let parsed: any; + try { + parsed = JSON.parse(String(raw)); + } catch {} + if (!parsed || parsed.type !== 'custom' || !parsed.event) return; + const listeners = this.customListeners.get(parsed.event); + if (!listeners?.size) return; + const client = this.getSocketClient(socket); + listeners.forEach((listener) => listener(parsed.data, client)); + }); + socket.on('error', (err) => { + console.log('ws error:', err); + + // config.logger.error(`${colors.red(`ws error:`)}\n${err.stack}`, { + // timestamp: true, + // error: err + // }); + }); + socket.send(JSON.stringify({ type: 'connected' })); + if (this.bufferedError) { + socket.send(JSON.stringify(this.bufferedError)); + this.bufferedError = null; + } + }); + + wss.on('error', (e: Error & { code: string }) => { + if (e.code === 'EADDRINUSE') { + console.log('WebSocket server error: Port is already in use'); + + // config.logger.error( + // colors.red(`WebSocket server error: Port is already in use`), + // { error: e } + // ); + } else { + console.log('WebSocket server error:', e.stack || e.message); + + // config.logger.error( + // colors.red(`WebSocket server error:\n${e.stack || e.message}`), + // { error: e } + // ); + } + }); + const self = this; + function getSocketClient(socket: WebSocketRaw) { + if (!this.clientsMap.has(socket)) { + this.clientsMap.set(socket, { + send: (...args: any) => { + let payload: HMRPayload; + if (typeof args[0] === 'string') { + payload = { + type: 'custom', + event: args[0], + data: args[1] + }; + } else { + payload = args[0]; + } + socket.send(JSON.stringify(payload)); + }, + // @ts-ignore + rawSend: (str: string) => socket.send(str), + socket + }); + } + return this.clientsMap.get(socket); + } + + return { + name: 'ws', + listen: () => { + // @ts-ignore + wsHttpServer?.listen(port, host); + }, + on: ((event: string, fn: () => void) => { + if (wsServerEvents.includes(event)) wss.on(event, fn); + else { + if (!this.customListeners.has(event)) { + this.customListeners.set(event, new Set()); + } + this.customListeners.get(event).add(fn); + } + }) as WebSocketServer['on'], + off: ((event: string, fn: () => void) => { + if (wsServerEvents.includes(event)) { + wss.off(event, fn); + } else { + this.customListeners.get(event)?.delete(fn); + } + }) as WebSocketServer['off'], + + get clients() { + // return new Set(Array.from(wss.clients).map(getSocketClient)); + return new Set( + Array.from(wss.clients).map((socket) => self.getSocketClient(socket)) + ); + }, + + send(...args: any[]) { + let payload: HMRPayload; + if (typeof args[0] === 'string') { + payload = { + type: 'custom', + event: args[0], + data: args[1] + }; + } else { + payload = args[0]; + } + + if (payload.type === 'error' && !wss.clients.size) { + this.bufferedError = payload; + return; + } + + const stringified = JSON.stringify(payload); + wss.clients.forEach((client) => { + // readyState 1 means the connection is open + if (client.readyState === 1) { + client.send(stringified); + } + }); + }, + + close() { + // should remove listener if hmr.server is set + // otherwise the old listener swallows all WebSocket connections + if (hmrServerWsListener && this.wsServer) { + this.wsServer.off('upgrade', hmrServerWsListener); + } + return new Promise((resolve, reject) => { + wss.clients.forEach((client) => { + client.terminate(); + }); + wss.close((err) => { + if (err) { + reject(err); + } else { + if (wsHttpServer) { + wsHttpServer.close((err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + } else { + resolve(); + } + } + }); + }); + } + }; + } + + getSocketClient(socket: WebSocketRaw) { + if (!this.clientsMap.has(socket)) { + this.clientsMap.set(socket, { + send: (...args) => { + let payload: HMRPayload; + if (typeof args[0] === 'string') { + payload = { + type: 'custom', + event: args[0], + data: args[1] + }; + } else { + payload = args[0]; + } + socket.send(JSON.stringify(payload)); + }, + // @ts-ignore + rawSend: (str: string) => socket.send(str), + socket + }); + } + return this.clientsMap.get(socket); } } diff --git a/packages/core/src/server/ws.ts b/packages/core/src/server/ws.ts index 1281974c44..f2117dd934 100644 --- a/packages/core/src/server/ws.ts +++ b/packages/core/src/server/ws.ts @@ -178,6 +178,7 @@ export default class WsServer implements IWebSocketServer { const listeners = this.customListeners.get(parsed.event); if (!listeners?.size) return; const client = this.getSocketClient(socket); + listeners.forEach((listener) => listener(parsed.data, client)); }); diff --git a/packages/core/src/watcher/index.ts b/packages/core/src/watcher/index.ts index f277289426..b4157f5d04 100644 --- a/packages/core/src/watcher/index.ts +++ b/packages/core/src/watcher/index.ts @@ -78,7 +78,10 @@ export class FileWatcher implements ImplFileWatcher { } try { - if (this.serverOrCompiler instanceof Server) { + // if (this.serverOrCompiler instanceof Server) { + // @ts-ignore + if (this.serverOrCompiler.compiler) { + // @ts-ignore await this.serverOrCompiler.hmrEngine.hmrUpdate(path); } @@ -141,8 +144,10 @@ export class FileWatcher implements ImplFileWatcher { private getCompilerFromServerOrCompiler( serverOrCompiler: Server | Compiler ): Compiler { - return serverOrCompiler instanceof Server - ? serverOrCompiler.getCompiler() + // @ts-ignore + return serverOrCompiler.getCompiler + ? // @ts-ignore + serverOrCompiler.getCompiler() : serverOrCompiler; } diff --git a/packages/runtime-plugin-hmr/src/hmr-client.ts b/packages/runtime-plugin-hmr/src/hmr-client.ts index 64c014bfda..1507660be7 100644 --- a/packages/runtime-plugin-hmr/src/hmr-client.ts +++ b/packages/runtime-plugin-hmr/src/hmr-client.ts @@ -35,6 +35,8 @@ export class HmrClient { `${socketProtocol}://${socketHostUrl}`, 'farm_hmr' ); + console.log(`${socketProtocol}://${socketHostUrl}`); + this.socket = socket; // listen for the message from the server // when the user save the file, the server will recompile the file(and its dependencies as long as its dependencies are changed) diff --git a/packages/runtime-plugin-hmr/src/index.ts b/packages/runtime-plugin-hmr/src/index.ts index fa3d95474f..dade759596 100644 --- a/packages/runtime-plugin-hmr/src/index.ts +++ b/packages/runtime-plugin-hmr/src/index.ts @@ -10,11 +10,11 @@ let hmrClient: HmrClient; export default ({ name: 'farm-runtime-hmr-client-plugin', bootstrap(moduleSystem) { - // hmrClient = new HmrClient(moduleSystem); - // hmrClient.connect(); + hmrClient = new HmrClient(moduleSystem); + hmrClient.connect(); }, moduleCreated(module) { // create a hot context for each module - // module.meta.hot = createHotContext(module.id, hmrClient); + module.meta.hot = createHotContext(module.id, hmrClient); } }); diff --git a/packages/utils/src/color.ts b/packages/utils/src/color.ts index a144f2bf40..233a1540d9 100644 --- a/packages/utils/src/color.ts +++ b/packages/utils/src/color.ts @@ -27,6 +27,8 @@ const gradientPurpleColor = [176, 106, 179]; const gradientPinkColor = [198, 66, 110]; const brandGradientColors = [255, 182, 193]; const brandGradientColors2 = [128, 0, 128]; +const gradientOrangeColor = [255, 165, 0]; +const gradientGoldColor = [255, 215, 0]; const argv = process.argv || [], env = process.env; @@ -175,17 +177,28 @@ export function interpolateColor( export const PersistentCacheBrand = brandColor('⚡️') + gradientString(`FULL EXTREME!`, [ + // gradientPurpleColor, + // interpolateColor(gradientPurpleColor, gradientPinkColor, 0.1), + // interpolateColor(gradientPurpleColor, gradientPinkColor, 0.2), + // interpolateColor(gradientPurpleColor, gradientPinkColor, 0.3), + // interpolateColor(gradientPurpleColor, gradientPinkColor, 0.4), + // interpolateColor(gradientPurpleColor, gradientPinkColor, 0.5), + // interpolateColor(gradientPurpleColor, gradientPinkColor, 0.6), + // interpolateColor(gradientPurpleColor, gradientPinkColor, 0.7), + // interpolateColor(gradientPurpleColor, gradientPinkColor, 0.8), + // interpolateColor(gradientPurpleColor, gradientPinkColor, 0.9), + // gradientPinkColor gradientPurpleColor, - interpolateColor(gradientPurpleColor, gradientPinkColor, 0.1), interpolateColor(gradientPurpleColor, gradientPinkColor, 0.2), - interpolateColor(gradientPurpleColor, gradientPinkColor, 0.3), interpolateColor(gradientPurpleColor, gradientPinkColor, 0.4), - interpolateColor(gradientPurpleColor, gradientPinkColor, 0.5), interpolateColor(gradientPurpleColor, gradientPinkColor, 0.6), - interpolateColor(gradientPurpleColor, gradientPinkColor, 0.7), interpolateColor(gradientPurpleColor, gradientPinkColor, 0.8), - interpolateColor(gradientPurpleColor, gradientPinkColor, 0.9), - gradientPinkColor + gradientPinkColor, + interpolateColor(gradientPinkColor, gradientOrangeColor, 0.3), + interpolateColor(gradientPinkColor, gradientOrangeColor, 0.6), + gradientOrangeColor, + interpolateColor(gradientOrangeColor, gradientGoldColor, 0.5), + gradientGoldColor ]); export function handleBrandText(text: string) { From 7165d695f37f741067d22fe2df52f27bd049f17b Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Thu, 1 Aug 2024 11:21:48 +0800 Subject: [PATCH 026/369] chore: update code --- examples/refactor-react/src/main.tsx | 2 +- packages/core/src/newServer/index.ts | 82 +++++++++++++++------------- packages/core/src/newServer/ws.ts | 2 +- 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/examples/refactor-react/src/main.tsx b/examples/refactor-react/src/main.tsx index e28f192115..419a29c9be 100644 --- a/examples/refactor-react/src/main.tsx +++ b/examples/refactor-react/src/main.tsx @@ -7,7 +7,7 @@ export function Main() { return ( <> -
2 +
Farm logo diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index 882880923d..927782d50a 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -82,59 +82,60 @@ export function noop() { } export class newServer { - private compiler: CompilerType; - ws: any; - config: ResolvedUserConfig; - serverConfig: CommonServerOptions & NormalizedServerConfig; + serverOptions: CommonServerOptions & NormalizedServerConfig; httpsOptions: HttpsServerOptions; - publicDir?: string; + // public assets directory + publicDir?: string | boolean; + // base path of server publicPath?: string; + // publicFile + publicFiles?: string[]; httpServer?: HttpServer; watcher: FileWatcher; hmrEngine?: HmrEngine; - logger: Logger; constructor( - compiler: CompilerType, - config: ResolvedUserConfig, - logger: Logger + private readonly compiler: CompilerType, + private readonly config: ResolvedUserConfig, + private readonly logger: Logger ) { - this.compiler = compiler; - this.config = config; - this.logger = logger; - if (!this.compiler) return; - - this.publicPath = - normalizePublicPath( - compiler.config.config.output.targetEnv, - compiler.config.config.output.publicPath, - logger, - false - ) || '/'; + if (!this.compiler) { + this.logger.error( + 'Compiler is not provided, server will not work, please provide a compiler e.q. `new Compiler(config)`' + ); + return; + } + this.resolveOptions(config); } - getCompiler(): CompilerType { + public getCompiler(): CompilerType { return this.compiler; } - async createServer() { - const initPublicFilesPromise = initPublicFiles(this.config); - const { root, server: serverConfig } = this.config; - this.httpsOptions = await resolveHttpsConfig(serverConfig.https); - const { middlewareMode } = serverConfig; + private resolveOptions(config: ResolvedUserConfig) { + const { targetEnv, publicPath } = config.compilation.output; + this.publicDir = config.compilation.assets.publicDir; + this.publicPath = + normalizePublicPath(targetEnv, publicPath, this.logger, false) || '/'; + + this.serverOptions = config.server as CommonServerOptions & + NormalizedServerConfig; + } + + public async createServer() { + this.httpsOptions = await resolveHttpsConfig(this.serverOptions.https); + const publicFiles = await this.handlePublicFiles(); + const { middlewareMode } = this.serverOptions; const middlewares = connect() as connect.Server; this.httpServer = middlewareMode ? null : await resolveHttpServer( - serverConfig as CommonServerOptions, + this.serverOptions as CommonServerOptions, middlewares, this.httpsOptions ); - const publicFiles = await initPublicFilesPromise; - const { publicDir } = this.config.compilation.assets; - this.createWebSocketServer(); this.hmrEngine = new HmrEngine( this.compiler, @@ -145,9 +146,9 @@ export class newServer { ); // middleware - middlewares.use(compression()); + // middlewares.use(compression()); - if (publicDir) { + if (this.publicDir) { middlewares.use(publicMiddleware(this.logger, this.config, publicFiles)); } // TODO todo add appType @@ -170,22 +171,25 @@ export class newServer { ); } + private async handlePublicFiles() { + const initPublicFilesPromise = initPublicFiles(this.config); + return await initPublicFilesPromise; + } + public async createWebSocketServer() { - // @ts-ignore if (!this.httpServer) { - throw new Error('Websocket requires a server.'); + throw new Error( + 'Websocket requires a http server. please check the server is be created' + ); } - const wsServer = new WsServer( + this.ws = new WsServer( this.httpServer, this.config, this.httpsOptions, this.publicPath, null ); - - const ws = wsServer.createWebSocketServer(); - this.ws = ws; } public async listen(): Promise { diff --git a/packages/core/src/newServer/ws.ts b/packages/core/src/newServer/ws.ts index ae4a3de1b6..ff4114b1af 100644 --- a/packages/core/src/newServer/ws.ts +++ b/packages/core/src/newServer/ws.ts @@ -110,7 +110,7 @@ export class WsServer { logger?: ILogger ) { this.logger = logger ?? new Logger(); - // this.createWebSocketServer(); + this.createWebSocketServer(); } createWebSocketServer() { From 8ddf964710f342d3a37b761d5a727ba5e8b06570 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Thu, 1 Aug 2024 11:42:48 +0800 Subject: [PATCH 027/369] chore: merge code --- packages/core/src/config/schema.ts | 236 +++++++++--------- packages/core/src/index.ts | 8 +- packages/core/src/newServer/hmr.ts | 46 ---- packages/core/src/newServer/http.ts | 139 ----------- packages/core/src/newServer/index.ts | 133 ---------- .../core/src/newServer/middlewares/cors.ts | 0 .../core/src/newServer/middlewares/error.ts | 0 .../core/src/newServer/middlewares/index.ts | 0 .../newServer/middlewares/lazy-compilation.ts | 0 .../src/newServer/middlewares/notFound.ts | 0 .../core/src/newServer/middlewares/proxy.ts | 15 -- .../src/newServer/middlewares/resource.ts | 0 .../core/src/newServer/middlewares/static.ts | 0 packages/core/src/newServer/publicDir.ts | 35 --- packages/core/src/newServer/type.ts | 100 -------- packages/core/src/newServer/ws.ts | 196 --------------- 16 files changed, 119 insertions(+), 789 deletions(-) delete mode 100644 packages/core/src/newServer/hmr.ts delete mode 100644 packages/core/src/newServer/http.ts delete mode 100644 packages/core/src/newServer/index.ts delete mode 100644 packages/core/src/newServer/middlewares/cors.ts delete mode 100644 packages/core/src/newServer/middlewares/error.ts delete mode 100644 packages/core/src/newServer/middlewares/index.ts delete mode 100644 packages/core/src/newServer/middlewares/lazy-compilation.ts delete mode 100644 packages/core/src/newServer/middlewares/notFound.ts delete mode 100644 packages/core/src/newServer/middlewares/proxy.ts delete mode 100644 packages/core/src/newServer/middlewares/resource.ts delete mode 100644 packages/core/src/newServer/middlewares/static.ts delete mode 100644 packages/core/src/newServer/publicDir.ts delete mode 100644 packages/core/src/newServer/type.ts delete mode 100644 packages/core/src/newServer/ws.ts diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index 017069e6cf..64c8675af9 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -1,15 +1,31 @@ -import http from 'http'; +import http from 'node:http'; import { SecureServerOptions } from 'node:http2'; import { z } from 'zod'; import { fromZodError } from 'zod-validation-error'; import type { UserConfig } from './types.js'; -const stringRewriteSchema = z.record(z.string(), z.string()); +const TARGET_ENV = { + BROWSER: 'browser', + NODE: 'node', + NODE_LEGACY: 'node-legacy', + NODE_NEXT: 'node-next', + NODE16: 'node16', + BROWSER_LEGACY: 'browser-legacy', + BROWSER_ESNEXT: 'browser-esnext', + BROWSER_ES2015: 'browser-es2015', + BROWSER_ES2017: 'browser-es2017', + LIBRARY: 'library', + LIBRARY_BROWSER: 'library-browser', + LIBRARY_NODE: 'library-node' +} as const; -const functionRewriteSchema = z.union([ - z.function().args(z.string(), z.any()).returns(z.string()), - z.function().args(z.string(), z.any()).returns(z.promise(z.string())) +const baseRewriteSchema = z.union([ + z.record(z.string(), z.string()), + z + .function() + .args(z.string(), z.any()) + .returns(z.union([z.string(), z.promise(z.string())])) ]); const pathFilterSchema = z.union([ @@ -21,40 +37,102 @@ const pathFilterSchema = z.union([ .returns(z.boolean()) ]); -const pathRewriteSchema = z.union([stringRewriteSchema, functionRewriteSchema]); +const pathRewriteSchema = baseRewriteSchema; + +const outputSchema = z + .object({ + entryFilename: z.string().optional(), + filename: z.string().optional(), + path: z.string().optional(), + publicPath: z.string().optional(), + assetsFilename: z.string().optional(), + targetEnv: z + .enum(Object.values(TARGET_ENV) as [string, ...string[]]) + .optional(), + format: z.enum(['cjs', 'esm']).optional() + }) + .strict() + .optional(); + +const serverSchema = z + .object({ + headers: z.record(z.string()).optional(), + port: z.number().positive().int().optional(), + host: z + .union([ + z.string().regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/), + z.literal('localhost'), + z.boolean() + ]) + .optional(), + open: z.boolean().optional(), + https: z.custom(), + cors: z.boolean().optional(), + proxy: z + .record( + z + .object({ + target: z.string(), + changeOrigin: z.boolean().optional(), + agent: z.any().optional(), + secure: z.boolean().optional(), + logs: z.any().optional(), + pathRewrite: pathRewriteSchema.optional(), + pathFilter: pathFilterSchema.optional(), + headers: z.record(z.string()).optional(), + on: z + .object({ + proxyReq: z + .function() + .args(z.any(), z.any(), z.any()) + .returns(z.void()) + .optional(), + proxyRes: z + .function() + .args(z.any(), z.any(), z.any()) + .returns(z.void()) + .optional(), + error: z + .function() + .args(z.instanceof(Error), z.any(), z.any()) + .returns(z.void()) + .optional() + }) + .optional() + }) + .passthrough() + ) + .optional(), + strictPort: z.boolean().optional(), + hmr: z + .union([ + z.boolean(), + z + .object({ + protocol: z.string().optional(), + host: z.union([z.string().min(1), z.boolean()]).optional(), + port: z.number().positive().int().optional(), + path: z.string().optional(), + watchOptions: z + .object({ + awaitWriteFinish: z.number().positive().int().optional() + }) + .optional(), + overlay: z.boolean().optional() + }) + .strict() + ]) + .optional(), + middlewares: z.array(z.any()).optional(), + writeToDisk: z.boolean().optional() + }) + .strict(); const compilationConfigSchema = z .object({ root: z.string().optional(), input: z.record(z.string()).optional(), - output: z - .object({ - entryFilename: z.string().optional(), - filename: z.string().optional(), - path: z.string().optional(), - publicPath: z.string().optional(), - assetsFilename: z.string().optional(), - targetEnv: z - .enum([ - 'browser', - 'node', - 'node-legacy', - 'node-next', - 'node16', - 'browser-legacy', - 'browser-esnext', - 'browser-es2015', - 'browser-es2017', - 'library', - 'library-browser', - 'library-node' - ]) - .optional(), - format: z.enum(['cjs', 'esm']).optional(), - clean: z.boolean().optional() - }) - .strict() - .optional(), + output: outputSchema, resolve: z .object({ extensions: z.array(z.string()).optional(), @@ -306,103 +384,17 @@ const FarmConfigSchema = z vitePlugins: z.array(z.any()).optional(), compilation: compilationConfigSchema.optional(), mode: z.string().optional(), - server: z - .object({ - headers: z.record(z.string()).optional(), - port: z.number().positive().int().optional(), - host: z - .union([ - z.string().regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/), - z.literal('localhost'), - z.boolean() - ]) - .optional(), - open: z.boolean().optional(), - https: z.custom(), - cors: z.boolean().optional(), - proxy: z - .record( - z - .object({ - target: z.string(), - changeOrigin: z.boolean().optional(), - agent: z.any().optional(), - secure: z.boolean().optional(), - logs: z.any().optional(), - pathRewrite: pathRewriteSchema.optional(), - pathFilter: pathFilterSchema.optional(), - headers: z.record(z.string()).optional(), - on: z - .object({ - proxyReq: z - .function() - .args( - z.instanceof(Object), - z.instanceof(Object), - z.instanceof(Object) - ) - .returns(z.void()) - .optional(), - proxyRes: z - .function() - .args( - z.instanceof(Object), - z.instanceof(Object), - z.instanceof(Object) - ) - .returns(z.void()) - .optional(), - error: z - .function() - .args( - z.instanceof(Error), - z.instanceof(Object), - z.instanceof(Object) - ) - .returns(z.void()) - .optional() - }) - .optional() - }) - .passthrough() - ) - .optional(), - strictPort: z.boolean().optional(), - hmr: z - .union([ - z.boolean(), - z - .object({ - protocol: z.string().optional(), - host: z.union([z.string().min(1), z.boolean()]).optional(), - port: z.number().positive().int().optional(), - path: z.string().optional(), - watchOptions: z - .object({ - awaitWriteFinish: z.number().positive().int().optional() - }) - .optional(), - overlay: z.boolean().optional() - }) - .strict() - ]) - .optional(), - middlewares: z.array(z.any()).optional(), - writeToDisk: z.boolean().optional() - }) - .strict() - .optional() + server: serverSchema.optional() }) .strict(); export function parseUserConfig(config: UserConfig): UserConfig { try { const parsed = FarmConfigSchema.parse(config); + // TODO type not need `as UserConfig` return parsed as UserConfig; - // return config as UserConfig; } catch (err) { const validationError = fromZodError(err); - // the error now is readable by the user throw new Error( `${validationError.toString()}. \n Please check your configuration file or command line configuration.` ); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index dc964ecdd1..562f8b644f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -67,8 +67,10 @@ export async function start( try { const resolvedUserConfig = await resolveConfig( inlineConfig, + 'start', 'development', - logger + 'development', + false ); if ( @@ -175,8 +177,8 @@ export async function watch( 'build', 'production', 'production', - logger, - false + false, + logger ); const lazyEnabled = resolvedUserConfig.compilation?.lazyCompilation; diff --git a/packages/core/src/newServer/hmr.ts b/packages/core/src/newServer/hmr.ts deleted file mode 100644 index 1a5eda65b3..0000000000 --- a/packages/core/src/newServer/hmr.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { - HMRBroadcasterClient, - HMRPayload, - InferCustomEventPayload -} from './type.js'; - -export interface HMRChannel { - /** - * Unique channel name - */ - name: string; - /** - * Broadcast events to all clients - */ - send(payload: HMRPayload): void; - /** - * Send custom event - */ - send(event: T, payload?: InferCustomEventPayload): void; - /** - * Handle custom event emitted by `import.meta.hot.send` - */ - on( - event: T, - listener: ( - data: InferCustomEventPayload, - client: HMRBroadcasterClient, - ...args: any[] - ) => void - ): void; - on(event: 'connection', listener: () => void): void; - /** - * Unregister event listener - */ - - // biome-ignore lint/complexity/noBannedTypes: - off(event: string, listener: Function): void; - /** - * Start listening for messages - */ - listen(): void; - /** - * Disconnect all clients, called when server is closed or restarted. - */ - close(): void; -} diff --git a/packages/core/src/newServer/http.ts b/packages/core/src/newServer/http.ts deleted file mode 100644 index a8f55ac02c..0000000000 --- a/packages/core/src/newServer/http.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** - * The following is modified based on source found in - * https://github.com/vitejs/vite/blob/main/packages/vite/src/node/env.ts - * - * MIT License - * Copyright (c) 2019-present, Yuxi (Evan) - * https://github.com/vitejs/vite/blob/main/LICENSE - * - * Farm draws on the code of part of the vite server in order to better achieve the compatibility - * progress of the vite ecosystem and the integrity of vite's ecological development, - * which can reduce many unknown or known problems. - */ - -import type { OutgoingHttpHeaders as HttpServerHeaders } from 'node:http'; -import type { ServerOptions as HttpsServerOptions } from 'node:https'; -import path from 'node:path'; -import connect from 'connect'; -import fse from 'fs-extra'; -import { Logger } from '../utils/logger.js'; -import { HttpServer } from './index.js'; -import { ProxyOptions } from './middlewares/proxy.js'; - -export interface CommonServerOptions { - port?: number; - strictPort?: boolean; - host?: string | boolean; - https?: HttpsServerOptions; - open?: boolean | string; - proxy?: Record; - cors?: CorsOptions | boolean; - headers?: HttpServerHeaders; -} - -export type CorsOrigin = boolean | string | RegExp | (string | RegExp)[]; - -export interface CorsOptions { - origin?: - | CorsOrigin - | ((origin: string, cb: (err: Error, origins: CorsOrigin) => void) => void); - methods?: string | string[]; - allowedHeaders?: string | string[]; - exposedHeaders?: string | string[]; - credentials?: boolean; - maxAge?: number; - preflightContinue?: boolean; - optionsSuccessStatus?: number; -} - -// For the unencrypted tls protocol, we use http service. -// In other cases, https / http2 is used. -export async function resolveHttpServer( - { proxy }: CommonServerOptions, - app: connect.Server, - httpsOptions?: HttpsServerOptions -): Promise { - if (!httpsOptions) { - const { createServer } = await import('node:http'); - return createServer(app); - } - - // EXISTING PROBLEM: - // https://github.com/http-party/node-http-proxy/issues/1237 - - // MAYBE SOLUTION: - // https://github.com/nxtedition/node-http2-proxy - // https://github.com/fastify/fastify-http-proxy - if (proxy) { - const { createServer } = await import('node:https'); - return createServer(httpsOptions, app); - } else { - const { createSecureServer } = await import('node:http2'); - return createSecureServer( - { - maxSessionMemory: 1000, - ...httpsOptions, - allowHTTP1: true - }, - // @ts-ignore - app - ); - } -} - -export async function resolveHttpsConfig( - https: HttpsServerOptions | undefined -): Promise { - if (!https) return undefined; - - const [ca, cert, key, pfx] = await Promise.all([ - readFileIfExists(https.ca), - readFileIfExists(https.cert), - readFileIfExists(https.key), - readFileIfExists(https.pfx) - ]); - return { ...https, ca, cert, key, pfx }; -} - -async function readFileIfExists(value?: string | Buffer | any[]) { - if (typeof value === 'string') { - return fse.readFile(path.resolve(value)).catch(() => value); - } - return value; -} - -export async function httpServerStart( - httpServer: HttpServer, - serverOptions: { - port: number; - strictPort: boolean | undefined; - host: string | undefined; - logger: Logger; - } -): Promise { - let { port, strictPort, host, logger } = serverOptions; - - return new Promise((resolve, reject) => { - const onError = (e: Error & { code?: string }) => { - if (e.code === 'EADDRINUSE') { - if (strictPort) { - httpServer.removeListener('error', onError); - reject(new Error(`Port ${port} is already in use`)); - } else { - logger.info(`Port ${port} is in use, trying another one...`); - httpServer.listen(++port, host); - } - } else { - httpServer.removeListener('error', onError); - reject(e); - } - }; - - httpServer.on('error', onError); - - httpServer.listen(port, host, () => { - httpServer.removeListener('error', onError); - resolve(port); - }); - }); -} diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts deleted file mode 100644 index 82bf2e29e5..0000000000 --- a/packages/core/src/newServer/index.ts +++ /dev/null @@ -1,133 +0,0 @@ -import type * as http from 'node:http'; -import type { Server } from 'node:http'; -import type { OutgoingHttpHeaders as HttpServerHeaders } from 'node:http'; -import { type Http2SecureServer } from 'node:http2'; -import type { ServerOptions as HttpsServerOptions } from 'node:https'; -import path from 'node:path'; -import { WatchOptions } from 'chokidar'; -import connect from 'connect'; -import fse from 'fs-extra'; -import { Compiler } from '../compiler/index.js'; -import { normalizePublicPath } from '../config/normalize-config/normalize-output.js'; -import { NormalizedServerConfig, ResolvedUserConfig } from '../config/types.js'; -import { logger } from '../utils/logger.js'; -import { initPublicFiles } from '../utils/publicDir.js'; -import { FileWatcher } from '../watcher/index.js'; -import { HMRChannel } from './hmr.js'; -import { - CommonServerOptions, - resolveHttpServer, - resolveHttpsConfig -} from './http.js'; -import { WsServer } from './ws.js'; -export type HttpServer = http.Server | Http2SecureServer; - -type CompilerType = Compiler | null; - -export interface HmrOptions { - protocol?: string; - host?: string; - port?: number; - clientPort?: number; - path?: string; - timeout?: number; - overlay?: boolean; - server?: Server; - /** @internal */ - channels?: HMRChannel[]; -} - -export interface ServerOptions extends CommonServerOptions { - /** - * Configure HMR-specific options (port, host, path & protocol) - */ - hmr?: HmrOptions | boolean; - /** - * Do not start the websocket connection. - * @experimental - */ - ws?: false; - /** - * chokidar watch options or null to disable FS watching - * https://github.com/paulmillr/chokidar#api - */ - watchOptions?: WatchOptions | null; - /** - * Create dev server to be used as a middleware in an existing server - * @default false - */ - middlewareMode?: - | boolean - | { - /** - * Parent server instance to attach to - * - * This is needed to proxy WebSocket connections to the parent server. - */ - server: http.Server; - }; - origin?: string; -} -export class newServer { - private compiler: CompilerType; - - ws: WsServer; - config: ResolvedUserConfig; - serverConfig: CommonServerOptions & NormalizedServerConfig; - httpsOptions: HttpsServerOptions; - publicDir?: string; - publicPath?: string; - server?: HttpServer; - watcher: FileWatcher; - - constructor(compiler: CompilerType, config: ResolvedUserConfig) { - this.compiler = compiler; - this.config = config; - - if (!this.compiler) return; - - this.publicPath = - normalizePublicPath( - compiler.config.config.output.targetEnv, - compiler.config.config.output.publicPath, - logger, - false - ) || '/'; - } - - getCompiler(): CompilerType { - return this.compiler; - } - - async createServer() { - const initPublicFilesPromise = initPublicFiles(this.config); - const { root, server: serverConfig } = this.config; - this.httpsOptions = await resolveHttpsConfig(serverConfig.https); - const { middlewareMode } = serverConfig; - const middlewares = connect() as connect.Server; - this.server = middlewareMode - ? null - : await resolveHttpServer( - serverConfig as CommonServerOptions, - middlewares, - this.httpsOptions - ); - - const publicFiles = await initPublicFilesPromise; - const { publicDir } = this.config.compilation.assets; - this.createWebSocketServer(); - } - - public async createWebSocketServer() { - if (!this.server) { - throw new Error('Websocket requires a server.'); - } - const wsServer = new WsServer( - this.server, - this.config, - this.httpsOptions, - this.publicPath, - null - ); - } -} diff --git a/packages/core/src/newServer/middlewares/cors.ts b/packages/core/src/newServer/middlewares/cors.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core/src/newServer/middlewares/error.ts b/packages/core/src/newServer/middlewares/error.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core/src/newServer/middlewares/index.ts b/packages/core/src/newServer/middlewares/index.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core/src/newServer/middlewares/lazy-compilation.ts b/packages/core/src/newServer/middlewares/lazy-compilation.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core/src/newServer/middlewares/notFound.ts b/packages/core/src/newServer/middlewares/notFound.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core/src/newServer/middlewares/proxy.ts b/packages/core/src/newServer/middlewares/proxy.ts deleted file mode 100644 index 9c24cace87..0000000000 --- a/packages/core/src/newServer/middlewares/proxy.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type * as http from 'node:http'; -// import type * as net from 'node:net'; -// import connect from 'connect'; -import httpProxy from 'http-proxy'; - -export interface ProxyOptions extends httpProxy.ServerOptions { - rewrite?: (path: string) => string; - configure?: (proxy: httpProxy, options: ProxyOptions) => void; - bypass?: ( - req: http.IncomingMessage, - res: http.ServerResponse, - options: ProxyOptions - ) => void | null | undefined | false | string; - rewriteWsOrigin?: boolean | undefined; -} diff --git a/packages/core/src/newServer/middlewares/resource.ts b/packages/core/src/newServer/middlewares/resource.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core/src/newServer/middlewares/static.ts b/packages/core/src/newServer/middlewares/static.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core/src/newServer/publicDir.ts b/packages/core/src/newServer/publicDir.ts deleted file mode 100644 index 9b6725ce51..0000000000 --- a/packages/core/src/newServer/publicDir.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ResolvedUserConfig } from '../config/types.js'; -import { recursiveReaddir } from '../utils/index.js'; - -export const ERR_SYMLINK_IN_RECURSIVE_READDIR = - 'ERR_SYMLINK_IN_RECURSIVE_READDIR'; - -const publicFilesMap = new WeakMap>(); - -export async function initPublicFiles( - config: ResolvedUserConfig -): Promise | undefined> { - let fileNames: string[]; - const publicDir: string = config.compilation?.assets?.publicDir as string; - console.log(publicDir); - - try { - fileNames = await recursiveReaddir(publicDir); - } catch (e) { - if (e.code === ERR_SYMLINK_IN_RECURSIVE_READDIR) { - return; - } - throw e; - } - const publicFiles = new Set( - fileNames.map((fileName) => fileName.slice(publicDir.length)) - ); - publicFilesMap.set(config, publicFiles); - return publicFiles; -} - -export function getPublicFiles( - config: ResolvedUserConfig -): Set | undefined { - return publicFilesMap.get(config); -} diff --git a/packages/core/src/newServer/type.ts b/packages/core/src/newServer/type.ts deleted file mode 100644 index 1fe2aaf024..0000000000 --- a/packages/core/src/newServer/type.ts +++ /dev/null @@ -1,100 +0,0 @@ -export type HMRPayload = - | ConnectedPayload - | UpdatePayload - | FullReloadPayload - | CustomPayload - | ErrorPayload - | PrunePayload - -export interface ConnectedPayload { - type: 'connected' -} - -export interface UpdatePayload { - type: 'update' - updates: Update[] -} - -export interface Update { - type: 'js-update' | 'css-update' - path: string - acceptedPath: string - timestamp: number - /** @internal */ - explicitImportRequired?: boolean - /** @internal */ - isWithinCircularImport?: boolean - /** @internal */ - ssrInvalidates?: string[] -} - -export interface PrunePayload { - type: 'prune' - paths: string[] -} - -export interface FullReloadPayload { - type: 'full-reload' - path?: string - /** @internal */ - triggeredBy?: string -} - -export interface CustomPayload { - type: 'custom' - event: string - data?: any -} - -export interface ErrorPayload { - type: 'error' - err: { - [name: string]: any - message: string - stack: string - id?: string - frame?: string - plugin?: string - pluginCode?: string - loc?: { - file?: string - line: number - column: number - } - } -} - -export interface CustomEventMap { - 'vite:beforeUpdate': UpdatePayload - 'vite:afterUpdate': UpdatePayload - 'vite:beforePrune': PrunePayload - 'vite:beforeFullReload': FullReloadPayload - 'vite:error': ErrorPayload - 'vite:invalidate': InvalidatePayload - 'vite:ws:connect': WebSocketConnectionPayload - 'vite:ws:disconnect': WebSocketConnectionPayload -} - -export interface WebSocketConnectionPayload { - webSocket: WebSocket -} - -export interface InvalidatePayload { - path: string - message: string | undefined -} - -export type InferCustomEventPayload = - T extends keyof CustomEventMap ? CustomEventMap[T] : any - - -export interface HMRBroadcasterClient { - /** - * Send event to the client - */ - send(payload: HMRPayload): void - /** - * Send custom event - */ - send(event: string, payload?: CustomPayload['data']): void -} diff --git a/packages/core/src/newServer/ws.ts b/packages/core/src/newServer/ws.ts deleted file mode 100644 index 5460a61313..0000000000 --- a/packages/core/src/newServer/ws.ts +++ /dev/null @@ -1,196 +0,0 @@ -import type { IncomingMessage, Server } from 'node:http'; -import { STATUS_CODES, createServer as createHttpServer } from 'node:http'; -import type { ServerOptions as HttpsServerOptions } from 'node:https'; -import { createServer as createHttpsServer } from 'node:https'; -import type { Socket } from 'node:net'; -import path from 'node:path'; -import type { Duplex } from 'node:stream'; -import type { WebSocket as WebSocketRaw } from 'ws'; -import { WebSocketServer as WebSocketServerRaw_ } from 'ws'; -import { NormalizedServerConfig, ResolvedUserConfig } from '../config/types.js'; -import { HmrEngine } from '../server/hmr-engine.js'; -import { WebSocket as WebSocketTypes } from '../types/ws.js'; -import { ILogger, Logger } from '../utils/logger.js'; -import { isObject } from '../utils/share.js'; -import { HMRChannel } from './hmr.js'; -import { CommonServerOptions } from './http.js'; -import { HttpServer, ServerOptions } from './index.js'; -import { - CustomPayload, - ErrorPayload, - HMRPayload, - InferCustomEventPayload -} from './type.js'; - -export interface WebSocketServer extends HMRChannel { - /** - * Listen on port and host - */ - listen(): void; - /** - * Get all connected clients. - */ - clients: Set; - /** - * Disconnect all clients and terminate the server. - */ - close(): Promise; - /** - * Handle custom event emitted by `import.meta.hot.send` - */ - on: WebSocketTypes.Server['on'] & { - ( - event: T, - listener: WebSocketCustomListener> - ): void; - }; - /** - * Unregister event listener. - */ - off: WebSocketTypes.Server['off'] & { - // biome-ignore lint/complexity/noBannedTypes: - (event: string, listener: Function): void; - }; -} - -export interface WebSocketClient { - /** - * Send event to the client - */ - send(payload: HMRPayload): void; - /** - * Send custom event - */ - send(event: string, payload?: CustomPayload['data']): void; - /** - * The raw WebSocket instance - * @advanced - */ - socket: WebSocketTypes; -} - -const wsServerEvents = [ - 'connection', - 'error', - 'headers', - 'listening', - 'message' -]; - -function noop() { - // noop -} - -const HMR_HEADER = 'farm_hmr'; - -export type WebSocketCustomListener = ( - data: T, - client: WebSocketClient -) => void; - -const WebSocketServerRaw = process.versions.bun - ? // @ts-expect-error: Bun defines `import.meta.require` - import.meta.require('ws').WebSocketServer - : WebSocketServerRaw_; - -export class WsServer { - public wss: WebSocketRaw; - public customListeners = new Map>>(); - public clientsMap = new WeakMap(); - public bufferedError: ErrorPayload | null = null; - public logger: ILogger; - public wsServerOrHmrServer: Server; - - constructor( - private httpServer: HttpServer, - private config: ResolvedUserConfig, - private httpsOptions: HttpsServerOptions, - private publicPath: string, - private hmrEngine: HmrEngine, - logger?: ILogger - ) { - this.logger = logger ?? new Logger(); - this.createWebSocketServer(); - } - - createWebSocketServer() { - const serverConfig = this.config.server as ServerOptions; - if (serverConfig.ws === false) { - return { - name: 'ws', - get clients() { - return new Set(); - }, - async close() { - // noop - }, - on: noop as any as WebSocketServer['on'], - off: noop as any as WebSocketServer['off'], - listen: noop, - send: noop - }; - } - let wss: WebSocketServerRaw_; - let wsHttpServer: Server | undefined = undefined; - - const hmr = isObject(serverConfig.hmr) && serverConfig.hmr; - const hmrServer = hmr && hmr.server; - const hmrPort = hmr && hmr.port; - const portsAreCompatible = !hmrPort || hmrPort === serverConfig.port; - // @ts-ignore - this.wsServerOrHmrServer = - hmrServer || (portsAreCompatible && this.httpServer); - let hmrServerWsListener: ( - req: InstanceType, - socket: Duplex, - head: Buffer - ) => void; - const port = hmrPort || 9000; - const host = (hmr && hmr.host) || undefined; - - if (this.wsServerOrHmrServer) { - let hmrBase = this.publicPath; - const hmrPath = hmr ? hmr.path : undefined; - if (hmrPath) { - hmrBase = path.posix.join(hmrBase, hmrPath as string); - } - wss = new WebSocketServerRaw({ noServer: true }); - hmrServerWsListener = (req, socket, head) => { - if ( - req.headers['sec-websocket-protocol'] === HMR_HEADER && - req.url === hmrBase - ) { - wss.handleUpgrade(req, socket as Socket, head, (ws) => { - wss.emit('connection', ws, req); - }); - } - }; - this.wsServerOrHmrServer.on('upgrade', hmrServerWsListener); - } else { - // http server request handler keeps the same with - // https://github.com/websockets/ws/blob/45e17acea791d865df6b255a55182e9c42e5877a/lib/websocket-server.js#L88-L96 - const route = ((_, res) => { - const statusCode = 426; - const body = STATUS_CODES[statusCode]; - if (!body) - throw new Error( - `No body text found for the ${statusCode} status code` - ); - - res.writeHead(statusCode, { - 'Content-Length': body.length, - 'Content-Type': 'text/plain' - }); - res.end(body); - }) as Parameters[1]; - if (this.httpsOptions) { - wsHttpServer = createHttpsServer(this.httpsOptions, route); - } else { - wsHttpServer = createHttpServer(route); - } - // vite dev server in middleware mode - // need to call ws listen manually - wss = new WebSocketServerRaw({ server: wsHttpServer }); - } - } -} From bc380b8d20f708a878e3e3af7593bcff077ee67d Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Thu, 1 Aug 2024 14:00:36 +0800 Subject: [PATCH 028/369] chore: update types --- packages/core/src/config/index.ts | 3 +-- packages/core/src/config/types.ts | 14 ++++++++++++-- packages/core/src/plugin/js/farm-to-vite-config.ts | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index 49934d101e..d7aacb28f5 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -497,8 +497,7 @@ export const DEFAULT_HMR_OPTIONS: Required = { watchOptions: {}, clientPort: 9000, timeout: 0, - server: null, - channels: [] + server: null }; export const DEFAULT_DEV_SERVER_OPTIONS: NormalizedServerConfig = { diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index 5ed93901b8..541668e643 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -10,7 +10,17 @@ import type { RustPlugin } from '../plugin/rust/index.js'; import type { JsPlugin } from '../plugin/type.js'; import type { Config } from '../types/binding.js'; import type { Logger } from '../utils/index.js'; -import { HmrOptions } from '../newServer/index.js'; + +export interface HmrOptions { + protocol?: string; + host?: string; + port?: number; + clientPort?: number; + path?: string; + timeout?: number; + overlay?: boolean; + server?: Server; +} export interface ConfigEnv { mode: string; @@ -157,7 +167,7 @@ export interface FarmCLIPreviewOptions { export interface FarmCliOptions extends FarmCLIBuildOptions, - FarmCLIPreviewOptions { + FarmCLIPreviewOptions { logger?: Logger; config?: string; configFile?: string; diff --git a/packages/core/src/plugin/js/farm-to-vite-config.ts b/packages/core/src/plugin/js/farm-to-vite-config.ts index ed624086c1..b1d44ca1c8 100644 --- a/packages/core/src/plugin/js/farm-to-vite-config.ts +++ b/packages/core/src/plugin/js/farm-to-vite-config.ts @@ -375,6 +375,7 @@ export function viteConfigToFarmConfig( if (config.server) { farmConfig.server ??= {}; + // @ts-ignore farmConfig.server.hmr = config.server.hmr; farmConfig.server.port = config.server.port; From 758f12f0a656c202d02abfd9dc6230917e1d6be7 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Thu, 1 Aug 2024 14:01:43 +0800 Subject: [PATCH 029/369] chore: add handlePing middleware --- packages/core/src/newServer/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index 927782d50a..cfac30d0b0 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -148,6 +148,14 @@ export class newServer { // middleware // middlewares.use(compression()); + middlewares.use(function handleHMRPingMiddleware(req, res, next) { + if (req.headers['accept'] === 'text/x-farm-ping') { + res.writeHead(204).end(); + } else { + next(); + } + }); + if (this.publicDir) { middlewares.use(publicMiddleware(this.logger, this.config, publicFiles)); } From e2de6f9bc525b0d5ff3b3571da7d5d121ead66bc Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Fri, 2 Aug 2024 12:03:33 +0800 Subject: [PATCH 030/369] fix: lazy compilation in mode error --- packages/core/src/config/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index d7aacb28f5..1b0b5915b5 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -171,7 +171,7 @@ export async function resolveConfig( resolvedUserConfig.compilation = await normalizeUserCompilationConfig( resolvedUserConfig, - 'development' + mode as CompilationMode ); resolvedUserConfig.root = resolvedUserConfig.compilation.root; @@ -193,7 +193,6 @@ export async function resolveConfig( resolvedUserConfig.compilation.resolve.alias as unknown as Array ); } - return resolvedUserConfig; } From a42bc420b3bbf1592d45e980ed4bef492bd1f170 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Tue, 6 Aug 2024 09:58:44 +0800 Subject: [PATCH 031/369] fix: initial methods params modify --- packages/core/src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 355821ba20..1c7096f0f1 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -68,8 +68,10 @@ export async function start( try { const resolvedUserConfig = await resolveConfig( inlineConfig, + 'start', 'development', - logger + 'development', + true ); if ( @@ -176,7 +178,6 @@ export async function watch( 'build', 'production', 'production', - logger, false ); From aa6e126e72a7d35d7d5ada49cff2804b6ed14680 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Tue, 6 Aug 2024 10:44:54 +0800 Subject: [PATCH 032/369] chore: update ws error --- examples/arco-pro/farm.config.ts | 1 + packages/core/src/newServer/hmr-engine.ts | 5 ++-- packages/core/src/newServer/ws.ts | 33 ++++++++++++----------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/examples/arco-pro/farm.config.ts b/examples/arco-pro/farm.config.ts index 6f4b761603..7f82ffe932 100644 --- a/examples/arco-pro/farm.config.ts +++ b/examples/arco-pro/farm.config.ts @@ -12,6 +12,7 @@ export default defineConfig((env) => { // minify: false, presetEnv: false, // persistentCache: false, + lazyCompilation: false, resolve: { symlinks: true, alias: { diff --git a/packages/core/src/newServer/hmr-engine.ts b/packages/core/src/newServer/hmr-engine.ts index 173d877304..70fdf1e39f 100644 --- a/packages/core/src/newServer/hmr-engine.ts +++ b/packages/core/src/newServer/hmr-engine.ts @@ -40,7 +40,8 @@ export class HmrEngine { this._devServer = devServer; // this._lastAttemptWasError = false; this._lastModifiedTimestamp = new Map(); - this.ws = ws; + // @ts-ignore + this.ws = ws.wss; } callUpdates(result: JsUpdateResult) { @@ -140,7 +141,7 @@ export class HmrEngine { this.ws.clients.forEach((client: WebSocketClient) => { // @ts-ignore - client.rawSend(` + client.send(` { type: 'farm-update', result: ${resultStr} diff --git a/packages/core/src/newServer/ws.ts b/packages/core/src/newServer/ws.ts index ff4114b1af..c8fe05c76d 100644 --- a/packages/core/src/newServer/ws.ts +++ b/packages/core/src/newServer/ws.ts @@ -94,7 +94,7 @@ const WebSocketServerRaw = process.versions.bun : WebSocketServerRaw_; export class WsServer { - public wss: WebSocketRaw; + public wss: WebSocketServerRaw_; public customListeners = new Map>>(); public clientsMap = new WeakMap(); public bufferedError: ErrorPayload | null = null; @@ -114,7 +114,7 @@ export class WsServer { } createWebSocketServer() { - const serverConfig = this.config.server as ServerOptions; + const serverConfig = this.config.server as unknown as ServerOptions; if (serverConfig.ws === false) { return { name: 'ws', @@ -130,7 +130,6 @@ export class WsServer { send: noop }; } - let wss: WebSocketServerRaw_; let wsHttpServer: Server | undefined = undefined; const hmr = isObject(serverConfig.hmr) && serverConfig.hmr; @@ -153,14 +152,14 @@ export class WsServer { if (hmrPath) { hmrBase = path.posix.join(hmrBase, hmrPath as string); } - wss = new WebSocketServerRaw({ noServer: true }); + this.wss = new WebSocketServerRaw({ noServer: true }); hmrServerWsListener = (req, socket, head) => { if ( req.headers['sec-websocket-protocol'] === HMR_HEADER && req.url === hmrBase ) { - wss.handleUpgrade(req, socket as Socket, head, (ws) => { - wss.emit('connection', ws, req); + this.wss.handleUpgrade(req, socket as Socket, head, (ws) => { + this.wss.emit('connection', ws, req); }); } }; @@ -189,10 +188,10 @@ export class WsServer { } // vite dev server in middleware mode // need to call ws listen manually - wss = new WebSocketServerRaw({ server: wsHttpServer }); + this.wss = new WebSocketServerRaw({ server: wsHttpServer }); } - wss.on('connection', (socket) => { + this.wss.on('connection', (socket) => { socket.on('message', (raw) => { if (!this.customListeners.size) return; let parsed: any; @@ -220,7 +219,7 @@ export class WsServer { } }); - wss.on('error', (e: Error & { code: string }) => { + this.wss.on('error', (e: Error & { code: string }) => { if (e.code === 'EADDRINUSE') { console.log('WebSocket server error: Port is already in use'); @@ -269,7 +268,7 @@ export class WsServer { wsHttpServer?.listen(port, host); }, on: ((event: string, fn: () => void) => { - if (wsServerEvents.includes(event)) wss.on(event, fn); + if (wsServerEvents.includes(event)) this.wss.on(event, fn); else { if (!this.customListeners.has(event)) { this.customListeners.set(event, new Set()); @@ -279,7 +278,7 @@ export class WsServer { }) as WebSocketServer['on'], off: ((event: string, fn: () => void) => { if (wsServerEvents.includes(event)) { - wss.off(event, fn); + this.wss.off(event, fn); } else { this.customListeners.get(event)?.delete(fn); } @@ -288,7 +287,9 @@ export class WsServer { get clients() { // return new Set(Array.from(wss.clients).map(getSocketClient)); return new Set( - Array.from(wss.clients).map((socket) => self.getSocketClient(socket)) + Array.from(this.wss.clients).map((socket: any) => + self.getSocketClient(socket) + ) ); }, @@ -304,13 +305,13 @@ export class WsServer { payload = args[0]; } - if (payload.type === 'error' && !wss.clients.size) { + if (payload.type === 'error' && !this.wss.clients.size) { this.bufferedError = payload; return; } const stringified = JSON.stringify(payload); - wss.clients.forEach((client) => { + this.wss.clients.forEach((client: any) => { // readyState 1 means the connection is open if (client.readyState === 1) { client.send(stringified); @@ -325,10 +326,10 @@ export class WsServer { this.wsServer.off('upgrade', hmrServerWsListener); } return new Promise((resolve, reject) => { - wss.clients.forEach((client) => { + this.wss.clients.forEach((client: any) => { client.terminate(); }); - wss.close((err) => { + this.wss.close((err: any) => { if (err) { reject(err); } else { From 6973fc9ecce909749cae2d83bbd9cfd36847b60f Mon Sep 17 00:00:00 2001 From: "zengwenjie.paq" Date: Tue, 6 Aug 2024 20:36:47 +0800 Subject: [PATCH 033/369] fix: optimize the judgment conditions for warning --- packages/core/src/newServer/middlewares/public.ts | 14 +++++++------- packages/core/src/utils/path.ts | 9 +++------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/core/src/newServer/middlewares/public.ts b/packages/core/src/newServer/middlewares/public.ts index 96c2fb2950..fd40eeb269 100644 --- a/packages/core/src/newServer/middlewares/public.ts +++ b/packages/core/src/newServer/middlewares/public.ts @@ -85,22 +85,22 @@ export function publicMiddleware( res: any, next: () => void ) { - const url = removeHashFromPath(req.url); - const filePath = toFilePath(url); - + const url = req.url; + const cleanedUrl = removeHashFromPath(url); + const cleanFilePath = toFilePath(cleanedUrl); // If it is not equal, it means that it is recognized as a module if ( publicDir.startsWith(withTrailingSlash(root)) && - publicFiles.has(url) && - req.url !== url + publicFiles.has(cleanFilePath) && + cleanedUrl !== url ) { - const publicDirWarning = warnAboutPublicDir(url, publicPath); + const publicDirWarning = warnAboutPublicDir(cleanFilePath, publicPath); if (publicDirWarning) { logger.warn(publicDirWarning); } } - if (publicFiles && !publicFiles.has(filePath)) { + if (publicFiles && !publicFiles.has(toFilePath(url))) { return next(); } diff --git a/packages/core/src/utils/path.ts b/packages/core/src/utils/path.ts index f6ac7c7df1..1ae4fc072e 100644 --- a/packages/core/src/utils/path.ts +++ b/packages/core/src/utils/path.ts @@ -15,10 +15,7 @@ export function stripQueryAndHash(path: string): string { return path.replace(postfixRE, ''); } -export function removeHashFromPath(url: string): string { - const hashPattern = /(_[a-zA-Z\d]{4,8})\./; - - const newURL = url.replace(hashPattern, '.'); - - return newURL; +export function removeHashFromPath(path: string): string { + const hashRE = /([_-][a-f0-9]{4,12})(\.[^./]+(\.[^./]+)*)$/; + return path.replace(hashRE, '$2'); } From f9c1a6317dde31f4d9d1fe87028b378d0ed89ff6 Mon Sep 17 00:00:00 2001 From: ADNY <66500121+ErKeLost@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:35:01 +0800 Subject: [PATCH 034/369] chore: perf server code (#1697) * chore: perf server code * chore: perf server code * fix: hmr error --- cspell.json | 1 + examples/refactor-react/farm.config.ts | 12 +- packages/core/src/newServer/hmr-engine.ts | 75 +++++----- packages/core/src/newServer/index.ts | 139 +++++++++--------- .../core/src/newServer/middlewares/hmrPing.ts | 13 ++ .../src/newServer/middlewares/htmlFallback.ts | 9 +- .../core/src/newServer/middlewares/index.ts | 9 ++ .../core/src/newServer/middlewares/public.ts | 9 +- .../src/newServer/middlewares/resource.ts | 9 +- packages/core/src/newServer/preview.ts | 1 + packages/core/src/newServer/ws.ts | 37 +++-- packages/runtime-plugin-hmr/src/hmr-client.ts | 7 +- 12 files changed, 168 insertions(+), 153 deletions(-) create mode 100644 packages/core/src/newServer/middlewares/hmrPing.ts create mode 100644 packages/core/src/newServer/preview.ts diff --git a/cspell.json b/cspell.json index bab42cf9a4..8f7028c42e 100644 --- a/cspell.json +++ b/cspell.json @@ -73,6 +73,7 @@ "hashbrown", "hasher", "Hensel", + "HMRPing", "icns", "idents", "IHDR", diff --git a/examples/refactor-react/farm.config.ts b/examples/refactor-react/farm.config.ts index d2298b49bb..24656d9be3 100644 --- a/examples/refactor-react/farm.config.ts +++ b/examples/refactor-react/farm.config.ts @@ -3,15 +3,17 @@ import { defineConfig } from '@farmfe/core'; export default defineConfig({ plugins: ['@farmfe/plugin-react'], compilation: { - // presetEnv: false, - // progress: false, - // sourcemap: false, - // persistentCache: false, + presetEnv: false, + minify: false, + progress: false, + sourcemap: false, + persistentCache: false, runtime: { isolate: true } }, server: { - writeToDisk: true, + port: 3212, + // writeToDisk: true, } }); diff --git a/packages/core/src/newServer/hmr-engine.ts b/packages/core/src/newServer/hmr-engine.ts index 70fdf1e39f..9c6a476400 100644 --- a/packages/core/src/newServer/hmr-engine.ts +++ b/packages/core/src/newServer/hmr-engine.ts @@ -19,25 +19,27 @@ import { WebSocketClient, WebSocketServer } from './ws.js'; export class HmrEngine { private _updateQueue: string[] = []; - // private _updateResults: Map = + private _updateResults: Map; - private _compiler: Compiler; - private _devServer: HttpServer; private _onUpdates: ((result: JsUpdateResult) => void)[]; private _lastModifiedTimestamp: Map; - - public config: UserConfig; - public ws: WebSocketServer; constructor( - compiler: Compiler, - devServer: HttpServer, - config: UserConfig, - ws: WebSocketServer, - private _logger: Logger + // compiler: Compiler, + // devServer: HttpServer, + // config: UserConfig, + // ws: WebSocketServer, + // private _logger: Logger + private readonly app: any ) { - this._compiler = compiler; - this._devServer = devServer; + const { + compiler, + httpServer, + resolvedUserConfig: config, + ws, + logger + } = this.app; + // this._lastAttemptWasError = false; this._lastModifiedTimestamp = new Map(); // @ts-ignore @@ -65,13 +67,13 @@ export class HmrEngine { let updatedFilesStr = queue .map((item) => { if (isAbsolute(item)) { - return relative(this._compiler.config.config.root, item); + return relative(this.app.compiler.config.config.root, item); } else { - const resolvedPath = this._compiler.transformModulePath( - this._compiler.config.config.root, + const resolvedPath = this.app.compiler.transformModulePath( + this.app.compiler.config.config.root, item ); - return relative(this._compiler.config.config.root, resolvedPath); + return relative(this.app.compiler.config.config.root, resolvedPath); } }) .join(', '); @@ -82,20 +84,20 @@ export class HmrEngine { try { // we must add callback before update - this._compiler.onUpdateFinish(async () => { + this.app.compiler.onUpdateFinish(async () => { // if there are more updates, recompile again if (this._updateQueue.length > 0) { await this.recompileAndSendResult(); } - if (this.config?.server.writeToDisk) { - this._compiler.writeResourcesToDisk(); + if (this.app.resolvedUserConfig?.server.writeToDisk) { + this.app.compiler.writeResourcesToDisk(); } }); - checkClearScreen(this._compiler.config.config); + checkClearScreen(this.app.compiler.config.config); const start = Date.now(); - const result = await this._compiler.update(queue); - this._logger.info( + const result = await this.app.compiler.update(queue); + this.app.logger.info( `${bold(cyan(updatedFilesStr))} updated in ${bold( green(`${Date.now() - start}ms`) )}` @@ -113,6 +115,8 @@ export class HmrEngine { if (!dynamicResourcesMap) { dynamicResourcesMap = {} as Record; } + + // @ts-ignore dynamicResourcesMap[key] = value.map((r) => ({ path: r[0], type: r[1] as 'script' | 'link' @@ -138,8 +142,7 @@ export class HmrEngine { }`; this.callUpdates(result); - - this.ws.clients.forEach((client: WebSocketClient) => { + this.app.ws.wss.clients.forEach((client: WebSocketClient) => { // @ts-ignore client.send(` { @@ -149,9 +152,8 @@ export class HmrEngine { `); }); } catch (err) { - checkClearScreen(this._compiler.config.config); - console.log(err); - // throw new Error(logError(err) as unknown as string); + // checkClearScreen(this.app.compiler.config.config); + throw new Error(err); } }; @@ -159,7 +161,10 @@ export class HmrEngine { const paths = Array.isArray(absPath) ? absPath : [absPath]; for (const path of paths) { - if (this._compiler.hasModule(path) && !this._updateQueue.includes(path)) { + if ( + this.app.compiler.hasModule(path) && + !this._updateQueue.includes(path) + ) { if (fse.existsSync(path)) { const lastModifiedTimestamp = this._lastModifiedTimestamp.get(path); const currentTimestamp = (await stat(path)).mtime.toISOString(); @@ -174,7 +179,7 @@ export class HmrEngine { } } - if (!this._compiler.compiling && this._updateQueue.length > 0) { + if (!this.app.compiler.compiling && this._updateQueue.length > 0) { try { await this.recompileAndSendResult(); } catch (e) { @@ -183,17 +188,19 @@ export class HmrEngine { const errorStr = `${JSON.stringify({ message: serialization })}`; - this.ws.clients.forEach((client: WebSocketClient) => { + this.app.ws.wss.clients.forEach((client: WebSocketClient) => { // @ts-ignore - client.rawSend(` + // client.rawSend(` + client.send(` { type: 'error', err: ${errorStr}, - overlay: ${(this.config.server.hmr as UserHmrConfig).overlay} + overlay: ${(this.app.resolvedUserConfig.server.hmr as UserHmrConfig).overlay} } `); }); - this._logger.error(e); + // this.app.logger.error(e); + throw new Error(`hmr update failed: ${e.stack}`); } } } diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index cfac30d0b0..d593f3e0a4 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -1,8 +1,11 @@ import type * as http from 'node:http'; -import type { Server } from 'node:http'; -import type { OutgoingHttpHeaders as HttpServerHeaders } from 'node:http'; -import { type Http2SecureServer } from 'node:http2'; -import type { ServerOptions as HttpsServerOptions } from 'node:https'; +import type { + ServerOptions as HttpsServerOptions, + IncomingMessage, + OutgoingHttpHeaders, + Server +} from 'node:http'; +import type { Http2SecureServer } from 'node:http2'; import path from 'node:path'; import { WatchOptions } from 'chokidar'; import compression from 'compression'; @@ -24,11 +27,13 @@ import { resolveHttpServer, resolveHttpsConfig } from './http.js'; +import { HMRPingMiddleware } from './middlewares/hmrPing.js'; import { htmlFallbackMiddleware } from './middlewares/htmlFallback.js'; import { publicMiddleware } from './middlewares/public.js'; import { resourceMiddleware } from './middlewares/resource.js'; import { WebSocketClient, WebSocketServer, WsServer } from './ws.js'; -export type HttpServer = http.Server | Http2SecureServer; + +export type HttpServer = Server | Http2SecureServer; type CompilerType = Compiler | null; @@ -90,23 +95,23 @@ export class newServer { // base path of server publicPath?: string; // publicFile - publicFiles?: string[]; + publicFiles?: Set; httpServer?: HttpServer; watcher: FileWatcher; hmrEngine?: HmrEngine; + middlewares: connect.Server; constructor( private readonly compiler: CompilerType, - private readonly config: ResolvedUserConfig, + private readonly resolvedUserConfig: ResolvedUserConfig, private readonly logger: Logger ) { if (!this.compiler) { - this.logger.error( - 'Compiler is not provided, server will not work, please provide a compiler e.q. `new Compiler(config)`' + throw new Error( + 'Compiler is not provided. Server initialization failed. Please provide a compiler instance, e.g., `new Compiler(config)`.' ); - return; } - this.resolveOptions(config); + this.resolveOptions(resolvedUserConfig); } public getCompiler(): CompilerType { @@ -124,64 +129,54 @@ export class newServer { } public async createServer() { - this.httpsOptions = await resolveHttpsConfig(this.serverOptions.https); - const publicFiles = await this.handlePublicFiles(); - const { middlewareMode } = this.serverOptions; - const middlewares = connect() as connect.Server; - this.httpServer = middlewareMode - ? null - : await resolveHttpServer( - this.serverOptions as CommonServerOptions, - middlewares, - this.httpsOptions - ); - - this.createWebSocketServer(); - this.hmrEngine = new HmrEngine( - this.compiler, - this.httpServer, - this.config, - this.ws, - this.logger - ); - - // middleware - // middlewares.use(compression()); - - middlewares.use(function handleHMRPingMiddleware(req, res, next) { - if (req.headers['accept'] === 'text/x-farm-ping') { - res.writeHead(204).end(); - } else { - next(); - } - }); + try { + const { https, middlewareMode } = this.serverOptions; + + this.httpsOptions = await resolveHttpsConfig(https); + this.publicFiles = await this.handlePublicFiles(); + + this.middlewares = connect() as connect.Server; + this.httpServer = middlewareMode + ? null + : await resolveHttpServer( + this.serverOptions as CommonServerOptions, + this.middlewares, + this.httpsOptions + ); + + // init websocket server + this.createWebSocketServer(); + + // init hmr engine + this.createHmrEngine(); + + // init middlewares + this.initializeMiddlewares(); + } catch (error) { + throw new Error(`handle create server error: ${error}`); + } + } + + private initializeMiddlewares() { + this.middlewares.use(HMRPingMiddleware()); if (this.publicDir) { - middlewares.use(publicMiddleware(this.logger, this.config, publicFiles)); + this.middlewares.use(publicMiddleware(this)); } // TODO todo add appType - middlewares.use( - htmlFallbackMiddleware( - this.httpServer, - this.compiler, - this.publicPath, - this.config - ) - ); - - middlewares.use( - resourceMiddleware( - this.httpServer, - this.compiler, - this.publicPath, - this.config - ) - ); + this.middlewares.use(htmlFallbackMiddleware(this)); + + this.middlewares.use(resourceMiddleware(this)); } - private async handlePublicFiles() { - const initPublicFilesPromise = initPublicFiles(this.config); - return await initPublicFilesPromise; + public createHmrEngine() { + if (!this.httpServer) { + throw new Error( + 'HmrEngine requires a http server. please check the server is be created' + ); + } + + this.hmrEngine = new HmrEngine(this); } public async createWebSocketServer() { @@ -191,13 +186,7 @@ export class newServer { ); } - this.ws = new WsServer( - this.httpServer, - this.config, - this.httpsOptions, - this.publicPath, - null - ); + this.ws = new WsServer(this); } public async listen(): Promise { @@ -205,7 +194,8 @@ export class newServer { this.logger.warn('HTTP server is not created yet'); return; } - const { port, open, protocol, hostname } = this.config.server; + // TODO open browser when server is ready && open config is true + const { port, open, protocol, hostname } = this.resolvedUserConfig.server; const start = Date.now(); await this.compile(); @@ -223,10 +213,15 @@ export class newServer { throw new Error(logError(err) as unknown as string); } - if (this.config.server.writeToDisk) { + if (this.resolvedUserConfig.server.writeToDisk) { this.compiler.writeResourcesToDisk(); } else { this.compiler.callWriteResourcesHook(); } } + + private async handlePublicFiles() { + const initPublicFilesPromise = initPublicFiles(this.resolvedUserConfig); + return await initPublicFilesPromise; + } } diff --git a/packages/core/src/newServer/middlewares/hmrPing.ts b/packages/core/src/newServer/middlewares/hmrPing.ts new file mode 100644 index 0000000000..0c9bc452bd --- /dev/null +++ b/packages/core/src/newServer/middlewares/hmrPing.ts @@ -0,0 +1,13 @@ +export function HMRPingMiddleware() { + return function handleHMRPingMiddleware( + req: any, + res: any, + next: () => void + ) { + if (req.headers['accept'] === 'text/x-farm-ping') { + res.writeHead(204).end(); + } else { + next(); + } + }; +} diff --git a/packages/core/src/newServer/middlewares/htmlFallback.ts b/packages/core/src/newServer/middlewares/htmlFallback.ts index 91e0d0064b..5a21e2b33d 100644 --- a/packages/core/src/newServer/middlewares/htmlFallback.ts +++ b/packages/core/src/newServer/middlewares/htmlFallback.ts @@ -5,12 +5,7 @@ import { commonFsUtils } from '../../utils/fsUtils.js'; import { cleanUrl } from '../../utils/url.js'; import { HttpServer } from '../index.js'; -export function htmlFallbackMiddleware( - server: HttpServer, - compiler: Compiler, - publicPath: string, - config: ResolvedUserConfig -) { +export function htmlFallbackMiddleware(app: any) { return async function htmlFallbackMiddleware( req: any, res: any, @@ -33,7 +28,7 @@ export function htmlFallbackMiddleware( } const url = cleanUrl(req.url); const pathname = decodeURIComponent(url); - + const { resolvedUserConfig: config } = app; if (pathname.endsWith('.html')) { const filePath = path.join(config.root, pathname); if (commonFsUtils.existsSync(filePath)) { diff --git a/packages/core/src/newServer/middlewares/index.ts b/packages/core/src/newServer/middlewares/index.ts index e69de29bb2..70b337a74f 100644 --- a/packages/core/src/newServer/middlewares/index.ts +++ b/packages/core/src/newServer/middlewares/index.ts @@ -0,0 +1,9 @@ +export * from './cors.js'; +export * from './error.js'; +export * from './htmlFallback.js'; +export * from './lazyCompilation.js'; +export * from './notFound.js'; +export * from './proxy.js'; +export * from './static.js'; +export * from './resource.js'; +export * from './public.js'; diff --git a/packages/core/src/newServer/middlewares/public.ts b/packages/core/src/newServer/middlewares/public.ts index fd40eeb269..60f0d2f8b9 100644 --- a/packages/core/src/newServer/middlewares/public.ts +++ b/packages/core/src/newServer/middlewares/public.ts @@ -45,12 +45,9 @@ function warnAboutPublicDir(url: string, publicPath: string) { return warning; } -export function publicMiddleware( - logger: Logger, - config: ResolvedUserConfig, - publicFiles?: Set -) { - const { publicDir, root } = config; +export function publicMiddleware(app: any) { + const { resolvedUserConfig: config, publicDir, publicFiles, logger } = app; + const { root } = config; const publicPath = `${publicDir.slice(root.length)}`; const headers = config.server.headers; const serve = sirv(publicDir, { diff --git a/packages/core/src/newServer/middlewares/resource.ts b/packages/core/src/newServer/middlewares/resource.ts index 03dfb8c68a..bd53a67d5f 100644 --- a/packages/core/src/newServer/middlewares/resource.ts +++ b/packages/core/src/newServer/middlewares/resource.ts @@ -17,12 +17,7 @@ interface RealResourcePath { resource: Buffer; } -export function resourceMiddleware( - server: HttpServer, - compiler: Compiler, - publicPath: string, - config: ResolvedUserConfig -) { +export function resourceMiddleware(app: any) { return async function generateResourceMiddleware( req: any, res: any, @@ -33,7 +28,7 @@ export function resourceMiddleware( } const url = req.url && cleanUrl(req.url); - + const { compiler, resolvedUserConfig: config, publicPath } = app; // TODO resolve html but not input file html // htmlFallbackMiddleware appends '.html' to URLs // if (url?.endsWith('.html') && req.headers['sec-fetch-dest'] !== 'script') { diff --git a/packages/core/src/newServer/preview.ts b/packages/core/src/newServer/preview.ts new file mode 100644 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/packages/core/src/newServer/preview.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/core/src/newServer/ws.ts b/packages/core/src/newServer/ws.ts index c8fe05c76d..9cf0d220ea 100644 --- a/packages/core/src/newServer/ws.ts +++ b/packages/core/src/newServer/ws.ts @@ -7,7 +7,7 @@ import path from 'node:path'; import type { Duplex } from 'node:stream'; import type { WebSocket as WebSocketRaw } from 'ws'; import { WebSocketServer as WebSocketServerRaw_ } from 'ws'; -import { NormalizedServerConfig, ResolvedUserConfig } from '../config/types.js'; +import { NormalizedServerConfig } from '../config/types.js'; import { HmrEngine } from '../server/hmr-engine.js'; import { WebSocket as WebSocketTypes } from '../types/ws.js'; import { ILogger, Logger } from '../utils/logger.js'; @@ -102,19 +102,21 @@ export class WsServer { public wsServer: Server; constructor( - private httpServer: HttpServer, - private config: ResolvedUserConfig, - private httpsOptions: HttpsServerOptions, - private publicPath: string, - private hmrEngine: HmrEngine, - logger?: ILogger + // private httpServer: HttpServer, + // private config: ResolvedUserConfig, + // private httpsOptions: HttpsServerOptions, + // private publicPath: string, + // private hmrEngine: HmrEngine, + // logger?: ILogger + private readonly app: any ) { - this.logger = logger ?? new Logger(); + this.logger = app.logger ?? new Logger(); this.createWebSocketServer(); } createWebSocketServer() { - const serverConfig = this.config.server as unknown as ServerOptions; + const { resolvedUserConfig: config } = this.app; + const serverConfig = config.server as unknown as ServerOptions; if (serverConfig.ws === false) { return { name: 'ws', @@ -137,7 +139,7 @@ export class WsServer { const hmrPort = hmr && hmr.port; const portsAreCompatible = !hmrPort || hmrPort === serverConfig.port; // @ts-ignore - this.wsServer = hmrServer || (portsAreCompatible && this.httpServer); + this.wsServer = hmrServer || (portsAreCompatible && this.app.httpServer); let hmrServerWsListener: ( req: InstanceType, socket: Duplex, @@ -147,7 +149,8 @@ export class WsServer { const host = (hmr && hmr.host) || undefined; if (this.wsServer) { - let hmrBase = this.publicPath; + let hmrBase = this.app.publicPath; + const hmrPath = hmr ? hmr.path : undefined; if (hmrPath) { hmrBase = path.posix.join(hmrBase, hmrPath as string); @@ -181,8 +184,9 @@ export class WsServer { }); res.end(body); }) as Parameters[1]; - if (this.httpsOptions) { - wsHttpServer = createHttpsServer(this.httpsOptions, route); + + if (this.app.httpsOptions) { + wsHttpServer = createHttpsServer(this.app.httpsOptions, route); } else { wsHttpServer = createHttpServer(route); } @@ -205,12 +209,7 @@ export class WsServer { listeners.forEach((listener) => listener(parsed.data, client)); }); socket.on('error', (err) => { - console.log('ws error:', err); - - // config.logger.error(`${colors.red(`ws error:`)}\n${err.stack}`, { - // timestamp: true, - // error: err - // }); + throw new Error(`ws error:\n${err.stack}`); }); socket.send(JSON.stringify({ type: 'connected' })); if (this.bufferedError) { diff --git a/packages/runtime-plugin-hmr/src/hmr-client.ts b/packages/runtime-plugin-hmr/src/hmr-client.ts index 1507660be7..e156c4101f 100644 --- a/packages/runtime-plugin-hmr/src/hmr-client.ts +++ b/packages/runtime-plugin-hmr/src/hmr-client.ts @@ -35,7 +35,6 @@ export class HmrClient { `${socketProtocol}://${socketHostUrl}`, 'farm_hmr' ); - console.log(`${socketProtocol}://${socketHostUrl}`); this.socket = socket; // listen for the message from the server @@ -67,9 +66,11 @@ export class HmrClient { this.notifyListeners('vite:ws:disconnect', { webSocket: socket }); this.notifyListeners('farm:ws:disconnect', { webSocket: socket }); - logger.debug('disconnected from the server, please reload the page.'); + logger.debug( + 'disconnected from the server, Please refresh the page manually. If you still encounter errors, this may be a farm bug. Please submit an issue. https://github.com/farm-fe/farm/issues' + ); await waitForSuccessfulPing(socketProtocol, `${socketHostUrl}`); - location.reload(); + // location.reload(); }); return socket; From ba15ef92d6b10c9de6a152e6d559273d2759527f Mon Sep 17 00:00:00 2001 From: ADNY <66500121+ErKeLost@users.noreply.github.com> Date: Thu, 8 Aug 2024 14:41:27 +0800 Subject: [PATCH 035/369] fix: vite hmr reload page (#1701) * fix: vite hmr error * chore: update code * chore: update code * chore: update code * chore: update code * chore: update ws send instance --- examples/refactor-react/farm.config.ts | 2 +- .../vite-adapter-react/src/pages/index.tsx | 1 + examples/vite-adapter-svelte/src/App.svelte | 10 +- examples/vite-adapter-vue/components.d.ts | 3 - examples/vite-adapter-vue/farm.config.ts | 5 +- examples/vite-adapter-vue/src/App.vue | 14 +- .../src/components/Formatter.vue | 2 +- examples/vite-adapter-vue/svg-component.d.ts | 20 ++- examples/vue3/README.md | 37 ++++ examples/vue3/farm.config.ts | 14 ++ examples/vue3/index.html | 16 ++ examples/vue3/package.json | 18 ++ examples/vue3/public/favicon.ico | Bin 0 -> 4154 bytes examples/vue3/src/App.vue | 35 ++++ examples/vue3/src/assets/logo.png | Bin 0 -> 16859 bytes examples/vue3/src/assets/vue.svg | 1 + examples/vue3/src/components/HelloWorld.vue | 32 ++++ examples/vue3/src/env.d.ts | 2 + examples/vue3/src/index.ts | 5 + examples/vue3/src/style.css | 80 +++++++++ examples/vue3/tsconfig.json | 25 +++ examples/vue3/tsconfig.node.json | 11 ++ packages/cli/src/index.ts | 2 +- packages/core/src/index.ts | 41 +++-- packages/core/src/newServer/hmr-engine.ts | 17 +- packages/core/src/newServer/index.ts | 44 ++++- packages/core/src/newServer/ws.ts | 77 ++++---- .../core/src/plugin/js/vite-plugin-adapter.ts | 26 +-- packages/core/src/server/hmr-engine.ts | 1 + .../core/src/server/middlewares/resources.ts | 144 +++++++-------- packages/core/src/utils/logger.ts | 4 +- packages/runtime-plugin-hmr/src/hmr-client.ts | 3 +- pnpm-lock.yaml | 170 ++++-------------- 33 files changed, 535 insertions(+), 327 deletions(-) create mode 100644 examples/vue3/README.md create mode 100644 examples/vue3/farm.config.ts create mode 100644 examples/vue3/index.html create mode 100644 examples/vue3/package.json create mode 100644 examples/vue3/public/favicon.ico create mode 100644 examples/vue3/src/App.vue create mode 100644 examples/vue3/src/assets/logo.png create mode 100644 examples/vue3/src/assets/vue.svg create mode 100644 examples/vue3/src/components/HelloWorld.vue create mode 100644 examples/vue3/src/env.d.ts create mode 100644 examples/vue3/src/index.ts create mode 100644 examples/vue3/src/style.css create mode 100644 examples/vue3/tsconfig.json create mode 100644 examples/vue3/tsconfig.node.json diff --git a/examples/refactor-react/farm.config.ts b/examples/refactor-react/farm.config.ts index 24656d9be3..41d7186d37 100644 --- a/examples/refactor-react/farm.config.ts +++ b/examples/refactor-react/farm.config.ts @@ -7,7 +7,7 @@ export default defineConfig({ minify: false, progress: false, sourcemap: false, - persistentCache: false, + // persistentCache: false, runtime: { isolate: true } diff --git a/examples/vite-adapter-react/src/pages/index.tsx b/examples/vite-adapter-react/src/pages/index.tsx index 8ea6ca5810..f722a7913b 100644 --- a/examples/vite-adapter-react/src/pages/index.tsx +++ b/examples/vite-adapter-react/src/pages/index.tsx @@ -14,6 +14,7 @@ import './main.css'; Farm logo + 113222222222222 React logo diff --git a/examples/vite-adapter-svelte/src/App.svelte b/examples/vite-adapter-svelte/src/App.svelte index b89ddf3138..6052b1223c 100644 --- a/examples/vite-adapter-svelte/src/App.svelte +++ b/examples/vite-adapter-svelte/src/App.svelte @@ -1,7 +1,7 @@
@@ -23,9 +23,7 @@ Powered by Farm and svelet vite plugin

-

- Click on the Farm and Svelte logos to learn more -

+

Click on the Farm and Svelte logos to learn more

\ No newline at end of file + diff --git a/examples/vite-adapter-vue/svg-component.d.ts b/examples/vite-adapter-vue/svg-component.d.ts index a7a37656d4..d58a707ca6 100644 --- a/examples/vite-adapter-vue/svg-component.d.ts +++ b/examples/vite-adapter-vue/svg-component.d.ts @@ -1,10 +1,20 @@ declare module '~virtual/svg-component' { - const MySvgIcon: (props: { - name: "icon-vue", - className?:string - style?: React.CSSProperties - })=> JSX.Element; + const MySvgIcon: import("vue").DefineComponent<{ + name: { + type: import("vue").PropType<"icon-vue">; + default: string; + required: true; + }; + }, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly; + default: string; + required: true; + }; + }>>, { + name: "icon-vue"; + }>; export const svgNames: ["icon-vue"]; export type SvgName = "icon-vue"; export default MySvgIcon; diff --git a/examples/vue3/README.md b/examples/vue3/README.md new file mode 100644 index 0000000000..0600bdd5bf --- /dev/null +++ b/examples/vue3/README.md @@ -0,0 +1,37 @@ +# Farm + Vue + +This template should help you start developing using Vue and TypeScript in Farm. + +## Setup + +Install the dependencies: + +```bash +pnpm install +``` + +## Get Started + +Start the dev server: + +```bash +pnpm start +``` + +Build the app for production: + +```bash +pnpm build +``` + +Preview the Production build product: + +```bash +pnpm preview +``` + +Clear persistent cache local files + +```bash +pnpm clean +``` diff --git a/examples/vue3/farm.config.ts b/examples/vue3/farm.config.ts new file mode 100644 index 0000000000..6108cac05c --- /dev/null +++ b/examples/vue3/farm.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@farmfe/core'; +import vue from '@vitejs/plugin-vue'; + +export default defineConfig({ + vitePlugins: [ + vue(), + ], + compilation: { + mode: 'production' + }, + server: { + port: 5232 + } +}); diff --git a/examples/vue3/index.html b/examples/vue3/index.html new file mode 100644 index 0000000000..92b55b9d35 --- /dev/null +++ b/examples/vue3/index.html @@ -0,0 +1,16 @@ + + + + + + + + Farm + Vue3 + TS + + + +
+ + + + \ No newline at end of file diff --git a/examples/vue3/package.json b/examples/vue3/package.json new file mode 100644 index 0000000000..7e33a6e7a9 --- /dev/null +++ b/examples/vue3/package.json @@ -0,0 +1,18 @@ +{ + "name": "farm", + "version": "1.0.0", + "scripts": { + "dev": "farm start", + "start": "farm start", + "build": "farm build", + "preview": "farm preview", + "clean": "farm clean" + }, + "dependencies": { + "vue": "^3.4.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "core-js": "^3.30.1" + } +} diff --git a/examples/vue3/public/favicon.ico b/examples/vue3/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..80465dedc03cfd08b0e3b118db6e765f65e3adc3 GIT binary patch literal 4154 zcmb`~duWw)9LMqBoO)WB8>rdNSy794a8L80HKCYy;X53Ll~aB<(pZM`qE!25qV^T{4`B6-myS?o2hN82+<+U< zgU>Js#Y@ls0rgpHaWfVd>OhcuLiH?%JvX{-jp-L?TuqIfpde{Z+6RpMT(1M2a zNgW#BR8$vQhXMP8dvl>UUXQDxF|NSvPbf6_&zLFD zH5>=EtG%cFqj(pZ5A8>dbr{yJ+S~!fc|+tT()+LzipxT%okH!;)YStw?b>8VB6{p_in}7AeAdaJ^{r}^?eMB-Gk({ zrayu9w#~ow!{$co^?m3pP+TWG|G2Mpr`Uyk4031DEWT^Zs#|q!fzAf4HC z@HD383zV1%YP(h6O6-MVF$0><`LHpo%n?h&yyCS6;aV%P*?jSIU3mWM_tJK}3hkK- z(TTZGyGg9VBE;t=>{Gt7qs&mJ>d|=ip#xfr=c5gZ$yw07U$FsIX?|Ok>qC96J=cd; z@;YC2-m6XRg(lYaG*Z2nG~YT0)YowAdafLws6MUp<@g2%pfgBwk;0cy``Y{OLgf^v zvdn?TV0Do;U>(}g2+jRrsC}dJR{Q2sg!{9Maj?GBP`Bpc6{z<{_vLJy;6Olit;eS4G)6KtfV<)|&@?~GFW7k{s0_}^bcdli`x%y$}s)w9QNY*W`%sMACqBL=U`#(}{kZI}9O!ob|625;;!v7E?e72>_ YXKTD4qPpQwz4tCb{gqHVI7FV$f0MB}F8}}l literal 0 HcmV?d00001 diff --git a/examples/vue3/src/App.vue b/examples/vue3/src/App.vue new file mode 100644 index 0000000000..58afa4176b --- /dev/null +++ b/examples/vue3/src/App.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/examples/vue3/src/assets/logo.png b/examples/vue3/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0caeb4381267cff0c3adf3020077c55bac612a20 GIT binary patch literal 16859 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 literal 0 HcmV?d00001 diff --git a/examples/vue3/src/assets/vue.svg b/examples/vue3/src/assets/vue.svg new file mode 100644 index 0000000000..770e9d333e --- /dev/null +++ b/examples/vue3/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/vue3/src/components/HelloWorld.vue b/examples/vue3/src/components/HelloWorld.vue new file mode 100644 index 0000000000..7135f2b544 --- /dev/null +++ b/examples/vue3/src/components/HelloWorld.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/examples/vue3/src/env.d.ts b/examples/vue3/src/env.d.ts new file mode 100644 index 0000000000..4ae8e90af7 --- /dev/null +++ b/examples/vue3/src/env.d.ts @@ -0,0 +1,2 @@ +declare module '*.vue'; +declare module '*.svg'; diff --git a/examples/vue3/src/index.ts b/examples/vue3/src/index.ts new file mode 100644 index 0000000000..2425c0f745 --- /dev/null +++ b/examples/vue3/src/index.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/examples/vue3/src/style.css b/examples/vue3/src/style.css new file mode 100644 index 0000000000..6efbecc027 --- /dev/null +++ b/examples/vue3/src/style.css @@ -0,0 +1,80 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #9F1A8F; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #9F1A8F; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #9F1A8F; + } + button { + background-color: #f9f9f9; + } +} diff --git a/examples/vue3/tsconfig.json b/examples/vue3/tsconfig.json new file mode 100644 index 0000000000..ee019e988f --- /dev/null +++ b/examples/vue3/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/examples/vue3/tsconfig.node.json b/examples/vue3/tsconfig.node.json new file mode 100644 index 0000000000..8d4232518e --- /dev/null +++ b/examples/vue3/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["farm.config.ts"] +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 0f2358327d..01bc90f374 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -98,8 +98,8 @@ cli // const { start }: any = await resolveCore(); handleAsyncOperationErrors( - start2(defaultOptions), // start(defaultOptions), + start2(defaultOptions), 'Failed to start server' ); } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4fa99b41b7..04cf5b1284 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -308,6 +308,7 @@ export async function createBundleHandler( if (resolvedUserConfig.compilation?.output?.clean) { compiler.removeOutputPathDir(); } + try { await compiler.compile(); } catch (err) { @@ -407,13 +408,13 @@ export async function createFileWatcher( resolvedUserConfig: ResolvedUserConfig, logger: Logger = new Logger() ) { - if ( - devServer.config.hmr && - resolvedUserConfig.compilation.mode === 'production' - ) { - logger.error('HMR cannot be enabled in production mode.'); - return; - } + // if ( + // devServer.config.hmr && + // resolvedUserConfig.compilation.mode === "production" + // ) { + // logger.error("HMR cannot be enabled in production mode."); + // return; + // } if (!devServer.config.hmr) { return; @@ -448,17 +449,17 @@ export async function createFileWatcher( } export async function createFileWatcher2( - devServer: HttpServer, + devServer: HttpServer & any, resolvedUserConfig: ResolvedUserConfig, logger: Logger = new Logger() ) { - if ( - resolvedUserConfig.server.hmr && - resolvedUserConfig.compilation.mode === 'production' - ) { - logger.error('HMR cannot be enabled in production mode.'); - return; - } + // if ( + // resolvedUserConfig.server.hmr && + // resolvedUserConfig.compilation.mode === "production" + // ) { + // logger.error("HMR cannot be enabled in production mode."); + // return; + // } if (!resolvedUserConfig.server.hmr) { return; @@ -470,7 +471,7 @@ export async function createFileWatcher2( // @ts-ignore const fileWatcher = new FileWatcher(devServer, resolvedUserConfig, logger); - // devServer.watcher = fileWatcher; + devServer.watcher = fileWatcher; await fileWatcher.watch(); const configFilePath = await getConfigFilePath(resolvedUserConfig.root); @@ -539,10 +540,12 @@ export async function start2( await server.createServer(); // @ts-ignore await createFileWatcher2(server, resolvedUserConfig, logger); + // call configureDevServer hook after both server and watcher are ready - // resolvedUserConfig.jsPlugins.forEach((plugin: JsPlugin) => - // plugin.configureDevServer?.(server) - // ); + resolvedUserConfig.jsPlugins.forEach((plugin: JsPlugin) => + // @ts-ignore + plugin.configureDevServer?.(server) + ); await server.listen(); // const devServer = await createDevServer( // compiler, diff --git a/packages/core/src/newServer/hmr-engine.ts b/packages/core/src/newServer/hmr-engine.ts index 9c6a476400..0869405689 100644 --- a/packages/core/src/newServer/hmr-engine.ts +++ b/packages/core/src/newServer/hmr-engine.ts @@ -19,7 +19,7 @@ import { WebSocketClient, WebSocketServer } from './ws.js'; export class HmrEngine { private _updateQueue: string[] = []; - private _updateResults: Map; + // private _updateResults: Map; private _onUpdates: ((result: JsUpdateResult) => void)[]; @@ -32,18 +32,8 @@ export class HmrEngine { // private _logger: Logger private readonly app: any ) { - const { - compiler, - httpServer, - resolvedUserConfig: config, - ws, - logger - } = this.app; - // this._lastAttemptWasError = false; this._lastModifiedTimestamp = new Map(); - // @ts-ignore - this.ws = ws.wss; } callUpdates(result: JsUpdateResult) { @@ -95,11 +85,11 @@ export class HmrEngine { }); checkClearScreen(this.app.compiler.config.config); - const start = Date.now(); + const start = performance.now(); const result = await this.app.compiler.update(queue); this.app.logger.info( `${bold(cyan(updatedFilesStr))} updated in ${bold( - green(`${Date.now() - start}ms`) + green(`${performance.now() - start}ms`) )}` ); @@ -142,6 +132,7 @@ export class HmrEngine { }`; this.callUpdates(result); + this.app.ws.wss.clients.forEach((client: WebSocketClient) => { // @ts-ignore client.send(` diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index d593f3e0a4..68c9f03bab 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -102,7 +102,7 @@ export class newServer { middlewares: connect.Server; constructor( - private readonly compiler: CompilerType, + private compiler: CompilerType, private readonly resolvedUserConfig: ResolvedUserConfig, private readonly logger: Logger ) { @@ -144,11 +144,15 @@ export class newServer { this.httpsOptions ); + // init hmr engine When actually updating, we need to get the clients of ws for broadcast, 、 + // so we can instantiate hmrEngine by default at the beginning. + this.createHmrEngine(); + // init websocket server this.createWebSocketServer(); - // init hmr engine - this.createHmrEngine(); + // invalidate vite handler + this.invalidateVite(); // init middlewares this.initializeMiddlewares(); @@ -189,6 +193,17 @@ export class newServer { this.ws = new WsServer(this); } + private invalidateVite() { + // Note: path should be Farm's id, which is a relative path in dev mode, + // but in vite, it's a url path like /xxx/xxx.js + this.ws.wss.on('vite:invalidate', ({ path, message }: any) => { + // find hmr boundary starting from the parent of the file + this.logger.info(`HMR invalidate: ${path}. ${message ?? ''} `); + const parentFiles = this.compiler.getParentFiles(path); + this.hmrEngine.hmrUpdate(parentFiles, true); + }); + } + public async listen(): Promise { if (!this.httpServer) { this.logger.warn('HTTP server is not created yet'); @@ -197,15 +212,25 @@ export class newServer { // TODO open browser when server is ready && open config is true const { port, open, protocol, hostname } = this.resolvedUserConfig.server; - const start = Date.now(); - await this.compile(); - bootstrap(Date.now() - start, this.compiler.config); + // compile the project and start the dev server + await this.startCompilation(); + + // watch extra files after compile + this.watcher?.watchExtraFiles?.(); this.httpServer.listen(port, hostname.name, () => { console.log(`Server running at ${protocol}://${hostname.name}:${port}/`); }); } + addWatchFile(root: string, deps: string[]): void { + this.getCompiler().addExtraWatchFile(root, deps); + } + + setCompiler(compiler: Compiler) { + this.compiler = compiler; + } + private async compile(): Promise { try { await this.compiler.compile(); @@ -220,6 +245,13 @@ export class newServer { } } + private async startCompilation() { + const start = performance.now(); + await this.compile(); + const duration = performance.now() - start; + bootstrap(duration, this.compiler.config); + } + private async handlePublicFiles() { const initPublicFilesPromise = initPublicFiles(this.resolvedUserConfig); return await initPublicFilesPromise; diff --git a/packages/core/src/newServer/ws.ts b/packages/core/src/newServer/ws.ts index 9cf0d220ea..9eb91c796d 100644 --- a/packages/core/src/newServer/ws.ts +++ b/packages/core/src/newServer/ws.ts @@ -115,6 +115,7 @@ export class WsServer { } createWebSocketServer() { + const self = this; const { resolvedUserConfig: config } = this.app; const serverConfig = config.server as unknown as ServerOptions; if (serverConfig.ws === false) { @@ -201,7 +202,14 @@ export class WsServer { let parsed: any; try { parsed = JSON.parse(String(raw)); - } catch {} + } catch { + this.logger.error('Failed to parse WebSocket message: ' + raw); + } + // transform vite js-update to farm update + if (parsed?.type === 'js-update' && parsed?.path) { + this.app.hmrEngine.hmrUpdate(parsed.path, true); + return; + } if (!parsed || parsed.type !== 'custom' || !parsed.event) return; const listeners = this.customListeners.get(parsed.event); if (!listeners?.size) return; @@ -211,7 +219,9 @@ export class WsServer { socket.on('error', (err) => { throw new Error(`ws error:\n${err.stack}`); }); + socket.send(JSON.stringify({ type: 'connected' })); + if (this.bufferedError) { socket.send(JSON.stringify(this.bufferedError)); this.bufferedError = null; @@ -220,45 +230,11 @@ export class WsServer { this.wss.on('error', (e: Error & { code: string }) => { if (e.code === 'EADDRINUSE') { - console.log('WebSocket server error: Port is already in use'); - - // config.logger.error( - // colors.red(`WebSocket server error: Port is already in use`), - // { error: e } - // ); + throw new Error('WebSocket server error: Port is already in use'); } else { - console.log('WebSocket server error:', e.stack || e.message); - - // config.logger.error( - // colors.red(`WebSocket server error:\n${e.stack || e.message}`), - // { error: e } - // ); + throw new Error(`WebSocket server error ${e.stack || e.message}`); } }); - const self = this; - function getSocketClient(socket: WebSocketRaw) { - if (!this.clientsMap.has(socket)) { - this.clientsMap.set(socket, { - send: (...args: any) => { - let payload: HMRPayload; - if (typeof args[0] === 'string') { - payload = { - type: 'custom', - event: args[0], - data: args[1] - }; - } else { - payload = args[0]; - } - socket.send(JSON.stringify(payload)); - }, - // @ts-ignore - rawSend: (str: string) => socket.send(str), - socket - }); - } - return this.clientsMap.get(socket); - } return { name: 'ws', @@ -284,7 +260,6 @@ export class WsServer { }) as WebSocketServer['off'], get clients() { - // return new Set(Array.from(wss.clients).map(getSocketClient)); return new Set( Array.from(this.wss.clients).map((socket: any) => self.getSocketClient(socket) @@ -350,6 +325,32 @@ export class WsServer { }; } + send(...args: any[]) { + let payload: HMRPayload; + if (typeof args[0] === 'string') { + payload = { + type: 'custom', + event: args[0], + data: args[1] + }; + } else { + payload = args[0]; + } + + if (payload.type === 'error' && !this.wss.clients.size) { + this.bufferedError = payload; + return; + } + + const stringified = JSON.stringify(payload); + this.wss.clients.forEach((client: any) => { + // readyState 1 means the connection is open + if (client.readyState === 1) { + client.send(stringified); + } + }); + } + getSocketClient(socket: WebSocketRaw) { if (!this.clientsMap.has(socket)) { this.clientsMap.set(socket, { diff --git a/packages/core/src/plugin/js/vite-plugin-adapter.ts b/packages/core/src/plugin/js/vite-plugin-adapter.ts index 264ce53400..7b5e0a3d07 100644 --- a/packages/core/src/plugin/js/vite-plugin-adapter.ts +++ b/packages/core/src/plugin/js/vite-plugin-adapter.ts @@ -270,7 +270,7 @@ export class VitePluginAdapter implements JsPlugin { } } - async configureDevServer(devServer: Server) { + async configureDevServer(devServer: any) { const hook = this.wrapRawPluginHook( 'configureServer', this._rawPlugin.configureServer @@ -285,17 +285,19 @@ export class VitePluginAdapter implements JsPlugin { if (hook) { await hook(this._viteDevServer); this._viteDevServer.middlewareCallbacks.forEach((cb) => { - devServer.app().use((ctx, koaNext) => { - return new Promise((resolve, reject) => { - // koaNext is async, but vite's next is sync, we need a adapter here - const next = (err: Error) => { - if (err) reject(err); - koaNext().then(resolve); - }; - - return cb(ctx.req, ctx.res, next); - }); - }); + // console.log(cb); + devServer.middlewares.use(cb); + // devServer.app().use((ctx: any, koaNext: any) => { + // return new Promise((resolve, reject) => { + // // koaNext is async, but vite's next is sync, we need a adapter here + // const next = (err: Error) => { + // if (err) reject(err); + // koaNext().then(resolve); + // }; + + // return cb(ctx.req, ctx.res, next); + // }); + // }); }); } } diff --git a/packages/core/src/server/hmr-engine.ts b/packages/core/src/server/hmr-engine.ts index ced71df7ed..3eafb273ee 100644 --- a/packages/core/src/server/hmr-engine.ts +++ b/packages/core/src/server/hmr-engine.ts @@ -85,6 +85,7 @@ export class HmrEngine { checkClearScreen(this._compiler.config.config); const start = Date.now(); const result = await this._compiler.update(queue); + this._logger.info( `${bold(cyan(updatedFilesStr))} updated in ${bold( green(`${Date.now() - start}ms`) diff --git a/packages/core/src/server/middlewares/resources.ts b/packages/core/src/server/middlewares/resources.ts index 60c86ddff1..175a42db4f 100644 --- a/packages/core/src/server/middlewares/resources.ts +++ b/packages/core/src/server/middlewares/resources.ts @@ -113,78 +113,78 @@ export function resourcesMiddleware(compiler: Compiler, serverContext: Server) { return; } - // const { fullPath, resourceWithoutPublicPath } = normalizePathByPublicPath( - // publicPath, - // stripQueryAndHashUrl - // ); - - // // if resource is image or font, try it in local file system to be compatible with vue - // { - // // try local file system - // const absPath = path.join( - // compiler.config.config.root, - // resourceWithoutPublicPath - // ); - // // const mimeStr = mime.lookup(absPath); - - // if ( - // existsSync(absPath) && - // statSync(absPath).isFile() - // // mimeStr && - // // (mimeStr.startsWith('image') || mimeStr.startsWith('font')) - // ) { - // ctx.type = extname(fullPath); - // ctx.body = readFileSync(absPath); - // return; - // } - - // // try local file system with publicDir - // const absPathPublicDir = path.resolve( - // compiler.config.config.root, - // compiler.config.config.assets.publicDir, - // resourceWithoutPublicPath - // ); - - // if (existsSync(absPathPublicDir) && statSync(absPathPublicDir).isFile()) { - // ctx.type = extname(fullPath); - // ctx.body = readFileSync(absPathPublicDir); - // return; - // } - // } - - // // if resource is not found and spa is not disabled, find the closest index.html from resourcePath - // { - // // if request mime is not html, return 404 - // if (!ctx.accepts('html')) { - // ctx.status = 404; - // } else if (config.spa !== false) { - // const pathComps = resourceWithoutPublicPath.split('/'); - - // while (pathComps.length > 0) { - // const pathStr = pathComps.join('/') + '.html'; - // const resource = compiler.resources()[pathStr]; - - // if (resource) { - // ctx.type = '.html'; - // ctx.body = resource; - // return; - // } - - // pathComps.pop(); - // } - - // const indexHtml = compiler.resources()['index.html']; - - // if (indexHtml) { - // ctx.type = '.html'; - // ctx.body = indexHtml; - // return; - // } - // } else { - // // cannot find index.html, return 404 - // ctx.status = 404; - // } - // } + const { fullPath, resourceWithoutPublicPath } = normalizePathByPublicPath( + publicPath, + stripQueryAndHashUrl + ); + + // if resource is image or font, try it in local file system to be compatible with vue + { + // try local file system + const absPath = path.join( + compiler.config.config.root, + resourceWithoutPublicPath + ); + // const mimeStr = mime.lookup(absPath); + + if ( + existsSync(absPath) && + statSync(absPath).isFile() + // mimeStr && + // (mimeStr.startsWith('image') || mimeStr.startsWith('font')) + ) { + ctx.type = extname(fullPath); + ctx.body = readFileSync(absPath); + return; + } + + // try local file system with publicDir + const absPathPublicDir = path.resolve( + compiler.config.config.root, + compiler.config.config.assets.publicDir, + resourceWithoutPublicPath + ); + + if (existsSync(absPathPublicDir) && statSync(absPathPublicDir).isFile()) { + ctx.type = extname(fullPath); + ctx.body = readFileSync(absPathPublicDir); + return; + } + } + + // if resource is not found and spa is not disabled, find the closest index.html from resourcePath + { + // if request mime is not html, return 404 + if (!ctx.accepts('html')) { + ctx.status = 404; + } else if (config.spa !== false) { + const pathComps = resourceWithoutPublicPath.split('/'); + + while (pathComps.length > 0) { + const pathStr = pathComps.join('/') + '.html'; + const resource = compiler.resources()[pathStr]; + + if (resource) { + ctx.type = '.html'; + ctx.body = resource; + return; + } + + pathComps.pop(); + } + + const indexHtml = compiler.resources()['index.html']; + + if (indexHtml) { + ctx.type = '.html'; + ctx.body = indexHtml; + return; + } + } else { + // cannot find index.html, return 404 + ctx.status = 404; + } + } }; } diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts index 734f87179d..5877b006cc 100644 --- a/packages/core/src/utils/logger.ts +++ b/packages/core/src/utils/logger.ts @@ -227,7 +227,7 @@ export function bootstrap(times: number, config: Config) { const persistentCacheFlag = usePersistentCache ? colors.bold(PersistentCacheBrand) : ''; - + // TODO add timer transform ms / s console.log( '\n', colors.bold(colors.brandColor(`${'ϟ'} Farm v${version}`)) @@ -235,7 +235,7 @@ export function bootstrap(times: number, config: Config) { console.log( `${colors.bold(colors.green(` ✓`))} ${colors.bold( 'Ready in' - )} ${colors.bold(colors.green(`${times}ms`))} ${persistentCacheFlag}`, + )} ${colors.bold(colors.green(`${Math.floor(times) / 1000}s`))} ${persistentCacheFlag}`, '\n' ); } diff --git a/packages/runtime-plugin-hmr/src/hmr-client.ts b/packages/runtime-plugin-hmr/src/hmr-client.ts index e156c4101f..8fd24cb1c2 100644 --- a/packages/runtime-plugin-hmr/src/hmr-client.ts +++ b/packages/runtime-plugin-hmr/src/hmr-client.ts @@ -108,10 +108,9 @@ export class HmrClient { for (const id of result.changed) { moduleSystem.update(id, result.modules[id]); - if (!result.boundaries[id]) { // do not found boundary module, reload the window - location.reload(); + // location.reload(); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10087957a9..3caf33c69b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1951,6 +1951,19 @@ importers: specifier: ^4.4.0 version: 4.4.0(vite@5.2.8(@types/node@20.14.12)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1))(vue@3.3.12(typescript@5.4.5)) + examples/vue3: + dependencies: + vue: + specifier: ^3.4.0 + version: 3.4.27(typescript@5.4.5) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^5.0.4 + version: 5.0.4(vite@5.2.8(@types/node@20.14.12)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1))(vue@3.4.27(typescript@5.4.5)) + core-js: + specifier: ^3.30.1 + version: 3.37.1 + examples/x-data-spreadsheet: dependencies: jszip: @@ -3115,28 +3128,24 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - libc: [musl] '@biomejs/cli-linux-arm64@1.8.3': resolution: {integrity: sha512-fed2ji8s+I/m8upWpTJGanqiJ0rnlHOK3DdxsyVLZQ8ClY6qLuPc9uehCREBifRJLl/iJyQpHIRufLDeotsPtw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - libc: [glibc] '@biomejs/cli-linux-x64-musl@1.8.3': resolution: {integrity: sha512-UHrGJX7PrKMKzPGoEsooKC9jXJMa28TUSMjcIlbDnIO4EAavCoVmNQaIuUSH0Ls2mpGMwUIf+aZJv657zfWWjA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - libc: [musl] '@biomejs/cli-linux-x64@1.8.3': resolution: {integrity: sha512-I8G2QmuE1teISyT8ie1HXsjFRz9L1m5n83U1O6m30Kw+kPMPSKjag6QGUn+sXT8V+XWIZxFFBoTDEDZW2KPDDw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - libc: [glibc] '@biomejs/cli-win32-arm64@1.8.3': resolution: {integrity: sha512-J+Hu9WvrBevfy06eU1Na0lpc7uR9tibm9maHynLIoAjLZpQU3IW+OKHUtyL8p6/3pT2Ju5t5emReeIS2SAxhkQ==} @@ -3868,63 +3877,6 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@farmfe/plugin-react-darwin-arm64@1.2.0': - resolution: {integrity: sha512-9a8wp6lg8NytO+kU8hu2gCFer+PL4TJ92SkU/5v9xdcsioElWpnMDGlwcoI8bXqi60/WR8RyExsDIBubCgjbXQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@farmfe/plugin-react-darwin-x64@1.2.0': - resolution: {integrity: sha512-JXkdg3zmevlf+kbdd05+6x+L/l2IYY7Vm4hqkymbxlVdaFd2ydHmyMk9ekcmtkOijlUtEYoD3a9whstzvJ+FkA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@farmfe/plugin-react-linux-arm64-gnu@1.2.0': - resolution: {integrity: sha512-B98ldEqeJn6Uesnxr13Y/nFfIP4Qr8Svcd3mJqaOFcaOF9OZvRYFvQha1DRAoBrp8VhntghijqoWJeC1qKUhKw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@farmfe/plugin-react-linux-arm64-musl@1.2.0': - resolution: {integrity: sha512-o49P/vCWlqAkFeIVtZqy1OyyIlGHr2w+O9ty5ojwMGXGXHOrvBi1IL2ItlFqxUawweli6mNspDO0bJSJZ51gOw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@farmfe/plugin-react-linux-x64-gnu@1.2.0': - resolution: {integrity: sha512-Z1hX52mHllxXn6GolTpuN3sqmz8yku6N/rs0NHbjezgyRPWFWOMS7fnD6SMf/TPvRPGeRX1bj49rr9GMqsQEgQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@farmfe/plugin-react-linux-x64-musl@1.2.0': - resolution: {integrity: sha512-eZzEE9eCeteIpsQr1u4dnFzEEisYuuUIVhbNZX8mPCBYQ9ZB6RXMZYj3lmHgl3qNGagxH26msqcpr7t3U8qPuQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@farmfe/plugin-react-win32-arm64-msvc@1.2.0': - resolution: {integrity: sha512-JluDXSQFs3s5txZghCbeqdOjtocSW4vaoQWgcQQ88zpFrTlqqwg4xnrXdeC3CqgeNcVq5ZMJtx2VwsJqITvPxg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@farmfe/plugin-react-win32-ia32-msvc@1.2.0': - resolution: {integrity: sha512-b6I+qSG8+a59YE0d2J+QLWDi5qxQUY1C/TeYvGUBeoOs7/pCKdznvd2eQJ5N9Yvafzn6zlN9//oz1A/VLvqSBg==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@farmfe/plugin-react-win32-x64-msvc@1.2.0': - resolution: {integrity: sha512-9GWEdbvGUB+ovdAAQhHF7l4v0MaTXjOIoQZd4g6+rGDQtMIx4d1M6EOPx4D1Yn9/+dI1157UWWt9PK9Lod2h+w==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@farmfe/plugin-react@1.2.0': - resolution: {integrity: sha512-S5kU7NgqiyyhnDsZ7DEvszQIE6sCA0CNp7oTbdDcPxotPNBoyOcBHviSP3P5jvtIv6mmlF8Me2C1aLWJQRw9PA==} - '@farmfe/utils@0.0.1': resolution: {integrity: sha512-QLbgNrojcvxfumXA/H329XAXhoCahmeSH3JmaiwwJEGS2QAmWfgAJMegjwlt6OmArGVO4gSbJ7Xbmm1idZZs+g==} @@ -4411,43 +4363,36 @@ packages: resolution: {integrity: sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.14.1': resolution: {integrity: sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-powerpc64le-gnu@4.14.1': resolution: {integrity: sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==} cpu: [ppc64le] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.14.1': resolution: {integrity: sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.14.1': resolution: {integrity: sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.14.1': resolution: {integrity: sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.14.1': resolution: {integrity: sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.14.1': resolution: {integrity: sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==} @@ -8040,21 +7985,18 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] farm-plugin-remove-console-linux-arm64-musl@0.1.8: resolution: {integrity: sha512-vT1fy3hBdIqLNMI+hXi7I+Wseep6v3pUY99BDOnrkkHFkIjVuDzHCrQEYGLJOLchZM9fJnqidkhGTi0K+LZd6g==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] farm-plugin-remove-console-linux-x64-gnu@0.1.8: resolution: {integrity: sha512-MJkZn+OeWig78DkAb5ur/lxXYYlZwAO05WvQHJOgQLSjxyOAVTDPYn2j+mCGgPrZdDwyghODM9UdVlxe1lMb7w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] farm-plugin-remove-console-linux-x64-musl@0.1.8: resolution: {integrity: sha512-ZObFlj+/ulEqUjr1Ggpm03FRcZQ5BZepK24kWf36pxrB6cn/dSv40GsXzzKELfPrh12Q15NJZVSW/0AT4SLnTw==} @@ -8100,21 +8042,18 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] farm-plugin-replace-dirname-linux-arm64-musl@0.2.1: resolution: {integrity: sha512-m3gH8ggczbRYTHZSNp3LjIQIcqhvDO4O78bxXc8O1ozKD8M47/YfQLyQV06M7H4rZ8s6XV3Bb1kAcRAASp3M5A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] farm-plugin-replace-dirname-linux-x64-gnu@0.2.1: resolution: {integrity: sha512-MehKkoM2RFw3sCnEu9nCbXKjxtC3hfTad0h/dC+Z8iEBcLEReVLoNzHWWUa6BxkxqDtB82/BWO/ObSUj/VUnwQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] farm-plugin-replace-dirname-linux-x64-musl@0.2.1: resolution: {integrity: sha512-o1qPZi16N/sHOteZYJVv6UmZFK3QKpVQrywk/4spJI0mPH9A9Y+G6iBE2Tqjb3d+1Hb6phr++EBJHZ2x1ajtGQ==} @@ -9560,28 +9499,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.25.1: resolution: {integrity: sha512-IhxVFJoTW8wq6yLvxdPvyHv4NjzcpN1B7gjxrY3uaykQNXPHNIpChLB52+wfH+yS58zm1PL4LemUp8u9Cfp6Bw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.25.1: resolution: {integrity: sha512-RXIaru79KrREPEd6WLXfKfIp4QzoppZvD3x7vuTKkDA64PwTzKJ2jaC43RZHRt8BmyIkRRlmywNhTRMbmkPYpA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.25.1: resolution: {integrity: sha512-TdcNqFsAENEEFr8fJWg0Y4fZ/nwuqTRsIr7W7t2wmDUlA8eSXVepeeONYcb+gtTj1RaXn/WgNLB45SFkz+XBZA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-x64-msvc@1.25.1: resolution: {integrity: sha512-9KZZkmmy9oGDSrnyHuxP6iMhbsgChUiu/NSgOx+U1I/wTngBStDf2i2aGRCHvFqj19HqqBEI4WuGVQBa2V6e0A==} @@ -13847,7 +13782,7 @@ snapshots: '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.3) '@babel/helpers': 7.24.1 - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 '@babel/template': 7.24.0 '@babel/traverse': 7.24.1 '@babel/types': 7.24.0 @@ -14274,13 +14209,13 @@ snapshots: '@babel/template@7.22.15': dependencies: '@babel/code-frame': 7.24.2 - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 '@babel/types': 7.24.0 '@babel/template@7.24.0': dependencies: '@babel/code-frame': 7.24.2 - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 '@babel/types': 7.24.0 '@babel/traverse@7.23.2': @@ -14291,7 +14226,7 @@ snapshots: '@babel/helper-function-name': 7.23.0 '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 '@babel/types': 7.24.0 debug: 4.3.5 globals: 11.12.0 @@ -14306,7 +14241,7 @@ snapshots: '@babel/helper-function-name': 7.23.0 '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 '@babel/types': 7.24.0 debug: 4.3.5 globals: 11.12.0 @@ -15504,45 +15439,6 @@ snapshots: '@eslint/js@8.57.0': {} - '@farmfe/plugin-react-darwin-arm64@1.2.0': - optional: true - - '@farmfe/plugin-react-darwin-x64@1.2.0': - optional: true - - '@farmfe/plugin-react-linux-arm64-gnu@1.2.0': - optional: true - - '@farmfe/plugin-react-linux-arm64-musl@1.2.0': - optional: true - - '@farmfe/plugin-react-linux-x64-gnu@1.2.0': - optional: true - - '@farmfe/plugin-react-linux-x64-musl@1.2.0': - optional: true - - '@farmfe/plugin-react-win32-arm64-msvc@1.2.0': - optional: true - - '@farmfe/plugin-react-win32-ia32-msvc@1.2.0': - optional: true - - '@farmfe/plugin-react-win32-x64-msvc@1.2.0': - optional: true - - '@farmfe/plugin-react@1.2.0': - optionalDependencies: - '@farmfe/plugin-react-darwin-arm64': 1.2.0 - '@farmfe/plugin-react-darwin-x64': 1.2.0 - '@farmfe/plugin-react-linux-arm64-gnu': 1.2.0 - '@farmfe/plugin-react-linux-arm64-musl': 1.2.0 - '@farmfe/plugin-react-linux-x64-gnu': 1.2.0 - '@farmfe/plugin-react-linux-x64-musl': 1.2.0 - '@farmfe/plugin-react-win32-arm64-msvc': 1.2.0 - '@farmfe/plugin-react-win32-ia32-msvc': 1.2.0 - '@farmfe/plugin-react-win32-x64-msvc': 1.2.0 - '@farmfe/utils@0.0.1': {} '@floating-ui/core@1.5.0': @@ -17325,7 +17221,7 @@ snapshots: '@types/babel__template@7.4.3': dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 '@babel/types': 7.24.0 '@types/babel__traverse@7.20.3': @@ -17957,9 +17853,9 @@ snapshots: '@vue-macros/common@1.10.0(rollup@4.14.1)(vue@3.3.7(typescript@5.4.5))': dependencies: - '@babel/types': 7.23.6 + '@babel/types': 7.24.0 '@rollup/pluginutils': 5.1.0(rollup@4.14.1) - '@vue/compiler-sfc': 3.4.15 + '@vue/compiler-sfc': 3.4.27 ast-kit: 0.11.3(rollup@4.14.1) local-pkg: 0.5.0 magic-string-ast: 0.3.0 @@ -17996,8 +17892,8 @@ snapshots: '@babel/core': 7.24.3 '@babel/helper-module-imports': 7.22.15 '@babel/helper-plugin-utils': 7.24.0 - '@babel/parser': 7.24.1 - '@vue/compiler-sfc': 3.4.15 + '@babel/parser': 7.24.5 + '@vue/compiler-sfc': 3.4.27 '@vue/babel-plugin-transform-vue-jsx@1.4.0(@babel/core@7.24.3)': dependencies: @@ -18062,21 +17958,21 @@ snapshots: '@vue/compiler-core@3.3.12': dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 '@vue/shared': 3.3.12 estree-walker: 2.0.2 source-map-js: 1.2.0 '@vue/compiler-core@3.3.7': dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 '@vue/shared': 3.3.7 estree-walker: 2.0.2 source-map-js: 1.2.0 '@vue/compiler-core@3.4.15': dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 '@vue/shared': 3.4.15 entities: 4.5.0 estree-walker: 2.0.2 @@ -18125,7 +18021,7 @@ snapshots: '@vue/compiler-sfc@2.7.16': dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 postcss: 8.4.39 source-map: 0.6.1 optionalDependencies: @@ -18133,7 +18029,7 @@ snapshots: '@vue/compiler-sfc@3.3.12': dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 '@vue/compiler-core': 3.3.12 '@vue/compiler-dom': 3.3.12 '@vue/compiler-ssr': 3.3.12 @@ -18142,7 +18038,7 @@ snapshots: estree-walker: 2.0.2 magic-string: 0.30.10 postcss: 8.4.39 - source-map-js: 1.0.2 + source-map-js: 1.2.0 '@vue/compiler-sfc@3.3.7': dependencies: @@ -18159,7 +18055,7 @@ snapshots: '@vue/compiler-sfc@3.4.15': dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 '@vue/compiler-core': 3.4.15 '@vue/compiler-dom': 3.4.15 '@vue/compiler-ssr': 3.4.15 @@ -18307,7 +18203,7 @@ snapshots: '@vue/reactivity-transform@3.3.12': dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 '@vue/compiler-core': 3.3.12 '@vue/shared': 3.3.12 estree-walker: 2.0.2 @@ -18315,7 +18211,7 @@ snapshots: '@vue/reactivity-transform@3.3.7': dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 '@vue/compiler-core': 3.3.7 '@vue/shared': 3.3.7 estree-walker: 2.0.2 @@ -18924,7 +18820,7 @@ snapshots: ast-walker-scope@0.5.0(rollup@4.14.1): dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.5 ast-kit: 0.9.5(rollup@4.14.1) transitivePeerDependencies: - rollup From 04b8c5f0191b0438fbe39fa54c4ec53aaf9e7a32 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Thu, 8 Aug 2024 15:17:22 +0800 Subject: [PATCH 036/369] chore: perf core code --- .vscode/settings.json | 2 +- packages/core/src/compiler/index.ts | 61 ++++++++++++----------------- packages/core/src/config/index.ts | 36 ++++++++++++++++- packages/core/src/index.ts | 27 +------------ 4 files changed, 62 insertions(+), 64 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index fc7d12d02e..fb3656b114 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,7 +4,7 @@ "biome.enabled": true, "editor.defaultFormatter": "biomejs.biome", "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[javascript]": { "editor.defaultFormatter": "biomejs.biome" diff --git a/packages/core/src/compiler/index.ts b/packages/core/src/compiler/index.ts index 295f410a41..f33c0b328a 100644 --- a/packages/core/src/compiler/index.ts +++ b/packages/core/src/compiler/index.ts @@ -64,11 +64,7 @@ export class Compiler { } async compile() { - if (this.compiling) { - this.logger.error('Already compiling', { - exit: true - }); - } + this.checkCompiling(); this.compiling = true; if (process.env.FARM_PROFILE) { @@ -80,11 +76,8 @@ export class Compiler { } compileSync() { - if (this.compiling) { - this.logger.error('Already compiling', { - exit: true - }); - } + this.checkCompiling(); + this.compiling = true; this._bindingCompiler.compileSync(); this.compiling = false; @@ -163,22 +156,16 @@ export class Compiler { } pluginStats() { - return this._bindingCompiler.pluginStats() as PluginStats; + return this._bindingCompiler.pluginStats(); } writeResourcesToDisk(): void { const resources = this.resources(); - const configOutputPath = this.config.config.output.path; - const outputPath = path.isAbsolute(configOutputPath) - ? configOutputPath - : path.join(this.config.config.root, configOutputPath); + const outputPath = this.getOutputPath(); for (const [name, resource] of Object.entries(resources)) { - // remove query params and hash of name - const nameWithoutQuery = name.split('?')[0]; - const nameWithoutHash = nameWithoutQuery.split('#')[0]; + const filePath = path.join(outputPath, name.split(/[?#]/)[0]); - let filePath = path.join(outputPath, nameWithoutHash); if (!existsSync(path.dirname(filePath))) { mkdirSync(path.dirname(filePath), { recursive: true }); } @@ -190,7 +177,7 @@ export class Compiler { } callWriteResourcesHook() { - for (const jsPlugin of this.config.jsPlugins ?? []) { + for (const jsPlugin of this.config.jsPlugins) { jsPlugin.writeResources?.executor?.({ resourcesMap: this._bindingCompiler.resourcesMap() as Record< string, @@ -223,15 +210,7 @@ export class Compiler { p = p.slice(0, -VIRTUAL_FARM_DYNAMIC_IMPORT_SUFFIX.length); } - if (path.isAbsolute(p)) { - return p; - } - - if (p.includes('?')) { - return path.join(root, p.split('?')[0]); - } - - return path.join(root, p); + return path.isAbsolute(p) ? p : path.join(root, p.split('?')[0]); } onUpdateFinish(cb: () => void) { @@ -239,12 +218,7 @@ export class Compiler { } outputPath() { - const { output, root } = this.config.config; - const configOutputPath = output.path; - const outputPath = path.isAbsolute(configOutputPath) - ? configOutputPath - : path.join(root, configOutputPath); - return outputPath; + return this.getOutputPath(); } addExtraWatchFile(root: string, paths: string[]) { @@ -274,4 +248,21 @@ export class Compiler { getResourcePotRecordsById(id: string) { return this._bindingCompiler.getResourcePotRecordsById(id); } + + private checkCompiling() { + if (this.compiling) { + this.logger.error('Already compiling', { + exit: true + }); + } + } + + private getOutputPath(): string { + const { output, root } = this.config.config; + const configOutputPath = output.path; + const outputPath = path.isAbsolute(configOutputPath) + ? configOutputPath + : path.join(root, configOutputPath); + return outputPath; + } } diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index 1b0b5915b5..dc03ed667f 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -50,6 +50,7 @@ import { parseUserConfig } from './schema.js'; import { externalAdapter } from '../plugin/js/external-adapter.js'; import { convertErrorMessage } from '../utils/error.js'; +import { resolveHostname } from '../utils/http.js'; import merge from '../utils/merge.js'; import { CUSTOM_KEYS, @@ -93,14 +94,14 @@ export function defineFarmConfig(config: UserConfigExport): UserConfigExport { */ export async function resolveConfig( inlineOptions: FarmCliOptions & UserConfig, - command: 'start' | 'build' | 'preview', + command: 'start' | 'build' | 'watch' | 'preview', defaultMode: CompilationMode = 'development', defaultNodeEnv: CompilationMode = 'development', isPreview = false, logger?: Logger ): Promise { logger = logger ?? new Logger(); - + // TODO mode 这块还是不对 要区分 mode 和 build 还是 dev 环境 const compileMode = defaultMode; const mode = inlineOptions.mode || defaultMode; const isNodeEnvSet = !!process.env.NODE_ENV; @@ -193,6 +194,25 @@ export async function resolveConfig( resolvedUserConfig.compilation.resolve.alias as unknown as Array ); } + + switch (configEnv.command) { + case 'start': + if ( + resolvedUserConfig.compilation.lazyCompilation && + typeof resolvedUserConfig.server?.host === 'string' + ) { + await setLazyCompilationDefine(resolvedUserConfig); + } + break; + case 'watch': + if (resolvedUserConfig.compilation?.lazyCompilation) { + await setLazyCompilationDefine(resolvedUserConfig); + } + break; + default: + break; + } + return resolvedUserConfig; } @@ -1022,3 +1042,15 @@ export function getFilePath(outputPath: string, fileName: string): string { ? pathToFileURL(path.join(outputPath, fileName)).toString() : path.join(outputPath, fileName); } + +async function setLazyCompilationDefine( + resolvedUserConfig: ResolvedUserConfig +) { + const hostname = await resolveHostname(resolvedUserConfig.server.host); + resolvedUserConfig.compilation.define = { + ...(resolvedUserConfig.compilation.define ?? {}), + FARM_LAZY_COMPILE_SERVER_URL: `${ + resolvedUserConfig.server.protocol || 'http' + }://${hostname.host || 'localhost'}:${resolvedUserConfig.server.port}` + }; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 562f8b644f..395fef39cc 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -55,7 +55,6 @@ import { lazyCompilation } from './server/middlewares/lazy-compilation.js'; import { ConfigWatcher } from './watcher/config-watcher.js'; import type { JsPlugin } from './plugin/type.js'; -import { resolveHostname } from './utils/http.js'; export async function start( inlineConfig?: FarmCliOptions & UserConfig @@ -73,13 +72,6 @@ export async function start( false ); - if ( - resolvedUserConfig.compilation.lazyCompilation && - typeof resolvedUserConfig.server?.host === 'string' - ) { - await setLazyCompilationDefine(resolvedUserConfig); - } - const compiler = await createCompiler(resolvedUserConfig, logger); const devServer = await createDevServer( @@ -181,12 +173,6 @@ export async function watch( logger ); - const lazyEnabled = resolvedUserConfig.compilation?.lazyCompilation; - - if (lazyEnabled) { - await setLazyCompilationDefine(resolvedUserConfig); - } - const compilerFileWatcher = await createBundleHandler( resolvedUserConfig, logger, @@ -195,6 +181,7 @@ export async function watch( let devServer: Server | undefined; // create dev server for lazy compilation + const lazyEnabled = resolvedUserConfig.compilation.lazyCompilation; if (lazyEnabled) { devServer = new Server({ logger, @@ -456,18 +443,6 @@ export function logFileChanges(files: string[], root: string, logger: Logger) { ); } -async function setLazyCompilationDefine( - resolvedUserConfig: ResolvedUserConfig -) { - const hostname = await resolveHostname(resolvedUserConfig.server.host); - resolvedUserConfig.compilation.define = { - ...(resolvedUserConfig.compilation.define ?? {}), - FARM_LAZY_COMPILE_SERVER_URL: `${ - resolvedUserConfig.server.protocol || 'http' - }://${hostname.host || 'localhost'}:${resolvedUserConfig.server.port}` - }; -} - export { defineFarmConfig as defineConfig } from './config/index.js'; export { loadEnv }; From 902c5c85de4eb6e7bf8ecbc07bcc27488818baa3 Mon Sep 17 00:00:00 2001 From: xiaomo Date: Fri, 9 Aug 2024 12:48:16 +0800 Subject: [PATCH 037/369] feat: cacheDir check for the FILLEXTREME (#1708) --- packages/core/src/server/index.ts | 12 +++++++- packages/core/src/utils/cacheDir.ts | 31 +++++++++++++++++++++ packages/core/src/utils/index.ts | 1 + packages/core/src/utils/logger.ts | 4 +-- packages/core/src/watcher/create-watcher.ts | 19 ++++--------- 5 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 packages/core/src/utils/cacheDir.ts diff --git a/packages/core/src/server/index.ts b/packages/core/src/server/index.ts index 8b08c430cf..d66cd2f291 100644 --- a/packages/core/src/server/index.ts +++ b/packages/core/src/server/index.ts @@ -25,6 +25,8 @@ import { Logger, bootstrap, clearScreen, + getCacheDir, + isCacheDirExists, normalizeBasePath, printServerUrls } from '../utils/index.js'; @@ -125,6 +127,14 @@ export class Server implements ImplDevServer { } const { port, open, protocol, hostname } = this.config; + // check if cache dir exists + const hasCacheDir = await isCacheDirExists( + getCacheDir( + this.compiler.config.config.root, + this.compiler.config.config.persistentCache + ) + ); + const start = Date.now(); // compile the project and start the dev server await this.compile(); @@ -132,7 +142,7 @@ export class Server implements ImplDevServer { // watch extra files after compile this.watcher?.watchExtraFiles?.(); - bootstrap(Date.now() - start, this.compiler.config); + bootstrap(Date.now() - start, this.compiler.config, hasCacheDir); await this.startServer(this.config); diff --git a/packages/core/src/utils/cacheDir.ts b/packages/core/src/utils/cacheDir.ts new file mode 100644 index 0000000000..112a0d6349 --- /dev/null +++ b/packages/core/src/utils/cacheDir.ts @@ -0,0 +1,31 @@ +import fs from 'fs'; +import path from 'node:path'; + +import { PersistentCacheConfig } from '../types/binding.js'; + +export function getCacheDir( + root: string, + persistentCache?: boolean | PersistentCacheConfig +) { + let cacheDir = path.resolve(root, 'node_modules', '.farm', 'cache'); + + if (typeof persistentCache === 'object' && persistentCache.cacheDir) { + cacheDir = persistentCache.cacheDir; + + if (!path.isAbsolute(cacheDir)) { + cacheDir = path.resolve(root, cacheDir); + } + } + + return cacheDir; +} + +export async function isCacheDirExists(dir: string): Promise { + try { + const hasCacheDir = fs.readdirSync(dir, { withFileTypes: true }); + + return !!(hasCacheDir && hasCacheDir.length); + } catch (e) { + return false; + } +} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index e720cc724c..c011f57dba 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -8,3 +8,4 @@ export * from './path.js'; export * from './publicDir.js'; export * from './rebase-url.js'; export * from './plugin-utils.js'; +export * from './cacheDir.js'; diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts index 734f87179d..469d34dd01 100644 --- a/packages/core/src/utils/logger.ts +++ b/packages/core/src/utils/logger.ts @@ -222,8 +222,8 @@ export function bootstrapLogger(options?: LoggerOptions): Logger { return new Logger(options); } -export function bootstrap(times: number, config: Config) { - const usePersistentCache = config.config.persistentCache; +export function bootstrap(times: number, config: Config, hasCacheDir: boolean) { + const usePersistentCache = config.config.persistentCache && hasCacheDir; const persistentCacheFlag = usePersistentCache ? colors.bold(PersistentCacheBrand) : ''; diff --git a/packages/core/src/watcher/create-watcher.ts b/packages/core/src/watcher/create-watcher.ts index 4877aae249..45bf141580 100644 --- a/packages/core/src/watcher/create-watcher.ts +++ b/packages/core/src/watcher/create-watcher.ts @@ -3,7 +3,7 @@ import path from 'node:path'; import chokidar, { FSWatcher, WatchOptions } from 'chokidar'; import glob from 'fast-glob'; -import { ResolvedUserConfig } from '../index.js'; +import { ResolvedUserConfig, getCacheDir } from '../index.js'; function resolveChokidarOptions( config: ResolvedUserConfig, @@ -11,18 +11,11 @@ function resolveChokidarOptions( ) { const { ignored = [], ...userChokidarOptions } = config.server?.hmr?.watchOptions ?? {}; - let cacheDir = path.resolve(config.root, 'node_modules', '.farm', 'cache'); - - if ( - typeof config.compilation?.persistentCache === 'object' && - config.compilation.persistentCache.cacheDir - ) { - cacheDir = config.compilation.persistentCache.cacheDir; - - if (!path.isAbsolute(cacheDir)) { - cacheDir = path.resolve(config.root, cacheDir); - } - } + + const cacheDir = getCacheDir( + config.root, + config.compilation?.persistentCache + ); const options: WatchOptions = { ignored: [ From 5217d400aa5e477d26d26dbecf0c3035b5539adf Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Fri, 9 Aug 2024 15:25:58 +0800 Subject: [PATCH 038/369] chore: update format timer config and show cache text logic --- examples/react/farm.config.js | 4 ++++ packages/core/src/config/schema.ts | 1 + packages/core/src/config/types.ts | 1 + packages/core/src/newServer/index.ts | 8 +++++++- packages/core/src/utils/logger.ts | 5 +++-- packages/core/src/utils/share.ts | 13 +++++++++++++ packages/core/src/watcher/create-watcher.ts | 5 +---- 7 files changed, 30 insertions(+), 7 deletions(-) diff --git a/examples/react/farm.config.js b/examples/react/farm.config.js index 09ae49b6f3..1172e8e3bf 100644 --- a/examples/react/farm.config.js +++ b/examples/react/farm.config.js @@ -5,6 +5,10 @@ export default defineConfig(() => { compilation: { sourcemap: true, // persistentCache: false, + persistentCache: { + // cacheDir: "node_modules/.farm/adny", + cacheDir: "node_modules/adny/cache", + }, presetEnv: false, progress: false // output: { diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index 80c439de63..e32202fe49 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -383,6 +383,7 @@ const FarmConfigSchema = z envDir: z.string().optional(), envPrefix: z.union([z.string(), z.array(z.string())]).optional(), publicDir: z.string().optional(), + formatTimer: z.string().optional(), plugins: z.array(z.any()).optional(), vitePlugins: z.array(z.any()).optional(), compilation: compilationConfigSchema.optional(), diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index bb9d872bc5..c259e738d0 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -100,6 +100,7 @@ export interface UserConfig { envDir?: string; envPrefix?: string | string[]; publicDir?: string; + formatTimer?: 'ms' | 's'; /** js plugin(which is a javascript object) and rust plugin(which is string refer to a .farm file or a package) */ plugins?: (RustPlugin | JsPlugin | JsPlugin[] | undefined | null | false)[]; /** vite plugins */ diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index 68c9f03bab..53de132734 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -16,6 +16,7 @@ import { Compiler } from '../compiler/index.js'; import { normalizePublicPath } from '../config/normalize-config/normalize-output.js'; import { NormalizedServerConfig, ResolvedUserConfig } from '../config/types.js'; import { logError } from '../server/error.js'; +import { getCacheDir, isCacheDirExists } from '../utils/cacheDir.js'; import { Logger, bootstrap, logger } from '../utils/logger.js'; import { initPublicFiles } from '../utils/publicDir.js'; import { isObject } from '../utils/share.js'; @@ -246,10 +247,15 @@ export class newServer { } private async startCompilation() { + // check if cache dir exists + const { root, persistentCache } = this.compiler.config.config; + const hasCacheDir = await isCacheDirExists( + getCacheDir(root, persistentCache) + ); const start = performance.now(); await this.compile(); const duration = performance.now() - start; - bootstrap(duration, this.compiler.config); + bootstrap(duration, this.compiler.config, hasCacheDir); } private async handlePublicFiles() { diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts index ff72e4a902..6801899a90 100644 --- a/packages/core/src/utils/logger.ts +++ b/packages/core/src/utils/logger.ts @@ -1,7 +1,7 @@ import { Config } from '../types/binding.js'; import { ColorFunction, PersistentCacheBrand, colors } from './color.js'; /* eslint-disable @typescript-eslint/no-explicit-any */ -import { pad, version } from './share.js'; +import { formatExecutionTime, pad, version } from './share.js'; type LogLevelNames = 'trace' | 'debug' | 'info' | 'warn' | 'error'; @@ -232,10 +232,11 @@ export function bootstrap(times: number, config: Config, hasCacheDir: boolean) { '\n', colors.bold(colors.brandColor(`${'ϟ'} Farm v${version}`)) ); + console.log( `${colors.bold(colors.green(` ✓`))} ${colors.bold( 'Ready in' - )} ${colors.bold(colors.green(`${Math.floor(times) / 1000}s`))} ${persistentCacheFlag}`, + )} ${colors.bold(colors.green(formatExecutionTime(times, 's')))} ${persistentCacheFlag}`, '\n' ); } diff --git a/packages/core/src/utils/share.ts b/packages/core/src/utils/share.ts index dd1a8ac0e6..ffe914ec7b 100644 --- a/packages/core/src/utils/share.ts +++ b/packages/core/src/utils/share.ts @@ -177,3 +177,16 @@ export function tryStatSync(file: string): fs.Stats | undefined { return fs.statSync(file, { throwIfNoEntry: false }); } catch {} } + +export function formatExecutionTime( + time: number, + format: 'ms' | 's' = 'ms' +): string { + switch (format) { + case 's': + return `${Math.floor(time) / 1000}s`; + case 'ms': + default: + return `${Math.floor(time)}ms`; + } +} diff --git a/packages/core/src/watcher/create-watcher.ts b/packages/core/src/watcher/create-watcher.ts index 45bf141580..c9078ddb16 100644 --- a/packages/core/src/watcher/create-watcher.ts +++ b/packages/core/src/watcher/create-watcher.ts @@ -12,10 +12,7 @@ function resolveChokidarOptions( const { ignored = [], ...userChokidarOptions } = config.server?.hmr?.watchOptions ?? {}; - const cacheDir = getCacheDir( - config.root, - config.compilation?.persistentCache - ); + const cacheDir = getCacheDir(config.root, config.compilation.persistentCache); const options: WatchOptions = { ignored: [ From c31bc2a395fef15ab36553f1effc693d27a57cc8 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Fri, 9 Aug 2024 15:46:29 +0800 Subject: [PATCH 039/369] chore: adaptor vite vue plugin middleware --- packages/core/src/newServer/index.ts | 3 ++ .../src/newServer/middlewares/adaptorVite.ts | 42 +++++++++++++++++++ .../src/newServer/middlewares/resource.ts | 15 +------ packages/core/src/newServer/publicDir.ts | 15 +++++++ packages/core/src/utils/logger.ts | 2 +- 5 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 packages/core/src/newServer/middlewares/adaptorVite.ts diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index 53de132734..72d3ba0ba8 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -28,6 +28,7 @@ import { resolveHttpServer, resolveHttpsConfig } from './http.js'; +import { adaptorViteMiddleware } from './middlewares/adaptorVite.js'; import { HMRPingMiddleware } from './middlewares/hmrPing.js'; import { htmlFallbackMiddleware } from './middlewares/htmlFallback.js'; import { publicMiddleware } from './middlewares/public.js'; @@ -172,6 +173,8 @@ export class newServer { this.middlewares.use(htmlFallbackMiddleware(this)); this.middlewares.use(resourceMiddleware(this)); + + this.middlewares.use(adaptorViteMiddleware(this)); } public createHmrEngine() { diff --git a/packages/core/src/newServer/middlewares/adaptorVite.ts b/packages/core/src/newServer/middlewares/adaptorVite.ts new file mode 100644 index 0000000000..9964d21019 --- /dev/null +++ b/packages/core/src/newServer/middlewares/adaptorVite.ts @@ -0,0 +1,42 @@ +/** + * when use vite-plugin-vue some assets resource not compiled in dev mode + * so we need to invalidate vite handler to recompile + * and automatically res.body to resolve this asset resource e.g: img + * if resource is image or font, try it in local file system to be compatible with vue + */ + +import { existsSync, readFileSync, statSync } from 'node:fs'; +import path, { extname } from 'node:path'; +import { stripQueryAndHash } from '../../utils/path.js'; +import { normalizePathByPublicPath } from '../publicDir.js'; +import { send } from '../send.js'; + +export function adaptorViteMiddleware(app: any) { + return function handleAdaptorViteMiddleware( + req: any, + res: any, + next: () => void + ) { + let stripQueryAndHashUrl = stripQueryAndHash(req.url); + const { resourceWithoutPublicPath } = normalizePathByPublicPath( + app.publicPath, + stripQueryAndHashUrl + ); + + // try local file system + const localFilePath = path.join( + app.compiler.config.config.root, + resourceWithoutPublicPath + ); + + // try local file system + if (existsSync(localFilePath) && statSync(localFilePath).isFile()) { + const headers = app.resolvedUserConfig.server.headers; + send(req, res, readFileSync(localFilePath), stripQueryAndHashUrl, { + headers + }); + return; + } + next(); + }; +} diff --git a/packages/core/src/newServer/middlewares/resource.ts b/packages/core/src/newServer/middlewares/resource.ts index bd53a67d5f..e4b2c577af 100644 --- a/packages/core/src/newServer/middlewares/resource.ts +++ b/packages/core/src/newServer/middlewares/resource.ts @@ -10,6 +10,7 @@ import { } from '../../utils/index.js'; import { cleanUrl } from '../../utils/url.js'; import { HttpServer } from '../index.js'; +import { normalizePathByPublicPath } from '../publicDir.js'; import { send } from '../send.js'; interface RealResourcePath { resourcePath: string; @@ -48,7 +49,7 @@ export function resourceMiddleware(app: any) { if (resourceResult) { // need judge if resource is a deps node_modules set cache-control to 1 year - const headers = config.server.headers || {}; + const headers = config.server.headers; send(req, res, resourceResult.resource, url, { headers }); return; } @@ -87,15 +88,3 @@ function findResource( }; } } - -function normalizePathByPublicPath(publicPath: string, resourcePath: string) { - const base = publicPath.match(/^https?:\/\//) ? '' : publicPath; - let resourceWithoutPublicPath = resourcePath; - - if (base && resourcePath.startsWith(base)) { - resourcePath = resourcePath.replace(new RegExp(`([^/]+)${base}`), '$1/'); - resourceWithoutPublicPath = resourcePath.slice(base.length); - } - - return { resourceWithoutPublicPath, fullPath: resourcePath }; -} diff --git a/packages/core/src/newServer/publicDir.ts b/packages/core/src/newServer/publicDir.ts index 71ff3e2326..6880c6df4d 100644 --- a/packages/core/src/newServer/publicDir.ts +++ b/packages/core/src/newServer/publicDir.ts @@ -32,3 +32,18 @@ export function getPublicFiles( ): Set | undefined { return publicFilesMap.get(config); } + +export function normalizePathByPublicPath( + publicPath: string, + resourcePath: string +) { + const base = publicPath.match(/^https?:\/\//) ? '' : publicPath; + let resourceWithoutPublicPath = resourcePath; + + if (base && resourcePath.startsWith(base)) { + resourcePath = resourcePath.replace(new RegExp(`([^/]+)${base}`), '$1/'); + resourceWithoutPublicPath = resourcePath.slice(base.length); + } + + return { resourceWithoutPublicPath, fullPath: resourcePath }; +} diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts index 6801899a90..80ce86d332 100644 --- a/packages/core/src/utils/logger.ts +++ b/packages/core/src/utils/logger.ts @@ -227,7 +227,7 @@ export function bootstrap(times: number, config: Config, hasCacheDir: boolean) { const persistentCacheFlag = usePersistentCache ? colors.bold(PersistentCacheBrand) : ''; - // TODO add timer transform ms / s + console.log( '\n', colors.bold(colors.brandColor(`${'ϟ'} Farm v${version}`)) From 9fe5d7828284a5345525d6860bbcd2ab8d5b40d6 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Fri, 9 Aug 2024 16:02:41 +0800 Subject: [PATCH 040/369] chore: update methods --- examples/vue3/src/App.vue | 2 +- packages/core/src/newServer/hmr-engine.ts | 10 ++++++++-- packages/core/src/newServer/index.ts | 2 +- packages/core/src/newServer/middlewares/adaptorVite.ts | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/examples/vue3/src/App.vue b/examples/vue3/src/App.vue index 58afa4176b..ace3cfbdc2 100644 --- a/examples/vue3/src/App.vue +++ b/examples/vue3/src/App.vue @@ -9,7 +9,7 @@ console.log(a);
- + 222222 diff --git a/packages/core/src/newServer/hmr-engine.ts b/packages/core/src/newServer/hmr-engine.ts index 0869405689..fee923cd67 100644 --- a/packages/core/src/newServer/hmr-engine.ts +++ b/packages/core/src/newServer/hmr-engine.ts @@ -13,7 +13,13 @@ import { } from '../config/index.js'; import { HttpServer } from '../newServer/index.js'; import type { JsUpdateResult } from '../types/binding.js'; -import { Logger, bold, cyan, green } from '../utils/index.js'; +import { + Logger, + bold, + cyan, + formatExecutionTime, + green +} from '../utils/index.js'; import { logError } from './error.js'; import { WebSocketClient, WebSocketServer } from './ws.js'; @@ -89,7 +95,7 @@ export class HmrEngine { const result = await this.app.compiler.update(queue); this.app.logger.info( `${bold(cyan(updatedFilesStr))} updated in ${bold( - green(`${performance.now() - start}ms`) + green(formatExecutionTime(performance.now() - start, 's')) )}` ); diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index 72d3ba0ba8..eef868aa7f 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -174,7 +174,7 @@ export class newServer { this.middlewares.use(resourceMiddleware(this)); - this.middlewares.use(adaptorViteMiddleware(this)); + // this.middlewares.use(adaptorViteMiddleware(this)); } public createHmrEngine() { diff --git a/packages/core/src/newServer/middlewares/adaptorVite.ts b/packages/core/src/newServer/middlewares/adaptorVite.ts index 9964d21019..c58afffbdb 100644 --- a/packages/core/src/newServer/middlewares/adaptorVite.ts +++ b/packages/core/src/newServer/middlewares/adaptorVite.ts @@ -6,7 +6,7 @@ */ import { existsSync, readFileSync, statSync } from 'node:fs'; -import path, { extname } from 'node:path'; +import path from 'node:path'; import { stripQueryAndHash } from '../../utils/path.js'; import { normalizePathByPublicPath } from '../publicDir.js'; import { send } from '../send.js'; From 98585416b5b50c3447c4e8a4e1f49ab8e409768a Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Fri, 9 Aug 2024 16:10:38 +0800 Subject: [PATCH 041/369] chore: update methods --- examples/vue3/src/App.vue | 2 +- packages/core/src/plugin/js/vite-plugin-adapter.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/vue3/src/App.vue b/examples/vue3/src/App.vue index ace3cfbdc2..0f0986053f 100644 --- a/examples/vue3/src/App.vue +++ b/examples/vue3/src/App.vue @@ -9,7 +9,7 @@ console.log(a);
- 222222 + 22222222222222222 diff --git a/packages/core/src/plugin/js/vite-plugin-adapter.ts b/packages/core/src/plugin/js/vite-plugin-adapter.ts index 7b5e0a3d07..402e0188d9 100644 --- a/packages/core/src/plugin/js/vite-plugin-adapter.ts +++ b/packages/core/src/plugin/js/vite-plugin-adapter.ts @@ -284,8 +284,8 @@ export class VitePluginAdapter implements JsPlugin { if (hook) { await hook(this._viteDevServer); + this._viteDevServer.middlewareCallbacks.forEach((cb) => { - // console.log(cb); devServer.middlewares.use(cb); // devServer.app().use((ctx: any, koaNext: any) => { // return new Promise((resolve, reject) => { From c24ac3c74693645520a5c11b08ccbd4564a3bb1a Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Fri, 9 Aug 2024 17:26:47 +0800 Subject: [PATCH 042/369] fix: vite hmr error --- examples/vue-antdv/src/main.vue | 6 +++--- examples/vue3/farm.config.ts | 15 +++++---------- examples/vue3/src/App.vue | 2 +- packages/core/src/config/index.ts | 1 + packages/core/src/newServer/index.ts | 4 ++-- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/examples/vue-antdv/src/main.vue b/examples/vue-antdv/src/main.vue index 7eb0a102f6..c1eb04774a 100644 --- a/examples/vue-antdv/src/main.vue +++ b/examples/vue-antdv/src/main.vue @@ -7,10 +7,10 @@ To Test - To Main + To - To Nest Test3 + To Nest Test3222222 @@ -122,4 +122,4 @@ export default defineComponent({ .site-layout-background { background: #fff; } - \ No newline at end of file + diff --git a/examples/vue3/farm.config.ts b/examples/vue3/farm.config.ts index 6108cac05c..426e27989f 100644 --- a/examples/vue3/farm.config.ts +++ b/examples/vue3/farm.config.ts @@ -1,14 +1,9 @@ -import { defineConfig } from '@farmfe/core'; -import vue from '@vitejs/plugin-vue'; +import { defineConfig } from "@farmfe/core"; +import vue from "@vitejs/plugin-vue"; export default defineConfig({ - vitePlugins: [ - vue(), - ], - compilation: { - mode: 'production' - }, + vitePlugins: [vue()], server: { - port: 5232 - } + port: 5232, + }, }); diff --git a/examples/vue3/src/App.vue b/examples/vue3/src/App.vue index 0f0986053f..58afa4176b 100644 --- a/examples/vue3/src/App.vue +++ b/examples/vue3/src/App.vue @@ -9,7 +9,7 @@ console.log(a);
- 22222222222222222 + diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index 7fd9d0cab0..7c1d8f4125 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -102,6 +102,7 @@ export async function resolveConfig( ): Promise { logger = logger ?? new Logger(); // TODO mode 这块还是不对 要区分 mode 和 build 还是 dev 环境 + // TODO 在使用 vite 插件的时候 不要在开发环境使用 生产环境的mode vue 插件会导致 hmr 失效 记在文档里 const compileMode = defaultMode; const mode = inlineOptions.mode || defaultMode; const isNodeEnvSet = !!process.env.NODE_ENV; diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index eef868aa7f..7560e63dff 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -169,12 +169,12 @@ export class newServer { if (this.publicDir) { this.middlewares.use(publicMiddleware(this)); } - // TODO todo add appType + // TODO todo add appType 这块要判断 单页面还是 多页面 多 html 处理不一样 this.middlewares.use(htmlFallbackMiddleware(this)); this.middlewares.use(resourceMiddleware(this)); - // this.middlewares.use(adaptorViteMiddleware(this)); + this.middlewares.use(adaptorViteMiddleware(this)); } public createHmrEngine() { From d1ccbee59a6d3887eb5b18f73a73a0fb0a777882 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Fri, 9 Aug 2024 17:31:41 +0800 Subject: [PATCH 043/369] chore: add code spell and remove biome lips --- packages/core/src/newServer/hmr.ts | 1 - packages/core/src/newServer/ws.ts | 1 - packages/core/src/utils/fsUtils.ts | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/core/src/newServer/hmr.ts b/packages/core/src/newServer/hmr.ts index 1a5eda65b3..29b99664b8 100644 --- a/packages/core/src/newServer/hmr.ts +++ b/packages/core/src/newServer/hmr.ts @@ -33,7 +33,6 @@ export interface HMRChannel { * Unregister event listener */ - // biome-ignore lint/complexity/noBannedTypes: off(event: string, listener: Function): void; /** * Start listening for messages diff --git a/packages/core/src/newServer/ws.ts b/packages/core/src/newServer/ws.ts index 9eb91c796d..2d68ad6973 100644 --- a/packages/core/src/newServer/ws.ts +++ b/packages/core/src/newServer/ws.ts @@ -48,7 +48,6 @@ export interface WebSocketServer extends HMRChannel { * Unregister event listener. */ off: WebSocketTypes.Server['off'] & { - // biome-ignore lint/complexity/noBannedTypes: (event: string, listener: Function): void; }; } diff --git a/packages/core/src/utils/fsUtils.ts b/packages/core/src/utils/fsUtils.ts index 65b43a3f28..d8b4f36f3f 100644 --- a/packages/core/src/utils/fsUtils.ts +++ b/packages/core/src/utils/fsUtils.ts @@ -82,8 +82,8 @@ function optimizeSafeRealPathSync() { exec('net use', (error, stdout) => { if (error) return; const lines = stdout.split('\n'); - // OK Y: \\NETWORKA\Foo Microsoft Windows Network - // OK Z: \\NETWORKA\Bar Microsoft Windows Network + // OK Y: \\NETWORK\Foo Microsoft Windows Network + // OK Z: \\NETWORK\Bar Microsoft Windows Network for (const line of lines) { const m = line.match(parseNetUseRE); if (m) windowsNetworkMap.set(m[3], m[2]); From 78be76d40530725ffcfa568062126c38a1bbb3e8 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Mon, 12 Aug 2024 13:47:52 +0800 Subject: [PATCH 044/369] chore: update code --- examples/vue3/src/App.vue | 18 ++- packages/core/src/newServer/index.ts | 10 ++ .../core/src/newServer/middlewares/proxy.ts | 128 +++++++++++++++++- 3 files changed, 152 insertions(+), 4 deletions(-) diff --git a/examples/vue3/src/App.vue b/examples/vue3/src/App.vue index 58afa4176b..1d92a4f4ea 100644 --- a/examples/vue3/src/App.vue +++ b/examples/vue3/src/App.vue @@ -2,7 +2,20 @@ import { ref } from "vue"; import HelloWorld from "./components/HelloWorld.vue"; const a = ref(666); -console.log(a); + +const incrementA = () => { + a.value++; +}; + +const getMessage = () => { + return a.value % 2 === 0 ? "a 是偶数" : "a 是奇数"; +}; + +fetch( + "https://apis.juhe.cn/environment/river?key=caab07f6c5df752d2e28edfe447ae6d0", +) + .then((res) => res.json()) + .then((data) => console.log(data)); + --> diff --git a/examples/vue3/src/pages/about.vue b/examples/vue3/src/pages/about.vue index 363598fb34..48f51ed287 100644 --- a/examples/vue3/src/pages/about.vue +++ b/examples/vue3/src/pages/about.vue @@ -1,9 +1,10 @@ - + + diff --git a/examples/visualizer/package.json b/examples/visualizer/package.json new file mode 100644 index 0000000000..d88cd5f3e2 --- /dev/null +++ b/examples/visualizer/package.json @@ -0,0 +1,26 @@ +{ + "name": "visualizer", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "farm", + "start": "farm start --debug", + "build": "farm build", + "preview": "farm preview", + "clean": "farm clean" + }, + "dependencies": { + "react": "18", + "react-dom": "18" + }, + "devDependencies": { + "@farmfe/cli": "workspace:*", + "@farmfe/core": "workspace:*", + "@farmfe/js-plugin-visualizer": "workspace:*", + "@farmfe/plugin-react": "workspace:*", + "@types/react": "18", + "core-js": "^3.36.1", + "@types/react-dom": "18", + "react-refresh": "^0.14.0" + } +} diff --git a/examples/visualizer/public/favicon.ico b/examples/visualizer/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..80465dedc03cfd08b0e3b118db6e765f65e3adc3 GIT binary patch literal 4154 zcmb`~duWw)9LMqBoO)WB8>rdNSy794a8L80HKCYy;X53Ll~aB<(pZM`qE!25qV^T{4`B6-myS?o2hN82+<+U< zgU>Js#Y@ls0rgpHaWfVd>OhcuLiH?%JvX{-jp-L?TuqIfpde{Z+6RpMT(1M2a zNgW#BR8$vQhXMP8dvl>UUXQDxF|NSvPbf6_&zLFD zH5>=EtG%cFqj(pZ5A8>dbr{yJ+S~!fc|+tT()+LzipxT%okH!;)YStw?b>8VB6{p_in}7AeAdaJ^{r}^?eMB-Gk({ zrayu9w#~ow!{$co^?m3pP+TWG|G2Mpr`Uyk4031DEWT^Zs#|q!fzAf4HC z@HD383zV1%YP(h6O6-MVF$0><`LHpo%n?h&yyCS6;aV%P*?jSIU3mWM_tJK}3hkK- z(TTZGyGg9VBE;t=>{Gt7qs&mJ>d|=ip#xfr=c5gZ$yw07U$FsIX?|Ok>qC96J=cd; z@;YC2-m6XRg(lYaG*Z2nG~YT0)YowAdafLws6MUp<@g2%pfgBwk;0cy``Y{OLgf^v zvdn?TV0Do;U>(}g2+jRrsC}dJR{Q2sg!{9Maj?GBP`Bpc6{z<{_vLJy;6Olit;eS4G)6KtfV<)|&@?~GFW7k{s0_}^bcdli`x%y$}s)w9QNY*W`%sMACqBL=U`#(}{kZI}9O!ob|625;;!v7E?e72>_ YXKTD4qPpQwz4tCb{gqHVI7FV$f0MB}F8}}l literal 0 HcmV?d00001 diff --git a/examples/visualizer/public/react.svg b/examples/visualizer/public/react.svg new file mode 100644 index 0000000000..6c87de9bb3 --- /dev/null +++ b/examples/visualizer/public/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/visualizer/src/assets/base/react.svg b/examples/visualizer/src/assets/base/react.svg new file mode 100644 index 0000000000..6c87de9bb3 --- /dev/null +++ b/examples/visualizer/src/assets/base/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/visualizer/src/assets/logo.png b/examples/visualizer/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0caeb4381267cff0c3adf3020077c55bac612a20 GIT binary patch literal 16859 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 literal 0 HcmV?d00001 diff --git a/examples/visualizer/src/assets/react.svg b/examples/visualizer/src/assets/react.svg new file mode 100644 index 0000000000..6c87de9bb3 --- /dev/null +++ b/examples/visualizer/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/visualizer/src/index.css b/examples/visualizer/src/index.css new file mode 100644 index 0000000000..6cc4daf982 --- /dev/null +++ b/examples/visualizer/src/index.css @@ -0,0 +1,69 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #9f1a8f; + text-decoration: inherit; +} +a:hover { + color: #9f1a8f; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #9f1a8f; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #9F1A8F; + } + button { + background-color: #f9f9f9; + } +} diff --git a/examples/visualizer/src/index.tsx b/examples/visualizer/src/index.tsx new file mode 100644 index 0000000000..39df64e842 --- /dev/null +++ b/examples/visualizer/src/index.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { Main } from './main'; +import './index.css' + + +const container = document.querySelector('#root'); +// biome-ignore lint/style/noNonNullAssertion: +const root = createRoot(container!); + +root.render(
); diff --git a/examples/visualizer/src/main.css b/examples/visualizer/src/main.css new file mode 100644 index 0000000000..97420f8caf --- /dev/null +++ b/examples/visualizer/src/main.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #9F1A8Faa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #9761fbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/examples/visualizer/src/main.tsx b/examples/visualizer/src/main.tsx new file mode 100644 index 0000000000..92662eded0 --- /dev/null +++ b/examples/visualizer/src/main.tsx @@ -0,0 +1,31 @@ +import { useState } from "react"; +import "./main.css"; +import reactLogo from "/react.svg"; +import FarmLogo from "./assets/logo.png"; +export function Main() { + const [count, setCount] = useState(0); + return ( + <> + +

Farm + react

+
+ +

+ Edit src/main.tsx and save to test HMR +

+
+

+ Click on the Farm and React logos to learn more +

+ + ); +} diff --git a/examples/visualizer/src/typings.d.ts b/examples/visualizer/src/typings.d.ts new file mode 100644 index 0000000000..fa0a2da548 --- /dev/null +++ b/examples/visualizer/src/typings.d.ts @@ -0,0 +1,3 @@ +declare module '*.svg'; +declare module '*.png'; +declare module '*.css'; diff --git a/examples/visualizer/tsconfig.json b/examples/visualizer/tsconfig.json new file mode 100644 index 0000000000..7a7611e4a3 --- /dev/null +++ b/examples/visualizer/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/examples/visualizer/tsconfig.node.json b/examples/visualizer/tsconfig.node.json new file mode 100644 index 0000000000..8d4232518e --- /dev/null +++ b/examples/visualizer/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["farm.config.ts"] +} diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index ba099f1bad..0045717a4f 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -115,6 +115,7 @@ export class NewServer extends httpServer { compiler: CompilerType; root: string; closeHttpServerFn: () => Promise; + postConfigureServerHooks: ((() => void) | void)[] = []; constructor( readonly resolvedUserConfig: ResolvedUserConfig, logger: Logger @@ -168,10 +169,12 @@ export class NewServer extends httpServer { // invalidate vite handler this.#invalidateVite(); + this.#createWatcher(); + // init middlewares this.#initializeMiddlewares(); - this.#createWatcher(); + this.handleConfigureServer(); if (!middlewareMode && this.httpServer) { this.httpServer.once('listening', () => { @@ -182,9 +185,8 @@ export class NewServer extends httpServer { }); } // TODO apply server configuration hooks from plugins e.g. vite configureServer - const postHooks: ((() => void) | void)[] = []; + // const postHooks: ((() => void) | void)[] = []; // console.log(this.resolvedUserConfig.jsPlugins); - // TODO 要在这里做 vite 插件和 js 插件的适配器 // for (const hook of getPluginHooks(applyPlugins, "configureServer")) { // postHooks.push(await hook(reflexServer)); @@ -195,53 +197,86 @@ export class NewServer extends httpServer { } } + async handleConfigureServer() { + const reflexServer = new Proxy(this, { + get: (_, property: keyof NewServer) => { + return this[property]; + }, + set: (_, property: keyof NewServer, value: never) => { + //@ts-ignore + this[property] = value; + return true; + } + }); + const { jsPlugins } = this.resolvedUserConfig; + // const postHooks: ((() => void) | void)[] = []; + // TODO type error and 而且还要排序 插件排序 + // @ts-ignore + for (const hook of getPluginHooks(jsPlugins, 'configureServer')) { + this.postConfigureServerHooks.push(await hook(reflexServer)); + } + } + /** * */ async #createWatcher() { this.watcher = new Watcher(this.resolvedUserConfig); - this.watcher.createWatcher(); - // this.watcher.watcher.on("change", async (file: string | string[] | any) => { - // const isConfigFile = this.resolvedUserConfig.configFilePath === file; - // const isConfigDependencyFile = - // this.resolvedUserConfig.configFileDependencies.some( - // (name) => file === name, - // ); - // const isEnvFile = this.resolvedUserConfig.envFiles.some( - // (name) => file === name, - // ); - // if (isConfigFile || isConfigDependencyFile || isEnvFile) { - // debugServer?.(`[config change] ${colors.dim(file)}`); - // this.close(); - // } - // // TODO 做一个 onHmrUpdate 方法 - // try { - // this.hmrEngine.hmrUpdate(file); - // } catch (error) { - // this.logger.error(`Farm Hmr Update Error: ${error}`); - // } - // }); - // const handleUpdateFinish = (updateResult: JsUpdateResult) => { - // const added = [ - // ...updateResult.added, - // ...updateResult.extraWatchResult.add, - // ].map((addedModule) => { - // const resolvedPath = this.compiler.transformModulePath( - // this.root, - // addedModule, - // ); - // return resolvedPath; - // }); - // const filteredAdded = added.filter((file) => - // this.watcher.filterWatchFile(file, this.root), - // ); - - // if (filteredAdded.length > 0) { - // this.watcher.watcher.add(filteredAdded); - // } - // }; - - // this.hmrEngine?.onUpdateFinish(handleUpdateFinish); + + await this.watcher.createWatcher(); + + this.watcher.watcher.on('change', async (file: string | string[] | any) => { + const isConfigFile = this.resolvedUserConfig.configFilePath === file; + const isConfigDependencyFile = + this.resolvedUserConfig.configFileDependencies.some( + (name) => file === name + ); + const isEnvFile = this.resolvedUserConfig.envFiles.some( + (name) => file === name + ); + if (isConfigFile || isConfigDependencyFile || isEnvFile) { + debugServer?.(`[config change] ${colors.dim(file)}`); + await this.close(); + console.log('重启大法'); + + setTimeout(() => { + this.restartServer(); + }, 3000); + } + // TODO 做一个 onHmrUpdate 方法 + try { + this.hmrEngine.hmrUpdate(file); + } catch (error) { + this.logger.error(`Farm Hmr Update Error: ${error}`); + } + }); + const handleUpdateFinish = (updateResult: JsUpdateResult) => { + const added = [ + ...updateResult.added, + ...updateResult.extraWatchResult.add + ].map((addedModule) => { + const resolvedPath = this.compiler.transformModulePath( + this.root, + addedModule + ); + return resolvedPath; + }); + const filteredAdded = added.filter((file) => + this.watcher.filterWatchFile(file, this.root) + ); + + if (filteredAdded.length > 0) { + this.watcher.watcher.add(filteredAdded); + } + }; + + this.hmrEngine?.onUpdateFinish(handleUpdateFinish); + } + + async restartServer() { + console.log('开启重启大法呜啦啦'); + await this.createServer(); + await this.listen(); } /** @@ -305,7 +340,7 @@ export class NewServer extends httpServer { await this.#startCompile(); // watch extra files after compile - // this.watcher?.watchExtraFiles?.(); + this.watcher?.watchExtraFiles?.(); // !__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ && await this.displayServerUrls(this.serverOptions, this.publicPath); @@ -445,6 +480,8 @@ export class NewServer extends httpServer { this.middlewares.use(adaptorViteMiddleware(this)); } + this.postConfigureServerHooks.forEach((fn) => fn && fn()); + // TODO todo add appType 这块要判断 单页面还是 多页面 多 html 处理不一样 this.middlewares.use(htmlFallbackMiddleware(this)); diff --git a/packages/core/src/watcher/index.ts b/packages/core/src/watcher/index.ts index 515ead6555..7513caad43 100644 --- a/packages/core/src/watcher/index.ts +++ b/packages/core/src/watcher/index.ts @@ -53,7 +53,6 @@ export default class Watcher implements ImplFileWatcher { } getExtraWatchedFiles(compiler?: Compiler | null) { - // const compiler = !watchCompiler ? this.getCompiler() : watchCompiler; this.extraWatchedFiles = [ ...compiler.resolvedModulePaths(this.config.root), ...compiler.resolvedWatchPaths() @@ -161,22 +160,13 @@ export default class Watcher implements ImplFileWatcher { // } async createWatcher() { - // add 监听额外的文件 需要new 一个 compiler instance - // const Compiler = (await import("../compiler/index.js")).Compiler; - // const compiler = new Compiler({ - // config: { ...this.config.compilation, progress: false }, - // jsPlugins: this.config.jsPlugins, - // rustPlugins: this.config.rustPlugins, - // }); - const compiler = createInlineCompiler(this.config, { progress: false }); - + const compiler = await createInlineCompiler(this.config, { + progress: false + }); // TODO type error here // @ts-ignore const enabledWatcher = this.config.watch !== null; - const files = [ - this.config.root, - ...this.getExtraWatchedFiles(await compiler) - ]; + const files = [this.config.root, ...this.getExtraWatchedFiles(compiler)]; this.watcher = enabledWatcher ? (chokidar.watch(files, this.resolvedWatchOptions) as FSWatcher) @@ -221,23 +211,12 @@ export default class Watcher implements ImplFileWatcher { }; } - // getCompiler(): Compiler { - // //@ts-ignore - // return this.compiler; - // } - - // setCompiler(compiler: Compiler) { - // this.compiler = compiler; - // } - async close() { if (this.watcher) { - // this.close = true; debugWatcher?.('close watcher'); await this.watcher.close(); this.watcher = null; } - // this.compiler = null; } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00268778e1..7aceff3d42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1519,6 +1519,40 @@ importers: specifier: ^5.2.6 version: 5.2.8(@types/node@22.5.0)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1) + examples/visualizer: + dependencies: + react: + specifier: '18' + version: 18.2.0 + react-dom: + specifier: '18' + version: 18.2.0(react@18.2.0) + devDependencies: + '@farmfe/cli': + specifier: workspace:* + version: link:../../packages/cli + '@farmfe/core': + specifier: workspace:* + version: link:../../packages/core + '@farmfe/js-plugin-visualizer': + specifier: workspace:* + version: link:../../js-plugins/visualizer + '@farmfe/plugin-react': + specifier: workspace:* + version: link:../../rust-plugins/react + '@types/react': + specifier: '18' + version: 18.2.35 + '@types/react-dom': + specifier: '18' + version: 18.2.14 + core-js: + specifier: ^3.36.1 + version: 3.37.1 + react-refresh: + specifier: ^0.14.0 + version: 0.14.2 + examples/vite-adapter-react: dependencies: core-js: @@ -1606,7 +1640,7 @@ importers: version: 4.0.0 svelte-check: specifier: ^3.6.2 - version: 3.6.2(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0) + version: 3.6.2(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0) tslib: specifier: ^2.6.2 version: 2.6.2 @@ -1983,7 +2017,7 @@ importers: dependencies: unplugin-auto-import: specifier: ^0.16.7 - version: 0.16.7(@vueuse/core@9.13.0(vue@3.4.27(typescript@5.5.4)))(rollup@4.14.1) + version: 0.16.7(@vueuse/core@9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)))(rollup@4.14.1) unplugin-vue-router: specifier: ^0.7.0 version: 0.7.0(rollup@4.14.1)(vue-router@4.4.3(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) @@ -15219,7 +15253,7 @@ snapshots: yjs: 13.6.18 zustand: 4.5.5(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0) zustand-middleware-yjs: 1.3.1(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0) - zustand-utils: 1.3.2(react@18.2.0)(zustand@4.5.5(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0)) + zustand-utils: 1.3.2(react@18.2.0)(zustand@4.5.5(@types/react@18.2.35)(immer@10.0.3)(react@18.2.0)) transitivePeerDependencies: - '@emotion/react' - '@types/react' @@ -20880,6 +20914,11 @@ snapshots: vue: 3.4.15(typescript@5.5.4) optional: true + '@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4))': + dependencies: + vue: 3.4.27(typescript@5.5.4) + optional: true + '@vue/devtools-api@6.5.1': {} '@vue/devtools-api@6.6.3': {} @@ -21028,12 +21067,12 @@ snapshots: - '@vue/composition-api' - vue - '@vueuse/core@9.13.0(vue@3.4.27(typescript@5.5.4))': + '@vueuse/core@9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4))': dependencies: '@types/web-bluetooth': 0.0.16 '@vueuse/metadata': 9.13.0 - '@vueuse/shared': 9.13.0(vue@3.4.27(typescript@5.5.4)) - vue-demi: 0.14.10(vue@3.4.27(typescript@5.5.4)) + '@vueuse/shared': 9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) + vue-demi: 0.14.10(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -21048,9 +21087,9 @@ snapshots: - '@vue/composition-api' - vue - '@vueuse/shared@9.13.0(vue@3.4.27(typescript@5.5.4))': + '@vueuse/shared@9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4))': dependencies: - vue-demi: 0.14.10(vue@3.4.27(typescript@5.5.4)) + vue-demi: 0.14.10(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -27469,12 +27508,13 @@ snapshots: postcss: 8.4.39 ts-node: 10.9.1(@types/node@22.5.0)(typescript@5.5.4) - postcss-load-config@4.0.1(postcss@8.4.40): + postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)): dependencies: lilconfig: 2.1.0 yaml: 2.3.4 optionalDependencies: postcss: 8.4.40 + ts-node: 10.9.1(@types/node@22.5.0)(typescript@5.2.2) optional: true postcss-merge-rules@7.0.2(postcss@8.4.39): @@ -29436,7 +29476,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.6.2(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0): + svelte-check@3.6.2(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0): dependencies: '@jridgewell/trace-mapping': 0.3.20 chokidar: 3.5.3 @@ -29445,7 +29485,7 @@ snapshots: picocolors: 1.0.0 sade: 1.8.1 svelte: 4.0.0 - svelte-preprocess: 5.1.3(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0)(typescript@5.4.5) + svelte-preprocess: 5.1.3(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0)(typescript@5.4.5) typescript: 5.4.5 transitivePeerDependencies: - '@babel/core' @@ -29462,7 +29502,7 @@ snapshots: dependencies: svelte: 4.0.0 - svelte-preprocess@5.1.3(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0)(typescript@5.4.5): + svelte-preprocess@5.1.3(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0)(typescript@5.4.5): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -29474,7 +29514,7 @@ snapshots: '@babel/core': 7.25.2 less: 4.2.0 postcss: 8.4.40 - postcss-load-config: 4.0.1(postcss@8.4.40) + postcss-load-config: 4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)) sass: 1.74.1 typescript: 5.4.5 @@ -29911,6 +29951,25 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.5.0 + acorn: 8.12.0 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.2.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optional: true + ts-node@10.9.1(@types/node@22.5.0)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -30216,7 +30275,7 @@ snapshots: transitivePeerDependencies: - rollup - unplugin-auto-import@0.16.7(@vueuse/core@9.13.0(vue@3.4.27(typescript@5.5.4)))(rollup@4.14.1): + unplugin-auto-import@0.16.7(@vueuse/core@9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)))(rollup@4.14.1): dependencies: '@antfu/utils': 0.7.10 '@rollup/pluginutils': 5.1.0(rollup@4.14.1) @@ -30227,7 +30286,7 @@ snapshots: unimport: 3.4.0(rollup@4.14.1) unplugin: 1.10.1 optionalDependencies: - '@vueuse/core': 9.13.0(vue@3.4.27(typescript@5.5.4)) + '@vueuse/core': 9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) transitivePeerDependencies: - rollup @@ -30828,9 +30887,11 @@ snapshots: optionalDependencies: '@vue/composition-api': 1.7.2(vue@3.4.15(typescript@5.5.4)) - vue-demi@0.14.10(vue@3.4.27(typescript@5.5.4)): + vue-demi@0.14.10(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)): dependencies: vue: 3.4.27(typescript@5.5.4) + optionalDependencies: + '@vue/composition-api': 1.7.2(vue@3.4.27(typescript@5.5.4)) optional: true vue-demi@0.14.7(@vue/composition-api@1.7.2(vue@3.4.15(typescript@5.5.4)))(vue@3.4.15(typescript@5.5.4)): @@ -31289,13 +31350,6 @@ snapshots: react: 18.2.0 zustand: 4.5.5(@types/react@18.2.35)(immer@10.0.3)(react@18.2.0) - zustand-utils@1.3.2(react@18.2.0)(zustand@4.5.5(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0)): - dependencies: - '@babel/runtime': 7.25.0 - fast-deep-equal: 3.1.3 - react: 18.2.0 - zustand: 4.5.5(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0) - zustand@4.5.5(@types/react@18.2.35)(immer@10.0.3)(react@18.2.0): dependencies: use-sync-external-store: 1.2.2(react@18.2.0) From 8a2b13c23c0e395008113700a049f26fcab3afd4 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Tue, 3 Sep 2024 17:59:19 +0800 Subject: [PATCH 092/369] fix: configureServer execution order --- examples/visualizer/farm.config.ts | 17 +++- examples/visualizer/package.json | 3 +- packages/core/src/newServer/index.ts | 5 +- pnpm-lock.yaml | 127 ++++++++++++--------------- 4 files changed, 72 insertions(+), 80 deletions(-) diff --git a/examples/visualizer/farm.config.ts b/examples/visualizer/farm.config.ts index e912274720..bed4d0ed6a 100644 --- a/examples/visualizer/farm.config.ts +++ b/examples/visualizer/farm.config.ts @@ -1,10 +1,20 @@ import { defineConfig } from "@farmfe/core"; // import viewer from "@farmfe/js-plugin-visualizer"; import react from "@farmfe/plugin-react"; +import compression from "compression"; +const myPlugin = () => ({ + name: "configure-server", + configureServer(server) { + server.middlewares.use(compression()); + server.middlewares.use((req, res, next) => { + // 自定义请求处理... + next(); + }); + }, +}); + export default defineConfig({ - plugins: [ - react(), - ], + plugins: [react(), myPlugin()], compilation: { output: { // publicPath: "/aaa/", @@ -12,6 +22,5 @@ export default defineConfig({ }, server: { // port: 3000, - }, }); diff --git a/examples/visualizer/package.json b/examples/visualizer/package.json index d88cd5f3e2..9cc2da0355 100644 --- a/examples/visualizer/package.json +++ b/examples/visualizer/package.json @@ -19,8 +19,9 @@ "@farmfe/js-plugin-visualizer": "workspace:*", "@farmfe/plugin-react": "workspace:*", "@types/react": "18", - "core-js": "^3.36.1", "@types/react-dom": "18", + "compression": "^1.7.4", + "core-js": "^3.36.1", "react-refresh": "^0.14.0" } } diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts index 0045717a4f..7cda0cc0ce 100644 --- a/packages/core/src/newServer/index.ts +++ b/packages/core/src/newServer/index.ts @@ -171,11 +171,11 @@ export class NewServer extends httpServer { this.#createWatcher(); + this.handleConfigureServer(); + // init middlewares this.#initializeMiddlewares(); - this.handleConfigureServer(); - if (!middlewareMode && this.httpServer) { this.httpServer.once('listening', () => { // update actual port since this may be different from initial value @@ -209,7 +209,6 @@ export class NewServer extends httpServer { } }); const { jsPlugins } = this.resolvedUserConfig; - // const postHooks: ((() => void) | void)[] = []; // TODO type error and 而且还要排序 插件排序 // @ts-ignore for (const hook of getPluginHooks(jsPlugins, 'configureServer')) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7aceff3d42..362958ed65 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1546,6 +1546,9 @@ importers: '@types/react-dom': specifier: '18' version: 18.2.14 + compression: + specifier: ^1.7.4 + version: 1.7.4 core-js: specifier: ^3.36.1 version: 3.37.1 @@ -1640,7 +1643,7 @@ importers: version: 4.0.0 svelte-check: specifier: ^3.6.2 - version: 3.6.2(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0) + version: 3.6.2(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0) tslib: specifier: ^2.6.2 version: 2.6.2 @@ -2017,7 +2020,7 @@ importers: dependencies: unplugin-auto-import: specifier: ^0.16.7 - version: 0.16.7(@vueuse/core@9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)))(rollup@4.14.1) + version: 0.16.7(@vueuse/core@9.13.0(vue@3.4.27(typescript@5.5.4)))(rollup@4.14.1) unplugin-vue-router: specifier: ^0.7.0 version: 0.7.0(rollup@4.14.1)(vue-router@4.4.3(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) @@ -15253,7 +15256,7 @@ snapshots: yjs: 13.6.18 zustand: 4.5.5(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0) zustand-middleware-yjs: 1.3.1(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0) - zustand-utils: 1.3.2(react@18.2.0)(zustand@4.5.5(@types/react@18.2.35)(immer@10.0.3)(react@18.2.0)) + zustand-utils: 1.3.2(react@18.2.0)(zustand@4.5.5(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0)) transitivePeerDependencies: - '@emotion/react' - '@types/react' @@ -15514,7 +15517,7 @@ snapshots: '@babel/traverse': 7.24.1 '@babel/types': 7.24.0 convert-source-map: 2.0.0 - debug: 4.3.5 + debug: 4.3.6 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -15638,7 +15641,7 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-plugin-utils': 7.24.0 - debug: 4.3.5 + debug: 4.3.6 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -16207,7 +16210,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.5 '@babel/types': 7.24.0 - debug: 4.3.5 + debug: 4.3.6 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -16222,7 +16225,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.5 '@babel/types': 7.24.0 - debug: 4.3.5 + debug: 4.3.6 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -17014,7 +17017,7 @@ snapshots: '@electron/rebuild': 3.6.0 '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 - debug: 4.3.5 + debug: 4.3.6 find-up: 5.0.0 fs-extra: 10.1.0 log-symbols: 4.1.0 @@ -17145,7 +17148,7 @@ snapshots: dependencies: '@electron-forge/shared-types': 7.4.0 '@malept/cross-spawn-promise': 2.0.0 - debug: 4.3.5 + debug: 4.3.6 fs-extra: 10.1.0 username: 5.1.0 transitivePeerDependencies: @@ -17228,7 +17231,7 @@ snapshots: '@electron/notarize@2.2.1': dependencies: - debug: 4.3.5 + debug: 4.3.6 fs-extra: 9.1.0 promise-retry: 2.0.1 transitivePeerDependencies: @@ -17236,7 +17239,7 @@ snapshots: '@electron/notarize@2.3.2': dependencies: - debug: 4.3.5 + debug: 4.3.6 fs-extra: 9.1.0 promise-retry: 2.0.1 transitivePeerDependencies: @@ -17245,7 +17248,7 @@ snapshots: '@electron/osx-sign@1.0.5': dependencies: compare-version: 0.1.2 - debug: 4.3.5 + debug: 4.3.6 fs-extra: 10.1.0 isbinaryfile: 4.0.10 minimist: 1.2.8 @@ -17256,7 +17259,7 @@ snapshots: '@electron/osx-sign@1.3.0': dependencies: compare-version: 0.1.2 - debug: 4.3.5 + debug: 4.3.6 fs-extra: 10.1.0 isbinaryfile: 4.0.10 minimist: 1.2.8 @@ -17312,7 +17315,7 @@ snapshots: dependencies: '@electron/asar': 3.2.10 '@malept/cross-spawn-promise': 1.1.1 - debug: 4.3.5 + debug: 4.3.6 dir-compare: 3.3.0 fs-extra: 9.1.0 minimatch: 3.1.2 @@ -17324,7 +17327,7 @@ snapshots: dependencies: '@electron/asar': 3.2.10 '@malept/cross-spawn-promise': 2.0.0 - debug: 4.3.5 + debug: 4.3.6 dir-compare: 4.2.0 fs-extra: 11.2.0 minimatch: 9.0.5 @@ -17335,7 +17338,7 @@ snapshots: '@electron/windows-sign@1.1.2': dependencies: cross-dirname: 0.1.0 - debug: 4.3.5 + debug: 4.3.6 fs-extra: 11.2.0 minimist: 1.2.8 postject: 1.0.0-alpha.6 @@ -17663,7 +17666,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.5 + debug: 4.3.6 espree: 9.6.1 globals: 13.24.0 ignore: 5.2.4 @@ -17716,7 +17719,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.5 + debug: 4.3.6 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -18003,7 +18006,7 @@ snapshots: '@malept/flatpak-bundler@0.4.0': dependencies: - debug: 4.3.5 + debug: 4.3.6 fs-extra: 9.1.0 lodash: 4.17.21 tmp-promise: 3.0.3 @@ -18576,7 +18579,7 @@ snapshots: '@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.1(svelte@4.0.0)(vite@5.2.8(@types/node@22.5.0)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1)))(svelte@4.0.0)(vite@5.2.8(@types/node@22.5.0)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1))': dependencies: '@sveltejs/vite-plugin-svelte': 3.0.1(svelte@4.0.0)(vite@5.2.8(@types/node@22.5.0)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1)) - debug: 4.3.4 + debug: 4.3.6 svelte: 4.0.0 vite: 5.2.8(@types/node@22.5.0)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1) transitivePeerDependencies: @@ -20124,7 +20127,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.4.5) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.4.5) - debug: 4.3.5 + debug: 4.3.6 eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.4.5) optionalDependencies: @@ -20138,7 +20141,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.5 + debug: 4.3.6 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -20914,11 +20917,6 @@ snapshots: vue: 3.4.15(typescript@5.5.4) optional: true - '@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4))': - dependencies: - vue: 3.4.27(typescript@5.5.4) - optional: true - '@vue/devtools-api@6.5.1': {} '@vue/devtools-api@6.6.3': {} @@ -21067,12 +21065,12 @@ snapshots: - '@vue/composition-api' - vue - '@vueuse/core@9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4))': + '@vueuse/core@9.13.0(vue@3.4.27(typescript@5.5.4))': dependencies: '@types/web-bluetooth': 0.0.16 '@vueuse/metadata': 9.13.0 - '@vueuse/shared': 9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) - vue-demi: 0.14.10(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) + '@vueuse/shared': 9.13.0(vue@3.4.27(typescript@5.5.4)) + vue-demi: 0.14.10(vue@3.4.27(typescript@5.5.4)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -21087,9 +21085,9 @@ snapshots: - '@vue/composition-api' - vue - '@vueuse/shared@9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4))': + '@vueuse/shared@9.13.0(vue@3.4.27(typescript@5.5.4))': dependencies: - vue-demi: 0.14.10(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) + vue-demi: 0.14.10(vue@3.4.27(typescript@5.5.4)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -23371,7 +23369,7 @@ snapshots: dependencies: '@malept/cross-spawn-promise': 1.1.1 asar: 3.2.0 - debug: 4.3.5 + debug: 4.3.6 fs-extra: 9.1.0 glob: 7.2.3 lodash: 4.17.21 @@ -24427,7 +24425,7 @@ snapshots: galactus@1.0.0: dependencies: - debug: 4.3.5 + debug: 4.3.6 flora-colossus: 2.0.0 fs-extra: 10.1.0 transitivePeerDependencies: @@ -24889,7 +24887,7 @@ snapshots: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.5 + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -24920,7 +24918,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.5 + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -25341,7 +25339,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.5 + debug: 4.3.6 istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -25861,7 +25859,7 @@ snapshots: koa-send@5.0.1: dependencies: - debug: 4.3.5 + debug: 4.3.6 http-errors: 1.8.1 resolve-path: 1.4.0 transitivePeerDependencies: @@ -27508,13 +27506,12 @@ snapshots: postcss: 8.4.39 ts-node: 10.9.1(@types/node@22.5.0)(typescript@5.5.4) - postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)): + postcss-load-config@4.0.1(postcss@8.4.40): dependencies: lilconfig: 2.1.0 yaml: 2.3.4 optionalDependencies: postcss: 8.4.40 - ts-node: 10.9.1(@types/node@22.5.0)(typescript@5.2.2) optional: true postcss-merge-rules@7.0.2(postcss@8.4.39): @@ -28388,7 +28385,7 @@ snapshots: read-binary-file-arch@1.0.6: dependencies: - debug: 4.3.5 + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -29430,7 +29427,7 @@ snapshots: sumchecker@3.0.1: dependencies: - debug: 4.3.5 + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -29438,7 +29435,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.3.5 + debug: 4.3.6 fast-safe-stringify: 2.1.1 form-data: 4.0.0 formidable: 2.1.2 @@ -29476,7 +29473,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.6.2(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0): + svelte-check@3.6.2(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0): dependencies: '@jridgewell/trace-mapping': 0.3.20 chokidar: 3.5.3 @@ -29485,7 +29482,7 @@ snapshots: picocolors: 1.0.0 sade: 1.8.1 svelte: 4.0.0 - svelte-preprocess: 5.1.3(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0)(typescript@5.4.5) + svelte-preprocess: 5.1.3(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0)(typescript@5.4.5) typescript: 5.4.5 transitivePeerDependencies: - '@babel/core' @@ -29502,7 +29499,7 @@ snapshots: dependencies: svelte: 4.0.0 - svelte-preprocess@5.1.3(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0)(typescript@5.4.5): + svelte-preprocess@5.1.3(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0)(typescript@5.4.5): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -29514,7 +29511,7 @@ snapshots: '@babel/core': 7.25.2 less: 4.2.0 postcss: 8.4.40 - postcss-load-config: 4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)) + postcss-load-config: 4.0.1(postcss@8.4.40) sass: 1.74.1 typescript: 5.4.5 @@ -29951,25 +29948,6 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 22.5.0 - acorn: 8.12.0 - acorn-walk: 8.3.2 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.2.2 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - optional: true - ts-node@10.9.1(@types/node@22.5.0)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -30275,7 +30253,7 @@ snapshots: transitivePeerDependencies: - rollup - unplugin-auto-import@0.16.7(@vueuse/core@9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)))(rollup@4.14.1): + unplugin-auto-import@0.16.7(@vueuse/core@9.13.0(vue@3.4.27(typescript@5.5.4)))(rollup@4.14.1): dependencies: '@antfu/utils': 0.7.10 '@rollup/pluginutils': 5.1.0(rollup@4.14.1) @@ -30286,7 +30264,7 @@ snapshots: unimport: 3.4.0(rollup@4.14.1) unplugin: 1.10.1 optionalDependencies: - '@vueuse/core': 9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) + '@vueuse/core': 9.13.0(vue@3.4.27(typescript@5.5.4)) transitivePeerDependencies: - rollup @@ -30516,7 +30494,7 @@ snapshots: vite-node@1.4.0(@types/node@22.5.0)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1): dependencies: cac: 6.7.14 - debug: 4.3.5 + debug: 4.3.6 pathe: 1.1.2 picocolors: 1.0.1 vite: 5.2.8(@types/node@22.5.0)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1) @@ -30887,11 +30865,9 @@ snapshots: optionalDependencies: '@vue/composition-api': 1.7.2(vue@3.4.15(typescript@5.5.4)) - vue-demi@0.14.10(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)): + vue-demi@0.14.10(vue@3.4.27(typescript@5.5.4)): dependencies: vue: 3.4.27(typescript@5.5.4) - optionalDependencies: - '@vue/composition-api': 1.7.2(vue@3.4.27(typescript@5.5.4)) optional: true vue-demi@0.14.7(@vue/composition-api@1.7.2(vue@3.4.15(typescript@5.5.4)))(vue@3.4.15(typescript@5.5.4)): @@ -31350,6 +31326,13 @@ snapshots: react: 18.2.0 zustand: 4.5.5(@types/react@18.2.35)(immer@10.0.3)(react@18.2.0) + zustand-utils@1.3.2(react@18.2.0)(zustand@4.5.5(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0)): + dependencies: + '@babel/runtime': 7.25.0 + fast-deep-equal: 3.1.3 + react: 18.2.0 + zustand: 4.5.5(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0) + zustand@4.5.5(@types/react@18.2.35)(immer@10.0.3)(react@18.2.0): dependencies: use-sync-external-store: 1.2.2(react@18.2.0) From a9e57ed84be27bba922f4fca9703a2f84afec3bc Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Wed, 4 Sep 2024 10:25:14 +0800 Subject: [PATCH 093/369] chore: remove unless file prepare refactor stage: 1 --- packages/cli/src/utils.ts | 10 +- packages/core/src/config/index.ts | 29 +- packages/core/src/config/types.ts | 13 + packages/core/src/index.ts | 617 ++++++------ packages/core/src/newServer/error.ts | 95 -- packages/core/src/newServer/hmr-engine.ts | 209 ---- packages/core/src/newServer/index.ts | 625 ------------ .../core/src/newServer/middlewares/hmrPing.ts | 13 - .../core/src/newServer/middlewares/index.ts | 11 - .../core/src/newServer/middlewares/proxy.ts | 176 ---- .../core/src/newServer/middlewares/static.ts | 0 packages/core/src/newServer/open.ts | 129 --- packages/core/src/newServer/type.ts | 100 -- packages/core/src/newServer/ws.ts | 388 -------- .../core/src/old-watcher/config-watcher.ts | 62 -- .../core/src/old-watcher/create-watcher.ts | 53 -- packages/core/src/old-watcher/index.ts | 164 ---- .../core/src/plugin/js/vite-server-adapter.ts | 13 +- packages/core/src/server/hmr-engine.ts | 105 +- .../core/src/{newServer => server}/hmr.ts | 0 .../core/src/{newServer => server}/http.ts | 0 packages/core/src/server/index.ts | 895 +++++++++++------- .../middlewares/adaptorVite.ts | 0 packages/core/src/server/middlewares/cors.ts | 11 - .../middlewares/error.ts | 0 .../core/src/server/middlewares/headers.ts | 16 - .../core/src/server/middlewares/hmrPing.ts | 15 +- .../middlewares/htmlFallback.ts | 0 packages/core/src/server/middlewares/index.ts | 13 +- .../server/middlewares/lazy-compilation.ts | 103 -- .../middlewares/lazyCompilation.ts | 0 .../middlewares/notFound.ts | 0 packages/core/src/server/middlewares/proxy.ts | 226 +++-- .../middlewares/publicPath.ts | 0 .../middlewares/publicResource.ts | 0 .../middlewares/resource.ts | 0 .../core/src/server/middlewares/resources.ts | 207 ---- .../core/src/server/middlewares/static.ts | 60 -- .../core/src/{newServer => server}/preview.ts | 0 .../src/{newServer => server}/publicDir.ts | 0 .../core/src/{newServer => server}/send.ts | 0 packages/core/src/server/type.ts | 101 +- packages/core/src/server/ws.ts | 495 ++++++---- packages/core/src/watcher/create-watcher.ts | 52 - packages/core/src/watcher/index.ts | 3 +- 45 files changed, 1518 insertions(+), 3491 deletions(-) delete mode 100644 packages/core/src/newServer/error.ts delete mode 100644 packages/core/src/newServer/hmr-engine.ts delete mode 100644 packages/core/src/newServer/index.ts delete mode 100644 packages/core/src/newServer/middlewares/hmrPing.ts delete mode 100644 packages/core/src/newServer/middlewares/index.ts delete mode 100644 packages/core/src/newServer/middlewares/proxy.ts delete mode 100644 packages/core/src/newServer/middlewares/static.ts delete mode 100644 packages/core/src/newServer/open.ts delete mode 100644 packages/core/src/newServer/type.ts delete mode 100644 packages/core/src/newServer/ws.ts delete mode 100644 packages/core/src/old-watcher/config-watcher.ts delete mode 100644 packages/core/src/old-watcher/create-watcher.ts delete mode 100644 packages/core/src/old-watcher/index.ts rename packages/core/src/{newServer => server}/hmr.ts (100%) rename packages/core/src/{newServer => server}/http.ts (100%) rename packages/core/src/{newServer => server}/middlewares/adaptorVite.ts (100%) delete mode 100644 packages/core/src/server/middlewares/cors.ts rename packages/core/src/{newServer => server}/middlewares/error.ts (100%) delete mode 100644 packages/core/src/server/middlewares/headers.ts rename packages/core/src/{newServer => server}/middlewares/htmlFallback.ts (100%) delete mode 100644 packages/core/src/server/middlewares/lazy-compilation.ts rename packages/core/src/{newServer => server}/middlewares/lazyCompilation.ts (100%) rename packages/core/src/{newServer => server}/middlewares/notFound.ts (100%) rename packages/core/src/{newServer => server}/middlewares/publicPath.ts (100%) rename packages/core/src/{newServer => server}/middlewares/publicResource.ts (100%) rename packages/core/src/{newServer => server}/middlewares/resource.ts (100%) delete mode 100644 packages/core/src/server/middlewares/resources.ts rename packages/core/src/{newServer => server}/preview.ts (100%) rename packages/core/src/{newServer => server}/publicDir.ts (100%) rename packages/core/src/{newServer => server}/send.ts (100%) delete mode 100644 packages/core/src/watcher/create-watcher.ts diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index eb91de7648..eaac9ad8bb 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -1,6 +1,7 @@ import { readFileSync } from 'node:fs'; import path from 'node:path'; -import type { build, clean, preview, start, watch } from '@farmfe/core'; +// import type { build, clean, preview, start, watch } from '@farmfe/core'; +import type { clean, start } from '@farmfe/core'; import { Logger } from '@farmfe/core'; import type { @@ -24,12 +25,13 @@ const logger = new Logger(); */ export async function resolveCore(): Promise<{ start: typeof start; - build: typeof build; - watch: typeof watch; - preview: typeof preview; + build: any; + watch: any; + preview: any; clean: typeof clean; }> { try { + // @ts-ignore return import('@farmfe/core'); } catch (err) { logger.error( diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index 7be4894889..67ececc3e0 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -542,7 +542,8 @@ export const DEFAULT_HMR_OPTIONS: Required = { watchOptions: {}, clientPort: 9000, timeout: 0, - server: null + server: null, + channels: [] }; export const DEFAULT_DEV_SERVER_OPTIONS: NormalizedServerConfig = { @@ -1046,19 +1047,19 @@ export async function resolveConfigFilePath( } } -export async function handleServerPortConflict( - resolvedUserConfig: ResolvedUserConfig, - logger: Logger, - mode?: CompilationMode -) { - // check port availability: auto increment the port if a conflict occurs - - try { - mode !== 'production' && - (await Server.resolvePortConflict(resolvedUserConfig.server, logger)); - // eslint-disable-next-line no-empty - } catch {} -} +// export async function handleServerPortConflict( +// resolvedUserConfig: ResolvedUserConfig, +// logger: Logger, +// mode?: CompilationMode +// ) { +// // check port availability: auto increment the port if a conflict occurs + +// try { +// mode !== 'production' && +// (await Server.resolvePortConflict(resolvedUserConfig.server, logger)); +// // eslint-disable-next-line no-empty +// } catch {} +// } export function checkClearScreen( inlineConfig: FarmCliOptions | ResolvedUserConfig diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index 3f0ee08403..72fb4ef547 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -8,9 +8,20 @@ import type { Options } from 'http-proxy-middleware'; import { Middleware } from 'koa'; import type { RustPlugin } from '../plugin/rust/index.js'; import type { JsPlugin } from '../plugin/type.js'; +import { HMRChannel } from '../server/hmr.js'; import type { Config } from '../types/binding.js'; import type { Logger } from '../utils/index.js'; +// export interface HmrOptions { +// protocol?: string; +// host?: string; +// port?: number; +// clientPort?: number; +// path?: string; +// timeout?: number; +// overlay?: boolean; +// server?: Server; +// } export interface HmrOptions { protocol?: string; host?: string; @@ -20,6 +31,8 @@ export interface HmrOptions { timeout?: number; overlay?: boolean; server?: Server; + /** @internal */ + channels?: HMRChannel[]; } export interface ConfigEnv { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c05bde5233..e717862697 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -37,8 +37,6 @@ import { normalizePublicDir, resolveConfig } from './config/index.js'; -import { NewServer } from './newServer/index.js'; -import { FileWatcher } from './old-watcher/index.js'; import { Server } from './server/index.js'; import { compilerHandler } from './utils/build.js'; import { colors } from './utils/color.js'; @@ -50,171 +48,171 @@ import type { ResolvedUserConfig, UserPreviewServerConfig } from './config/types.js'; -import { logError } from './server/error.js'; -import { lazyCompilation } from './server/middlewares/lazy-compilation.js'; +// import { logError } from './server/error.js'; +// import { lazyCompilation } from './server/middlewares/lazy-compilation.js'; import type { JsPlugin } from './plugin/type.js'; -export async function start( - inlineConfig?: FarmCliOptions & UserConfig -): Promise { - inlineConfig = inlineConfig ?? {}; - const logger = inlineConfig.logger ?? new Logger(); - setProcessEnv('development'); - - try { - const resolvedUserConfig = await resolveConfig( - inlineConfig, - 'start', - 'development', - 'development', - false - ); - - const compiler = await createCompiler(resolvedUserConfig, logger); - - const devServer = await createDevServer( - compiler, - resolvedUserConfig, - logger - ); - - await devServer.listen(); - } catch (error) { - logger.error('Failed to start the server', { exit: true, error }); - } -} - -export async function build( - inlineConfig?: FarmCliOptions & UserConfig -): Promise { - inlineConfig = inlineConfig ?? {}; - const logger = inlineConfig.logger ?? new Logger(); - setProcessEnv('production'); - - const resolvedUserConfig = await resolveConfig( - inlineConfig, - 'build', - 'production', - 'production', - false - ); - - try { - await createBundleHandler(resolvedUserConfig, logger); - // copy resources under publicDir to output.path - await copyPublicDirectory(resolvedUserConfig, logger); - } catch (err) { - logger.error(`Failed to build: ${err}`, { exit: true }); - } -} - -export async function preview(inlineConfig?: FarmCliOptions): Promise { - inlineConfig = inlineConfig ?? {}; - const logger = inlineConfig.logger ?? new Logger(); - const resolvedUserConfig = await resolveConfig( - inlineConfig, - 'preview', - 'production', - 'production', - true - ); - - const { root, output } = resolvedUserConfig.compilation; - const distDir = path.resolve(root, output.path); - - try { - statSync(distDir); - } catch (err) { - if (err.code === 'ENOENT') { - throw new Error( - `The directory "${distDir}" does not exist. Did you build your project?` - ); - } - } - - // reusing port conflict check from DevServer - const serverConfig = { - ...resolvedUserConfig.server, - host: inlineConfig.host ?? true, - port: - inlineConfig.port ?? - (Number(process.env.FARM_DEFAULT_SERVER_PORT) || 1911) - }; - await Server.resolvePortConflict(serverConfig, logger); - const port = serverConfig.port; - const host = serverConfig.host; - const previewOptions: UserPreviewServerConfig = { - ...serverConfig, - distDir, - output: { path: output.path, publicPath: output.publicPath }, - port, - host - }; - - const server = new Server({ logger }); - server.createPreviewServer(previewOptions); -} - -export async function watch( - inlineConfig?: FarmCliOptions & UserConfig -): Promise { - inlineConfig = inlineConfig ?? {}; - const logger = inlineConfig.logger ?? new Logger(); - setProcessEnv('development'); - - inlineConfig.server ??= {}; - inlineConfig.server.hmr ??= false; - - const resolvedUserConfig = await resolveConfig( - inlineConfig, - 'build', - 'production', - 'production', - false - ); - - const fileWatcher = await createBundleHandler( - resolvedUserConfig, - logger, - true - ); - - let devServer: Server | undefined; - // create dev server for lazy compilation - const lazyEnabled = resolvedUserConfig.compilation.lazyCompilation; - if (lazyEnabled) { - devServer = new Server({ - logger, - // TODO type error - // @ts-ignore - compiler: fileWatcher.serverOrCompiler as Compiler - }); - await devServer.createServer(resolvedUserConfig.server); - devServer.applyMiddlewares([lazyCompilation]); - await devServer.startServer(resolvedUserConfig.server); - } - - async function handleFileChange(files: string[]) { - logFileChanges(files, resolvedUserConfig.root, logger); - - try { - if (lazyEnabled && devServer) { - devServer.close(); - } - - __FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ = true; - - await fileWatcher?.close(); - - await watch(inlineConfig); - } catch (error) { - logger.error(`Error restarting the watcher: ${error.message}`); - } - } - - // fileWatcher.watchConfigs(handleFileChange); -} +// export async function start( +// inlineConfig?: FarmCliOptions & UserConfig +// ): Promise { +// inlineConfig = inlineConfig ?? {}; +// const logger = inlineConfig.logger ?? new Logger(); +// setProcessEnv('development'); + +// try { +// const resolvedUserConfig = await resolveConfig( +// inlineConfig, +// 'start', +// 'development', +// 'development', +// false +// ); + +// const compiler = await createCompiler(resolvedUserConfig, logger); + +// const devServer = await createDevServer( +// compiler, +// resolvedUserConfig, +// logger +// ); + +// await devServer.listen(); +// } catch (error) { +// logger.error('Failed to start the server', { exit: true, error }); +// } +// } + +// export async function build( +// inlineConfig?: FarmCliOptions & UserConfig +// ): Promise { +// inlineConfig = inlineConfig ?? {}; +// const logger = inlineConfig.logger ?? new Logger(); +// setProcessEnv('production'); + +// const resolvedUserConfig = await resolveConfig( +// inlineConfig, +// 'build', +// 'production', +// 'production', +// false +// ); + +// try { +// await createBundleHandler(resolvedUserConfig, logger); +// // copy resources under publicDir to output.path +// await copyPublicDirectory(resolvedUserConfig, logger); +// } catch (err) { +// logger.error(`Failed to build: ${err}`, { exit: true }); +// } +// } + +// export async function preview(inlineConfig?: FarmCliOptions): Promise { +// inlineConfig = inlineConfig ?? {}; +// const logger = inlineConfig.logger ?? new Logger(); +// const resolvedUserConfig = await resolveConfig( +// inlineConfig, +// 'preview', +// 'production', +// 'production', +// true +// ); + +// const { root, output } = resolvedUserConfig.compilation; +// const distDir = path.resolve(root, output.path); + +// try { +// statSync(distDir); +// } catch (err) { +// if (err.code === 'ENOENT') { +// throw new Error( +// `The directory "${distDir}" does not exist. Did you build your project?` +// ); +// } +// } + +// // reusing port conflict check from DevServer +// const serverConfig = { +// ...resolvedUserConfig.server, +// host: inlineConfig.host ?? true, +// port: +// inlineConfig.port ?? +// (Number(process.env.FARM_DEFAULT_SERVER_PORT) || 1911) +// }; +// await Server.resolvePortConflict(serverConfig, logger); +// const port = serverConfig.port; +// const host = serverConfig.host; +// const previewOptions: UserPreviewServerConfig = { +// ...serverConfig, +// distDir, +// output: { path: output.path, publicPath: output.publicPath }, +// port, +// host +// }; + +// // const server = new Server({ logger }); +// server.createPreviewServer(previewOptions); +// } + +// export async function watch( +// inlineConfig?: FarmCliOptions & UserConfig +// ): Promise { +// inlineConfig = inlineConfig ?? {}; +// const logger = inlineConfig.logger ?? new Logger(); +// setProcessEnv('development'); + +// inlineConfig.server ??= {}; +// inlineConfig.server.hmr ??= false; + +// const resolvedUserConfig = await resolveConfig( +// inlineConfig, +// 'build', +// 'production', +// 'production', +// false +// ); + +// const fileWatcher = await createBundleHandler( +// resolvedUserConfig, +// logger, +// true +// ); + +// let devServer: Server | undefined; +// // create dev server for lazy compilation +// const lazyEnabled = resolvedUserConfig.compilation.lazyCompilation; +// if (lazyEnabled) { +// devServer = new Server({ +// logger, +// // TODO type error +// // @ts-ignore +// compiler: fileWatcher.serverOrCompiler as Compiler +// }); +// await devServer.createServer(resolvedUserConfig.server); +// devServer.applyMiddlewares([lazyCompilation]); +// await devServer.startServer(resolvedUserConfig.server); +// } + +// async function handleFileChange(files: string[]) { +// logFileChanges(files, resolvedUserConfig.root, logger); + +// try { +// if (lazyEnabled && devServer) { +// devServer.close(); +// } + +// __FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ = true; + +// await fileWatcher?.close(); + +// await watch(inlineConfig); +// } catch (error) { +// logger.error(`Error restarting the watcher: ${error.message}`); +// } +// } + +// // fileWatcher.watchConfigs(handleFileChange); +// } export async function clean( rootPath: string, @@ -282,36 +280,37 @@ async function findNodeModulesRecursively(rootPath: string): Promise { return result; } -export async function createBundleHandler( - resolvedUserConfig: ResolvedUserConfig, - logger: Logger, - watchMode = false -) { - const compiler = await createCompiler(resolvedUserConfig, logger); - - await compilerHandler( - async () => { - if (resolvedUserConfig.compilation?.output?.clean) { - compiler.removeOutputPathDir(); - } - - try { - await compiler.compile(); - } catch (err) { - throw new Error(logError(err) as unknown as string); - } - compiler.writeResourcesToDisk(); - }, - resolvedUserConfig, - logger - ); - - if (resolvedUserConfig.compilation?.watch || watchMode) { - const watcher = new FileWatcher(compiler, resolvedUserConfig, logger); - await watcher.watch(); - return watcher; - } -} +// export async function createBundleHandler( +// resolvedUserConfig: ResolvedUserConfig, +// logger: Logger, +// watchMode = false +// ) { +// const compiler = await createCompiler(resolvedUserConfig, logger); + +// await compilerHandler( +// async () => { +// if (resolvedUserConfig.compilation?.output?.clean) { +// compiler.removeOutputPathDir(); +// } + +// try { +// await compiler.compile(); +// } catch (err) { +// // throw new Error(logError(err) as unknown as string); +// throw new Error(err as unknown as string); +// } +// compiler.writeResourcesToDisk(); +// }, +// resolvedUserConfig, +// logger +// ); + +// if (resolvedUserConfig.compilation?.watch || watchMode) { +// const watcher = new FileWatcher(compiler, resolvedUserConfig, logger); +// await watcher.watch(); +// return watcher; +// } +// } export async function createCompiler( resolvedUserConfig: ResolvedUserConfig, @@ -394,118 +393,118 @@ async function copyPublicDirectory( } } -export async function createDevServer( - compiler: Compiler, - resolvedUserConfig: ResolvedUserConfig, - logger: Logger -) { - const server = new Server({ compiler, logger }); - await server.createDevServer(resolvedUserConfig.server); - await createFileWatcher(server, resolvedUserConfig, logger); - // call configureDevServer hook after both server and watcher are ready - resolvedUserConfig.jsPlugins.forEach((plugin: JsPlugin) => - plugin.configureDevServer?.(server) - ); - - return server; -} - -export async function createFileWatcher( - devServer: Server, - resolvedUserConfig: ResolvedUserConfig, - logger: Logger = new Logger() -) { - // if ( - // devServer.config.hmr && - // resolvedUserConfig.compilation.mode === "production" - // ) { - // logger.error("HMR cannot be enabled in production mode."); - // return; - // } - - if (!devServer.config.hmr) { - return; - } - - if (devServer.watcher) { - return; - } - - const configFilePath = await getConfigFilePath(resolvedUserConfig.root); - const fileWatcher = new FileWatcher( - // @ts-ignore - devServer, - { ...resolvedUserConfig, configFilePath }, - logger - ); - devServer.watcher = fileWatcher; - await fileWatcher.watch(); - - // fileWatcher.watchConfigs(async (files: string[]) => { - // checkClearScreen(resolvedUserConfig); - - // devServer.restart(async () => { - // logFileChanges(files, resolvedUserConfig.root, logger); - // fileWatcher?.close(); - - // await devServer.close(); - // __FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ = true; - // await start(resolvedUserConfig as FarmCliOptions & UserConfig); - // }); - // }); - return fileWatcher; -} - -export async function createFileWatcher2( - devServer: NewServer, - resolvedUserConfig: ResolvedUserConfig, - logger: Logger = new Logger() -) { - // if ( - // resolvedUserConfig.server.hmr && - // resolvedUserConfig.compilation.mode === "production" - // ) { - // logger.error("HMR cannot be enabled in production mode."); - // return; - // } - - if (!resolvedUserConfig.server.hmr) { - return; - } - - // if (resolvedUserConfig.server.watcher) { - // return; - // } - - const configFilePath = await getConfigFilePath(resolvedUserConfig.root); - // const fileWatcher = new FileWatcher( - // devServer, - // { ...resolvedUserConfig, configFilePath }, - // logger, - // ); - // devServer.watcher = fileWatcher; - // await fileWatcher.watch(); - - // fileWatcher.watchConfigs(async (files: string[]) => { - // checkClearScreen(resolvedUserConfig); - // logFileChanges(files, resolvedUserConfig.root, logger); - - // await fileWatcher.close(); - // await devServer.close(); - // __FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ = true; - - // await start2(resolvedUserConfig as FarmCliOptions & UserConfig); - // // devServer.restart(async () => { - // // logFileChanges(files, resolvedUserConfig.root, logger); - // // farmWatcher?.close(); - - // // await devServer.close(); - // // __FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ = true; - // // await start(resolvedUserConfig as FarmCliOptions & UserConfig); - // // }); - // }); - // return fileWatcher; -} +// export async function createDevServer( +// compiler: Compiler, +// resolvedUserConfig: ResolvedUserConfig, +// logger: Logger +// ) { +// const server = new Server({ compiler, logger }); +// await server.createDevServer(resolvedUserConfig.server); +// await createFileWatcher(server, resolvedUserConfig, logger); +// // call configureDevServer hook after both server and watcher are ready +// resolvedUserConfig.jsPlugins.forEach((plugin: JsPlugin) => +// plugin.configureDevServer?.(server) +// ); + +// return server; +// } + +// export async function createFileWatcher( +// devServer: Server, +// resolvedUserConfig: ResolvedUserConfig, +// logger: Logger = new Logger() +// ) { +// if ( +// devServer.config.hmr && +// resolvedUserConfig.compilation.mode === "production" +// ) { +// logger.error("HMR cannot be enabled in production mode."); +// return; +// } + +// if (!devServer.config.hmr) { +// return; +// } + +// if (devServer.watcher) { +// return; +// } + +// const configFilePath = await getConfigFilePath(resolvedUserConfig.root); +// const fileWatcher = new FileWatcher( +// // @ts-ignore +// devServer, +// { ...resolvedUserConfig, configFilePath }, +// logger +// ); +// devServer.watcher = fileWatcher; +// await fileWatcher.watch(); + +// // fileWatcher.watchConfigs(async (files: string[]) => { +// // checkClearScreen(resolvedUserConfig); + +// // devServer.restart(async () => { +// // logFileChanges(files, resolvedUserConfig.root, logger); +// // fileWatcher?.close(); + +// // await devServer.close(); +// // __FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ = true; +// // await start(resolvedUserConfig as FarmCliOptions & UserConfig); +// // }); +// // }); +// return fileWatcher; +// } + +// export async function createFileWatcher2( +// devServer: Server, +// resolvedUserConfig: ResolvedUserConfig, +// logger: Logger = new Logger() +// ) { +// // if ( +// // resolvedUserConfig.server.hmr && +// // resolvedUserConfig.compilation.mode === "production" +// // ) { +// // logger.error("HMR cannot be enabled in production mode."); +// // return; +// // } + +// if (!resolvedUserConfig.server.hmr) { +// return; +// } + +// // if (resolvedUserConfig.server.watcher) { +// // return; +// // } + +// const configFilePath = await getConfigFilePath(resolvedUserConfig.root); +// // const fileWatcher = new FileWatcher( +// // devServer, +// // { ...resolvedUserConfig, configFilePath }, +// // logger, +// // ); +// // devServer.watcher = fileWatcher; +// // await fileWatcher.watch(); + +// // fileWatcher.watchConfigs(async (files: string[]) => { +// // checkClearScreen(resolvedUserConfig); +// // logFileChanges(files, resolvedUserConfig.root, logger); + +// // await fileWatcher.close(); +// // await devServer.close(); +// // __FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ = true; + +// // await start2(resolvedUserConfig as FarmCliOptions & UserConfig); +// // // devServer.restart(async () => { +// // // logFileChanges(files, resolvedUserConfig.root, logger); +// // // farmWatcher?.close(); + +// // // await devServer.close(); +// // // __FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ = true; +// // // await start(resolvedUserConfig as FarmCliOptions & UserConfig); +// // // }); +// // }); +// // return fileWatcher; +// } export function logFileChanges(files: string[], root: string, logger: Logger) { const changedFiles = files @@ -518,9 +517,9 @@ export function logFileChanges(files: string[], root: string, logger: Logger) { export { defineFarmConfig as defineConfig } from './config/index.js'; -export { loadEnv }; +export { loadEnv, Server }; -export async function start2( +export async function start( inlineConfig?: FarmCliOptions & UserConfig ): Promise { inlineConfig = inlineConfig ?? {}; @@ -536,7 +535,7 @@ export async function start2( false ); - const server = new NewServer(resolvedUserConfig, logger); + const server = new Server(resolvedUserConfig, logger); await server.createServer(); // TODO 这段逻辑放在 创建 http server 之后 放到 server 里面 diff --git a/packages/core/src/newServer/error.ts b/packages/core/src/newServer/error.ts deleted file mode 100644 index 467f338eca..0000000000 --- a/packages/core/src/newServer/error.ts +++ /dev/null @@ -1,95 +0,0 @@ -import type { RollupError } from 'rollup'; -import { colors } from '../utils/color.js'; -import { pad } from '../utils/share.js'; -// import { DevServer } from './index.js'; - -export function prepareError(err: Error & { potentialSolution?: string }) { - return { - message: stripAnsi(err.message), - stack: stripAnsi(cleanStack(err.stack || '')), - id: (err as RollupError).id, - frame: stripAnsi((err as RollupError).frame || ''), - plugin: (err as RollupError).plugin, - pluginCode: (err as RollupError).pluginCode?.toString(), - loc: (err as RollupError).loc, - potential: err.potentialSolution || '' - }; -} - -export function stripAnsi(str: string) { - // eslint-disable-next-line no-control-regex - return str.replace(/\x1b\[[0-9;]*m/g, ''); -} - -export function cleanStack(stack: string) { - return stack - .split(/\n/g) - .filter((l) => /^\s*at/.test(l)) - .join('\n'); -} - -export function buildErrorMessage( - err: RollupError & { source: string }, - args: string[] = [], - includeStack = true -): string { - if (err.plugin) args.push(` Plugin: ${colors.magenta(err.plugin)}`); - const loc = err.loc ? `:${err.loc.line}:${err.loc.column}` : ''; - if (err.id) args.push(` File: ${colors.cyan(err.id)}${loc}`); - if (err.frame) args.push(colors.yellow(pad(err.frame))); - else if (err.source) args.push(colors.yellow(err.source)); - if (includeStack && err.stack) args.push(pad(cleanStack(err.stack))); - return args.join('\n'); -} - -export function logError(err: Error, throwErrorFlag = true) { - let errorMessages: string[] = []; - try { - errorMessages = JSON.parse(err.message); - } catch (_) { - throw new Error(err.message); - } - - if (!Array.isArray(errorMessages) || errorMessages.length === 0) { - if (throwErrorFlag) { - throw new Error(err.message); - } - return err.message; - } - - const formattedErrorMessages = errorMessages.map((errorMsg: string) => { - try { - const parsedErrorMsg = JSON.parse(errorMsg); - if ( - parsedErrorMsg && - typeof parsedErrorMsg === 'object' && - (parsedErrorMsg.message || parsedErrorMsg.reason) - ) { - return `${buildErrorMessage(parsedErrorMsg, [ - colors.red( - `Internal server error: ${ - parsedErrorMsg.message || parsedErrorMsg.reason - }` - ) - ])}`; - } else { - return colors.red(errorMsg); - } - } catch { - return colors.red(errorMsg); - } - }); - const errorMessage = formattedErrorMessages.join('\n'); - if (throwErrorFlag) { - throw new Error(errorMessage); - } - return errorMessage; -} - -// TODO server logger e.g: DevServer.logger.error(msg); - -// server.ws.send({ -// type: 'error', -// err: prepareError(err) -// }); -// } diff --git a/packages/core/src/newServer/hmr-engine.ts b/packages/core/src/newServer/hmr-engine.ts deleted file mode 100644 index 2a339f5af2..0000000000 --- a/packages/core/src/newServer/hmr-engine.ts +++ /dev/null @@ -1,209 +0,0 @@ -import fse from 'fs-extra'; -// queue all updates and compile them one by one - -import { stat } from 'node:fs/promises'; -import { isAbsolute, relative } from 'node:path'; - -import type { Resource } from '@farmfe/runtime/src/resource-loader.js'; -import { Compiler } from '../compiler/index.js'; -import { - UserConfig, - UserHmrConfig, - checkClearScreen -} from '../config/index.js'; -import { HttpServer } from '../newServer/index.js'; -import type { JsUpdateResult } from '../types/binding.js'; -import { - Logger, - bold, - cyan, - formatExecutionTime, - green, - lightCyan -} from '../utils/index.js'; -import { logError } from './error.js'; -import { WebSocketClient, WebSocketServer } from './ws.js'; - -export class HmrEngine { - private _updateQueue: string[] = []; - // private _updateResults: Map; - - private _onUpdates: ((result: JsUpdateResult) => void)[]; - - private _lastModifiedTimestamp: Map; - constructor( - // compiler: Compiler, - // devServer: HttpServer, - // config: UserConfig, - // ws: WebSocketServer, - // private _logger: Logger - private readonly app: any - ) { - // this._lastAttemptWasError = false; - this._lastModifiedTimestamp = new Map(); - } - - callUpdates(result: JsUpdateResult) { - this._onUpdates?.forEach((cb) => cb(result)); - } - - onUpdateFinish(cb: (result: JsUpdateResult) => void) { - if (!this._onUpdates) { - this._onUpdates = []; - } - this._onUpdates.push(cb); - } - - recompileAndSendResult = async (): Promise => { - const queue = [...this._updateQueue]; - - if (queue.length === 0) { - return; - } - - let updatedFilesStr = queue - .map((item) => { - if (isAbsolute(item)) { - return relative(this.app.compiler.config.config.root, item); - } else { - const resolvedPath = this.app.compiler.transformModulePath( - this.app.compiler.config.config.root, - item - ); - return relative(this.app.compiler.config.config.root, resolvedPath); - } - }) - .join(', '); - if (updatedFilesStr.length >= 100) { - updatedFilesStr = - updatedFilesStr.slice(0, 100) + `...(${queue.length} files)`; - } - - try { - // we must add callback before update - this.app.compiler.onUpdateFinish(async () => { - // if there are more updates, recompile again - if (this._updateQueue.length > 0) { - await this.recompileAndSendResult(); - } - if (this.app.resolvedUserConfig?.server.writeToDisk) { - this.app.compiler.writeResourcesToDisk(); - } - }); - - checkClearScreen(this.app.compiler.config.config); - const start = performance.now(); - const result = await this.app.compiler.update(queue); - this.app.logger.info( - `${bold(lightCyan(updatedFilesStr))} updated in ${bold( - green(formatExecutionTime(performance.now() - start, 's')) - )}` - ); - - // clear update queue after update finished - this._updateQueue = this._updateQueue.filter( - (item) => !queue.includes(item) - ); - - let dynamicResourcesMap: Record = null; - - if (result.dynamicResourcesMap) { - for (const [key, value] of Object.entries(result.dynamicResourcesMap)) { - if (!dynamicResourcesMap) { - dynamicResourcesMap = {} as Record; - } - - // @ts-ignore - dynamicResourcesMap[key] = value.map((r) => ({ - path: r[0], - type: r[1] as 'script' | 'link' - })); - } - } - const { - added, - changed, - removed, - immutableModules, - mutableModules, - boundaries - } = result; - const resultStr = `{ - added: [${formatHmrResult(added)}], - changed: [${formatHmrResult(changed)}], - removed: [${formatHmrResult(removed)}], - immutableModules: ${JSON.stringify(immutableModules.trim())}, - mutableModules: ${JSON.stringify(mutableModules.trim())}, - boundaries: ${JSON.stringify(boundaries)}, - dynamicResourcesMap: ${JSON.stringify(dynamicResourcesMap)} - }`; - - this.callUpdates(result); - - this.app.ws.wss.clients.forEach((client: WebSocketClient) => { - // @ts-ignore - client.send(` - { - type: 'farm-update', - result: ${resultStr} - } - `); - }); - } catch (err) { - // checkClearScreen(this.app.compiler.config.config); - throw new Error(err); - } - }; - - async hmrUpdate(absPath: string | string[], force = false) { - const paths = Array.isArray(absPath) ? absPath : [absPath]; - - for (const path of paths) { - if ( - this.app.compiler.hasModule(path) && - !this._updateQueue.includes(path) - ) { - if (fse.existsSync(path)) { - const lastModifiedTimestamp = this._lastModifiedTimestamp.get(path); - const currentTimestamp = (await stat(path)).mtime.toISOString(); - // only update the file if the timestamp changed since last update - if (!force && lastModifiedTimestamp === currentTimestamp) { - continue; - } - this._lastModifiedTimestamp.set(path, currentTimestamp); - } - // push the path into the queue - this._updateQueue.push(path); - } - } - - if (!this.app.compiler.compiling && this._updateQueue.length > 0) { - try { - await this.recompileAndSendResult(); - } catch (e) { - // eslint-disable-next-line no-control-regex - const serialization = e.message.replace(/\x1b\[[0-9;]*m/g, ''); - const errorStr = `${JSON.stringify({ - message: serialization - })}`; - this.app.ws.wss.clients.forEach((client: WebSocketClient) => { - // @ts-ignore - // client.rawSend(` - client.send(` - { - type: 'error', - err: ${errorStr}, - overlay: ${(this.app.resolvedUserConfig.server.hmr as UserHmrConfig).overlay} - } - `); - }); - // this.app.logger.error(e); - throw new Error(`hmr update failed: ${e.stack}`); - } - } - } -} - -function formatHmrResult(array: string[]) { - return array.map((item) => `'${item.replaceAll('\\', '\\\\')}'`).join(', '); -} diff --git a/packages/core/src/newServer/index.ts b/packages/core/src/newServer/index.ts deleted file mode 100644 index 7cda0cc0ce..0000000000 --- a/packages/core/src/newServer/index.ts +++ /dev/null @@ -1,625 +0,0 @@ -import fs, { PathLike } from 'node:fs'; -import { WatchOptions } from 'chokidar'; -import connect from 'connect'; -import corsMiddleware from 'cors'; - -import { Compiler } from '../compiler/index.js'; -import { colors, createCompiler } from '../index.js'; -import Watcher from '../watcher/index.js'; -import { HmrEngine } from './hmr-engine.js'; -import { CommonServerOptions, httpServer } from './http.js'; -import { openBrowser } from './open.js'; -import { WsServer } from './ws.js'; - -import { __FARM_GLOBAL__ } from '../config/_global.js'; -import { getCacheDir, isCacheDirExists } from '../utils/cacheDir.js'; -import { Logger, bootstrap, logger } from '../utils/logger.js'; -import { initPublicFiles } from '../utils/publicDir.js'; -import { isObject } from '../utils/share.js'; - -import { - adaptorViteMiddleware, - hmrPingMiddleware, - htmlFallbackMiddleware, - lazyCompilationMiddleware, - notFoundMiddleware, - proxyMiddleware, - publicMiddleware, - publicPathMiddleware, - resourceMiddleware -} from './middlewares/index.js'; - -import type * as http from 'node:http'; -import type { ServerOptions as HttpsServerOptions, Server } from 'node:http'; -import type { Http2SecureServer } from 'node:http2'; -import type * as net from 'node:net'; -import type { HMRChannel } from './hmr.js'; - -import type { - NormalizedServerConfig, - ResolvedUserConfig -} from '../config/types.js'; -import { getPluginHooks, getSortedPluginHooks } from '../plugin/index.js'; -import { JsUpdateResult } from '../types/binding.js'; -import { createDebugger } from '../utils/debug.js'; - -export type HttpServer = Server | Http2SecureServer; - -type CompilerType = Compiler | undefined; - -export interface HmrOptions { - protocol?: string; - host?: string; - port?: number; - clientPort?: number; - path?: string; - timeout?: number; - overlay?: boolean; - server?: Server; - /** @internal */ - channels?: HMRChannel[]; -} - -export interface ServerOptions extends CommonServerOptions { - /** - * Configure HMR-specific options (port, host, path & protocol) - */ - hmr?: HmrOptions | boolean; - /** - * Do not start the websocket connection. - * @experimental - */ - ws?: false; - /** - * chokidar watch options or null to disable FS watching - * https://github.com/paulmillr/chokidar#api - */ - watchOptions?: WatchOptions | undefined; - /** - * Create dev server to be used as a middleware in an existing server - * @default false - */ - middlewareMode?: - | boolean - | { - /** - * Parent server instance to attach to - * - * This is needed to proxy WebSocket connections to the parent server. - */ - server: http.Server; - }; - origin?: string; -} - -export const debugServer = createDebugger('farm:server'); - -export function noop() { - // noop -} - -type ServerConfig = CommonServerOptions & NormalizedServerConfig; - -// TODO 改 newServer 的 name and PascalCase -export class NewServer extends httpServer { - ws: WsServer; - serverOptions: ServerConfig; - httpsOptions: HttpsServerOptions; - publicDir: string | boolean | undefined; - publicPath?: string; - publicFiles?: Set; - httpServer: HttpServer; - watcher: Watcher; - hmrEngine?: HmrEngine; - middlewares: connect.Server; - compiler: CompilerType; - root: string; - closeHttpServerFn: () => Promise; - postConfigureServerHooks: ((() => void) | void)[] = []; - constructor( - readonly resolvedUserConfig: ResolvedUserConfig, - logger: Logger - ) { - super(logger); - this.#resolveOptions(); - } - - /** - * Creates and initializes the server. - * - * This method performs the following operations: - * Resolves HTTPS configuration - * Handles public files - * Creates middleware - * Creates HTTP server (if not in middleware mode) - * Initializes HMR engine - * Creates WebSocket server - * Sets up Vite invalidation handler - * Initializes middlewares - * - * @returns {Promise} A promise that resolves when the server creation process is complete - * @throws {Error} If an error occurs during the server creation process - */ - async createServer(): Promise { - try { - const { https, middlewareMode } = this.serverOptions; - - this.httpsOptions = await this.resolveHttpsConfig(https); - this.publicFiles = await this.#handlePublicFiles(); - - this.middlewares = connect() as connect.Server; - this.httpServer = middlewareMode - ? null - : await this.resolveHttpServer( - this.serverOptions as CommonServerOptions, - this.middlewares, - this.httpsOptions - ); - - // close server function prepare promise - this.closeHttpServerFn = this.closeServer(); - - // init hmr engine When actually updating, we need to get the clients of ws for broadcast, 、 - // so we can instantiate hmrEngine by default at the beginning. - this.createHmrEngine(); - - // init websocket server - this.createWebSocketServer(); - - // invalidate vite handler - this.#invalidateVite(); - - this.#createWatcher(); - - this.handleConfigureServer(); - - // init middlewares - this.#initializeMiddlewares(); - - if (!middlewareMode && this.httpServer) { - this.httpServer.once('listening', () => { - // update actual port since this may be different from initial value - this.serverOptions.port = ( - this.httpServer.address() as net.AddressInfo - ).port; - }); - } - // TODO apply server configuration hooks from plugins e.g. vite configureServer - // const postHooks: ((() => void) | void)[] = []; - // console.log(this.resolvedUserConfig.jsPlugins); - // TODO 要在这里做 vite 插件和 js 插件的适配器 - // for (const hook of getPluginHooks(applyPlugins, "configureServer")) { - // postHooks.push(await hook(reflexServer)); - // } - } catch (error) { - this.logger.error(`Failed to create farm server: ${error}`); - throw error; - } - } - - async handleConfigureServer() { - const reflexServer = new Proxy(this, { - get: (_, property: keyof NewServer) => { - return this[property]; - }, - set: (_, property: keyof NewServer, value: never) => { - //@ts-ignore - this[property] = value; - return true; - } - }); - const { jsPlugins } = this.resolvedUserConfig; - // TODO type error and 而且还要排序 插件排序 - // @ts-ignore - for (const hook of getPluginHooks(jsPlugins, 'configureServer')) { - this.postConfigureServerHooks.push(await hook(reflexServer)); - } - } - - /** - * - */ - async #createWatcher() { - this.watcher = new Watcher(this.resolvedUserConfig); - - await this.watcher.createWatcher(); - - this.watcher.watcher.on('change', async (file: string | string[] | any) => { - const isConfigFile = this.resolvedUserConfig.configFilePath === file; - const isConfigDependencyFile = - this.resolvedUserConfig.configFileDependencies.some( - (name) => file === name - ); - const isEnvFile = this.resolvedUserConfig.envFiles.some( - (name) => file === name - ); - if (isConfigFile || isConfigDependencyFile || isEnvFile) { - debugServer?.(`[config change] ${colors.dim(file)}`); - await this.close(); - console.log('重启大法'); - - setTimeout(() => { - this.restartServer(); - }, 3000); - } - // TODO 做一个 onHmrUpdate 方法 - try { - this.hmrEngine.hmrUpdate(file); - } catch (error) { - this.logger.error(`Farm Hmr Update Error: ${error}`); - } - }); - const handleUpdateFinish = (updateResult: JsUpdateResult) => { - const added = [ - ...updateResult.added, - ...updateResult.extraWatchResult.add - ].map((addedModule) => { - const resolvedPath = this.compiler.transformModulePath( - this.root, - addedModule - ); - return resolvedPath; - }); - const filteredAdded = added.filter((file) => - this.watcher.filterWatchFile(file, this.root) - ); - - if (filteredAdded.length > 0) { - this.watcher.watcher.add(filteredAdded); - } - }; - - this.hmrEngine?.onUpdateFinish(handleUpdateFinish); - } - - async restartServer() { - console.log('开启重启大法呜啦啦'); - await this.createServer(); - await this.listen(); - } - - /** - * Creates and initializes the WebSocket server. - * @throws {Error} If the HTTP server is not created. - */ - async createWebSocketServer() { - if (!this.httpServer) { - throw new Error( - 'Websocket requires a http server. please check the server is be created' - ); - } - - this.ws = new WsServer(this); - } - - /** - * Creates and initializes the Hot Module Replacement (HMR) engine. - * @throws {Error} If the HTTP server is not created. - */ - createHmrEngine() { - if (!this.httpServer) { - throw new Error( - 'HmrEngine requires a http server. please check the server is be created' - ); - } - - this.hmrEngine = new HmrEngine(this); - } - - /** - * Starts the server and begins listening for connections. - * @returns {Promise} - * @throws {Error} If there's an error starting the server. - */ - async listen(): Promise { - if (!this.httpServer) { - this.logger.warn('HTTP server is not created yet'); - return; - } - // TODO open browser when server is ready && open config is true - - const { port, hostname, open, strictPort } = this.serverOptions; - - try { - const serverPort = await this.httpServerStart({ - port, - strictPort: strictPort, - host: hostname.host - }); - - // TODO 这块要重新设计 restart 还有 端口冲突的问题 - // this.resolvedUserConfig - this.resolvedUserConfig.compilation.define.FARM_HMR_PORT = - serverPort.toString(); - - // TODO 暂时注释掉 - this.compiler = await createCompiler(this.resolvedUserConfig, logger); - - // compile the project and start the dev server - await this.#startCompile(); - - // watch extra files after compile - this.watcher?.watchExtraFiles?.(); - // !__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ && - await this.displayServerUrls(this.serverOptions, this.publicPath); - - if (open) { - this.#openServerBrowser(); - } - } catch (error) { - this.logger.error(`start farm dev server error: ${error}`); - throw error; - } - } - - /** - * Starts the HTTP server. - * @protected - * @param {Object} serverOptions - The server options. - * @returns {Promise} The port the server is listening on. - * @throws {Error} If the server fails to start. - */ - protected async httpServerStart(serverOptions: { - port: number; - strictPort: boolean | undefined; - host: string | undefined; - }): Promise { - if (!this.httpServer) { - throw new Error('httpServer is not initialized'); - } - - let { port, strictPort, host } = serverOptions; - - return new Promise((resolve, reject) => { - const onError = (e: Error & { code?: string }) => { - if (e.code === 'EADDRINUSE') { - if (strictPort) { - this.httpServer.removeListener('error', onError); - reject(new Error(`Port ${port} is already in use`)); - } else { - console.info(`Port ${port} is in use, trying another one...`); - this.httpServer.listen(++port, host); - } - } else { - this.httpServer.removeListener('error', onError); - reject(e); - } - }; - - this.httpServer.on('error', onError); - - this.httpServer.listen(port, host, () => { - this.httpServer.removeListener('error', onError); - resolve(port); - }); - }); - } - - /** - * Get current compiler instance in the server - * @returns { CompilerType } return current compiler, may be compiler or undefined - */ - getCompiler(): CompilerType { - return this.compiler; - } - - /** - * Set current compiler instance in the server - * @param { Compiler } compiler - choose a new compiler instance - */ - setCompiler(compiler: Compiler) { - this.compiler = compiler; - } - - /** - * Adds additional files to be watched by the compiler. - * @param {string} root - The root directory. - * @param {string[]} deps - Array of file paths to be watched. - */ - addWatchFile(root: string, deps: string[]): void { - this.getCompiler().addExtraWatchFile(root, deps); - } - - /** - * resolve and setting server options - * - * this method extracts compilation and server options from resolvedUserConfig - * and set the publicPath and publicDir, and then resolve server options - * @private - * @returns { void } - */ - #resolveOptions() { - const { compilation, server } = this.resolvedUserConfig; - this.publicPath = compilation.output.publicPath; - - this.publicDir = compilation.assets.publicDir; - - this.serverOptions = server as CommonServerOptions & NormalizedServerConfig; - - this.root = compilation.root; - } - - /** - * Initializes and configures the middleware stack for the server. - * @private - */ - #initializeMiddlewares() { - this.middlewares.use(hmrPingMiddleware()); - - const { proxy, middlewareMode, cors } = this.serverOptions; - - if (cors) { - this.middlewares.use( - corsMiddleware(typeof cors === 'boolean' ? {} : cors) - ); - } - - if (proxy) { - const middlewareServer = - isObject(middlewareMode) && 'server' in middlewareMode - ? middlewareMode.server - : this.httpServer; - - this.middlewares.use(proxyMiddleware(this, middlewareServer)); - } - - if (this.publicPath !== '/') { - this.middlewares.use(publicPathMiddleware(this)); - } - - if (fs.existsSync(this.publicDir as PathLike)) { - this.middlewares.use(publicMiddleware(this)); - } - - if (this.resolvedUserConfig.compilation.lazyCompilation) { - this.middlewares.use(lazyCompilationMiddleware(this)); - } - - if (this.resolvedUserConfig.vitePlugins?.length) { - this.middlewares.use(adaptorViteMiddleware(this)); - } - - this.postConfigureServerHooks.forEach((fn) => fn && fn()); - - // TODO todo add appType 这块要判断 单页面还是 多页面 多 html 处理不一样 - this.middlewares.use(htmlFallbackMiddleware(this)); - - this.middlewares.use(resourceMiddleware(this)); - - this.middlewares.use(notFoundMiddleware()); - } - - /** - * Compiles the project. - * @private - * @returns {Promise} - * @throws {Error} If compilation fails. - */ - async #compile(): Promise { - try { - await this.compiler.compile(); - await (this.resolvedUserConfig.server.writeToDisk - ? this.compiler.writeResourcesToDisk() - : this.compiler.callWriteResourcesHook()); - } catch (err) { - this.logger.error('Compilation failed:', err); - throw err; - } - } - - /** - * Opens the server URL in the default browser. - * @private - */ - async #openServerBrowser() { - const url = - this.resolvedUrls?.local?.[0] ?? this.resolvedUrls?.network?.[0] ?? ''; - openBrowser(url); - } - - /** - * Starts the compilation process. - * @private - */ - async #startCompile() { - // check if cache dir exists - const { persistentCache } = this.compiler.config.config; - const hasCacheDir = await isCacheDirExists( - getCacheDir(this.root, persistentCache) - ); - const start = performance.now(); - await this.#compile(); - const duration = performance.now() - start; - bootstrap(duration, this.compiler.config, hasCacheDir); - } - - /** - * Handles the initialization of public files. - * @private - * @returns {Promise>} A promise that resolves to a set of public file paths. - */ - async #handlePublicFiles() { - const initPublicFilesPromise = initPublicFiles(this.resolvedUserConfig); - return await initPublicFilesPromise; - } - - /** - * Sets up the Vite invalidation handler. - * @private - */ - #invalidateVite(): void { - // Note: path should be Farm's id, which is a relative path in dev mode, - // but in vite, it's a url path like /xxx/xxx.js - this.ws.wss.on('vite:invalidate', ({ path, message }: any) => { - // find hmr boundary starting from the parent of the file - this.logger.info(`HMR invalidate: ${path}. ${message ?? ''} `); - const parentFiles = this.compiler.getParentFiles(path); - this.hmrEngine.hmrUpdate(parentFiles, true); - }); - } - async closeServerAndExit() { - try { - await this.httpServer.close(); - } finally { - process.exit(); - } - } - - closeServer(): () => Promise { - if (!this.httpServer) { - return () => Promise.resolve(); - } - debugServer?.(`prepare close dev server`); - - let hasListened = false; - const openSockets = new Set(); - - this.httpServer.on('connection', (socket) => { - openSockets.add(socket); - debugServer?.(`has open server socket ${openSockets}`); - - socket.on('close', () => { - debugServer?.('close all server socket'); - openSockets.delete(socket); - }); - }); - - this.httpServer.once('listening', () => { - hasListened = true; - }); - - return () => - new Promise((resolve, reject) => { - openSockets.forEach((s) => s.destroy()); - - if (hasListened) { - this.httpServer.close((err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - } else { - resolve(); - } - }); - } - - async close() { - if (!this.serverOptions.middlewareMode) { - teardownSIGTERMListener(this.closeServerAndExit); - } - - await Promise.allSettled([this.watcher.close(), this.closeHttpServerFn()]); - } -} - -export const teardownSIGTERMListener = ( - callback: () => Promise -): void => { - process.off('SIGTERM', callback); - if (process.env.CI !== 'true') { - process.stdin.off('end', callback); - } -}; diff --git a/packages/core/src/newServer/middlewares/hmrPing.ts b/packages/core/src/newServer/middlewares/hmrPing.ts deleted file mode 100644 index e7154cdb29..0000000000 --- a/packages/core/src/newServer/middlewares/hmrPing.ts +++ /dev/null @@ -1,13 +0,0 @@ -export function hmrPingMiddleware() { - return function handleHMRPingMiddleware( - req: any, - res: any, - next: () => void - ) { - if (req.headers['accept'] === 'text/x-farm-ping') { - res.writeHead(204).end(); - } else { - next(); - } - }; -} diff --git a/packages/core/src/newServer/middlewares/index.ts b/packages/core/src/newServer/middlewares/index.ts deleted file mode 100644 index d89c308425..0000000000 --- a/packages/core/src/newServer/middlewares/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from './error.js'; -export * from './htmlFallback.js'; -export * from './lazyCompilation.js'; -export * from './notFound.js'; -export * from './proxy.js'; -export * from './static.js'; -export * from './resource.js'; -export * from './publicResource.js'; -export * from './publicPath.js'; -export * from './hmrPing.js'; -export * from './adaptorVite.js'; diff --git a/packages/core/src/newServer/middlewares/proxy.ts b/packages/core/src/newServer/middlewares/proxy.ts deleted file mode 100644 index 0acb5bd081..0000000000 --- a/packages/core/src/newServer/middlewares/proxy.ts +++ /dev/null @@ -1,176 +0,0 @@ -import type * as http from 'node:http'; -import type * as net from 'node:net'; -import connect from 'connect'; -import httpProxy from 'http-proxy'; -import type Server from 'http-proxy'; -import { ResolvedUserConfig } from '../../config/types.js'; -import { colors } from '../../utils/color.js'; -export interface ProxyOptions extends httpProxy.ServerOptions { - rewrite?: (path: string) => string; - configure?: (proxy: httpProxy, options: ProxyOptions) => void; - bypass?: ( - req: http.IncomingMessage, - res: http.ServerResponse, - options: ProxyOptions - ) => void | null | undefined | false | string; - rewriteWsOrigin?: boolean | undefined; -} - -export function proxyMiddleware(app: any, server?: any) { - const { serverOptions, ResolvedUserConfig } = app; - - const proxies: Record = {}; - Object.keys(serverOptions.proxy).forEach((context) => { - let opts = serverOptions.proxy[context]; - if (!opts) { - return; - } - if (typeof opts === 'string') { - opts = { target: opts, changeOrigin: true } as ProxyOptions; - } - const proxy = httpProxy.createProxyServer(opts) as Server; - - if (opts.configure) { - opts.configure(proxy, opts); - } - - proxy.on('error', (err, req, originalRes) => { - // When it is ws proxy, res is net.Socket - // originalRes can be falsy if the proxy itself errored - const res = originalRes as http.ServerResponse | net.Socket | undefined; - if (!res) { - console.log( - `${colors.red(`http proxy error: ${err.message}`)}\n${err.stack}` - ); - } else if ('req' in res) { - // console.log( - // `${colors.red(`http proxy error: ${originalRes.req.url}`)}\n${ - // err.stack - // }`, - // ); - - if (!res.headersSent && !res.writableEnded) { - res - .writeHead(500, { - 'Content-Type': 'text/plain' - }) - .end(); - } - } else { - console.log(`${colors.red(`ws proxy error:`)}\n${err.stack}`); - res.end(); - } - }); - - proxy.on('proxyReqWs', (proxyReq, req, socket, options, head) => { - rewriteOriginHeader(proxyReq, options, ResolvedUserConfig); - - socket.on('error', (err) => { - console.log(`${colors.red(`ws proxy socket error:`)}\n${err.stack}`); - }); - }); - - // https://github.com/http-party/node-http-proxy/issues/1520#issue-877626125 - // https://github.com/chimurai/http-proxy-middleware/blob/cd58f962aec22c925b7df5140502978da8f87d5f/src/plugins/default/debug-proxy-errors-plugin.ts#L25-L37 - proxy.on('proxyRes', (proxyRes, req, res) => { - res.on('close', () => { - if (!res.writableEnded) { - proxyRes.destroy(); - } - }); - }); - - // clone before saving because http-proxy mutates the options - proxies[context] = [proxy, { ...opts }]; - }); - - if (app.httpServer) { - app.httpServer.on('upgrade', (req: any, socket: any, head: any) => { - const url = req.url; - for (const context in proxies) { - if (doesProxyContextMatchUrl(context, url)) { - const [proxy, opts] = proxies[context]; - if ( - opts.ws || - opts.target?.toString().startsWith('ws:') || - opts.target?.toString().startsWith('wss:') - ) { - if (opts.rewrite) { - req.url = opts.rewrite(url); - } - proxy.ws(req, socket, head); - return; - } - } - } - }); - } - return function handleProxyMiddleware( - req: http.IncomingMessage, - res: http.ServerResponse, - next: () => void - ) { - const url = req.url; - for (const context in proxies) { - if (doesProxyContextMatchUrl(context, url)) { - const [proxy, opts] = proxies[context]; - const options: httpProxy.ServerOptions = {}; - - if (opts.bypass) { - const bypassResult = opts.bypass(req, res, opts); - if (typeof bypassResult === 'string') { - req.url = bypassResult; - return next(); - } else if (bypassResult === false) { - res.statusCode = 404; - return res.end(); - } - } - - if (opts.rewrite) { - req.url = opts.rewrite(req.url!); - } - proxy.web(req, res, options); - return; - } - } - next(); - }; -} - -function rewriteOriginHeader( - proxyReq: http.ClientRequest, - options: ProxyOptions, - config: ResolvedUserConfig -) { - // Browsers may send Origin headers even with same-origin - // requests. It is common for WebSocket servers to check the Origin - // header, so if rewriteWsOrigin is true we change the Origin to match - // the target URL. - if (options.rewriteWsOrigin) { - const { target } = options; - - if (proxyReq.headersSent) { - console.warn( - 'Unable to rewrite Origin header as headers are already sent.' - ); - return; - } - - if (proxyReq.getHeader('origin') && target) { - const changedOrigin = - typeof target === 'object' - ? `${target.protocol}//${target.host}` - : target; - - proxyReq.setHeader('origin', changedOrigin); - } - } -} - -function doesProxyContextMatchUrl(context: string, url: string): boolean { - return ( - (context[0] === '^' && new RegExp(context).test(url)) || - url.startsWith(context) - ); -} diff --git a/packages/core/src/newServer/middlewares/static.ts b/packages/core/src/newServer/middlewares/static.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core/src/newServer/open.ts b/packages/core/src/newServer/open.ts deleted file mode 100644 index fb31ff9b2c..0000000000 --- a/packages/core/src/newServer/open.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * The following is modified based on source found in - * https://github.com/facebook/create-react-app - * - * MIT Licensed - * Copyright (c) 2015-present, Facebook, Inc. - * https://github.com/facebook/create-react-app/blob/master/LICENSE - */ - -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -import { execSync } from 'child_process'; -import { execa } from 'execa'; -import open from 'open'; -import { Logger, cyan, red } from '../utils/index.js'; - -// https://github.com/sindresorhus/open#app -const OSX_CHROME = 'google chrome'; - -const enum Actions { - NONE, - BROWSER, - SCRIPT -} - -function getBrowserEnv() { - // Attempt to honor this environment variable. - // It is specific to the operating system. - // See https://github.com/sindresorhus/open#app for documentation. - const value = process.env.BROWSER; - let action; - if (!value) { - // Default. - action = Actions.BROWSER; - } else if (value.toLowerCase().endsWith('.js')) { - action = Actions.SCRIPT; - } else if (value.toLowerCase() === 'none') { - action = Actions.NONE; - } else { - action = Actions.BROWSER; - } - return { action, value }; -} - -function executeNodeScript(scriptPath: string, url: string) { - const extraArgs = process.argv.slice(2); - const child = execa('node', [scriptPath, ...extraArgs, url], { - stdio: 'inherit' - }); - child.on('close', (code: number) => { - if (code !== 0) { - console.log(); - console.log( - red('The script specified as BROWSER environment variable failed.') - ); - console.log(cyan(scriptPath) + ' exited with code ' + code + '.'); - console.log(); - return; - } - }); - return true; -} - -function startBrowserProcess(browser: string | undefined, url: string) { - // If we're on OS X, the user hasn't specifically - // requested a different browser, we can try opening - // Chrome with AppleScript. This lets us reuse an - // existing tab when possible instead of creating a new one. - const shouldTryOpenChromeWithAppleScript = - process.platform === 'darwin' && - (typeof browser !== 'string' || browser === OSX_CHROME); - - const dirname = path.dirname(fileURLToPath(import.meta.url)); - if (shouldTryOpenChromeWithAppleScript) { - try { - // Try our best to reuse existing tab - // on OS X Google Chrome with AppleScript - execSync('ps cax | grep "Google Chrome"'); - execSync('osascript openChrome.applescript "' + encodeURI(url) + '"', { - cwd: path.resolve(dirname, '../../bin'), - stdio: 'ignore' - }); - return true; - } catch { - // Ignore errors - } - } - - // Another special case: on OS X, check if BROWSER has been set to "open". - // In this case, instead of passing the string `open` to `open` function (which won't work), - // just ignore it (thus ensuring the intended behavior, i.e. opening the system browser): - // https://github.com/facebook/create-react-app/pull/1690#issuecomment-283518768 - if (process.platform === 'darwin' && browser === 'open') { - browser = undefined; - } - - // Fallback to open - // (It will always open new tab) - try { - const options = browser ? { app: { name: browser, arguments: [] } } : {}; - const logger = new Logger(); - open(url, options).catch((e: Error) => { - logger.error(e); - }); // Prevent `unhandledRejection` error. - return true; - } catch { - return false; - } -} - -/** - * Reads the BROWSER environment variable and decides what to do with it. Returns - * true if it opened a browser or ran a node.js script, otherwise false. - */ -export function openBrowser(url: string) { - const { action, value } = getBrowserEnv(); - switch (action) { - case Actions.NONE: - // Special case: BROWSER="none" will prevent opening completely. - return false; - case Actions.SCRIPT: - return executeNodeScript(value, url); - case Actions.BROWSER: - return startBrowserProcess(value, url); - default: - throw new Error('Not implemented.'); - } -} diff --git a/packages/core/src/newServer/type.ts b/packages/core/src/newServer/type.ts deleted file mode 100644 index 1fe2aaf024..0000000000 --- a/packages/core/src/newServer/type.ts +++ /dev/null @@ -1,100 +0,0 @@ -export type HMRPayload = - | ConnectedPayload - | UpdatePayload - | FullReloadPayload - | CustomPayload - | ErrorPayload - | PrunePayload - -export interface ConnectedPayload { - type: 'connected' -} - -export interface UpdatePayload { - type: 'update' - updates: Update[] -} - -export interface Update { - type: 'js-update' | 'css-update' - path: string - acceptedPath: string - timestamp: number - /** @internal */ - explicitImportRequired?: boolean - /** @internal */ - isWithinCircularImport?: boolean - /** @internal */ - ssrInvalidates?: string[] -} - -export interface PrunePayload { - type: 'prune' - paths: string[] -} - -export interface FullReloadPayload { - type: 'full-reload' - path?: string - /** @internal */ - triggeredBy?: string -} - -export interface CustomPayload { - type: 'custom' - event: string - data?: any -} - -export interface ErrorPayload { - type: 'error' - err: { - [name: string]: any - message: string - stack: string - id?: string - frame?: string - plugin?: string - pluginCode?: string - loc?: { - file?: string - line: number - column: number - } - } -} - -export interface CustomEventMap { - 'vite:beforeUpdate': UpdatePayload - 'vite:afterUpdate': UpdatePayload - 'vite:beforePrune': PrunePayload - 'vite:beforeFullReload': FullReloadPayload - 'vite:error': ErrorPayload - 'vite:invalidate': InvalidatePayload - 'vite:ws:connect': WebSocketConnectionPayload - 'vite:ws:disconnect': WebSocketConnectionPayload -} - -export interface WebSocketConnectionPayload { - webSocket: WebSocket -} - -export interface InvalidatePayload { - path: string - message: string | undefined -} - -export type InferCustomEventPayload = - T extends keyof CustomEventMap ? CustomEventMap[T] : any - - -export interface HMRBroadcasterClient { - /** - * Send event to the client - */ - send(payload: HMRPayload): void - /** - * Send custom event - */ - send(event: string, payload?: CustomPayload['data']): void -} diff --git a/packages/core/src/newServer/ws.ts b/packages/core/src/newServer/ws.ts deleted file mode 100644 index e89eb90b34..0000000000 --- a/packages/core/src/newServer/ws.ts +++ /dev/null @@ -1,388 +0,0 @@ -import { STATUS_CODES, createServer as createHttpServer } from 'node:http'; -import { createServer as createHttpsServer } from 'node:https'; -import path from 'node:path'; -import { WebSocketServer as WebSocketServerRaw_ } from 'ws'; - -import { ILogger, Logger } from '../utils/logger.js'; -import { isObject } from '../utils/share.js'; -import { HMRChannel } from './hmr.js'; -import { ServerOptions } from './index.js'; - -import type { IncomingMessage, Server } from 'node:http'; -import type { Socket } from 'node:net'; -import type { Duplex } from 'node:stream'; -import type { WebSocket as WebSocketRaw } from 'ws'; -import type { WebSocket as WebSocketTypes } from '../types/ws.js'; - -import { - CustomPayload, - ErrorPayload, - HMRPayload, - InferCustomEventPayload -} from './type.js'; - -const WS_CONNECTED_MESSAGE = JSON.stringify({ type: 'connected' }); -const WS_CUSTOM_EVENT_TYPE = 'custom'; - -export interface WebSocketServer extends HMRChannel { - /** - * Listen on port and host - */ - listen(): void; - /** - * Get all connected clients. - */ - clients: Set; - /** - * Disconnect all clients and terminate the server. - */ - close(): Promise; - /** - * Handle custom event emitted by `import.meta.hot.send` - */ - on: WebSocketTypes.Server['on'] & { - ( - event: T, - listener: WebSocketCustomListener> - ): void; - }; - /** - * Unregister event listener. - */ - off: WebSocketTypes.Server['off'] & { - (event: string, listener: Function): void; - }; -} - -export interface WebSocketClient { - /** - * Send event to the client - */ - send(payload: HMRPayload): void; - /** - * Send custom event - */ - send(event: string, payload?: CustomPayload['data']): void; - /** - * The raw WebSocket instance - * @advanced - */ - socket: WebSocketTypes; -} - -const wsServerEvents = [ - 'connection', - 'error', - 'headers', - 'listening', - 'message' -]; - -function noop() { - // noop -} - -const HMR_HEADER = 'farm_hmr'; - -export type WebSocketCustomListener = ( - data: T, - client: WebSocketClient -) => void; - -const WebSocketServerRaw = process.versions.bun - ? // @ts-expect-error: Bun defines `import.meta.require` - import.meta.require('ws').WebSocketServer - : WebSocketServerRaw_; - -export class WsServer { - public wss: WebSocketServerRaw_; - public customListeners = new Map>>(); - public clientsMap = new WeakMap(); - public bufferedError: ErrorPayload | null = null; - public logger: ILogger; - public wsServer: any; - wsHttpServer: Server | undefined; - private serverConfig: ServerOptions; - private port: number; - private host: string | undefined; - private hmrServerWsListener: ( - req: InstanceType, - socket: Duplex, - head: Buffer - ) => void; - /** - * Creates a new WebSocket server instance. - * @param {any} app - The application instance containing configuration and logger. - */ - constructor(private readonly app: any) { - this.logger = app.logger ?? new Logger(); - this.serverConfig = app.resolvedUserConfig.server as ServerOptions; - this.createWebSocketServer(); - } - - /** - * Gets the server name. - * @returns {string} Returns "ws". - */ - get name(): string { - return 'ws'; - } - - /** - * Creates the WebSocket server. - */ - createWebSocketServer() { - if (this.serverConfig.ws === false) { - return { - name: 'ws', - get clients() { - return new Set(); - }, - async close() { - // noop - }, - on: noop as any as WebSocketServer['on'], - off: noop as any as WebSocketServer['off'], - listen: noop, - send: noop - }; - } - - const hmr = isObject(this.serverConfig.hmr) - ? this.serverConfig.hmr - : undefined; - const hmrServer = hmr?.server; - const hmrPort = hmr?.port; - const portsAreCompatible = !hmrPort || hmrPort === this.serverConfig.port; - this.wsServer = hmrServer || (portsAreCompatible && this.app.httpServer); - - this.port = (hmrPort as number) || 9000; - this.host = ((hmr && hmr.host) as string) || undefined; - - if (this.wsServer) { - let hmrBase = this.app.publicPath; - - const hmrPath = hmr?.path; - if (hmrPath) { - hmrBase = path.posix.join(hmrBase, hmrPath as string); - } - - this.wss = new WebSocketServerRaw({ noServer: true }); - this.hmrServerWsListener = (req, socket, head) => { - // TODO 这里需要处理一下 normalizePublicPath 的问题 hmrBase 路径匹配不到 req.url 的问题 - if ( - req.headers['sec-websocket-protocol'] === HMR_HEADER && - req.url === hmrBase - ) { - this.wss.handleUpgrade(req, socket as Socket, head, (ws) => { - this.wss.emit('connection', ws, req); - }); - } - }; - this.wsServer.on('upgrade', this.hmrServerWsListener); - } else { - // http server request handler keeps the same with - // https://github.com/websockets/ws/blob/45e17acea791d865df6b255a55182e9c42e5877a/lib/websocket-server.js#L88-L96 - const route = ((_, res) => { - const statusCode = 426; - const body = STATUS_CODES[statusCode]; - if (!body) - throw new Error( - `No body text found for the ${statusCode} status code` - ); - - res.writeHead(statusCode, { - 'Content-Length': body.length, - 'Content-Type': 'text/plain' - }); - res.end(body); - }) as Parameters[1]; - - if (this.app.httpsOptions) { - this.wsHttpServer = createHttpsServer(this.app.httpsOptions, route); - } else { - this.wsHttpServer = createHttpServer(route); - } - // vite dev server in middleware mode - // need to call ws listen manually - this.wss = new WebSocketServerRaw({ server: this.wsHttpServer }); - } - - this.wss.on('connection', (socket) => { - socket.on('message', (raw) => { - if (!this.customListeners.size) return; - let parsed: any; - try { - parsed = JSON.parse(String(raw)); - } catch { - this.logger.error('Failed to parse WebSocket message: ' + raw); - } - // transform vite js-update to farm update - if (parsed?.type === 'js-update' && parsed?.path) { - this.app.hmrEngine.hmrUpdate(parsed.path, true); - return; - } - if (!parsed || parsed.type !== WS_CUSTOM_EVENT_TYPE || !parsed.event) - return; - const listeners = this.customListeners.get(parsed.event); - if (!listeners?.size) return; - const client = this.#getSocketClient(socket); - listeners.forEach((listener) => listener(parsed.data, client)); - }); - socket.on('error', (err) => { - throw new Error(`WebSocket error: \n${err.stack}`); - }); - - socket.send(WS_CONNECTED_MESSAGE); - - if (this.bufferedError) { - socket.send(JSON.stringify(this.bufferedError)); - this.bufferedError = null; - } - }); - - this.wss.on('error', (e: Error & { code: string }) => { - if (e.code === 'EADDRINUSE') { - throw new Error('WebSocket server error: Port is already in use'); - } else { - throw new Error(`WebSocket server error ${e.stack || e.message}`); - } - }); - } - - /** - * Starts listening for WebSocket connections. - */ - listen() { - this.wsHttpServer?.listen(this.port, this.host); - } - - /** - * Adds a listener for the specified event. - * @param {string} event - The name of the event. - * @param {Function} fn - The listener function. - */ - on(event: string, fn: () => void) { - if (wsServerEvents.includes(event)) { - this.wss.on(event, fn); - } else { - if (!this.customListeners.has(event)) { - this.customListeners.set(event, new Set()); - } - this.customListeners.get(event).add(fn); - } - } - - /** - * Removes a listener for the specified event. - * @param {string} event - The name of the event. - * @param {Function} fn - The listener function to remove. - */ - off(event: string, fn: () => void) { - if (wsServerEvents.includes(event)) { - this.wss.off(event, fn); - } else { - this.customListeners.get(event)?.delete(fn); - } - } - - /** - * Gets all connected clients. - * @returns {Set} A set of connected clients. - */ - get clients() { - return new Set( - Array.from(this.wss.clients).map((socket: any) => - this.#getSocketClient(socket) - ) - ); - } - - /** - * Sends a message to all connected clients. - * @param {...any} args - The message arguments to send. - */ - send(...args: any[]) { - const payload: HMRPayload = this.#createPayload(...args); - if (payload.type === 'error' && !this.wss.clients.size) { - this.bufferedError = payload; - return; - } - - const stringified = JSON.stringify(payload); - this.wss.clients.forEach((client: any) => { - // readyState 1 means the connection is open - if (client.readyState === 1) { - client.send(stringified); - } - }); - } - - /** - * Closes the WebSocket server. - * @returns {Promise} A promise that resolves when the server is closed. - */ - async close() { - // should remove listener if hmr.server is set - // otherwise the old listener swallows all WebSocket connections - if (this.hmrServerWsListener && this.wsServer) { - this.wsServer.off('upgrade', this.hmrServerWsListener); - } - try { - this.wss.clients.forEach((client: any) => { - client.terminate(); - }); - await new Promise((resolve, reject) => { - this.wss.close((err: any) => (err ? reject(err) : resolve())); - }); - if (this.wsHttpServer) { - await new Promise((resolve, reject) => { - this.wsHttpServer.close((err: any) => - err ? reject(err) : resolve() - ); - }); - } - } catch (err) { - throw new Error(`Failed to close WebSocket server: ${err}`); - } - } - - /** - * Creates an HMR payload. - * @private - * @param {...any} args - The payload arguments. - * @returns {HMRPayload} The HMR payload object. - */ - #createPayload(...args: any[]): HMRPayload { - if (typeof args[0] === 'string') { - return { - type: 'custom', - event: args[0], - data: args[1] - }; - } else { - return args[0]; - } - } - - /** - * Gets the client object associated with a WebSocket. - * @private - * @param {WebSocketRaw} socket - The raw WebSocket object. - * @returns {WebSocketClient} The client object. - */ - #getSocketClient(socket: WebSocketRaw) { - if (!this.clientsMap.has(socket)) { - this.clientsMap.set(socket, { - send: (...args) => { - const payload: HMRPayload = this.#createPayload(...args); - socket.send(JSON.stringify(payload)); - }, - // @ts-ignore - rawSend: (str: string) => socket.send(str), - socket - }); - } - return this.clientsMap.get(socket); - } -} diff --git a/packages/core/src/old-watcher/config-watcher.ts b/packages/core/src/old-watcher/config-watcher.ts deleted file mode 100644 index de1cd661e2..0000000000 --- a/packages/core/src/old-watcher/config-watcher.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { existsSync } from 'fs'; -import { FSWatcher } from 'chokidar'; -import { ResolvedUserConfig } from '../config/index.js'; -import { createWatcher } from './create-watcher.js'; - -export class ConfigWatcher { - private watcher: FSWatcher; - private _close = false; - - constructor(private resolvedUserConfig: ResolvedUserConfig) { - if (!resolvedUserConfig) { - throw new Error( - 'Invalid resolvedUserConfig provided to Farm JsConfigWatcher' - ); - } - } - - watch(callback: (file: string[]) => void) { - async function handle(file: string[]) { - callback(file); - } - - const watchedFilesSet = new Set([ - ...(this.resolvedUserConfig.envFiles ?? []), - ...(this.resolvedUserConfig.configFileDependencies ?? []), - ...(this.resolvedUserConfig.configFilePath - ? [this.resolvedUserConfig.configFilePath] - : []) - ]); - - const watchedFiles = Array.from(watchedFilesSet).filter( - (file) => file && existsSync(file) - ); - const chokidarOptions = { - awaitWriteFinish: - process.platform === 'linux' - ? undefined - : { - stabilityThreshold: 10, - pollInterval: 80 - } - }; - this.watcher = createWatcher( - this.resolvedUserConfig, - watchedFiles, - chokidarOptions - ); - - this.watcher.on('change', (path) => { - if (this._close) return; - if (watchedFiles.includes(path)) { - handle([path]); - } - }); - return this; - } - - close() { - this._close = true; - this.watcher = null; - } -} diff --git a/packages/core/src/old-watcher/create-watcher.ts b/packages/core/src/old-watcher/create-watcher.ts deleted file mode 100644 index c9078ddb16..0000000000 --- a/packages/core/src/old-watcher/create-watcher.ts +++ /dev/null @@ -1,53 +0,0 @@ -import path from 'node:path'; - -import chokidar, { FSWatcher, WatchOptions } from 'chokidar'; -import glob from 'fast-glob'; - -import { ResolvedUserConfig, getCacheDir } from '../index.js'; - -function resolveChokidarOptions( - config: ResolvedUserConfig, - insideChokidarOptions: WatchOptions -) { - const { ignored = [], ...userChokidarOptions } = - config.server?.hmr?.watchOptions ?? {}; - - const cacheDir = getCacheDir(config.root, config.compilation.persistentCache); - - const options: WatchOptions = { - ignored: [ - '**/.git/**', - '**/node_modules/**', - '**/test-results/**', // Playwright - glob.escapePath(cacheDir) + '/**', - glob.escapePath( - path.resolve(config.root, config.compilation.output.path) - ) + '/**', - ...(Array.isArray(ignored) ? ignored : [ignored]) - ], - ignoreInitial: true, - ignorePermissionErrors: true, - // for windows and macos, we need to wait for the file to be written - awaitWriteFinish: - process.platform === 'linux' - ? undefined - : { - stabilityThreshold: 10, - pollInterval: 10 - }, - ...userChokidarOptions, - ...insideChokidarOptions - }; - - return options; -} - -export function createWatcher( - config: ResolvedUserConfig, - files: string[], - chokidarOptions?: WatchOptions -): FSWatcher { - const options = resolveChokidarOptions(config, chokidarOptions); - - return chokidar.watch(files, options); -} diff --git a/packages/core/src/old-watcher/index.ts b/packages/core/src/old-watcher/index.ts deleted file mode 100644 index b4157f5d04..0000000000 --- a/packages/core/src/old-watcher/index.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { createRequire } from 'node:module'; - -import { FSWatcher } from 'chokidar'; - -import { Compiler } from '../compiler/index.js'; -import { Server } from '../server/index.js'; -import { Logger, compilerHandler } from '../utils/index.js'; - -import { existsSync } from 'node:fs'; -import type { ResolvedUserConfig } from '../config/index.js'; -import type { JsUpdateResult } from '../types/binding.js'; -import { createWatcher } from './create-watcher.js'; - -interface ImplFileWatcher { - watch(): Promise; -} - -export class FileWatcher implements ImplFileWatcher { - private _root: string; - private _watcher: FSWatcher; - private _close = false; - private _watchedFiles = new Set(); - - constructor( - public serverOrCompiler: Server | Compiler, - public options: ResolvedUserConfig, - private _logger: Logger - ) { - this._root = options.root; - } - - getInternalWatcher() { - return this._watcher; - } - - filterWatchFile(file: string, root: string): boolean { - const suffix = process.platform === 'win32' ? '\\' : '/'; - - return ( - !file.startsWith(`${root}${suffix}`) && - !file.includes(`node_modules${suffix}`) && - !file.includes('\0') && - existsSync(file) - ); - } - - getExtraWatchedFiles() { - const compiler = this.getCompilerFromServerOrCompiler( - this.serverOrCompiler - ); - - return [ - ...compiler.resolvedModulePaths(this._root), - ...compiler.resolvedWatchPaths() - ].filter((file) => this.filterWatchFile(file, this._root)); - } - - watchExtraFiles() { - const files = this.getExtraWatchedFiles(); - - for (const file of files) { - if (!this._watchedFiles.has(file)) { - this._watcher.add(file); - this._watchedFiles.add(file); - } - } - } - - async watch() { - // Determine how to compile the project - const compiler = this.getCompilerFromServerOrCompiler( - this.serverOrCompiler - ); - - const handlePathChange = async (path: string): Promise => { - if (this._close) { - return; - } - - try { - // if (this.serverOrCompiler instanceof Server) { - // @ts-ignore - if (this.serverOrCompiler.compiler) { - // @ts-ignore - await this.serverOrCompiler.hmrEngine.hmrUpdate(path); - } - - if ( - this.serverOrCompiler instanceof Compiler && - this.serverOrCompiler.hasModule(path) - ) { - compilerHandler( - async () => { - const result = await compiler.update([path], true); - handleUpdateFinish(result); - compiler.writeResourcesToDisk(); - }, - this.options, - this._logger, - { clear: true } - ); - } - } catch (error) { - this._logger.error(error); - } - }; - - const watchedFiles = this.getExtraWatchedFiles(); - - const files = [this.options.root, ...watchedFiles]; - this._watchedFiles = new Set(files); - this._watcher = createWatcher(this.options, files); - - this._watcher.on('change', (path) => { - if (this._close) return; - handlePathChange(path); - }); - - const handleUpdateFinish = (updateResult: JsUpdateResult) => { - const added = [ - ...updateResult.added, - ...updateResult.extraWatchResult.add - ].map((addedModule) => { - const resolvedPath = compiler.transformModulePath( - this._root, - addedModule - ); - return resolvedPath; - }); - const filteredAdded = added.filter((file) => - this.filterWatchFile(file, this._root) - ); - - if (filteredAdded.length > 0) { - this._watcher.add(filteredAdded); - } - }; - - if (this.serverOrCompiler instanceof Server) { - this.serverOrCompiler.hmrEngine?.onUpdateFinish(handleUpdateFinish); - } - } - - private getCompilerFromServerOrCompiler( - serverOrCompiler: Server | Compiler - ): Compiler { - // @ts-ignore - return serverOrCompiler.getCompiler - ? // @ts-ignore - serverOrCompiler.getCompiler() - : serverOrCompiler; - } - - close() { - this._close = true; - this._watcher = null; - this.serverOrCompiler = null; - } -} - -export function clearModuleCache(modulePath: string) { - const _require = createRequire(import.meta.url); - delete _require.cache[_require.resolve(modulePath)]; -} diff --git a/packages/core/src/plugin/js/vite-server-adapter.ts b/packages/core/src/plugin/js/vite-server-adapter.ts index fde319b234..a37a008440 100644 --- a/packages/core/src/plugin/js/vite-server-adapter.ts +++ b/packages/core/src/plugin/js/vite-server-adapter.ts @@ -1,11 +1,12 @@ // import { watch } from 'chokidar'; import { FSWatcher } from 'chokidar'; -import { Server } from '../../index.js'; -import { Server as httpServer } from '../../server/type.js'; -import WsServer from '../../server/ws.js'; +// import { Server } from '../../index.js'; +import { Server as httpServer } from '../../server/index.js'; +// import WsServer from '../../server/ws.js'; import { CompilationContext, ViteModule } from '../type.js'; import { throwIncompatibleError } from './utils.js'; +// TODO type error refactor vite adaptor export class ViteDevServerAdapter { moduleGraph: ViteModuleGraphAdapter; config: any; @@ -13,10 +14,10 @@ export class ViteDevServerAdapter { watcher: FSWatcher; middlewares: any; middlewareCallbacks: any[]; - ws: WsServer; + ws: any; httpServer: httpServer; - constructor(pluginName: string, config: any, server: Server) { + constructor(pluginName: string, config: any, server: any) { this.moduleGraph = createViteModuleGraphAdapter(pluginName); this.config = config; this.pluginName = pluginName; @@ -136,7 +137,7 @@ function proxyViteModuleNode( export function createViteDevServerAdapter( pluginName: string, config: any, - server: Server + server: any ) { const proxy = new Proxy( new ViteDevServerAdapter(pluginName, config, server), diff --git a/packages/core/src/server/hmr-engine.ts b/packages/core/src/server/hmr-engine.ts index 928d633053..d44564fadb 100644 --- a/packages/core/src/server/hmr-engine.ts +++ b/packages/core/src/server/hmr-engine.ts @@ -4,37 +4,41 @@ import fse from 'fs-extra'; import { stat } from 'node:fs/promises'; import { isAbsolute, relative } from 'node:path'; +import type { Resource } from '@farmfe/runtime/src/resource-loader.js'; import { Compiler } from '../compiler/index.js'; -import { checkClearScreen } from '../config/index.js'; +import { + UserConfig, + UserHmrConfig, + checkClearScreen +} from '../config/index.js'; +import { HttpServer } from '../server/index.js'; import type { JsUpdateResult } from '../types/binding.js'; import { Logger, bold, cyan, - getDynamicResources, - green + formatExecutionTime, + green, + lightCyan } from '../utils/index.js'; import { logError } from './error.js'; -import { Server } from './index.js'; -import { WebSocketClient } from './ws.js'; +import { WebSocketClient, WebSocketServer } from './ws.js'; export class HmrEngine { private _updateQueue: string[] = []; - // private _updateResults: Map = + // private _updateResults: Map; - private _compiler: Compiler; - private _devServer: Server; private _onUpdates: ((result: JsUpdateResult) => void)[]; private _lastModifiedTimestamp: Map; - constructor( - compiler: Compiler, - devServer: Server, - private _logger: Logger + // compiler: Compiler, + // devServer: HttpServer, + // config: UserConfig, + // ws: WebSocketServer, + // private _logger: Logger + private readonly app: any ) { - this._compiler = compiler; - this._devServer = devServer; // this._lastAttemptWasError = false; this._lastModifiedTimestamp = new Map(); } @@ -60,13 +64,13 @@ export class HmrEngine { let updatedFilesStr = queue .map((item) => { if (isAbsolute(item)) { - return relative(this._compiler.config.config.root, item); + return relative(this.app.compiler.config.config.root, item); } else { - const resolvedPath = this._compiler.transformModulePath( - this._compiler.config.config.root, + const resolvedPath = this.app.compiler.transformModulePath( + this.app.compiler.config.config.root, item ); - return relative(this._compiler.config.config.root, resolvedPath); + return relative(this.app.compiler.config.config.root, resolvedPath); } }) .join(', '); @@ -77,23 +81,22 @@ export class HmrEngine { try { // we must add callback before update - this._compiler.onUpdateFinish(async () => { + this.app.compiler.onUpdateFinish(async () => { // if there are more updates, recompile again if (this._updateQueue.length > 0) { await this.recompileAndSendResult(); } - if (this._devServer?.config?.writeToDisk) { - this._compiler.writeResourcesToDisk(); + if (this.app.resolvedUserConfig?.server.writeToDisk) { + this.app.compiler.writeResourcesToDisk(); } }); - checkClearScreen(this._compiler.config.config); - const start = Date.now(); - const result = await this._compiler.update(queue); - - this._logger.info( - `${bold(cyan(updatedFilesStr))} updated in ${bold( - green(`${Date.now() - start}ms`) + checkClearScreen(this.app.compiler.config.config); + const start = performance.now(); + const result = await this.app.compiler.update(queue); + this.app.logger.info( + `${bold(lightCyan(updatedFilesStr))} updated in ${bold( + green(formatExecutionTime(performance.now() - start, 's')) )}` ); @@ -102,9 +105,21 @@ export class HmrEngine { (item) => !queue.includes(item) ); - const { dynamicResources, dynamicModuleResourcesMap } = - getDynamicResources(result.dynamicResourcesMap); + let dynamicResourcesMap: Record = null; + if (result.dynamicResourcesMap) { + for (const [key, value] of Object.entries(result.dynamicResourcesMap)) { + if (!dynamicResourcesMap) { + dynamicResourcesMap = {} as Record; + } + + // @ts-ignore + dynamicResourcesMap[key] = value.map((r) => ({ + path: r[0], + type: r[1] as 'script' | 'link' + })); + } + } const { added, changed, @@ -120,14 +135,14 @@ export class HmrEngine { immutableModules: ${JSON.stringify(immutableModules.trim())}, mutableModules: ${JSON.stringify(mutableModules.trim())}, boundaries: ${JSON.stringify(boundaries)}, - dynamicResources: ${JSON.stringify(dynamicResources)}, - dynamicModuleResourcesMap: ${JSON.stringify(dynamicModuleResourcesMap)} + dynamicResourcesMap: ${JSON.stringify(dynamicResourcesMap)} }`; this.callUpdates(result); - this._devServer.ws.clients.forEach((client: WebSocketClient) => { - client.rawSend(` + this.app.ws.wss.clients.forEach((client: WebSocketClient) => { + // @ts-ignore + client.send(` { type: 'farm-update', result: ${resultStr} @@ -135,8 +150,8 @@ export class HmrEngine { `); }); } catch (err) { - checkClearScreen(this._compiler.config.config); - throw new Error(logError(err) as unknown as string); + // checkClearScreen(this.app.compiler.config.config); + throw new Error(err); } }; @@ -144,7 +159,10 @@ export class HmrEngine { const paths = Array.isArray(absPath) ? absPath : [absPath]; for (const path of paths) { - if (this._compiler.hasModule(path) && !this._updateQueue.includes(path)) { + if ( + this.app.compiler.hasModule(path) && + !this._updateQueue.includes(path) + ) { if (fse.existsSync(path)) { const lastModifiedTimestamp = this._lastModifiedTimestamp.get(path); const currentTimestamp = (await stat(path)).mtime.toISOString(); @@ -159,7 +177,7 @@ export class HmrEngine { } } - if (!this._compiler.compiling && this._updateQueue.length > 0) { + if (!this.app.compiler.compiling && this._updateQueue.length > 0) { try { await this.recompileAndSendResult(); } catch (e) { @@ -168,16 +186,19 @@ export class HmrEngine { const errorStr = `${JSON.stringify({ message: serialization })}`; - this._devServer.ws.clients.forEach((client: WebSocketClient) => { - client.rawSend(` + this.app.ws.wss.clients.forEach((client: WebSocketClient) => { + // @ts-ignore + // client.rawSend(` + client.send(` { type: 'error', err: ${errorStr}, - overlay: ${this._devServer.config.hmr.overlay} + overlay: ${(this.app.resolvedUserConfig.server.hmr as UserHmrConfig).overlay} } `); }); - this._logger.error(e); + // this.app.logger.error(e); + throw new Error(`hmr update failed: ${e.stack}`); } } } diff --git a/packages/core/src/newServer/hmr.ts b/packages/core/src/server/hmr.ts similarity index 100% rename from packages/core/src/newServer/hmr.ts rename to packages/core/src/server/hmr.ts diff --git a/packages/core/src/newServer/http.ts b/packages/core/src/server/http.ts similarity index 100% rename from packages/core/src/newServer/http.ts rename to packages/core/src/server/http.ts diff --git a/packages/core/src/server/index.ts b/packages/core/src/server/index.ts index 00d9d257be..a4622568e3 100644 --- a/packages/core/src/server/index.ts +++ b/packages/core/src/server/index.ts @@ -1,449 +1,630 @@ -import http from 'node:http'; -import http2 from 'node:http2'; -import * as httpsServer from 'node:https'; -import Koa from 'koa'; -import compression from 'koa-compress'; - -import path from 'node:path'; -import { promisify } from 'node:util'; +import fs, { PathLike } from 'node:fs'; +import { WatchOptions } from 'chokidar'; +import connect from 'connect'; +import corsMiddleware from 'cors'; + import { Compiler } from '../compiler/index.js'; -import { __FARM_GLOBAL__ } from '../config/_global.js'; -import { - DEFAULT_HMR_OPTIONS, - DevServerMiddleware, - NormalizedServerConfig, - UserPreviewServerConfig, - UserServerConfig, - normalizePublicDir -} from '../config/index.js'; -import { - getValidPublicPath, - normalizePublicPath -} from '../config/normalize-config/normalize-output.js'; -import { FileWatcher } from '../old-watcher/index.js'; -import { resolveHostname, resolveServerUrls } from '../utils/http.js'; -import { - Logger, - bootstrap, - clearScreen, - getCacheDir, - isCacheDirExists, - normalizeBasePath, - printServerUrls -} from '../utils/index.js'; -import { logError } from './error.js'; +import { colors, createCompiler } from '../index.js'; +import Watcher from '../watcher/index.js'; import { HmrEngine } from './hmr-engine.js'; -import { hmrPing } from './middlewares/hmrPing.js'; +import { CommonServerOptions, httpServer } from './http.js'; +import { openBrowser } from './open.js'; +import { WsServer } from './ws.js'; + +import { __FARM_GLOBAL__ } from '../config/_global.js'; +import { getCacheDir, isCacheDirExists } from '../utils/cacheDir.js'; +import { Logger, bootstrap, logger } from '../utils/logger.js'; +import { initPublicFiles } from '../utils/publicDir.js'; +import { isObject } from '../utils/share.js'; + import { - cors, - headers, - lazyCompilation, - proxy, - resources, - staticMiddleware + adaptorViteMiddleware, + hmrPingMiddleware, + htmlFallbackMiddleware, + lazyCompilationMiddleware, + notFoundMiddleware, + proxyMiddleware, + publicMiddleware, + publicPathMiddleware, + resourceMiddleware } from './middlewares/index.js'; -import { openBrowser } from './open.js'; -import { Server as httpServer } from './type.js'; -import WsServer from './ws.js'; - -/** - * Farm Dev Server, responsible for: - * * parse and normalize dev server options - * * launch http server based on options - * * compile the project in dev mode and serve the production - * * HMR middleware and websocket supported - */ -interface ServerUrls { - local: string[]; - network: string[]; + +import type * as http from 'node:http'; +import type { + Server as HttpBaseServer, + ServerOptions as HttpsServerOptions +} from 'node:http'; +import type { Http2SecureServer } from 'node:http2'; +import type * as net from 'node:net'; +import type { HMRChannel } from './hmr.js'; + +import type { + HmrOptions, + NormalizedServerConfig, + ResolvedUserConfig +} from '../config/types.js'; +import { getPluginHooks, getSortedPluginHooks } from '../plugin/index.js'; +import { JsUpdateResult } from '../types/binding.js'; +import { createDebugger } from '../utils/debug.js'; + +export type HttpServer = HttpBaseServer | Http2SecureServer; + +type CompilerType = Compiler | undefined; + +// export interface HmrOptions { +// protocol?: string; +// host?: string; +// port?: number; +// clientPort?: number; +// path?: string; +// timeout?: number; +// overlay?: boolean; +// server?: Server; +// /** @internal */ +// channels?: HMRChannel[]; +// } + +export interface ServerOptions extends CommonServerOptions { + /** + * Configure HMR-specific options (port, host, path & protocol) + */ + hmr?: HmrOptions | boolean; + /** + * Do not start the websocket connection. + * @experimental + */ + ws?: false; + /** + * chokidar watch options or null to disable FS watching + * https://github.com/paulmillr/chokidar#api + */ + watchOptions?: WatchOptions | undefined; + /** + * Create dev server to be used as a middleware in an existing server + * @default false + */ + middlewareMode?: + | boolean + | { + /** + * Parent server instance to attach to + * + * This is needed to proxy WebSocket connections to the parent server. + */ + server: http.Server; + }; + origin?: string; } -type ErrorMap = { - EACCES: string; - EADDRNOTAVAIL: string; -}; +export const debugServer = createDebugger('farm:server'); -interface ImplDevServer { - createServer(options: UserServerConfig): void; - createDevServer(options: UserServerConfig): void; - createPreviewServer(options: UserServerConfig): void; - listen(): Promise; - close(): Promise; - getCompiler(): Compiler; +export function noop() { + // noop } -export class Server implements ImplDevServer { - private _app: Koa; - private restart_promise: Promise | null = null; - private compiler: Compiler | null; - public logger: Logger; +type ServerConfig = CommonServerOptions & NormalizedServerConfig; +// TODO 改 Server 的 name and PascalCase +export class Server extends httpServer { ws: WsServer; - config: NormalizedServerConfig & UserPreviewServerConfig; - hmrEngine?: HmrEngine; - server?: httpServer; - publicDir?: string; + serverOptions: ServerConfig; + httpsOptions: HttpsServerOptions; + publicDir: string | boolean | undefined; publicPath?: string; - resolvedUrls?: ServerUrls; - watcher: FileWatcher; - - constructor({ - compiler = null, - logger - }: { - compiler?: Compiler | null; - logger: Logger; - }) { - this.compiler = compiler; - this.logger = logger ?? new Logger(); + publicFiles?: Set; + httpServer: HttpServer; + watcher: Watcher; + hmrEngine?: HmrEngine; + middlewares: connect.Server; + compiler: CompilerType; + root: string; + closeHttpServerFn: () => Promise; + postConfigureServerHooks: ((() => void) | void)[] = []; + constructor( + readonly resolvedUserConfig: ResolvedUserConfig, + logger: Logger + ) { + super(logger); + this.#resolveOptions(); + } - this.initializeKoaServer(); + /** + * Creates and initializes the server. + * + * This method performs the following operations: + * Resolves HTTPS configuration + * Handles public files + * Creates middleware + * Creates HTTP server (if not in middleware mode) + * Initializes HMR engine + * Creates WebSocket server + * Sets up Vite invalidation handler + * Initializes middlewares + * + * @returns {Promise} A promise that resolves when the server creation process is complete + * @throws {Error} If an error occurs during the server creation process + */ + async createServer(): Promise { + try { + const { https, middlewareMode } = this.serverOptions; - if (!compiler) return; + this.httpsOptions = await this.resolveHttpsConfig(https); + this.publicFiles = await this.#handlePublicFiles(); - this.publicDir = normalizePublicDir(compiler?.config.config.root); + this.middlewares = connect() as connect.Server; + this.httpServer = middlewareMode + ? null + : await this.resolveHttpServer( + this.serverOptions as CommonServerOptions, + this.middlewares, + this.httpsOptions + ); - this.publicPath = - normalizePublicPath( - compiler.config.config.output.targetEnv, - compiler.config.config.output.publicPath, - logger, - false - ) || '/'; - } + // close server function prepare promise + this.closeHttpServerFn = this.closeServer(); - getCompiler(): Compiler { - return this.compiler; - } + // init hmr engine When actually updating, we need to get the clients of ws for broadcast, 、 + // so we can instantiate hmrEngine by default at the beginning. + this.createHmrEngine(); - app(): Koa { - return this._app; - } + // init websocket server + this.createWebSocketServer(); - async listen(): Promise { - if (!this.server) { - this.logger.error('HTTP server is not created yet'); - return; - } + // invalidate vite handler + this.#invalidateVite(); - console.log(this.config); + this.#createWatcher(); - const { port, open, protocol, hostname } = this.config; + this.handleConfigureServer(); - // check if cache dir exists - const hasCacheDir = await isCacheDirExists( - getCacheDir( - this.compiler.config.config.root, - this.compiler.config.config.persistentCache - ) - ); + // init middlewares + this.#initializeMiddlewares(); - const start = Date.now(); - // compile the project and start the dev server - await this.compile(); + if (!middlewareMode && this.httpServer) { + this.httpServer.once('listening', () => { + // update actual port since this may be different from initial value + this.serverOptions.port = ( + this.httpServer.address() as net.AddressInfo + ).port; + }); + } + // TODO apply server configuration hooks from plugins e.g. vite configureServer + // const postHooks: ((() => void) | void)[] = []; + // console.log(this.resolvedUserConfig.jsPlugins); + // TODO 要在这里做 vite 插件和 js 插件的适配器 + // for (const hook of getPluginHooks(applyPlugins, "configureServer")) { + // postHooks.push(await hook(reflexServer)); + // } + } catch (error) { + this.logger.error(`Failed to create farm server: ${error}`); + throw error; + } + } - // watch extra files after compile - this.watcher?.watchExtraFiles?.(); + async handleConfigureServer() { + const reflexServer = new Proxy(this, { + get: (_, property: keyof Server) => { + //@ts-ignore + return this[property]; + }, + set: (_, property: keyof Server, value: never) => { + //@ts-ignore + this[property] = value; + return true; + } + }); + const { jsPlugins } = this.resolvedUserConfig; + // TODO type error and 而且还要排序 插件排序 + // @ts-ignore + for (const hook of getPluginHooks(jsPlugins, 'configureServer')) { + this.postConfigureServerHooks.push(await hook(reflexServer)); + } + } - bootstrap(Date.now() - start, this.compiler.config, hasCacheDir); + /** + * + */ + async #createWatcher() { + this.watcher = new Watcher(this.resolvedUserConfig); - await this.startServer(this.config); + await this.watcher.createWatcher(); - !__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ && - (await this.displayServerUrls()); + this.watcher.watcher.on('change', async (file: string | string[] | any) => { + const isConfigFile = this.resolvedUserConfig.configFilePath === file; + const isConfigDependencyFile = + this.resolvedUserConfig.configFileDependencies.some( + (name) => file === name + ); + const isEnvFile = this.resolvedUserConfig.envFiles.some( + (name) => file === name + ); + if (isConfigFile || isConfigDependencyFile || isEnvFile) { + debugServer?.(`[config change] ${colors.dim(file)}`); + await this.close(); + console.log('重启大法'); + + setTimeout(() => { + this.restartServer(); + }, 3000); + } + // TODO 做一个 onHmrUpdate 方法 + try { + this.hmrEngine.hmrUpdate(file); + } catch (error) { + this.logger.error(`Farm Hmr Update Error: ${error}`); + } + }); + const handleUpdateFinish = (updateResult: JsUpdateResult) => { + const added = [ + ...updateResult.added, + ...updateResult.extraWatchResult.add + ].map((addedModule) => { + const resolvedPath = this.compiler.transformModulePath( + this.root, + addedModule + ); + return resolvedPath; + }); + const filteredAdded = added.filter((file) => + this.watcher.filterWatchFile(file, this.root) + ); - if (open) { - let publicPath = getValidPublicPath(this.publicPath) || '/'; + if (filteredAdded.length > 0) { + this.watcher.watcher.add(filteredAdded); + } + }; - // const serverUrl = `${protocol}://${hostname.name}:${port}${publicPath}`; - // openBrowser(serverUrl); - } + this.hmrEngine?.onUpdateFinish(handleUpdateFinish); } - private async compile(): Promise { - try { - await this.compiler.compile(); - } catch (err) { - throw new Error(logError(err) as unknown as string); - } - - if (this.config.writeToDisk) { - this.compiler.writeResourcesToDisk(); - } else { - this.compiler.callWriteResourcesHook(); - } + async restartServer() { + console.log('开启重启大法呜啦啦'); + await this.createServer(); + await this.listen(); } - async startServer(serverOptions: UserServerConfig) { - const { port, hostname } = serverOptions; - const listen = promisify(this.server.listen).bind(this.server); - try { - await listen(port, hostname.host); - } catch (error) { - this.handleServerError(error, port, hostname.host); + /** + * Creates and initializes the WebSocket server. + * @throws {Error} If the HTTP server is not created. + */ + async createWebSocketServer() { + if (!this.httpServer) { + throw new Error( + 'Websocket requires a http server. please check the server is be created' + ); } + + this.ws = new WsServer(this); } - handleServerError( - error: Error & { code?: string }, - port: number, - host: string | undefined - ) { - const errorMap: ErrorMap = { - EACCES: `Permission denied to use port ${port} `, - EADDRNOTAVAIL: `The IP address host: ${host} is not available on this machine.` - }; + /** + * Creates and initializes the Hot Module Replacement (HMR) engine. + * @throws {Error} If the HTTP server is not created. + */ + createHmrEngine() { + if (!this.httpServer) { + throw new Error( + 'HmrEngine requires a http server. please check the server is be created' + ); + } - const errorMessage = - errorMap[error.code as keyof ErrorMap] || - `An error occurred: ${error.stack} `; - this.logger.error(errorMessage); + this.hmrEngine = new HmrEngine(this); } - async close() { - if (!this.server) { - this.logger.error('HTTP server is not created yet'); - } - // the server is already closed - if (!this.server.listening) { + /** + * Starts the server and begins listening for connections. + * @returns {Promise} + * @throws {Error} If there's an error starting the server. + */ + async listen(): Promise { + if (!this.httpServer) { + this.logger.warn('HTTP server is not created yet'); return; } - const promises = []; - if (this.ws) { - promises.push(this.ws.close()); - } + // TODO open browser when server is ready && open config is true - if (this.server) { - promises.push(new Promise((resolve) => this.server.close(resolve))); - } + const { port, hostname, open, strictPort } = this.serverOptions; - await Promise.all(promises); - } + try { + const serverPort = await this.httpServerStart({ + port, + strictPort: strictPort, + host: hostname.host + }); - async restart(promise: () => Promise) { - if (!this.restart_promise) { - this.restart_promise = promise(); - } - return this.restart_promise; - } + // TODO 这块要重新设计 restart 还有 端口冲突的问题 + // this.resolvedUserConfig + this.resolvedUserConfig.compilation.define.FARM_HMR_PORT = + serverPort.toString(); - private initializeKoaServer() { - this._app = new Koa(); - } + // TODO 暂时注释掉 + this.compiler = await createCompiler(this.resolvedUserConfig, logger); - public async createServer( - options: NormalizedServerConfig & UserPreviewServerConfig - ) { - const { https, host } = options; - const protocol = https ? 'https' : 'http'; - const hostname = await resolveHostname(host); - const publicPath = getValidPublicPath( - this.compiler?.config.config.output?.publicPath ?? - options?.output.publicPath - ); - // TODO refactor previewServer If it's preview server, then you can't use create server. we need to create a new one because hmr is false when you preview. - const hmrPath = normalizeBasePath( - path.join(publicPath, options.hmr.path ?? DEFAULT_HMR_OPTIONS.path) - ); + // compile the project and start the dev server + await this.#startCompile(); - this.config = { - ...options, - port: Number(process.env.FARM_DEV_SERVER_PORT || options.port), - hmr: { - ...options.hmr, - path: hmrPath - }, - protocol, - hostname - }; + // watch extra files after compile + this.watcher?.watchExtraFiles?.(); + // !__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ && + await this.displayServerUrls(this.serverOptions, this.publicPath); - const isProxy = options.proxy; - if (https) { - if (isProxy) { - this.server = httpsServer.createServer(https, this._app.callback()); - } else { - this.server = http2.createSecureServer( - { - maxSessionMemory: 1000, - ...https, - allowHTTP1: true - }, - this._app.callback() - ); + if (open) { + this.#openServerBrowser(); } - } else { - this.server = http.createServer(this._app.callback()); + } catch (error) { + this.logger.error(`start farm dev server error: ${error}`); + throw error; } } - public createWebSocket() { - if (!this.server) { - throw new Error('Websocket requires a server.'); + /** + * Starts the HTTP server. + * @protected + * @param {Object} serverOptions - The server options. + * @returns {Promise} The port the server is listening on. + * @throws {Error} If the server fails to start. + */ + protected async httpServerStart(serverOptions: { + port: number; + strictPort: boolean | undefined; + host: string | undefined; + }): Promise { + if (!this.httpServer) { + throw new Error('httpServer is not initialized'); } - this.ws = new WsServer(this.server, this.config, this.hmrEngine); - } - private invalidateVite() { - // Note: path should be Farm's id, which is a relative path in dev mode, - // but in vite, it's a url path like /xxx/xxx.js - this.ws.on('vite:invalidate', ({ path, message }) => { - // find hmr boundary starting from the parent of the file - this.logger.info(`HMR invalidate: ${path}. ${message ?? ''} `); - const parentFiles = this.compiler.getParentFiles(path); - this.hmrEngine.hmrUpdate(parentFiles, true); + let { port, strictPort, host } = serverOptions; + + return new Promise((resolve, reject) => { + const onError = (e: Error & { code?: string }) => { + if (e.code === 'EADDRINUSE') { + if (strictPort) { + this.httpServer.removeListener('error', onError); + reject(new Error(`Port ${port} is already in use`)); + } else { + console.info(`Port ${port} is in use, trying another one...`); + this.httpServer.listen(++port, host); + } + } else { + this.httpServer.removeListener('error', onError); + reject(e); + } + }; + + this.httpServer.on('error', onError); + + this.httpServer.listen(port, host, () => { + this.httpServer.removeListener('error', onError); + resolve(port); + }); }); } - public async createPreviewServer(options: UserPreviewServerConfig) { - await this.createServer(options as NormalizedServerConfig); + /** + * Get current compiler instance in the server + * @returns { CompilerType } return current compiler, may be compiler or undefined + */ + getCompiler(): CompilerType { + return this.compiler; + } + + /** + * Set current compiler instance in the server + * @param { Compiler } compiler - choose a new compiler instance + */ + setCompiler(compiler: Compiler) { + this.compiler = compiler; + } + + /** + * Adds additional files to be watched by the compiler. + * @param {string} root - The root directory. + * @param {string[]} deps - Array of file paths to be watched. + */ + addWatchFile(root: string, deps: string[]): void { + this.getCompiler().addExtraWatchFile(root, deps); + } - this.applyPreviewServerMiddlewares(this.config.middlewares); + /** + * resolve and setting server options + * + * this method extracts compilation and server options from resolvedUserConfig + * and set the publicPath and publicDir, and then resolve server options + * @private + * @returns { void } + */ + #resolveOptions() { + const { compilation, server } = this.resolvedUserConfig; + this.publicPath = compilation.output.publicPath; - await this.startServer(this.config); + this.publicDir = compilation.assets.publicDir; - await this.displayServerUrls(true); + this.serverOptions = server as CommonServerOptions & NormalizedServerConfig; + + this.root = compilation.root; } - public async createDevServer(options: NormalizedServerConfig) { - if (!this.compiler) { - throw new Error('DevServer requires a compiler for development mode.'); + /** + * Initializes and configures the middleware stack for the server. + * @private + */ + #initializeMiddlewares() { + this.middlewares.use(hmrPingMiddleware()); + + const { proxy, middlewareMode, cors } = this.serverOptions; + + if (cors) { + this.middlewares.use( + corsMiddleware(typeof cors === 'boolean' ? {} : cors) + ); } - await this.createServer(options); + if (proxy) { + const middlewareServer = + isObject(middlewareMode) && 'server' in middlewareMode + ? middlewareMode.server + : this.httpServer; - this.hmrEngine = new HmrEngine(this.compiler, this, this.logger); + this.middlewares.use(proxyMiddleware(this, middlewareServer)); + } - this.createWebSocket(); + if (this.publicPath !== '/') { + this.middlewares.use(publicPathMiddleware(this)); + } - this.invalidateVite(); + if (fs.existsSync(this.publicDir as PathLike)) { + this.middlewares.use(publicMiddleware(this)); + } - this.applyServerMiddlewares(options.middlewares); - } + if (this.resolvedUserConfig.compilation.lazyCompilation) { + this.middlewares.use(lazyCompilationMiddleware(this)); + } - static async resolvePortConflict( - normalizedDevConfig: NormalizedServerConfig, - logger: Logger - ): Promise { - let devPort = normalizedDevConfig.port; - let hmrPort = normalizedDevConfig.hmr.port; - - const { strictPort, host } = normalizedDevConfig; - const httpServer = http.createServer(); - const isPortAvailable = (portToCheck: number) => { - return new Promise((resolve, reject) => { - const onError = async (error: { code: string }) => { - if (error.code === 'EADDRINUSE') { - clearScreen(); - if (strictPort) { - httpServer.removeListener('error', onError); - reject(new Error(`Port ${devPort} is already in use`)); - } else { - logger.warn(`Port ${devPort} is in use, trying another one...`); - httpServer.removeListener('error', onError); - resolve(false); - } - } else { - logger.error(`Error in httpServer: ${error} `); - reject(true); - } - }; - httpServer.on('error', onError); - httpServer.on('listening', () => { - httpServer.close(); - resolve(true); - }); - httpServer.listen(portToCheck, host as string); - }); - }; + if (this.resolvedUserConfig.vitePlugins?.length) { + this.middlewares.use(adaptorViteMiddleware(this)); + } - let isPortAvailableResult = await isPortAvailable(devPort); + this.postConfigureServerHooks.forEach((fn) => fn && fn()); - while (isPortAvailableResult === false) { - if (typeof normalizedDevConfig.hmr === 'object') { - normalizedDevConfig.hmr.port = ++hmrPort; - } + // TODO todo add appType 这块要判断 单页面还是 多页面 多 html 处理不一样 + this.middlewares.use(htmlFallbackMiddleware(this)); + + this.middlewares.use(resourceMiddleware(this)); - normalizedDevConfig.port = ++devPort; - isPortAvailableResult = await isPortAvailable(devPort); + this.middlewares.use(notFoundMiddleware()); + } + + /** + * Compiles the project. + * @private + * @returns {Promise} + * @throws {Error} If compilation fails. + */ + async #compile(): Promise { + try { + await this.compiler.compile(); + await (this.resolvedUserConfig.server.writeToDisk + ? this.compiler.writeResourcesToDisk() + : this.compiler.callWriteResourcesHook()); + } catch (err) { + this.logger.error('Compilation failed:', err); + throw err; } } /** - * Add listening files for root manually - * - * > listening file with root must as file. - * - * @param root - * @param deps + * Opens the server URL in the default browser. + * @private */ + async #openServerBrowser() { + const url = + this.resolvedUrls?.local?.[0] ?? this.resolvedUrls?.network?.[0] ?? ''; + openBrowser(url); + } - addWatchFile(root: string, deps: string[]): void { - this.getCompiler().addExtraWatchFile(root, deps); + /** + * Starts the compilation process. + * @private + */ + async #startCompile() { + // check if cache dir exists + const { persistentCache } = this.compiler.config.config; + const hasCacheDir = await isCacheDirExists( + getCacheDir(this.root, persistentCache) + ); + const start = performance.now(); + await this.#compile(); + const duration = performance.now() - start; + bootstrap(duration, this.compiler.config, hasCacheDir); } - applyMiddlewares(internalMiddlewares?: DevServerMiddleware[]) { - internalMiddlewares.forEach((middleware) => { - const middlewareImpl = middleware(this); + /** + * Handles the initialization of public files. + * @private + * @returns {Promise>} A promise that resolves to a set of public file paths. + */ + async #handlePublicFiles() { + const initPublicFilesPromise = initPublicFiles(this.resolvedUserConfig); + return await initPublicFilesPromise; + } - if (middlewareImpl) { - if (Array.isArray(middlewareImpl)) { - middlewareImpl.forEach((m) => { - this._app.use(m); - }); - } else { - this._app.use(middlewareImpl); - } - } + /** + * Sets up the Vite invalidation handler. + * @private + */ + #invalidateVite(): void { + // Note: path should be Farm's id, which is a relative path in dev mode, + // but in vite, it's a url path like /xxx/xxx.js + this.ws.wss.on('vite:invalidate', ({ path, message }: any) => { + // find hmr boundary starting from the parent of the file + this.logger.info(`HMR invalidate: ${path}. ${message ?? ''} `); + const parentFiles = this.compiler.getParentFiles(path); + this.hmrEngine.hmrUpdate(parentFiles, true); }); } - - setCompiler(compiler: Compiler) { - this.compiler = compiler; + async closeServerAndExit() { + try { + await this.httpServer.close(); + } finally { + process.exit(); + } } - private applyPreviewServerMiddlewares( - middlewares?: DevServerMiddleware[] - ): void { - const internalMiddlewares = [ - ...(middlewares || []), - compression, - proxy, - staticMiddleware - ]; - this.applyMiddlewares(internalMiddlewares as DevServerMiddleware[]); - } + closeServer(): () => Promise { + if (!this.httpServer) { + return () => Promise.resolve(); + } + debugServer?.(`prepare close dev server`); - private applyServerMiddlewares(middlewares?: DevServerMiddleware[]): void { - const internalMiddlewares = [ - ...(middlewares || []), - hmrPing, - headers, - lazyCompilation, - cors, - resources, - proxy - ]; - - this.applyMiddlewares(internalMiddlewares as DevServerMiddleware[]); - } + let hasListened = false; + const openSockets = new Set(); - private async displayServerUrls(showPreviewFlag = false) { - let publicPath = getValidPublicPath( - this.compiler - ? this.compiler.config.config.output?.publicPath - : this.config.output.publicPath - ); + this.httpServer.on('connection', (socket) => { + openSockets.add(socket); + debugServer?.(`has open server socket ${openSockets}`); - this.resolvedUrls = await resolveServerUrls( - this.server, - this.config, - publicPath - ); + socket.on('close', () => { + debugServer?.('close all server socket'); + openSockets.delete(socket); + }); + }); + + this.httpServer.once('listening', () => { + hasListened = true; + }); + + return () => + new Promise((resolve, reject) => { + openSockets.forEach((s) => s.destroy()); - if (this.resolvedUrls) { - printServerUrls(this.resolvedUrls, this.logger, showPreviewFlag); - } else { - throw new Error('cannot print server URLs with Server Error.'); + if (hasListened) { + this.httpServer.close((err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + } else { + resolve(); + } + }); + } + + async close() { + if (!this.serverOptions.middlewareMode) { + teardownSIGTERMListener(this.closeServerAndExit); } + + await Promise.allSettled([this.watcher.close(), this.closeHttpServerFn()]); } } + +export const teardownSIGTERMListener = ( + callback: () => Promise +): void => { + process.off('SIGTERM', callback); + if (process.env.CI !== 'true') { + process.stdin.off('end', callback); + } +}; diff --git a/packages/core/src/newServer/middlewares/adaptorVite.ts b/packages/core/src/server/middlewares/adaptorVite.ts similarity index 100% rename from packages/core/src/newServer/middlewares/adaptorVite.ts rename to packages/core/src/server/middlewares/adaptorVite.ts diff --git a/packages/core/src/server/middlewares/cors.ts b/packages/core/src/server/middlewares/cors.ts deleted file mode 100644 index b85b182d6a..0000000000 --- a/packages/core/src/server/middlewares/cors.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { default as koaCors } from '@koa/cors'; -import { Middleware } from 'koa'; -import { Server } from '../index.js'; - -export function cors(devSeverContext: Server): Middleware { - const { config } = devSeverContext; - if (!config.cors) return; - return koaCors(typeof config.cors === 'boolean' ? {} : config.cors); -} - -export const corsMiddleware = cors; diff --git a/packages/core/src/newServer/middlewares/error.ts b/packages/core/src/server/middlewares/error.ts similarity index 100% rename from packages/core/src/newServer/middlewares/error.ts rename to packages/core/src/server/middlewares/error.ts diff --git a/packages/core/src/server/middlewares/headers.ts b/packages/core/src/server/middlewares/headers.ts deleted file mode 100644 index 16b65ba359..0000000000 --- a/packages/core/src/server/middlewares/headers.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Middleware } from 'koa'; -import { Server } from '../index.js'; - -export function headers(devSeverContext: Server): Middleware { - const { config } = devSeverContext; - if (!config.headers) return; - - return async (ctx, next) => { - if (config.headers) { - for (const name in config.headers) { - ctx.set(name, config.headers[name] as string | string[]); - } - } - await next(); - }; -} diff --git a/packages/core/src/server/middlewares/hmrPing.ts b/packages/core/src/server/middlewares/hmrPing.ts index f9226e5cfb..e7154cdb29 100644 --- a/packages/core/src/server/middlewares/hmrPing.ts +++ b/packages/core/src/server/middlewares/hmrPing.ts @@ -1,10 +1,13 @@ -import { Context, Next } from 'koa'; -export function hmrPing() { - return async (ctx: Context, next: Next) => { - if (ctx.get('accept') === 'text/x-farm-ping') { - ctx.status = 204; +export function hmrPingMiddleware() { + return function handleHMRPingMiddleware( + req: any, + res: any, + next: () => void + ) { + if (req.headers['accept'] === 'text/x-farm-ping') { + res.writeHead(204).end(); } else { - await next(); + next(); } }; } diff --git a/packages/core/src/newServer/middlewares/htmlFallback.ts b/packages/core/src/server/middlewares/htmlFallback.ts similarity index 100% rename from packages/core/src/newServer/middlewares/htmlFallback.ts rename to packages/core/src/server/middlewares/htmlFallback.ts diff --git a/packages/core/src/server/middlewares/index.ts b/packages/core/src/server/middlewares/index.ts index 1f6d27e22e..d89c308425 100644 --- a/packages/core/src/server/middlewares/index.ts +++ b/packages/core/src/server/middlewares/index.ts @@ -1,6 +1,11 @@ -export * from './cors.js'; -export * from './headers.js'; -export * from './lazy-compilation.js'; +export * from './error.js'; +export * from './htmlFallback.js'; +export * from './lazyCompilation.js'; +export * from './notFound.js'; export * from './proxy.js'; -export * from './resources.js'; export * from './static.js'; +export * from './resource.js'; +export * from './publicResource.js'; +export * from './publicPath.js'; +export * from './hmrPing.js'; +export * from './adaptorVite.js'; diff --git a/packages/core/src/server/middlewares/lazy-compilation.ts b/packages/core/src/server/middlewares/lazy-compilation.ts deleted file mode 100644 index 04b51159af..0000000000 --- a/packages/core/src/server/middlewares/lazy-compilation.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Lazy compilation middleware. Using the same logic as HMR middleware - */ - -import { relative } from 'node:path'; -import { Context, Middleware, Next } from 'koa'; - -import { - VIRTUAL_FARM_DYNAMIC_IMPORT_SUFFIX, - bold, - checkClearScreen, - cyan, - getDynamicResources, - green -} from '../../index.js'; -import { Server } from '../index.js'; - -import { existsSync } from 'node:fs'; -import { logError } from '../error.js'; - -export function lazyCompilation(devSeverContext: Server): Middleware { - const compiler = devSeverContext.getCompiler(); - - if (!compiler.config.config?.lazyCompilation) { - return; - } - - return async (ctx: Context, next: Next) => { - if (ctx.path === '/__lazy_compile') { - console.log(ctx.query.paths); - - const paths = (ctx.query.paths as string).split(','); - const pathsStr = paths - .map((p) => { - if ( - p.startsWith('/') && - !p.endsWith(VIRTUAL_FARM_DYNAMIC_IMPORT_SUFFIX) && - !existsSync(p) - ) { - return p; - } - const resolvedPath = compiler.transformModulePath( - compiler.config.config.root, - p - ); - return relative(compiler.config.config.root, resolvedPath); - }) - .join(', '); - checkClearScreen(compiler.config.config); - devSeverContext.logger.info(`Lazy compiling ${bold(cyan(pathsStr))}`); - const start = Date.now(); - // sync update when node is true - let result; - try { - // sync regenerate resources - result = await compiler.update(paths, true, false, false); - } catch (e) { - logError(e); - } - - if (!result) { - return; - } - - if (ctx.query.node || devSeverContext.config.writeToDisk) { - compiler.writeResourcesToDisk(); - } - - devSeverContext.logger.info( - `${bold(green(`✓`))} Lazy compilation done(${bold( - cyan(pathsStr) - )}) in ${bold(green(`${Date.now() - start}ms`))}.` - ); - - if (result) { - const { dynamicResources, dynamicModuleResourcesMap } = - getDynamicResources(result.dynamicResourcesMap); - - const returnObj = `{ - "dynamicResources": ${JSON.stringify(dynamicResources)}, - "dynamicModuleResourcesMap": ${JSON.stringify( - dynamicModuleResourcesMap - )} - }`; - const code = !ctx.query.node - ? `export default ${returnObj}` - : returnObj; - ctx.type = !ctx.query.node - ? 'application/javascript' - : 'application/json'; - ctx.body = code; - // enable cors - ctx.set('Access-Control-Allow-Origin', '*'); - ctx.set('Access-Control-Allow-Methods', '*'); - ctx.set('Access-Control-Allow-Headers', '*'); - } else { - throw new Error(`Lazy compilation result not found for paths ${paths}`); - } - } else { - await next(); - } - }; -} diff --git a/packages/core/src/newServer/middlewares/lazyCompilation.ts b/packages/core/src/server/middlewares/lazyCompilation.ts similarity index 100% rename from packages/core/src/newServer/middlewares/lazyCompilation.ts rename to packages/core/src/server/middlewares/lazyCompilation.ts diff --git a/packages/core/src/newServer/middlewares/notFound.ts b/packages/core/src/server/middlewares/notFound.ts similarity index 100% rename from packages/core/src/newServer/middlewares/notFound.ts rename to packages/core/src/server/middlewares/notFound.ts diff --git a/packages/core/src/server/middlewares/proxy.ts b/packages/core/src/server/middlewares/proxy.ts index 2815a63743..0acb5bd081 100644 --- a/packages/core/src/server/middlewares/proxy.ts +++ b/packages/core/src/server/middlewares/proxy.ts @@ -1,98 +1,176 @@ -import { Options, createProxyMiddleware } from 'http-proxy-middleware'; -import { Context, Middleware, Next } from 'koa'; - -import { UserConfig, UserHmrConfig } from '../../config/types.js'; -import { Logger } from '../../utils/logger.js'; -import type { Server } from '../index.js'; +import type * as http from 'node:http'; +import type * as net from 'node:net'; +import connect from 'connect'; +import httpProxy from 'http-proxy'; +import type Server from 'http-proxy'; +import { ResolvedUserConfig } from '../../config/types.js'; +import { colors } from '../../utils/color.js'; +export interface ProxyOptions extends httpProxy.ServerOptions { + rewrite?: (path: string) => string; + configure?: (proxy: httpProxy, options: ProxyOptions) => void; + bypass?: ( + req: http.IncomingMessage, + res: http.ServerResponse, + options: ProxyOptions + ) => void | null | undefined | false | string; + rewriteWsOrigin?: boolean | undefined; +} -export function useProxy( - options: UserConfig['server'], - devSeverContext: Server, - logger: Logger -) { - const proxyOption = options['proxy']; - for (const path of Object.keys(proxyOption)) { - let opts = proxyOption[path] as Options; +export function proxyMiddleware(app: any, server?: any) { + const { serverOptions, ResolvedUserConfig } = app; + const proxies: Record = {}; + Object.keys(serverOptions.proxy).forEach((context) => { + let opts = serverOptions.proxy[context]; + if (!opts) { + return; + } if (typeof opts === 'string') { - opts = { target: opts, changeOrigin: true }; + opts = { target: opts, changeOrigin: true } as ProxyOptions; } + const proxy = httpProxy.createProxyServer(opts) as Server; + + if (opts.configure) { + opts.configure(proxy, opts); + } + + proxy.on('error', (err, req, originalRes) => { + // When it is ws proxy, res is net.Socket + // originalRes can be falsy if the proxy itself errored + const res = originalRes as http.ServerResponse | net.Socket | undefined; + if (!res) { + console.log( + `${colors.red(`http proxy error: ${err.message}`)}\n${err.stack}` + ); + } else if ('req' in res) { + // console.log( + // `${colors.red(`http proxy error: ${originalRes.req.url}`)}\n${ + // err.stack + // }`, + // ); + + if (!res.headersSent && !res.writableEnded) { + res + .writeHead(500, { + 'Content-Type': 'text/plain' + }) + .end(); + } + } else { + console.log(`${colors.red(`ws proxy error:`)}\n${err.stack}`); + res.end(); + } + }); + + proxy.on('proxyReqWs', (proxyReq, req, socket, options, head) => { + rewriteOriginHeader(proxyReq, options, ResolvedUserConfig); - const proxyMiddleware = createProxyMiddleware(opts); - const server = devSeverContext.server; - const hmrOptions = options.hmr as UserHmrConfig; - if (server) { - server.on('upgrade', (req, socket: any, head) => { - if (req.url === hmrOptions.path) return; - for (const path in options.proxy) { - const opts = proxyOption[path] as Options; + socket.on('error', (err) => { + console.log(`${colors.red(`ws proxy socket error:`)}\n${err.stack}`); + }); + }); + + // https://github.com/http-party/node-http-proxy/issues/1520#issue-877626125 + // https://github.com/chimurai/http-proxy-middleware/blob/cd58f962aec22c925b7df5140502978da8f87d5f/src/plugins/default/debug-proxy-errors-plugin.ts#L25-L37 + proxy.on('proxyRes', (proxyRes, req, res) => { + res.on('close', () => { + if (!res.writableEnded) { + proxyRes.destroy(); + } + }); + }); + + // clone before saving because http-proxy mutates the options + proxies[context] = [proxy, { ...opts }]; + }); + + if (app.httpServer) { + app.httpServer.on('upgrade', (req: any, socket: any, head: any) => { + const url = req.url; + for (const context in proxies) { + if (doesProxyContextMatchUrl(context, url)) { + const [proxy, opts] = proxies[context]; if ( opts.ws || opts.target?.toString().startsWith('ws:') || opts.target?.toString().startsWith('wss:') ) { - const proxy = createProxyMiddleware(opts); - if (opts.pathRewrite) { - const fromPath = Object.keys(opts.pathRewrite)[0]; - const toPath: string = ( - opts.pathRewrite as { [regexp: string]: string } - )[fromPath]; - req.url = rewritePath(req.url, fromPath, toPath); + if (opts.rewrite) { + req.url = opts.rewrite(url); } - proxy.upgrade(req, socket, head); + proxy.ws(req, socket, head); return; } } - }); - } - - const errorHandlerMiddleware = async (ctx: Context, next: Next) => { - try { - await new Promise((resolve, reject) => { - proxyMiddleware(ctx.req, ctx.res, (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - await next(); - } catch (err) { - logger.error(`Error in proxy for path ${path}: \n ${err.stack}`); } - }; - - try { - if (path.length > 0) { - const pathRegex = new RegExp(path); - const app = devSeverContext.app(); - app.use((ctx, next) => { - if (pathRegex.test(ctx.path)) { - return errorHandlerMiddleware(ctx, next); + }); + } + return function handleProxyMiddleware( + req: http.IncomingMessage, + res: http.ServerResponse, + next: () => void + ) { + const url = req.url; + for (const context in proxies) { + if (doesProxyContextMatchUrl(context, url)) { + const [proxy, opts] = proxies[context]; + const options: httpProxy.ServerOptions = {}; + + if (opts.bypass) { + const bypassResult = opts.bypass(req, res, opts); + if (typeof bypassResult === 'string') { + req.url = bypassResult; + return next(); + } else if (bypassResult === false) { + res.statusCode = 404; + return res.end(); } - return next(); - }); + } + + if (opts.rewrite) { + req.url = opts.rewrite(req.url!); + } + proxy.web(req, res, options); + return; } - } catch (err) { - logger.error(`Error setting proxy for path ${path}: \n ${err.stack}`); } - } + next(); + }; } -export function proxy(devSeverContext: Server): Middleware { - const { config, logger } = devSeverContext; - if (!config.proxy) { - return; - } +function rewriteOriginHeader( + proxyReq: http.ClientRequest, + options: ProxyOptions, + config: ResolvedUserConfig +) { + // Browsers may send Origin headers even with same-origin + // requests. It is common for WebSocket servers to check the Origin + // header, so if rewriteWsOrigin is true we change the Origin to match + // the target URL. + if (options.rewriteWsOrigin) { + const { target } = options; - useProxy(config, devSeverContext, logger); -} + if (proxyReq.headersSent) { + console.warn( + 'Unable to rewrite Origin header as headers are already sent.' + ); + return; + } -function rewritePath(path: string, fromPath: RegExp | string, toPath: string) { - if (fromPath instanceof RegExp) { - return path.replace(fromPath, toPath); - } else { - return path.replace(new RegExp(fromPath), toPath); + if (proxyReq.getHeader('origin') && target) { + const changedOrigin = + typeof target === 'object' + ? `${target.protocol}//${target.host}` + : target; + + proxyReq.setHeader('origin', changedOrigin); + } } } + +function doesProxyContextMatchUrl(context: string, url: string): boolean { + return ( + (context[0] === '^' && new RegExp(context).test(url)) || + url.startsWith(context) + ); +} diff --git a/packages/core/src/newServer/middlewares/publicPath.ts b/packages/core/src/server/middlewares/publicPath.ts similarity index 100% rename from packages/core/src/newServer/middlewares/publicPath.ts rename to packages/core/src/server/middlewares/publicPath.ts diff --git a/packages/core/src/newServer/middlewares/publicResource.ts b/packages/core/src/server/middlewares/publicResource.ts similarity index 100% rename from packages/core/src/newServer/middlewares/publicResource.ts rename to packages/core/src/server/middlewares/publicResource.ts diff --git a/packages/core/src/newServer/middlewares/resource.ts b/packages/core/src/server/middlewares/resource.ts similarity index 100% rename from packages/core/src/newServer/middlewares/resource.ts rename to packages/core/src/server/middlewares/resource.ts diff --git a/packages/core/src/server/middlewares/resources.ts b/packages/core/src/server/middlewares/resources.ts deleted file mode 100644 index 175a42db4f..0000000000 --- a/packages/core/src/server/middlewares/resources.ts +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Serve resources that stored in memory. This middleware will be enabled when server.writeToDisk is false. - */ - -import { ReadStream, existsSync, readFileSync, statSync } from 'node:fs'; -import path, { extname } from 'node:path'; -import { Context, Middleware, Next } from 'koa'; -import koaStatic from 'koa-static'; -import { Compiler } from '../../compiler/index.js'; -import { - generateFileTree, - generateFileTreeHtml, - stripQueryAndHash -} from '../../utils/index.js'; -import { Server } from '../index.js'; - -interface RealResourcePath { - resourcePath: string; - rawPath: string; - resource: Buffer; -} - -function normalizePathByPublicPath(publicPath: string, resourcePath: string) { - const base = publicPath.match(/^https?:\/\//) ? '' : publicPath; - let resourceWithoutPublicPath = resourcePath; - - if (base && resourcePath.startsWith(base)) { - resourcePath = resourcePath.replace(new RegExp(`([^/]+)${base}`), '$1/'); - resourceWithoutPublicPath = resourcePath.slice(base.length); - } - - return { resourceWithoutPublicPath, fullPath: resourcePath }; -} - -function outputFilesMiddleware(compiler: Compiler): Middleware { - return async (ctx: Context, next: Next) => { - if (ctx.path === '/_output_files') { - const files = Object.keys(compiler.resources()).sort(); - const fileTree = generateFileTree(files); - ctx.type = '.html'; - ctx.body = generateFileTreeHtml(fileTree); - } else { - await next(); - } - }; -} - -function findResource( - paths: string[], - compiler: Compiler, - publicPath: string -): true | undefined | RealResourcePath { - for (const resourcePath of new Set(paths)) { - const { resourceWithoutPublicPath } = normalizePathByPublicPath( - publicPath, - resourcePath - ); - - const resource = compiler.resource(resourceWithoutPublicPath); - - if (resource) { - return { - resource, - resourcePath: resourceWithoutPublicPath, - rawPath: resourcePath - }; - } - } -} - -export function resourcesMiddleware(compiler: Compiler, serverContext: Server) { - return async (ctx: Context, next: Next) => { - await next(); - if (ctx.method !== 'HEAD' && ctx.method !== 'GET') return; - const hasHtmlPathWithPublicDir = path.resolve( - serverContext.publicDir, - 'index.html' - ); - - let isSkipPublicHtml; - if (ctx.body instanceof ReadStream) { - const readStream = ctx.body; - isSkipPublicHtml = readStream.path === hasHtmlPathWithPublicDir; - } - - // the response is already handled - if ((ctx.body || ctx.status !== 404) && !isSkipPublicHtml) return; - - const { config, publicPath } = serverContext; - // if compiler is compiling, wait for it to finish - if (compiler.compiling) { - await new Promise((resolve) => { - compiler.onUpdateFinish(() => resolve(undefined)); - }); - } - // Fallback to index.html if the resource is not found - const url = ctx.url?.slice(1) || 'index.html'; // remove leading slash - - let stripQueryAndHashUrl = stripQueryAndHash(url); - const resourceResult = findResource( - [url, stripQueryAndHashUrl], - compiler, - publicPath - ); - - if (resourceResult === true) { - return; - } - - if (resourceResult) { - ctx.type = extname(ctx?.path?.slice?.(1) || 'index.html'); - ctx.body = resourceResult.resource; - return; - } - - const { fullPath, resourceWithoutPublicPath } = normalizePathByPublicPath( - publicPath, - stripQueryAndHashUrl - ); - - // if resource is image or font, try it in local file system to be compatible with vue - { - // try local file system - const absPath = path.join( - compiler.config.config.root, - resourceWithoutPublicPath - ); - // const mimeStr = mime.lookup(absPath); - - if ( - existsSync(absPath) && - statSync(absPath).isFile() - // mimeStr && - // (mimeStr.startsWith('image') || mimeStr.startsWith('font')) - ) { - ctx.type = extname(fullPath); - ctx.body = readFileSync(absPath); - return; - } - - // try local file system with publicDir - const absPathPublicDir = path.resolve( - compiler.config.config.root, - compiler.config.config.assets.publicDir, - resourceWithoutPublicPath - ); - - if (existsSync(absPathPublicDir) && statSync(absPathPublicDir).isFile()) { - ctx.type = extname(fullPath); - ctx.body = readFileSync(absPathPublicDir); - return; - } - } - - // if resource is not found and spa is not disabled, find the closest index.html from resourcePath - { - // if request mime is not html, return 404 - if (!ctx.accepts('html')) { - ctx.status = 404; - } else if (config.spa !== false) { - const pathComps = resourceWithoutPublicPath.split('/'); - - while (pathComps.length > 0) { - const pathStr = pathComps.join('/') + '.html'; - const resource = compiler.resources()[pathStr]; - - if (resource) { - ctx.type = '.html'; - ctx.body = resource; - return; - } - - pathComps.pop(); - } - - const indexHtml = compiler.resources()['index.html']; - - if (indexHtml) { - ctx.type = '.html'; - ctx.body = indexHtml; - return; - } - } else { - // cannot find index.html, return 404 - ctx.status = 404; - } - } - }; -} - -export function resources(devSeverContext: Server): Middleware | Middleware[] { - const middlewares = [outputFilesMiddleware(devSeverContext.getCompiler())]; - if (!devSeverContext.config.writeToDisk) { - middlewares.push( - resourcesMiddleware(devSeverContext.getCompiler(), devSeverContext) - ); - } else { - middlewares.push( - koaStatic(devSeverContext.getCompiler().config.config.output.path, { - extensions: ['html'] - }) - ); - } - - middlewares.push(koaStatic(devSeverContext.publicDir)); - return middlewares; -} diff --git a/packages/core/src/server/middlewares/static.ts b/packages/core/src/server/middlewares/static.ts index 4c36bc501f..e69de29bb2 100644 --- a/packages/core/src/server/middlewares/static.ts +++ b/packages/core/src/server/middlewares/static.ts @@ -1,60 +0,0 @@ -import fs from 'fs'; -import path, { relative } from 'path'; -import { Context, Middleware, Next } from 'koa'; -import serve from 'koa-static'; -import { Server } from '../index.js'; - -export function staticMiddleware(devServerContext: Server): Middleware { - const { config } = devServerContext; - - const staticMiddleware = serve(config.distDir, { - // multiple page maybe receive "about", should auto try extension - extensions: ['html'] - }); - - // Fallback - const fallbackMiddleware: Middleware = async (ctx: Context, next: Next) => { - await next(); - - // If staticMiddleware doesn't find the file, try to serve index.html - if (ctx.status === 404 && !ctx.body) { - ctx.type = 'html'; - ctx.body = fs.createReadStream(path.join(config.distDir, 'index.html')); - } - }; - - return async (ctx: Context, next: Next) => { - if (ctx.status !== 404 || ctx.body) { - await next(); - return; - } - - const requestPath = ctx.request?.path; - let modifiedPath = requestPath; - - if (requestPath) { - if (config.output.publicPath.startsWith('/')) { - modifiedPath = requestPath.substring(config.output.publicPath.length); - } else { - const publicPath = relative( - path.join(config.distDir, config.output.publicPath), - config.distDir - ); - modifiedPath = requestPath.substring(publicPath.length + 1); - } - } - - ctx.request.path = `/${modifiedPath}`; - - try { - // Serve middleware for static files - await staticMiddleware(ctx, async () => { - // If staticMiddleware doesn't find the file or refresh current page router, execute fallbackMiddleware - await fallbackMiddleware(ctx, next); - }); - } catch (error) { - devServerContext.logger.error('Static file handling error:', error); - ctx.status = 500; - } - }; -} diff --git a/packages/core/src/newServer/preview.ts b/packages/core/src/server/preview.ts similarity index 100% rename from packages/core/src/newServer/preview.ts rename to packages/core/src/server/preview.ts diff --git a/packages/core/src/newServer/publicDir.ts b/packages/core/src/server/publicDir.ts similarity index 100% rename from packages/core/src/newServer/publicDir.ts rename to packages/core/src/server/publicDir.ts diff --git a/packages/core/src/newServer/send.ts b/packages/core/src/server/send.ts similarity index 100% rename from packages/core/src/newServer/send.ts rename to packages/core/src/server/send.ts diff --git a/packages/core/src/server/type.ts b/packages/core/src/server/type.ts index 6e6a93f64f..b8f0391368 100644 --- a/packages/core/src/server/type.ts +++ b/packages/core/src/server/type.ts @@ -1,4 +1,99 @@ -import http from 'node:http'; -import http2 from 'node:http2'; +export type HMRPayload = + | ConnectedPayload + | UpdatePayload + | FullReloadPayload + | CustomPayload + | ErrorPayload + | PrunePayload; -export type Server = http.Server | http2.Http2SecureServer; +export interface ConnectedPayload { + type: 'connected'; +} + +export interface UpdatePayload { + type: 'update'; + updates: Update[]; +} + +export interface Update { + type: 'js-update' | 'css-update'; + path: string; + acceptedPath: string; + timestamp: number; + /** @internal */ + explicitImportRequired?: boolean; + /** @internal */ + isWithinCircularImport?: boolean; + /** @internal */ + ssrInvalidates?: string[]; +} + +export interface PrunePayload { + type: 'prune'; + paths: string[]; +} + +export interface FullReloadPayload { + type: 'full-reload'; + path?: string; + /** @internal */ + triggeredBy?: string; +} + +export interface CustomPayload { + type: 'custom'; + event: string; + data?: any; +} + +export interface ErrorPayload { + type: 'error'; + err: { + [name: string]: any; + message: string; + stack: string; + id?: string; + frame?: string; + plugin?: string; + pluginCode?: string; + loc?: { + file?: string; + line: number; + column: number; + }; + }; +} + +export interface CustomEventMap { + 'vite:beforeUpdate': UpdatePayload; + 'vite:afterUpdate': UpdatePayload; + 'vite:beforePrune': PrunePayload; + 'vite:beforeFullReload': FullReloadPayload; + 'vite:error': ErrorPayload; + 'vite:invalidate': InvalidatePayload; + 'vite:ws:connect': WebSocketConnectionPayload; + 'vite:ws:disconnect': WebSocketConnectionPayload; +} + +export interface WebSocketConnectionPayload { + webSocket: WebSocket; +} + +export interface InvalidatePayload { + path: string; + message: string | undefined; +} + +export type InferCustomEventPayload = + T extends keyof CustomEventMap ? CustomEventMap[T] : any; + +export interface HMRBroadcasterClient { + /** + * Send event to the client + */ + send(payload: HMRPayload): void; + /** + * Send custom event + */ + send(event: string, payload?: CustomPayload['data']): void; +} diff --git a/packages/core/src/server/ws.ts b/packages/core/src/server/ws.ts index f2117dd934..e89eb90b34 100644 --- a/packages/core/src/server/ws.ts +++ b/packages/core/src/server/ws.ts @@ -1,24 +1,73 @@ -import type { IncomingMessage } from 'node:http'; +import { STATUS_CODES, createServer as createHttpServer } from 'node:http'; +import { createServer as createHttpsServer } from 'node:https'; +import path from 'node:path'; +import { WebSocketServer as WebSocketServerRaw_ } from 'ws'; + +import { ILogger, Logger } from '../utils/logger.js'; +import { isObject } from '../utils/share.js'; +import { HMRChannel } from './hmr.js'; +import { ServerOptions } from './index.js'; + +import type { IncomingMessage, Server } from 'node:http'; +import type { Socket } from 'node:net'; import type { Duplex } from 'node:stream'; -import type { WebSocket as WebSocketRawType } from 'ws'; - -import { WebSocket, WebSocketServer as WebSocketServerRaw } from 'ws'; -import { Logger, NormalizedServerConfig, red } from '../index.js'; -import { HmrEngine } from './hmr-engine.js'; -import { Server } from './type.js'; - -import type { ILogger } from '../index.js'; - -const HMR_HEADER = 'farm_hmr'; - -export interface IWebSocketServer { - clients: Set; +import type { WebSocket as WebSocketRaw } from 'ws'; +import type { WebSocket as WebSocketTypes } from '../types/ws.js'; + +import { + CustomPayload, + ErrorPayload, + HMRPayload, + InferCustomEventPayload +} from './type.js'; + +const WS_CONNECTED_MESSAGE = JSON.stringify({ type: 'connected' }); +const WS_CUSTOM_EVENT_TYPE = 'custom'; + +export interface WebSocketServer extends HMRChannel { + /** + * Listen on port and host + */ listen(): void; - send(payload: any): void; - send(event: T, payload?: any): void; + /** + * Get all connected clients. + */ + clients: Set; + /** + * Disconnect all clients and terminate the server. + */ close(): Promise; - on(event: string, listener: any): void; - off(event: string, listener: any): void; + /** + * Handle custom event emitted by `import.meta.hot.send` + */ + on: WebSocketTypes.Server['on'] & { + ( + event: T, + listener: WebSocketCustomListener> + ): void; + }; + /** + * Unregister event listener. + */ + off: WebSocketTypes.Server['off'] & { + (event: string, listener: Function): void; + }; +} + +export interface WebSocketClient { + /** + * Send event to the client + */ + send(payload: HMRPayload): void; + /** + * Send custom event + */ + send(event: string, payload?: CustomPayload['data']): void; + /** + * The raw WebSocket instance + * @advanced + */ + socket: WebSocketTypes; } const wsServerEvents = [ @@ -28,138 +77,138 @@ const wsServerEvents = [ 'listening', 'message' ]; + +function noop() { + // noop +} + +const HMR_HEADER = 'farm_hmr'; + export type WebSocketCustomListener = ( data: T, client: WebSocketClient ) => void; -export interface WebSocketClient { - send(payload: any): void; - send(event: string, payload?: any['data']): void; - rawSend(str: string): void; - socket: WebSocketRawType; -} -export default class WsServer implements IWebSocketServer { - public wss: WebSocketServerRaw; +const WebSocketServerRaw = process.versions.bun + ? // @ts-expect-error: Bun defines `import.meta.require` + import.meta.require('ws').WebSocketServer + : WebSocketServerRaw_; + +export class WsServer { + public wss: WebSocketServerRaw_; public customListeners = new Map>>(); - public clientsMap = new WeakMap(); - public bufferedError: any = null; + public clientsMap = new WeakMap(); + public bufferedError: ErrorPayload | null = null; public logger: ILogger; - constructor( - private httpServer: Server, - private config: NormalizedServerConfig, - private hmrEngine: HmrEngine, - logger?: ILogger - ) { - this.logger = logger ?? new Logger(); - this.createWebSocketServer(); - } - - private createWebSocketServer() { - try { - const WebSocketServer = process.versions.bun - ? // @ts-expect-error: Bun defines `import.meta.require` - import.meta.require('ws').WebSocketServer - : WebSocketServerRaw; - this.wss = new WebSocketServer({ noServer: true }); - this.connection(); - // TODO IF not have httpServer - this.httpServer.on('upgrade', this.upgradeWsServer.bind(this)); - } catch (err) { - this.handleSocketError(err); - } - } - - private upgradeWsServer( - request: IncomingMessage, + public wsServer: any; + wsHttpServer: Server | undefined; + private serverConfig: ServerOptions; + private port: number; + private host: string | undefined; + private hmrServerWsListener: ( + req: InstanceType, socket: Duplex, head: Buffer - ) { - if (this.isHMRRequest(request)) { - this.handleHMRUpgrade(request, socket, head); - } + ) => void; + /** + * Creates a new WebSocket server instance. + * @param {any} app - The application instance containing configuration and logger. + */ + constructor(private readonly app: any) { + this.logger = app.logger ?? new Logger(); + this.serverConfig = app.resolvedUserConfig.server as ServerOptions; + this.createWebSocketServer(); } - listen() { - // TODO alone with use httpServer we need start this function - // Start listening for WebSocket connections + /** + * Gets the server name. + * @returns {string} Returns "ws". + */ + get name(): string { + return 'ws'; } - // Farm uses the `sendMessage` method in hmr and - // the send method is reserved for migration vite - send(...args: any[]) { - let payload: any; - if (typeof args[0] === 'string') { - payload = { - type: 'custom', - event: args[0], - data: args[1] + /** + * Creates the WebSocket server. + */ + createWebSocketServer() { + if (this.serverConfig.ws === false) { + return { + name: 'ws', + get clients() { + return new Set(); + }, + async close() { + // noop + }, + on: noop as any as WebSocketServer['on'], + off: noop as any as WebSocketServer['off'], + listen: noop, + send: noop }; - } else { - payload = args[0]; } - if (payload.type === 'error' && !this.wss.clients.size) { - this.bufferedError = payload; - return; - } + const hmr = isObject(this.serverConfig.hmr) + ? this.serverConfig.hmr + : undefined; + const hmrServer = hmr?.server; + const hmrPort = hmr?.port; + const portsAreCompatible = !hmrPort || hmrPort === this.serverConfig.port; + this.wsServer = hmrServer || (portsAreCompatible && this.app.httpServer); - const stringified = JSON.stringify(payload); - this.wss.clients.forEach((client) => { - // readyState 1 means the connection is open - if (client.readyState === 1) { - client.send(stringified); - } - }); - } - - private isHMRRequest(request: IncomingMessage): boolean { - return ( - request.url === this.config.hmr.path && - request.headers['sec-websocket-protocol'] === HMR_HEADER - ); - } - - private handleHMRUpgrade( - request: IncomingMessage, - socket: Duplex, - head: Buffer - ) { - this.wss.handleUpgrade(request, socket, head, (ws: WebSocketRawType) => { - this.wss.emit('connection', ws, request); - }); - } + this.port = (hmrPort as number) || 9000; + this.host = ((hmr && hmr.host) as string) || undefined; - get clients(): Set { - return new Set( - Array.from(this.wss.clients).map(this.getSocketClient.bind(this)) - ); - } - - // a custom method defined by farm to send custom events - public sendCustomEvent(event: T, payload?: any) { - // Send a custom event to all clients - this.send({ type: 'custom', event, data: payload }); - } + if (this.wsServer) { + let hmrBase = this.app.publicPath; - public on(event: string, listener: (...args: any[]) => void) { - if (wsServerEvents.includes(event)) { - this.wss.on(event, listener); - } else { - this.addCustomEventListener(event, listener); - } - } + const hmrPath = hmr?.path; + if (hmrPath) { + hmrBase = path.posix.join(hmrBase, hmrPath as string); + } - public off(event: string, listener: () => void) { - if (wsServerEvents.includes(event)) { - this.wss.off(event, listener); + this.wss = new WebSocketServerRaw({ noServer: true }); + this.hmrServerWsListener = (req, socket, head) => { + // TODO 这里需要处理一下 normalizePublicPath 的问题 hmrBase 路径匹配不到 req.url 的问题 + if ( + req.headers['sec-websocket-protocol'] === HMR_HEADER && + req.url === hmrBase + ) { + this.wss.handleUpgrade(req, socket as Socket, head, (ws) => { + this.wss.emit('connection', ws, req); + }); + } + }; + this.wsServer.on('upgrade', this.hmrServerWsListener); } else { - this.removeCustomEventListener(event, listener); + // http server request handler keeps the same with + // https://github.com/websockets/ws/blob/45e17acea791d865df6b255a55182e9c42e5877a/lib/websocket-server.js#L88-L96 + const route = ((_, res) => { + const statusCode = 426; + const body = STATUS_CODES[statusCode]; + if (!body) + throw new Error( + `No body text found for the ${statusCode} status code` + ); + + res.writeHead(statusCode, { + 'Content-Length': body.length, + 'Content-Type': 'text/plain' + }); + res.end(body); + }) as Parameters[1]; + + if (this.app.httpsOptions) { + this.wsHttpServer = createHttpsServer(this.app.httpsOptions, route); + } else { + this.wsHttpServer = createHttpServer(route); + } + // vite dev server in middleware mode + // need to call ws listen manually + this.wss = new WebSocketServerRaw({ server: this.wsHttpServer }); } - } - connection() { - this.wss.on('connection', (socket: WebSocketRawType) => { + this.wss.on('connection', (socket) => { socket.on('message', (raw) => { if (!this.customListeners.size) return; let parsed: any; @@ -170,116 +219,170 @@ export default class WsServer implements IWebSocketServer { } // transform vite js-update to farm update if (parsed?.type === 'js-update' && parsed?.path) { - this.hmrEngine.hmrUpdate(parsed.path, true); + this.app.hmrEngine.hmrUpdate(parsed.path, true); return; } - - if (!parsed || parsed.type !== 'custom' || !parsed.event) return; + if (!parsed || parsed.type !== WS_CUSTOM_EVENT_TYPE || !parsed.event) + return; const listeners = this.customListeners.get(parsed.event); if (!listeners?.size) return; - const client = this.getSocketClient(socket); - + const client = this.#getSocketClient(socket); listeners.forEach((listener) => listener(parsed.data, client)); }); - - socket.on('error', (err: Error & { code: string }) => { - return this.handleSocketError(err); + socket.on('error', (err) => { + throw new Error(`WebSocket error: \n${err.stack}`); }); - socket.send(JSON.stringify({ type: 'connected' })); + socket.send(WS_CONNECTED_MESSAGE); if (this.bufferedError) { socket.send(JSON.stringify(this.bufferedError)); this.bufferedError = null; } }); + + this.wss.on('error', (e: Error & { code: string }) => { + if (e.code === 'EADDRINUSE') { + throw new Error('WebSocket server error: Port is already in use'); + } else { + throw new Error(`WebSocket server error ${e.stack || e.message}`); + } + }); } - public async close() { - if (this.upgradeWsServer && this.httpServer) { - this.httpServer.off('upgrade', this.upgradeWsServer); + /** + * Starts listening for WebSocket connections. + */ + listen() { + this.wsHttpServer?.listen(this.port, this.host); + } + + /** + * Adds a listener for the specified event. + * @param {string} event - The name of the event. + * @param {Function} fn - The listener function. + */ + on(event: string, fn: () => void) { + if (wsServerEvents.includes(event)) { + this.wss.on(event, fn); + } else { + if (!this.customListeners.has(event)) { + this.customListeners.set(event, new Set()); + } + this.customListeners.get(event).add(fn); } - await this.terminateAllClients(); - await this.closeWebSocketServer(); - // TODO if not have httpServer we need close httpServer } - private terminateAllClients() { - const terminatePromises = Array.from(this.wss.clients).map((client) => { - return new Promise((resolve) => { - if (client.readyState === WebSocket.OPEN) { - client.send(JSON.stringify({ type: 'closing' })); - client.close(1000, 'Server shutdown'); - } - // Temporarily remove the direct shutdown of ws - // client.terminate(); - client.once('close', () => resolve(true)); - }); - }); - return Promise.all(terminatePromises); + /** + * Removes a listener for the specified event. + * @param {string} event - The name of the event. + * @param {Function} fn - The listener function to remove. + */ + off(event: string, fn: () => void) { + if (wsServerEvents.includes(event)) { + this.wss.off(event, fn); + } else { + this.customListeners.get(event)?.delete(fn); + } } - private closeWebSocketServer() { - return new Promise((resolve, reject) => { - this.wss.close((err) => { - if (err) { - reject(err); - } else { - // TODO if not have httpServer - resolve(true); - } - }); - }); + /** + * Gets all connected clients. + * @returns {Set} A set of connected clients. + */ + get clients() { + return new Set( + Array.from(this.wss.clients).map((socket: any) => + this.#getSocketClient(socket) + ) + ); } - private addCustomEventListener(event: string, listener: () => void) { - if (!this.customListeners.has(event)) { - this.customListeners.set(event, new Set()); + /** + * Sends a message to all connected clients. + * @param {...any} args - The message arguments to send. + */ + send(...args: any[]) { + const payload: HMRPayload = this.#createPayload(...args); + if (payload.type === 'error' && !this.wss.clients.size) { + this.bufferedError = payload; + return; } - this.customListeners.get(event).add(listener); - } - private removeCustomEventListener(event: string, listener: () => void) { - this.customListeners.get(event)?.delete(listener); + const stringified = JSON.stringify(payload); + this.wss.clients.forEach((client: any) => { + // readyState 1 means the connection is open + if (client.readyState === 1) { + client.send(stringified); + } + }); } - private getSocketClient(socket: WebSocketRawType) { - if (!this.clientsMap.has(socket)) { - this.clientsMap.set(socket, { - send: (...args) => this.sendMessage(socket, ...args), - socket, - rawSend: (str) => socket.send(str) + /** + * Closes the WebSocket server. + * @returns {Promise} A promise that resolves when the server is closed. + */ + async close() { + // should remove listener if hmr.server is set + // otherwise the old listener swallows all WebSocket connections + if (this.hmrServerWsListener && this.wsServer) { + this.wsServer.off('upgrade', this.hmrServerWsListener); + } + try { + this.wss.clients.forEach((client: any) => { + client.terminate(); + }); + await new Promise((resolve, reject) => { + this.wss.close((err: any) => (err ? reject(err) : resolve())); }); + if (this.wsHttpServer) { + await new Promise((resolve, reject) => { + this.wsHttpServer.close((err: any) => + err ? reject(err) : resolve() + ); + }); + } + } catch (err) { + throw new Error(`Failed to close WebSocket server: ${err}`); } - return this.clientsMap.get(socket); } - private sendMessage(socket: WebSocketRawType, ...args: any[]) { - let payload: any; + /** + * Creates an HMR payload. + * @private + * @param {...any} args - The payload arguments. + * @returns {HMRPayload} The HMR payload object. + */ + #createPayload(...args: any[]): HMRPayload { if (typeof args[0] === 'string') { - payload = { + return { type: 'custom', event: args[0], data: args[1] }; } else { - payload = args[0]; + return args[0]; } - socket.send(JSON.stringify(payload)); } - private handleSocketError(err: Error & { code: string }) { - if (err.code === 'EADDRINUSE') { - this.logger.error(red(`WebSocket server error: Port is already in use`), { - error: err + /** + * Gets the client object associated with a WebSocket. + * @private + * @param {WebSocketRaw} socket - The raw WebSocket object. + * @returns {WebSocketClient} The client object. + */ + #getSocketClient(socket: WebSocketRaw) { + if (!this.clientsMap.has(socket)) { + this.clientsMap.set(socket, { + send: (...args) => { + const payload: HMRPayload = this.#createPayload(...args); + socket.send(JSON.stringify(payload)); + }, + // @ts-ignore + rawSend: (str: string) => socket.send(str), + socket }); - } else { - this.logger.error( - red(`WebSocket server error:\n${err.stack || err.message}`), - { - error: err - } - ); } + return this.clientsMap.get(socket); } } diff --git a/packages/core/src/watcher/create-watcher.ts b/packages/core/src/watcher/create-watcher.ts deleted file mode 100644 index 8afe709617..0000000000 --- a/packages/core/src/watcher/create-watcher.ts +++ /dev/null @@ -1,52 +0,0 @@ -import path from 'node:path'; - -import chokidar, { FSWatcher, WatchOptions } from 'chokidar'; -import glob from 'fast-glob'; - -import { type ResolvedUserConfig, getCacheDir } from '../index.js'; - -function resolveChokidarOptions( - config: ResolvedUserConfig, - insideChokidarOptions: WatchOptions -): WatchOptions { - const { ignored = [], ...userChokidarOptions } = - config.server?.hmr?.watchOptions ?? {}; - - const cacheDir = getCacheDir(config.root, config.compilation.persistentCache); - - const options = { - ignored: [ - '**/.git/**', - '**/test-results/**', // Playwright - glob.escapePath(cacheDir) + '/**', - glob.escapePath( - path.resolve(config.root, config.compilation.output.path) - ) + '/**', - ...(Array.isArray(ignored) ? ignored : [ignored]) - ], - ignoreInitial: true, - ignorePermissionErrors: true, - // for windows and macos, we need to wait for the file to be written - awaitWriteFinish: - process.platform === 'linux' - ? undefined - : { - stabilityThreshold: 10, - pollInterval: 10 - }, - ...userChokidarOptions, - ...insideChokidarOptions - }; - - return options; -} - -export function createWatcher( - config: ResolvedUserConfig, - files: string[], - chokidarOptions?: WatchOptions -): FSWatcher { - const options = resolveChokidarOptions(config, chokidarOptions); - - return chokidar.watch(files, options); -} diff --git a/packages/core/src/watcher/index.ts b/packages/core/src/watcher/index.ts index 7513caad43..bbe8c0ccde 100644 --- a/packages/core/src/watcher/index.ts +++ b/packages/core/src/watcher/index.ts @@ -8,7 +8,7 @@ import { Compiler } from '../compiler/index.js'; import type { ResolvedUserConfig } from '../config/index.js'; import { createInlineCompiler } from '../index.js'; // import { Server } from '../server/index.js'; -import { NewServer } from '../newServer/index.js'; +import { Server } from '../server/index.js'; import type { JsUpdateResult } from '../types/binding.js'; import { createDebugger } from '../utils/debug.js'; import { @@ -17,7 +17,6 @@ import { compilerHandler, getCacheDir } from '../utils/index.js'; -import { createWatcher } from './create-watcher.js'; interface ImplFileWatcher { // watch(): Promise; From a705d4b50271b1d31da44e2bf8583f0d27548932 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Wed, 4 Sep 2024 10:25:21 +0800 Subject: [PATCH 094/369] chore: remove unless file prepare refactor stage: 1 --- packages/cli/src/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 01bc90f374..17881076fa 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -94,12 +94,10 @@ cli } }; - const { start2 }: any = await resolveCore(); - // const { start }: any = await resolveCore(); + const { start } = await resolveCore(); handleAsyncOperationErrors( - // start(defaultOptions), - start2(defaultOptions), + start(defaultOptions), 'Failed to start server' ); } From 4c8f08d4758e0aa4ecf53b113e9dfb9231befdfa Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Wed, 4 Sep 2024 16:08:03 +0800 Subject: [PATCH 095/369] chore: prepare refactor vite adapter --- examples/vue3/farm.config.ts | 24 +++++- examples/vue3/package.json | 1 + examples/vue3/src/App.vue | 2 +- js-plugins/electron/src/index.ts | 2 +- js-plugins/visualizer/src/dev.ts | 2 +- packages/cli/src/index.ts | 6 +- packages/core/scripts/build-cjs.mjs | 9 ++- packages/core/src/index.ts | 9 ++- packages/core/src/plugin/index.ts | 12 +++ .../core/src/plugin/js/vite-plugin-adapter.ts | 17 +--- .../core/src/plugin/js/vite-server-adapter.ts | 74 +++++++++--------- packages/core/src/plugin/type.ts | 2 +- packages/core/src/server/index.ts | 25 +++--- pnpm-lock.yaml | 78 ++++++++++++------- 14 files changed, 152 insertions(+), 111 deletions(-) diff --git a/examples/vue3/farm.config.ts b/examples/vue3/farm.config.ts index debf655e2d..a40f62049c 100644 --- a/examples/vue3/farm.config.ts +++ b/examples/vue3/farm.config.ts @@ -3,7 +3,21 @@ import vue from "@vitejs/plugin-vue"; // import { VueRouterAutoImports } from "unplugin-vue-router"; // import VueRouter from "unplugin-vue-router/vite"; // import AutoImport from 'unplugin-auto-import/vite'; - +import compression from "compression"; +const compressionMiddleware = () => { + return { + name: "compression", + configureServer(server) { + console.log("Middleware stack:", server.middlewares.stack.length); + // console.log("server", server.middlewares); + server.middlewares.use(compression()); + server.middlewares.use((req, res, next) => { + next(); + }); + console.log("Middleware count:", server.middlewares.stack.length); + }, + }; +}; export default defineConfig({ vitePlugins: [ // VueRouter(), @@ -11,10 +25,12 @@ export default defineConfig({ // imports: ["vue", VueRouterAutoImports], // }), vue(), + compressionMiddleware(), ], - // compilation: { - // persistentCache: false, - // }, + compilation: { + persistentCache: false, + }, + // plugins: [compressionMiddleware()], // server: { // port: 5232, // proxy: { diff --git a/examples/vue3/package.json b/examples/vue3/package.json index 4da281d2f5..1529fb4397 100644 --- a/examples/vue3/package.json +++ b/examples/vue3/package.json @@ -16,6 +16,7 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.4", + "compression": "^1.7.4", "core-js": "^3.30.1" } } diff --git a/examples/vue3/src/App.vue b/examples/vue3/src/App.vue index 8e290ad14a..684262cdb9 100644 --- a/examples/vue3/src/App.vue +++ b/examples/vue3/src/App.vue @@ -6,7 +6,7 @@ import HelloWorld from "./components/HelloWorld.vue";
- 123213123 + diff --git a/js-plugins/electron/src/index.ts b/js-plugins/electron/src/index.ts index 46305ba716..ac3bb5fdcd 100644 --- a/js-plugins/electron/src/index.ts +++ b/js-plugins/electron/src/index.ts @@ -46,7 +46,7 @@ export default function farmElectronPlugin( // config.compilation.assets.publicDir ??= '' return config; }, - configureDevServer(server) { + configureServer(server) { isDev = true; server.server?.once('listening', () => { diff --git a/js-plugins/visualizer/src/dev.ts b/js-plugins/visualizer/src/dev.ts index c0753d1a3f..352eb2a3e6 100644 --- a/js-plugins/visualizer/src/dev.ts +++ b/js-plugins/visualizer/src/dev.ts @@ -17,7 +17,7 @@ export default function farmRecorderPlugin(): JsPlugin { config.compilation = { ...config.compilation, record: true }; return config; }, - configureDevServer(server) { + configureServer(server) { const middlewares = [records] as DevServerMiddleware[]; server.applyMiddlewares(middlewares); } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 17881076fa..7d952537bc 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -137,10 +137,10 @@ cli treeShaking: options.treeShaking } }; + // TODO build + // const { build } = await resolveCore(); - const { build } = await resolveCore(); - - handleAsyncOperationErrors(build(defaultOptions), 'error during build'); + // handleAsyncOperationErrors(build(defaultOptions), 'error during build'); }); cli diff --git a/packages/core/scripts/build-cjs.mjs b/packages/core/scripts/build-cjs.mjs index 17c7044210..77ad4af230 100644 --- a/packages/core/scripts/build-cjs.mjs +++ b/packages/core/scripts/build-cjs.mjs @@ -1,11 +1,12 @@ import path from 'path'; // import { copyFile, readdir } from 'fs/promises'; -import { build } from '../dist/index.js'; +// TODO build cjs +// import { build } from '../dist/index.js'; -await build({ - configPath: path.join(process.cwd(), 'farm.config.ts') -}); +// await build({ +// configPath: path.join(process.cwd(), 'farm.config.ts') +// }); // if (!process.env.FARM_PUBLISH) { // // copy artifacts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e717862697..fa2880406e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -538,11 +538,12 @@ export async function start( const server = new Server(resolvedUserConfig, logger); await server.createServer(); + // TODO 这段逻辑放在 创建 http server 之后 放到 server 里面 - resolvedUserConfig.jsPlugins.forEach((plugin: JsPlugin) => - // @ts-ignore - plugin.configureDevServer?.(server) - ); + // resolvedUserConfig.jsPlugins.forEach((plugin: JsPlugin) => + // // @ts-ignore + // plugin.configureServer?.(server), + // ); server.listen(); } catch (error) { logger.error('Failed to start the server', { exit: true, error }); diff --git a/packages/core/src/plugin/index.ts b/packages/core/src/plugin/index.ts index 6e942fb305..f922589005 100644 --- a/packages/core/src/plugin/index.ts +++ b/packages/core/src/plugin/index.ts @@ -154,3 +154,15 @@ export function getSortedPluginHooks( ): any { return plugins.map((p: JsPlugin) => p[hookName]).filter(Boolean); } + +export function getPluginHooksThis( + plugins: JsPlugin[], + hookName: keyof JsPlugin +) { + return plugins + .map((p: JsPlugin) => + typeof p[hookName] === 'function' ? p[hookName].bind(p) : p[hookName] + ) + .filter(Boolean) + .filter(Boolean); +} diff --git a/packages/core/src/plugin/js/vite-plugin-adapter.ts b/packages/core/src/plugin/js/vite-plugin-adapter.ts index 8346760328..5b5afdd92f 100644 --- a/packages/core/src/plugin/js/vite-plugin-adapter.ts +++ b/packages/core/src/plugin/js/vite-plugin-adapter.ts @@ -270,12 +270,11 @@ export class VitePluginAdapter implements JsPlugin { } } - async configureDevServer(devServer: any) { + async configureServer(devServer: any) { const hook = this.wrapRawPluginHook( 'configureServer', this._rawPlugin.configureServer ); - this._viteDevServer = createViteDevServerAdapter( this.name, this._viteConfig, @@ -284,20 +283,6 @@ export class VitePluginAdapter implements JsPlugin { if (hook) { await hook(this._viteDevServer); - - this._viteDevServer.middlewareCallbacks.forEach((cb) => { - // devServer.middlewares.use(cb); - // devServer.app().use((ctx: any, koaNext: any) => { - // return new Promise((resolve, reject) => { - // // koaNext is async, but vite's next is sync, we need a adapter here - // const next = (err: Error) => { - // if (err) reject(err); - // koaNext().then(resolve); - // }; - // return cb(ctx.req, ctx.res, next); - // }); - // }); - }); } } diff --git a/packages/core/src/plugin/js/vite-server-adapter.ts b/packages/core/src/plugin/js/vite-server-adapter.ts index a37a008440..63613808d9 100644 --- a/packages/core/src/plugin/js/vite-server-adapter.ts +++ b/packages/core/src/plugin/js/vite-server-adapter.ts @@ -13,7 +13,6 @@ export class ViteDevServerAdapter { pluginName: string; watcher: FSWatcher; middlewares: any; - middlewareCallbacks: any[]; ws: any; httpServer: httpServer; @@ -27,44 +26,45 @@ export class ViteDevServerAdapter { this.watcher = server.watcher.getInternalWatcher(); - this.middlewareCallbacks = []; - this.middlewares = new Proxy( - { - use: (...args: any[]) => { - if ( - args.length === 2 && - typeof args[0] === 'string' && - typeof args[1] === 'function' - ) { - this.middlewareCallbacks.push((req: any, res: any, next: any) => { - const [url, cb] = args; - if (req.url.startsWith(url)) { - cb(req, res, next); - } - }); - } else if (args.length === 1 && typeof args[0] === 'function') { - this.middlewareCallbacks.push(args[0]); - } - } - }, - { - get(target, key) { - if (key === 'use') { - return target[key as keyof typeof target]; - } - - throwIncompatibleError( - pluginName, - 'viteDevServer.middlewares', - ['use'], - key - ); - } - } - ); + this.middlewares = server.middlewares; + // this.middlewares = new Proxy( + // { + // use: (...args: any[]) => { + // if ( + // args.length === 2 && + // typeof args[0] === "string" && + // typeof args[1] === "function" + // ) { + // this.middlewareCallbacks.push((req: any, res: any, next: any) => { + // const [url, cb] = args; + // if (req.url.startsWith(url)) { + // cb(req, res, next); + // } + // }); + // } else if (args.length === 1 && typeof args[0] === "function") { + // this.middlewareCallbacks.push(args[0]); + // } + // }, + // }, + // { + // get(target, key) { + // if (key === "use") { + // return target[key as keyof typeof target]; + // } + + // throwIncompatibleError( + // pluginName, + // "viteDevServer.middlewares", + // ["use"], + // key, + // ); + // }, + // }, + // ); this.ws = server.ws; - this.httpServer = server.server; + + this.httpServer = server.httpServer; } } diff --git a/packages/core/src/plugin/type.ts b/packages/core/src/plugin/type.ts index 34f0954566..07f5bf5ee7 100644 --- a/packages/core/src/plugin/type.ts +++ b/packages/core/src/plugin/type.ts @@ -141,7 +141,7 @@ export interface JsPlugin { * @param server * @returns */ - configureDevServer?: (server: Server) => void | Promise; + configureServer?: (server: Server) => void | Promise; /** * @param compiler * @returns diff --git a/packages/core/src/server/index.ts b/packages/core/src/server/index.ts index a4622568e3..bbf984a726 100644 --- a/packages/core/src/server/index.ts +++ b/packages/core/src/server/index.ts @@ -43,7 +43,11 @@ import type { NormalizedServerConfig, ResolvedUserConfig } from '../config/types.js'; -import { getPluginHooks, getSortedPluginHooks } from '../plugin/index.js'; +import { + getPluginHooks, + getPluginHooksThis, + getSortedPluginHooks +} from '../plugin/index.js'; import { JsUpdateResult } from '../types/binding.js'; import { createDebugger } from '../utils/debug.js'; @@ -173,12 +177,10 @@ export class Server extends httpServer { // invalidate vite handler this.#invalidateVite(); - this.#createWatcher(); - - this.handleConfigureServer(); + await this.#createWatcher(); // init middlewares - this.#initializeMiddlewares(); + await this.#initializeMiddlewares(); if (!middlewareMode && this.httpServer) { this.httpServer.once('listening', () => { @@ -216,7 +218,7 @@ export class Server extends httpServer { const { jsPlugins } = this.resolvedUserConfig; // TODO type error and 而且还要排序 插件排序 // @ts-ignore - for (const hook of getPluginHooks(jsPlugins, 'configureServer')) { + for (const hook of getPluginHooksThis(jsPlugins, 'configureServer')) { this.postConfigureServerHooks.push(await hook(reflexServer)); } } @@ -226,7 +228,6 @@ export class Server extends httpServer { */ async #createWatcher() { this.watcher = new Watcher(this.resolvedUserConfig); - await this.watcher.createWatcher(); this.watcher.watcher.on('change', async (file: string | string[] | any) => { @@ -241,11 +242,9 @@ export class Server extends httpServer { if (isConfigFile || isConfigDependencyFile || isEnvFile) { debugServer?.(`[config change] ${colors.dim(file)}`); await this.close(); - console.log('重启大法'); - setTimeout(() => { this.restartServer(); - }, 3000); + }, 1000); } // TODO 做一个 onHmrUpdate 方法 try { @@ -278,7 +277,6 @@ export class Server extends httpServer { } async restartServer() { - console.log('开启重启大法呜啦啦'); await this.createServer(); await this.listen(); } @@ -448,9 +446,11 @@ export class Server extends httpServer { * Initializes and configures the middleware stack for the server. * @private */ - #initializeMiddlewares() { + async #initializeMiddlewares() { this.middlewares.use(hmrPingMiddleware()); + await this.handleConfigureServer(); + const { proxy, middlewareMode, cors } = this.serverOptions; if (cors) { @@ -555,6 +555,7 @@ export class Server extends httpServer { #invalidateVite(): void { // Note: path should be Farm's id, which is a relative path in dev mode, // but in vite, it's a url path like /xxx/xxx.js + this.ws.wss.on('vite:invalidate', ({ path, message }: any) => { // find hmr boundary starting from the parent of the file this.logger.info(`HMR invalidate: ${path}. ${message ?? ''} `); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 362958ed65..55eb50d4ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -664,7 +664,7 @@ importers: version: 5.1.3(@types/eslint@8.56.10)(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.3.2) farmup: specifier: latest - version: 0.1.1 + version: 0.1.2 jest: specifier: ^29.5.0 version: 29.7.0(@types/node@20.12.12)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@20.12.12)(typescript@5.4.5)) @@ -1643,7 +1643,7 @@ importers: version: 4.0.0 svelte-check: specifier: ^3.6.2 - version: 3.6.2(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0) + version: 3.6.2(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0) tslib: specifier: ^2.6.2 version: 2.6.2 @@ -2020,7 +2020,7 @@ importers: dependencies: unplugin-auto-import: specifier: ^0.16.7 - version: 0.16.7(@vueuse/core@9.13.0(vue@3.4.27(typescript@5.5.4)))(rollup@4.14.1) + version: 0.16.7(@vueuse/core@9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)))(rollup@4.14.1) unplugin-vue-router: specifier: ^0.7.0 version: 0.7.0(rollup@4.14.1)(vue-router@4.4.3(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) @@ -2034,6 +2034,9 @@ importers: '@vitejs/plugin-vue': specifier: ^5.0.4 version: 5.1.2(vite@5.2.8(@types/node@22.5.0)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1))(vue@3.4.27(typescript@5.5.4)) + compression: + specifier: ^1.7.4 + version: 1.7.4 core-js: specifier: ^3.30.1 version: 3.37.1 @@ -9040,8 +9043,8 @@ packages: farm-plugin-replace-dirname@0.2.1: resolution: {integrity: sha512-aJ4euQzxoq0sVu4AwXrNQflHJrSZdrdApGEyVRtN6KiCop3CHXnTg9ydlyCNXN2unQB283aNjojvCd5E/32KgA==} - farmup@0.1.1: - resolution: {integrity: sha512-JXP4PJZOs1WzI/cHbCfVGuBca66++AVItFLoZNLESjnyua9jfFz+TzgeNIXLnfsbb7pMmDLjAt6j7oJEtKA10Q==} + farmup@0.1.2: + resolution: {integrity: sha512-mWdAVzyi3fnvL/ARNomH5jI8IahLAPuW9xg0BRSMO72qLrCI3w2hpOgmKfEiOJf742DRSvpH3Z3pcqAa32sLmw==} hasBin: true fast-deep-equal@3.1.3: @@ -15256,7 +15259,7 @@ snapshots: yjs: 13.6.18 zustand: 4.5.5(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0) zustand-middleware-yjs: 1.3.1(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0) - zustand-utils: 1.3.2(react@18.2.0)(zustand@4.5.5(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0)) + zustand-utils: 1.3.2(react@18.2.0)(zustand@4.5.5(@types/react@18.2.35)(immer@10.0.3)(react@18.2.0)) transitivePeerDependencies: - '@emotion/react' - '@types/react' @@ -20917,6 +20920,11 @@ snapshots: vue: 3.4.15(typescript@5.5.4) optional: true + '@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4))': + dependencies: + vue: 3.4.27(typescript@5.5.4) + optional: true + '@vue/devtools-api@6.5.1': {} '@vue/devtools-api@6.6.3': {} @@ -21065,12 +21073,12 @@ snapshots: - '@vue/composition-api' - vue - '@vueuse/core@9.13.0(vue@3.4.27(typescript@5.5.4))': + '@vueuse/core@9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4))': dependencies: '@types/web-bluetooth': 0.0.16 '@vueuse/metadata': 9.13.0 - '@vueuse/shared': 9.13.0(vue@3.4.27(typescript@5.5.4)) - vue-demi: 0.14.10(vue@3.4.27(typescript@5.5.4)) + '@vueuse/shared': 9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) + vue-demi: 0.14.10(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -21085,9 +21093,9 @@ snapshots: - '@vue/composition-api' - vue - '@vueuse/shared@9.13.0(vue@3.4.27(typescript@5.5.4))': + '@vueuse/shared@9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4))': dependencies: - vue-demi: 0.14.10(vue@3.4.27(typescript@5.5.4)) + vue-demi: 0.14.10(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -24108,9 +24116,10 @@ snapshots: farm-plugin-replace-dirname-win32-ia32-msvc: 0.2.1 farm-plugin-replace-dirname-win32-x64-msvc: 0.2.1 - farmup@0.1.1: + farmup@0.1.2: dependencies: '@farmfe/core': link:packages/core + tmp: 0.2.3 fast-deep-equal@3.1.3: {} @@ -27506,12 +27515,13 @@ snapshots: postcss: 8.4.39 ts-node: 10.9.1(@types/node@22.5.0)(typescript@5.5.4) - postcss-load-config@4.0.1(postcss@8.4.40): + postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)): dependencies: lilconfig: 2.1.0 yaml: 2.3.4 optionalDependencies: postcss: 8.4.40 + ts-node: 10.9.1(@types/node@22.5.0)(typescript@5.2.2) optional: true postcss-merge-rules@7.0.2(postcss@8.4.39): @@ -29473,7 +29483,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.6.2(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0): + svelte-check@3.6.2(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0): dependencies: '@jridgewell/trace-mapping': 0.3.20 chokidar: 3.5.3 @@ -29482,7 +29492,7 @@ snapshots: picocolors: 1.0.0 sade: 1.8.1 svelte: 4.0.0 - svelte-preprocess: 5.1.3(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0)(typescript@5.4.5) + svelte-preprocess: 5.1.3(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0)(typescript@5.4.5) typescript: 5.4.5 transitivePeerDependencies: - '@babel/core' @@ -29499,7 +29509,7 @@ snapshots: dependencies: svelte: 4.0.0 - svelte-preprocess@5.1.3(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0)(typescript@5.4.5): + svelte-preprocess@5.1.3(@babel/core@7.25.2)(less@4.2.0)(postcss-load-config@4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)))(postcss@8.4.40)(sass@1.74.1)(svelte@4.0.0)(typescript@5.4.5): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -29511,7 +29521,7 @@ snapshots: '@babel/core': 7.25.2 less: 4.2.0 postcss: 8.4.40 - postcss-load-config: 4.0.1(postcss@8.4.40) + postcss-load-config: 4.0.1(postcss@8.4.40)(ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2)) sass: 1.74.1 typescript: 5.4.5 @@ -29948,6 +29958,25 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + ts-node@10.9.1(@types/node@22.5.0)(typescript@5.2.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.5.0 + acorn: 8.12.0 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.2.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optional: true + ts-node@10.9.1(@types/node@22.5.0)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -30253,7 +30282,7 @@ snapshots: transitivePeerDependencies: - rollup - unplugin-auto-import@0.16.7(@vueuse/core@9.13.0(vue@3.4.27(typescript@5.5.4)))(rollup@4.14.1): + unplugin-auto-import@0.16.7(@vueuse/core@9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)))(rollup@4.14.1): dependencies: '@antfu/utils': 0.7.10 '@rollup/pluginutils': 5.1.0(rollup@4.14.1) @@ -30264,7 +30293,7 @@ snapshots: unimport: 3.4.0(rollup@4.14.1) unplugin: 1.10.1 optionalDependencies: - '@vueuse/core': 9.13.0(vue@3.4.27(typescript@5.5.4)) + '@vueuse/core': 9.13.0(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)) transitivePeerDependencies: - rollup @@ -30865,9 +30894,11 @@ snapshots: optionalDependencies: '@vue/composition-api': 1.7.2(vue@3.4.15(typescript@5.5.4)) - vue-demi@0.14.10(vue@3.4.27(typescript@5.5.4)): + vue-demi@0.14.10(@vue/composition-api@1.7.2(vue@3.4.27(typescript@5.5.4)))(vue@3.4.27(typescript@5.5.4)): dependencies: vue: 3.4.27(typescript@5.5.4) + optionalDependencies: + '@vue/composition-api': 1.7.2(vue@3.4.27(typescript@5.5.4)) optional: true vue-demi@0.14.7(@vue/composition-api@1.7.2(vue@3.4.15(typescript@5.5.4)))(vue@3.4.15(typescript@5.5.4)): @@ -31326,13 +31357,6 @@ snapshots: react: 18.2.0 zustand: 4.5.5(@types/react@18.2.35)(immer@10.0.3)(react@18.2.0) - zustand-utils@1.3.2(react@18.2.0)(zustand@4.5.5(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0)): - dependencies: - '@babel/runtime': 7.25.0 - fast-deep-equal: 3.1.3 - react: 18.2.0 - zustand: 4.5.5(@types/react@18.2.35)(immer@9.0.21)(react@18.2.0) - zustand@4.5.5(@types/react@18.2.35)(immer@10.0.3)(react@18.2.0): dependencies: use-sync-external-store: 1.2.2(react@18.2.0) From 66036467e17e22a0873b7e5c4d3c9f8f3cfe01bb Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Wed, 4 Sep 2024 18:00:08 +0800 Subject: [PATCH 096/369] chore: prepare write restartServer logic --- examples/vue3/farm.config.ts | 2 +- examples/vue3/src/App.vue | 2 +- .../core/src/plugin/js/vite-server-adapter.ts | 34 ------------------- packages/core/src/server/index.ts | 7 ++-- 4 files changed, 5 insertions(+), 40 deletions(-) diff --git a/examples/vue3/farm.config.ts b/examples/vue3/farm.config.ts index a40f62049c..66eba1e824 100644 --- a/examples/vue3/farm.config.ts +++ b/examples/vue3/farm.config.ts @@ -28,7 +28,7 @@ export default defineConfig({ compressionMiddleware(), ], compilation: { - persistentCache: false, + // persistentCache: false, }, // plugins: [compressionMiddleware()], // server: { diff --git a/examples/vue3/src/App.vue b/examples/vue3/src/App.vue index 684262cdb9..26416d485b 100644 --- a/examples/vue3/src/App.vue +++ b/examples/vue3/src/App.vue @@ -6,7 +6,7 @@ import HelloWorld from "./components/HelloWorld.vue";
- + 请问请问俄武器22wwwwwwwwwwwqweqweqw123123131123123123 diff --git a/packages/core/src/plugin/js/vite-server-adapter.ts b/packages/core/src/plugin/js/vite-server-adapter.ts index 63613808d9..dbe2d77732 100644 --- a/packages/core/src/plugin/js/vite-server-adapter.ts +++ b/packages/core/src/plugin/js/vite-server-adapter.ts @@ -27,40 +27,6 @@ export class ViteDevServerAdapter { this.watcher = server.watcher.getInternalWatcher(); this.middlewares = server.middlewares; - // this.middlewares = new Proxy( - // { - // use: (...args: any[]) => { - // if ( - // args.length === 2 && - // typeof args[0] === "string" && - // typeof args[1] === "function" - // ) { - // this.middlewareCallbacks.push((req: any, res: any, next: any) => { - // const [url, cb] = args; - // if (req.url.startsWith(url)) { - // cb(req, res, next); - // } - // }); - // } else if (args.length === 1 && typeof args[0] === "function") { - // this.middlewareCallbacks.push(args[0]); - // } - // }, - // }, - // { - // get(target, key) { - // if (key === "use") { - // return target[key as keyof typeof target]; - // } - - // throwIncompatibleError( - // pluginName, - // "viteDevServer.middlewares", - // ["use"], - // key, - // ); - // }, - // }, - // ); this.ws = server.ws; diff --git a/packages/core/src/server/index.ts b/packages/core/src/server/index.ts index bbf984a726..cac14cb86d 100644 --- a/packages/core/src/server/index.ts +++ b/packages/core/src/server/index.ts @@ -228,6 +228,7 @@ export class Server extends httpServer { */ async #createWatcher() { this.watcher = new Watcher(this.resolvedUserConfig); + await this.watcher.createWatcher(); this.watcher.watcher.on('change', async (file: string | string[] | any) => { @@ -241,10 +242,7 @@ export class Server extends httpServer { ); if (isConfigFile || isConfigDependencyFile || isEnvFile) { debugServer?.(`[config change] ${colors.dim(file)}`); - await this.close(); - setTimeout(() => { - this.restartServer(); - }, 1000); + this.restartServer(); } // TODO 做一个 onHmrUpdate 方法 try { @@ -277,6 +275,7 @@ export class Server extends httpServer { } async restartServer() { + await this.close(); await this.createServer(); await this.listen(); } From 76632e2e5958222ec7a97f5538e9eb9a3429dc75 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Thu, 5 Sep 2024 11:14:46 +0800 Subject: [PATCH 097/369] chore: pre restartServer full create new Server and full reload page --- examples/vue-nativeui/farm.config.ts | 16 +++- examples/vue-nativeui/package.json | 1 + examples/vue3/farm.config.ts | 8 +- examples/vue3/src/App.vue | 2 +- packages/core/src/config/index.ts | 3 +- .../normalize-config/normalize-output.ts | 4 +- packages/core/src/index.ts | 15 +-- packages/core/src/server/index.ts | 92 ++++++++++++++++--- packages/core/src/utils/rebase-url.ts | 4 +- packages/core/src/utils/share.ts | 6 -- pnpm-lock.yaml | 3 + 11 files changed, 107 insertions(+), 47 deletions(-) diff --git a/examples/vue-nativeui/farm.config.ts b/examples/vue-nativeui/farm.config.ts index 4d77c5df11..c879dedded 100644 --- a/examples/vue-nativeui/farm.config.ts +++ b/examples/vue-nativeui/farm.config.ts @@ -3,7 +3,15 @@ import * as process from "process"; import { defineConfig } from "@farmfe/core"; import vue from "@vitejs/plugin-vue"; - +import compression from "compression"; +const compressionMiddleware = () => { + return { + name: "compression", + configureServer(server) { + server.middlewares.use(compression()); + }, + }; +}; export default defineConfig({ compilation: { presetEnv: { @@ -18,9 +26,9 @@ export default defineConfig({ }, persistentCache: false, output: { - filename: 'static/[name].[hash].[ext]', - assetsFilename: 'static/[resourceName].[ext]' + filename: "static/[name].[hash].[ext]", + assetsFilename: "static/[resourceName].[ext]", }, }, - vitePlugins: [vue()], + vitePlugins: [vue(), compressionMiddleware()], }); diff --git a/examples/vue-nativeui/package.json b/examples/vue-nativeui/package.json index 771de7a2ac..6e6442c2f2 100644 --- a/examples/vue-nativeui/package.json +++ b/examples/vue-nativeui/package.json @@ -16,6 +16,7 @@ "@farmfe/cli": "workspace:*", "@farmfe/core": "workspace:*", "@vitejs/plugin-vue": "^5.1.1", + "compression": "^1.7.4", "core-js": "^3.37.1", "naive-ui": "^2.39.0" } diff --git a/examples/vue3/farm.config.ts b/examples/vue3/farm.config.ts index 66eba1e824..20a6594d22 100644 --- a/examples/vue3/farm.config.ts +++ b/examples/vue3/farm.config.ts @@ -8,13 +8,11 @@ const compressionMiddleware = () => { return { name: "compression", configureServer(server) { - console.log("Middleware stack:", server.middlewares.stack.length); // console.log("server", server.middlewares); server.middlewares.use(compression()); server.middlewares.use((req, res, next) => { next(); }); - console.log("Middleware count:", server.middlewares.stack.length); }, }; }; @@ -27,9 +25,15 @@ export default defineConfig({ vue(), compressionMiddleware(), ], + compilation: { // persistentCache: false, }, + server: { + port:5233 + + } + // plugins: [compressionMiddleware()], // server: { // port: 5232, diff --git a/examples/vue3/src/App.vue b/examples/vue3/src/App.vue index 26416d485b..684262cdb9 100644 --- a/examples/vue3/src/App.vue +++ b/examples/vue3/src/App.vue @@ -6,7 +6,7 @@ import HelloWorld from "./components/HelloWorld.vue";
- 请问请问俄武器22wwwwwwwwwwwqweqweqw123123131123123123 + diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index 67ececc3e0..0c191bc49c 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -29,7 +29,6 @@ import { isEmptyObject, isObject, isWindows, - normalizeBasePath, normalizePath, transformAliasWithVite } from '../utils/index.js'; @@ -414,7 +413,7 @@ export async function normalizeUserCompilationConfig( resolvedCompilation.output.publicPath ); const serverOptions = resolvedUserConfig.server; - const defineHmrPath = normalizeBasePath( + const defineHmrPath = normalizePath( path.join(publicPath, resolvedUserConfig.server.hmr.path) ); diff --git a/packages/core/src/config/normalize-config/normalize-output.ts b/packages/core/src/config/normalize-config/normalize-output.ts index fadede7630..686e8d5903 100644 --- a/packages/core/src/config/normalize-config/normalize-output.ts +++ b/packages/core/src/config/normalize-config/normalize-output.ts @@ -7,7 +7,7 @@ import { Logger } from '../../utils/logger.js'; import { FARM_TARGET_BROWSER_ENVS, mapTargetEnvValue, - normalizeBasePath + normalizePath } from '../../utils/share.js'; import { ResolvedCompilation } from '../types.js'; @@ -262,7 +262,7 @@ export function getValidPublicPath(publicPath = '/'): string { if (publicPath.startsWith('/')) { validPublicPath = publicPath; } else if (publicPath.startsWith('.')) { - validPublicPath = normalizeBasePath(path.join('/', publicPath)); + validPublicPath = normalizePath(path.join('/', publicPath)); } return validPublicPath; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index fa2880406e..51608c7cc1 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -527,23 +527,10 @@ export async function start( setProcessEnv('development'); try { - const resolvedUserConfig = await resolveConfig( - inlineConfig, - 'start', - 'development', - 'development', - false - ); - - const server = new Server(resolvedUserConfig, logger); + const server = new Server(inlineConfig, logger); await server.createServer(); - // TODO 这段逻辑放在 创建 http server 之后 放到 server 里面 - // resolvedUserConfig.jsPlugins.forEach((plugin: JsPlugin) => - // // @ts-ignore - // plugin.configureServer?.(server), - // ); server.listen(); } catch (error) { logger.error('Failed to start the server', { exit: true, error }); diff --git a/packages/core/src/server/index.ts b/packages/core/src/server/index.ts index cac14cb86d..1f24468c69 100644 --- a/packages/core/src/server/index.ts +++ b/packages/core/src/server/index.ts @@ -4,7 +4,7 @@ import connect from 'connect'; import corsMiddleware from 'cors'; import { Compiler } from '../compiler/index.js'; -import { colors, createCompiler } from '../index.js'; +import { colors, createCompiler, resolveConfig } from '../index.js'; import Watcher from '../watcher/index.js'; import { HmrEngine } from './hmr-engine.js'; import { CommonServerOptions, httpServer } from './http.js'; @@ -15,7 +15,7 @@ import { __FARM_GLOBAL__ } from '../config/_global.js'; import { getCacheDir, isCacheDirExists } from '../utils/cacheDir.js'; import { Logger, bootstrap, logger } from '../utils/logger.js'; import { initPublicFiles } from '../utils/publicDir.js'; -import { isObject } from '../utils/share.js'; +import { isObject, normalizePath } from '../utils/share.js'; import { adaptorViteMiddleware, @@ -39,9 +39,11 @@ import type * as net from 'node:net'; import type { HMRChannel } from './hmr.js'; import type { + FarmCliOptions, HmrOptions, NormalizedServerConfig, - ResolvedUserConfig + ResolvedUserConfig, + UserConfig } from '../config/types.js'; import { getPluginHooks, @@ -122,14 +124,14 @@ export class Server extends httpServer { middlewares: connect.Server; compiler: CompilerType; root: string; + resolvedUserConfig: ResolvedUserConfig; closeHttpServerFn: () => Promise; postConfigureServerHooks: ((() => void) | void)[] = []; constructor( - readonly resolvedUserConfig: ResolvedUserConfig, + readonly inlineConfig: FarmCliOptions & UserConfig, logger: Logger ) { super(logger); - this.#resolveOptions(); } /** @@ -150,13 +152,21 @@ export class Server extends httpServer { */ async createServer(): Promise { try { - const { https, middlewareMode } = this.serverOptions; + this.resolvedUserConfig = await resolveConfig( + this.inlineConfig, + 'start', + 'development', + 'development', + false + ); + this.#resolveOptions(); - this.httpsOptions = await this.resolveHttpsConfig(https); + this.httpsOptions = await this.resolveHttpsConfig( + this.serverOptions.https + ); this.publicFiles = await this.#handlePublicFiles(); - this.middlewares = connect() as connect.Server; - this.httpServer = middlewareMode + this.httpServer = this.serverOptions.middlewareMode ? null : await this.resolveHttpServer( this.serverOptions as CommonServerOptions, @@ -182,7 +192,7 @@ export class Server extends httpServer { // init middlewares await this.#initializeMiddlewares(); - if (!middlewareMode && this.httpServer) { + if (!this.serverOptions.middlewareMode && this.httpServer) { this.httpServer.once('listening', () => { // update actual port since this may be different from initial value this.serverOptions.port = ( @@ -232,6 +242,7 @@ export class Server extends httpServer { await this.watcher.createWatcher(); this.watcher.watcher.on('change', async (file: string | string[] | any) => { + file = normalizePath(file); const isConfigFile = this.resolvedUserConfig.configFilePath === file; const isConfigDependencyFile = this.resolvedUserConfig.configFileDependencies.some( @@ -242,7 +253,11 @@ export class Server extends httpServer { ); if (isConfigFile || isConfigDependencyFile || isEnvFile) { debugServer?.(`[config change] ${colors.dim(file)}`); - this.restartServer(); + try { + await this.restartServer(); + } catch (e) { + this.logger.error(colors.red(e)); + } } // TODO 做一个 onHmrUpdate 方法 try { @@ -275,9 +290,49 @@ export class Server extends httpServer { } async restartServer() { + if (this.serverOptions.middlewareMode) { + // TODO restart + await this.restart(); + return; + } + const { port: prevPort, host: prevHost } = this.serverOptions; + // TODO 把所有 要打印的 url 配置出来 不是直接打印所有 url + const prevUrls = this.resolvedUrls; + // console.log(prevHost, prevPort); + + // console.log(this.resolvedUrls); + // await this.createServer(); + // await this.listen(); + // console.log(this.middlewares.stack); + await this.restart(); + console.log('调用了几次啊'); + + const { port, host } = this.serverOptions; + if ( + port !== prevPort || + host !== prevHost || + this.hasUrlsChanged(prevUrls, this.resolvedUrls) + ) { + console.log('端口或者 host 发生变化'); + } + + // this._transferState(newServer); + } + + hasUrlsChanged(oldUrls: any, newUrls: any) { + return !( + oldUrls === newUrls || + (oldUrls && + newUrls && + arrayEqual(oldUrls.local, newUrls.local) && + arrayEqual(oldUrls.network, newUrls.network)) + ); + } + + async restart() { await this.close(); await this.createServer(); - await this.listen(); + this.listen(); } /** @@ -342,8 +397,8 @@ export class Server extends httpServer { // watch extra files after compile this.watcher?.watchExtraFiles?.(); - // !__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ && - await this.displayServerUrls(this.serverOptions, this.publicPath); + !__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ && + (await this.displayServerUrls(this.serverOptions, this.publicPath)); if (open) { this.#openServerBrowser(); @@ -628,3 +683,12 @@ export const teardownSIGTERMListener = ( process.stdin.off('end', callback); } }; + +export function arrayEqual(a: any[], b: any[]): boolean { + if (a === b) return true; + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; +} diff --git a/packages/core/src/utils/rebase-url.ts b/packages/core/src/utils/rebase-url.ts index 9061dbeca0..d8a1dbf275 100644 --- a/packages/core/src/utils/rebase-url.ts +++ b/packages/core/src/utils/rebase-url.ts @@ -26,7 +26,7 @@ SOFTWARE. import path from 'node:path'; import fse from 'fs-extra'; -import { normalizeBasePath } from './share.js'; +import { normalizePath } from './share.js'; const nonEscapedDoubleQuoteRe = /(?(target: T | T[]): T[] { return Array.isArray(target) ? target : [target]; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55eb50d4ee..82a7399443 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1978,6 +1978,9 @@ importers: '@vitejs/plugin-vue': specifier: ^5.1.1 version: 5.1.2(vite@5.2.8(@types/node@22.5.0)(less@4.2.0)(lightningcss@1.25.1)(sass@1.74.1)(terser@5.31.1))(vue@3.4.35(typescript@5.5.4)) + compression: + specifier: ^1.7.4 + version: 1.7.4 core-js: specifier: ^3.37.1 version: 3.37.1 From db0463aa1f1409425205d7e4f0d00ab0e1136164 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Thu, 5 Sep 2024 14:48:34 +0800 Subject: [PATCH 098/369] chore: optimize server code --- examples/vue3/farm.config.ts | 12 +-- examples/vue3/src/App.vue | 1 + packages/core/src/plugin/index.ts | 5 +- packages/core/src/server/index.ts | 147 ++++++++++++------------------ packages/core/src/utils/http.ts | 9 ++ packages/core/src/utils/share.ts | 9 ++ 6 files changed, 85 insertions(+), 98 deletions(-) diff --git a/examples/vue3/farm.config.ts b/examples/vue3/farm.config.ts index 20a6594d22..e95706cf1c 100644 --- a/examples/vue3/farm.config.ts +++ b/examples/vue3/farm.config.ts @@ -8,14 +8,11 @@ const compressionMiddleware = () => { return { name: "compression", configureServer(server) { - // console.log("server", server.middlewares); server.middlewares.use(compression()); - server.middlewares.use((req, res, next) => { - next(); - }); }, }; }; + export default defineConfig({ vitePlugins: [ // VueRouter(), @@ -27,12 +24,11 @@ export default defineConfig({ ], compilation: { - // persistentCache: false, + persistentCache: false, }, server: { - port:5233 - - } + port: 5233, + }, // plugins: [compressionMiddleware()], // server: { diff --git a/examples/vue3/src/App.vue b/examples/vue3/src/App.vue index 684262cdb9..ea4636682f 100644 --- a/examples/vue3/src/App.vue +++ b/examples/vue3/src/App.vue @@ -1,5 +1,6 @@