@callstack/repack-dev-server 5.1.3 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { Writable } from 'node:stream';
2
+ import util from 'node:util';
2
3
  import middie from '@fastify/middie';
3
4
  import fastifySensible from '@fastify/sensible';
4
- import { createDevMiddleware } from '@react-native/dev-middleware';
5
5
  import Fastify from 'fastify';
6
6
  import { createProxyMiddleware } from 'http-proxy-middleware';
7
7
  import apiPlugin from './plugins/api/apiPlugin.js';
@@ -62,22 +62,34 @@ export async function createServer(config) {
62
62
  },
63
63
  });
64
64
  let handledDevMiddlewareNotice = false;
65
- const devMiddleware = createDevMiddleware({
65
+ const devMiddleware = options.devMiddleware.createDevMiddleware({
66
66
  projectRoot: options.rootDir,
67
67
  serverBaseUrl: options.url,
68
68
  logger: {
69
- error: instance.log.error,
70
- warn: instance.log.warn,
71
- info: (...message) => {
72
- if (!handledDevMiddlewareNotice) {
73
- if (message.join().includes('JavaScript logs have moved!')) {
74
- handledDevMiddlewareNotice = true;
69
+ error: (...msg) => {
70
+ const message = util.format(...msg);
71
+ instance.log.error(message);
72
+ },
73
+ warn: (...msg) => {
74
+ const message = util.format(...msg);
75
+ instance.log.warn(message);
76
+ },
77
+ info: (...msg) => {
78
+ const message = util.format(...msg);
79
+ try {
80
+ if (!handledDevMiddlewareNotice) {
81
+ if (message.includes('JavaScript logs have moved!')) {
82
+ handledDevMiddlewareNotice = true;
83
+ return;
84
+ }
85
+ }
86
+ else {
87
+ instance.log.debug(message);
75
88
  return;
76
89
  }
77
90
  }
78
- else {
79
- instance.log.debug(message);
80
- return;
91
+ catch (e) {
92
+ console.log(e);
81
93
  }
82
94
  },
83
95
  },
@@ -119,7 +131,7 @@ export async function createServer(config) {
119
131
  delegate,
120
132
  });
121
133
  await instance.register(devtoolsPlugin, {
122
- rootDir: options.rootDir,
134
+ delegate,
123
135
  });
124
136
  await instance.register(symbolicatePlugin, {
125
137
  delegate,
@@ -136,11 +148,32 @@ export async function createServer(config) {
136
148
  }
137
149
  return payload;
138
150
  });
139
- // Register dev middleware
140
- instance.use(devMiddleware.middleware);
141
- // Register proxy middlewares
142
- proxyMiddlewares?.forEach((proxyMiddleware) => {
143
- instance.use(proxyMiddleware);
151
+ // Setup middlewares
152
+ // Expose built-in middlewares to setupMiddlewares
153
+ const builtInMiddlewares = [
154
+ {
155
+ name: 'dev-middleware',
156
+ middleware: devMiddleware.middleware,
157
+ },
158
+ ...(proxyMiddlewares?.map((proxyMiddleware, index) => ({
159
+ name: `proxy-middleware-${index}`,
160
+ middleware: proxyMiddleware,
161
+ })) ?? []),
162
+ ];
163
+ const finalMiddlewares = options.setupMiddlewares(builtInMiddlewares, instance);
164
+ // Register middlewares
165
+ finalMiddlewares.forEach((middleware) => {
166
+ if (typeof middleware === 'object') {
167
+ if (middleware.path !== undefined) {
168
+ instance.use(middleware.path, middleware.middleware);
169
+ }
170
+ else {
171
+ instance.use(middleware.middleware);
172
+ }
173
+ }
174
+ else {
175
+ instance.use(middleware);
176
+ }
144
177
  });
145
178
  // Register routes
146
179
  instance.get('/', async () => delegate.messages.getHello());
@@ -14,9 +14,9 @@ async function compilerPlugin(instance, { delegate }) {
14
14
  },
15
15
  },
16
16
  handler: async (request, reply) => {
17
- const filename = request.params['*'];
17
+ const filepath = request.params['*'];
18
18
  let { platform } = request.query;
19
- if (!filename) {
19
+ if (!filepath) {
20
20
  // This technically should never happen - this route should not be called if file is missing.
21
21
  request.log.debug('File was not provided');
22
22
  return reply.notFound('File was not provided');
@@ -32,9 +32,11 @@ async function compilerPlugin(instance, { delegate }) {
32
32
  status: 'Bundling with Re.Pack',
33
33
  }));
34
34
  };
35
+ // immediately send 1% progress
36
+ setImmediate(() => sendProgress({ completed: 1, total: 100 }));
35
37
  try {
36
- const asset = await delegate.compiler.getAsset(filename, platform, sendProgress);
37
- const mimeType = delegate.compiler.getMimeType(filename, platform, asset);
38
+ const asset = await delegate.compiler.getAsset(filepath, platform, sendProgress);
39
+ const mimeType = delegate.compiler.getMimeType(filepath, platform, asset);
38
40
  if (multipart) {
39
41
  const buffer = Buffer.isBuffer(asset) ? asset : Buffer.from(asset);
40
42
  multipart.setHeader('Content-Type', `${mimeType}; charset=UTF-8`);
@@ -1,4 +1,7 @@
1
1
  import type { FastifyInstance } from 'fastify';
2
- declare function devtoolsPlugin(instance: FastifyInstance): Promise<void>;
2
+ import type { Server } from '../../types.js';
3
+ declare function devtoolsPlugin(instance: FastifyInstance, { delegate }: {
4
+ delegate: Server.Delegate;
5
+ }): Promise<void>;
3
6
  declare const _default: typeof devtoolsPlugin;
4
7
  export default _default;
@@ -8,7 +8,7 @@ function parseRequestBody(body) {
8
8
  return JSON.parse(body);
9
9
  throw new Error(`Unsupported body type: ${typeof body}`);
10
10
  }
11
- async function devtoolsPlugin(instance) {
11
+ async function devtoolsPlugin(instance, { delegate }) {
12
12
  // reference implementation in `@react-native-community/cli-server-api`:
13
13
  // https://github.com/react-native-community/cli/blob/46436a12478464752999d34ed86adf3212348007/packages/cli-server-api/src/openURLMiddleware.ts
14
14
  instance.route({
@@ -27,8 +27,8 @@ async function devtoolsPlugin(instance) {
27
27
  url: '/open-stack-frame',
28
28
  handler: async (request, reply) => {
29
29
  const { file, lineNumber } = parseRequestBody(request.body);
30
- // TODO fix rewriting of `webpack://` to rootDir of the project
31
- launchEditor(`${file}:${lineNumber}`, process.env.REACT_EDITOR);
30
+ const filepath = delegate.devTools?.resolveProjectPath(file) ?? file;
31
+ launchEditor(`${filepath}:${lineNumber}`, process.env.REACT_EDITOR);
32
32
  reply.send('OK');
33
33
  },
34
34
  });
@@ -131,16 +131,21 @@ export class Symbolicator {
131
131
  collapse: false,
132
132
  };
133
133
  }
134
- const lookup = consumer.originalPositionFor({
134
+ let lookup = consumer.originalPositionFor({
135
135
  line: frame.lineNumber,
136
136
  column: frame.column,
137
137
  bias: SourceMapConsumer.LEAST_UPPER_BOUND,
138
138
  });
139
- // If lookup fails, we get the same shape object, but with
140
- // all values set to null
141
139
  if (!lookup.source) {
142
- // It is better to gracefully return the original frame
143
- // than to throw an exception
140
+ // fallback to GREATEST_LOWER_BOUND
141
+ lookup = consumer.originalPositionFor({
142
+ line: frame.lineNumber,
143
+ column: frame.column,
144
+ bias: SourceMapConsumer.GREATEST_LOWER_BOUND,
145
+ });
146
+ }
147
+ // return the original frame when both lookups fail
148
+ if (!lookup.source) {
144
149
  return {
145
150
  ...frame,
146
151
  collapse: false,
package/dist/types.d.ts CHANGED
@@ -1,15 +1,25 @@
1
- import type { ServerOptions as HttpsServerOptions } from 'node:https';
2
- import type { FastifyBaseLogger } from 'fastify';
1
+ import type * as Http from 'node:http';
2
+ import type * as Https from 'node:https';
3
+ import type * as DevMiddleware from '@react-native/dev-middleware';
4
+ import type { FastifyBaseLogger, FastifyInstance } from 'fastify';
3
5
  import type { Options as ProxyOptions } from 'http-proxy-middleware';
4
6
  import type { CompilerDelegate } from './plugins/compiler/types.js';
5
7
  import type { CodeFrame, InputStackFrame, ReactNativeStackFrame, StackFrame, SymbolicatorDelegate, SymbolicatorResults } from './plugins/symbolicate/types.js';
6
8
  import type { NormalizedOptions } from './utils/normalizeOptions.js';
9
+ type MiddlewareHandler<RequestInternal extends Http.IncomingMessage = Http.IncomingMessage, ResponseInternal extends Http.ServerResponse = Http.ServerResponse> = (req: RequestInternal, res: ResponseInternal, next: (err?: any) => void) => void | Promise<void>;
10
+ type MiddlewareObject<RequestInternal extends Http.IncomingMessage = Http.IncomingMessage, ResponseInternal extends Http.ServerResponse = Http.ServerResponse> = {
11
+ name?: string;
12
+ path?: string;
13
+ middleware: MiddlewareHandler<RequestInternal, ResponseInternal>;
14
+ };
15
+ export type Middleware<RequestInternal extends Http.IncomingMessage = Http.IncomingMessage, ResponseInternal extends Http.ServerResponse = Http.ServerResponse> = MiddlewareObject<RequestInternal, ResponseInternal> | MiddlewareHandler<RequestInternal, ResponseInternal>;
7
16
  export type { CompilerDelegate };
8
17
  export type { CodeFrame, InputStackFrame, ReactNativeStackFrame, StackFrame, SymbolicatorDelegate, SymbolicatorResults, };
9
18
  interface ProxyConfig extends ProxyOptions {
10
19
  path?: ProxyOptions['pathFilter'];
11
20
  context?: ProxyOptions['pathFilter'];
12
21
  }
22
+ export type SetupMiddlewaresFunction = (middlewares: Middleware[], devServer: FastifyInstance) => Middleware[];
13
23
  export interface DevServerOptions {
14
24
  /**
15
25
  * Hostname or IP address under which to run the development server.
@@ -27,8 +37,10 @@ export interface DevServerOptions {
27
37
  type: 'http';
28
38
  } | {
29
39
  type: 'https';
30
- options?: HttpsServerOptions;
40
+ options?: Https.ServerOptions;
31
41
  };
42
+ /** Function to customize middleware setup. Receives built-in middlewares and Fastify instance. */
43
+ setupMiddlewares?: SetupMiddlewaresFunction;
32
44
  }
33
45
  export declare namespace Server {
34
46
  /** Development server configuration. */
@@ -44,6 +56,8 @@ export declare namespace Server {
44
56
  rootDir: string;
45
57
  /** Whether to enable logging requests. */
46
58
  logRequests?: boolean;
59
+ /** `@react-native/dev-middleware` module. */
60
+ devMiddleware: typeof DevMiddleware;
47
61
  }
48
62
  /**
49
63
  * A complete delegate with implementations for all server functionalities.
@@ -51,6 +65,8 @@ export declare namespace Server {
51
65
  interface Delegate {
52
66
  /** A compiler delegate. */
53
67
  compiler: CompilerDelegate;
68
+ /** A DevTools delegate. */
69
+ devTools?: DevToolsDelegate;
54
70
  /** A symbolicator delegate. */
55
71
  symbolicator: SymbolicatorDelegate;
56
72
  /** A logger delegate. */
@@ -101,6 +117,18 @@ export declare namespace Server {
101
117
  */
102
118
  onMessage: (log: any) => void;
103
119
  }
120
+ /**
121
+ * Delegate with implementation for dev tools functions.
122
+ */
123
+ interface DevToolsDelegate {
124
+ /**
125
+ * Resolve the project filepath with [projectRoot] prefix.
126
+ *
127
+ * @param filepath The filepath to resolve.
128
+ * @returns The resolved project path.
129
+ */
130
+ resolveProjectPath: (filepath: string) => string;
131
+ }
104
132
  /**
105
133
  * Delegate with implementation for messages used in route handlers.
106
134
  */
@@ -1,6 +1,7 @@
1
1
  import type { ServerOptions as HttpsServerOptions } from 'node:https';
2
+ import type * as DevMiddleware from '@react-native/dev-middleware';
2
3
  import type { Options as ProxyOptions } from 'http-proxy-middleware';
3
- import type { Server } from '../types.js';
4
+ import type { Server, SetupMiddlewaresFunction } from '../types.js';
4
5
  export interface NormalizedOptions {
5
6
  host: string;
6
7
  port: number;
@@ -9,6 +10,8 @@ export interface NormalizedOptions {
9
10
  proxy: ProxyOptions[] | undefined;
10
11
  url: string;
11
12
  disableRequestLogging: boolean;
13
+ devMiddleware: typeof DevMiddleware;
12
14
  rootDir: string;
15
+ setupMiddlewares: SetupMiddlewaresFunction;
13
16
  }
14
17
  export declare function normalizeOptions(options: Server.Options): NormalizedOptions;
@@ -21,6 +21,10 @@ function normalizeProxyOptions(proxyOptions, fallbackTarget) {
21
21
  }
22
22
  return undefined;
23
23
  }
24
+ function normalizeSetupMiddlewares(setupMiddlewares) {
25
+ // create a passthrough function if no setupMiddlewares is provided
26
+ return setupMiddlewares ?? ((middlewares) => middlewares);
27
+ }
24
28
  export function normalizeOptions(options) {
25
29
  const host = options.host ?? 'localhost';
26
30
  const port = options.port ?? 8081;
@@ -29,6 +33,7 @@ export function normalizeOptions(options) {
29
33
  const protocol = https ? 'https' : 'http';
30
34
  const url = `${protocol}://${host}:${options.port}`;
31
35
  const proxy = normalizeProxyOptions(options.proxy, url);
36
+ const setupMiddlewares = normalizeSetupMiddlewares(options.setupMiddlewares);
32
37
  return {
33
38
  // webpack dev server compatible options
34
39
  host,
@@ -37,9 +42,13 @@ export function normalizeOptions(options) {
37
42
  hot,
38
43
  proxy,
39
44
  url,
45
+ // dev middleware
46
+ devMiddleware: options.devMiddleware,
40
47
  // fastify options
41
48
  disableRequestLogging: !options.logRequests,
42
49
  // project options
43
50
  rootDir: options.rootDir,
51
+ // custom middleware setup
52
+ setupMiddlewares,
44
53
  };
45
54
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@callstack/repack-dev-server",
3
3
  "description": "A bundler-agnostic development server for React Native applications as part of @callstack/repack.",
4
4
  "license": "MIT",
5
- "version": "5.1.3",
5
+ "version": "5.2.0",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
@@ -35,7 +35,6 @@
35
35
  "@babel/code-frame": "^7.26.2",
36
36
  "@fastify/middie": "^8.3.0",
37
37
  "@fastify/sensible": "^5.5.0",
38
- "@react-native/dev-middleware": "^0.78.0",
39
38
  "fastify": "^4.24.3",
40
39
  "fastify-favicon": "^4.3.0",
41
40
  "fastify-plugin": "^4.5.1",
@@ -47,8 +46,9 @@
47
46
  "ws": "^8.18.1"
48
47
  },
49
48
  "devDependencies": {
49
+ "@react-native/dev-middleware": "^0.81.0",
50
50
  "@types/babel__code-frame": "^7.0.6",
51
- "@types/node": "^18",
51
+ "@types/node": "^20",
52
52
  "@types/ws": "^8.18.0",
53
53
  "typescript": "^5.8.3"
54
54
  },