From 4e2e31bdd397c45a0d441b2479d4c7bf5492ee4f Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 11 Jun 2025 15:27:05 +0200 Subject: [PATCH 01/19] chore: disable sourcemaps for repack --- packages/repack/babel.config.js | 2 ++ packages/repack/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/repack/babel.config.js b/packages/repack/babel.config.js index ba56551cc..f604f1c2c 100644 --- a/packages/repack/babel.config.js +++ b/packages/repack/babel.config.js @@ -1,6 +1,8 @@ module.exports = { presets: ['@babel/preset-typescript'], plugins: ['@babel/plugin-transform-export-namespace-from'], + sourceMaps: false, + inputSourceMap: false, overrides: [ { include: ['./src/**/implementation'], diff --git a/packages/repack/package.json b/packages/repack/package.json index 5b33a8bfe..3a7c1fce2 100644 --- a/packages/repack/package.json +++ b/packages/repack/package.json @@ -47,7 +47,7 @@ "access": "public" }, "scripts": { - "build:js": "babel src --out-dir dist --extensions \".js,.ts\" --source-maps --ignore \"**/__tests__/**\" --delete-dir-on-start", + "build:js": "babel src --out-dir dist --extensions \".js,.ts\" --ignore \"**/__tests__/**\" --delete-dir-on-start", "build:ts": "tsc -p tsconfig.build.json --emitDeclarationOnly", "build": "pnpm run \"/^build:.*/\"", "test": "jest", From c17575d0dd6511453138c4bfb358dd6763cd4d0e Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 11 Jun 2025 15:29:53 +0200 Subject: [PATCH 02/19] fix: disable sourcemaps for runtime modules --- packages/repack/babel.config.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/repack/babel.config.js b/packages/repack/babel.config.js index f604f1c2c..c64d92ff5 100644 --- a/packages/repack/babel.config.js +++ b/packages/repack/babel.config.js @@ -1,13 +1,15 @@ module.exports = { presets: ['@babel/preset-typescript'], plugins: ['@babel/plugin-transform-export-namespace-from'], - sourceMaps: false, - inputSourceMap: false, overrides: [ { include: ['./src/**/implementation'], comments: false, }, + { + include: ['./src/**/implementation', './src/modules'], + sourceMaps: false, + }, { exclude: ['./src/**/implementation', './src/modules'], presets: [ From 4424c2f106966018229968dd1c0b336631fab8ee Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 11 Jun 2025 15:30:28 +0200 Subject: [PATCH 03/19] feat: sourcemap dir structure --- .../repack/src/plugins/SourceMapPlugin.ts | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/packages/repack/src/plugins/SourceMapPlugin.ts b/packages/repack/src/plugins/SourceMapPlugin.ts index 907fde838..284657546 100644 --- a/packages/repack/src/plugins/SourceMapPlugin.ts +++ b/packages/repack/src/plugins/SourceMapPlugin.ts @@ -1,3 +1,4 @@ +import path from 'node:path'; import type { Compiler, RspackPluginInstance } from '@rspack/core'; import { ConfigurationError } from './utils/ConfigurationError.js'; @@ -14,6 +15,10 @@ export class SourceMapPlugin implements RspackPluginInstance { return; } + const host = compiler.options.devServer!.host; + const port = compiler.options.devServer!.port; + const namespace = `http://${host}:${port}`; + const format = compiler.options.devtool; // disable builtin sourcemap generation compiler.options.devtool = false; @@ -24,8 +29,8 @@ export class SourceMapPlugin implements RspackPluginInstance { const devtoolNamespace = compiler.options.output.devtoolNamespace ?? compiler.options.output.uniqueName; - const devtoolModuleFilenameTemplate = - compiler.options.output.devtoolModuleFilenameTemplate; + // const devtoolModuleFilenameTemplate = + // compiler.options.output.devtoolModuleFilenameTemplate; const devtoolFallbackModuleFilenameTemplate = compiler.options.output.devtoolFallbackModuleFilenameTemplate; @@ -48,14 +53,41 @@ export class SourceMapPlugin implements RspackPluginInstance { const moduleMaps = format.includes('module'); const noSources = format.includes('nosources'); - // TODO Fix sourcemap directory structure - // Right now its very messy and not every node module is inside of the node module - // like React Devtools backend etc or some symilinked module appear with relative path - // We should normalize this through a custom handler and provide an output similar to Metro new compiler.webpack.SourceMapDevToolPlugin({ test: /\.([cm]?jsx?|bundle)$/, filename: '[file].map', - moduleFilenameTemplate: devtoolModuleFilenameTemplate, + moduleFilenameTemplate: (info) => { + // inlined modules + if (!info.identifier) { + return `${namespace}`; + } + + const [prefix, ...parts] = info.resourcePath.split('/'); + + // prefixed modules like React DevTools Backend + if (prefix !== '.' && prefix !== '..') { + const resourcePath = parts.filter((part) => part !== '..').join('/'); + return `webpack://${prefix}/${resourcePath}`; + } + + const hasValidAbsolutePath = path.isAbsolute(info.absoluteResourcePath); + + // project root + if (hasValidAbsolutePath && info.resourcePath.startsWith('./')) { + return `[projectRoot]${info.resourcePath.slice(1)}`; + } + + // outside of project root + if (hasValidAbsolutePath && info.resourcePath.startsWith('../')) { + const parts = info.resourcePath.split('/'); + const upLevel = parts.filter((part) => part === '..').length; + const restPath = parts.slice(parts.lastIndexOf('..') + 1).join('/'); + const rootRef = `[projectRoot^${upLevel}]`; + return `${rootRef}${restPath ? '/' + restPath : ''}`; + } + + return `[unknownOrigin]/${path.basename(info.identifier)}`; + }, fallbackModuleFilenameTemplate: devtoolFallbackModuleFilenameTemplate, append: hidden ? false From 9b4146770e06216b2e78be8d43cf89af4ef00a67 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 11 Jun 2025 17:25:13 +0200 Subject: [PATCH 04/19] fix: launch editor with proper project filepaths --- .../src/plugins/devtools/devtoolsPlugin.ts | 14 +++++++----- .../src/utils/parseSourceFilename.ts | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 packages/dev-server/src/utils/parseSourceFilename.ts diff --git a/packages/dev-server/src/plugins/devtools/devtoolsPlugin.ts b/packages/dev-server/src/plugins/devtools/devtoolsPlugin.ts index 5644a46e1..5fe718f6c 100644 --- a/packages/dev-server/src/plugins/devtools/devtoolsPlugin.ts +++ b/packages/dev-server/src/plugins/devtools/devtoolsPlugin.ts @@ -2,6 +2,7 @@ import type { FastifyInstance } from 'fastify'; import fastifyPlugin from 'fastify-plugin'; import launchEditor from 'launch-editor'; import open from 'open'; +import { parseSourceFilename } from '../../utils/parseSourceFilename.js'; interface OpenURLRequestBody { url: string; @@ -18,7 +19,10 @@ function parseRequestBody(body: unknown): T { throw new Error(`Unsupported body type: ${typeof body}`); } -async function devtoolsPlugin(instance: FastifyInstance) { +async function devtoolsPlugin( + instance: FastifyInstance, + { rootDir }: { rootDir: string } +) { // reference implementation in `@react-native-community/cli-server-api`: // https://github.com/react-native-community/cli/blob/46436a12478464752999d34ed86adf3212348007/packages/cli-server-api/src/openURLMiddleware.ts instance.route({ @@ -37,11 +41,9 @@ async function devtoolsPlugin(instance: FastifyInstance) { method: ['POST'], url: '/open-stack-frame', handler: async (request, reply) => { - const { file, lineNumber } = parseRequestBody( - request.body - ); - // TODO fix rewriting of `webpack://` to rootDir of the project - launchEditor(`${file}:${lineNumber}`, process.env.REACT_EDITOR); + const body = parseRequestBody(request.body); + const filepath = parseSourceFilename(body.file, rootDir); + launchEditor(`${filepath}:${body.lineNumber}`, process.env.REACT_EDITOR); reply.send('OK'); }, }); diff --git a/packages/dev-server/src/utils/parseSourceFilename.ts b/packages/dev-server/src/utils/parseSourceFilename.ts new file mode 100644 index 000000000..cb059fab6 --- /dev/null +++ b/packages/dev-server/src/utils/parseSourceFilename.ts @@ -0,0 +1,22 @@ +import path from 'node:path'; + +export function parseSourceFilename(filename: string, rootDir: string) { + const prefix = filename.split('/')[0]; + let filepath = filename; + + // Handle [projectRoot] and [projectRoot^N] prefixes + const projectRootMatch = prefix.match(/^\[projectRoot(?:\^(\d+))?\]$/); + if (projectRootMatch) { + const upLevels = projectRootMatch[1]; + if (upLevels) { + // For [projectRoot^N], go up N levels from rootDir + const upPath = '../'.repeat(Number(upLevels)); + filepath = filepath.replace(`${prefix}/`, rootDir + '/' + upPath); + } else { + // For plain [projectRoot], just replace with rootDir + filepath = filepath.replace('[projectRoot]', rootDir); + } + } + + return path.resolve(filepath); +} From f684761178d8b8de1b75e8251487ca562bb60eb5 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Wed, 11 Jun 2025 17:25:45 +0200 Subject: [PATCH 05/19] fix: try lookup with different bias before fallback --- .../src/plugins/symbolicate/Symbolicator.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/dev-server/src/plugins/symbolicate/Symbolicator.ts b/packages/dev-server/src/plugins/symbolicate/Symbolicator.ts index 05ec13fc6..bdb022209 100644 --- a/packages/dev-server/src/plugins/symbolicate/Symbolicator.ts +++ b/packages/dev-server/src/plugins/symbolicate/Symbolicator.ts @@ -162,17 +162,23 @@ export class Symbolicator { }; } - const lookup = consumer.originalPositionFor({ + let lookup = consumer.originalPositionFor({ line: frame.lineNumber, column: frame.column, bias: SourceMapConsumer.LEAST_UPPER_BOUND, }); - // If lookup fails, we get the same shape object, but with - // all values set to null if (!lookup.source) { - // It is better to gracefully return the original frame - // than to throw an exception + // fallback to GREATEST_LOWER_BOUND + lookup = consumer.originalPositionFor({ + line: frame.lineNumber, + column: frame.column, + bias: SourceMapConsumer.GREATEST_LOWER_BOUND, + }); + } + + // return the original frame when both lookups fail + if (!lookup.source) { return { ...frame, collapse: false, From 2226ca75f2a034e6b3dce4657c0b062953054c3b Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Fri, 13 Jun 2025 01:14:01 +0200 Subject: [PATCH 06/19] feat: parse projectRoot paths --- apps/tester-federation-v2/ios/Podfile.lock | 6 +-- packages/dev-server/src/createServer.ts | 2 +- .../src/plugins/compiler/compilerPlugin.ts | 8 ++-- .../src/plugins/devtools/devtoolsPlugin.ts | 7 +-- packages/dev-server/src/types.ts | 16 +++++++ .../src/utils/parseSourceFilename.ts | 22 ---------- packages/repack/src/commands/common/index.ts | 3 +- .../src/commands/common/parseFileUrl.ts | 29 ------------- .../repack/src/commands/common/parseUrl.ts | 28 ++++++++++++ .../src/commands/common/resolveProjectPath.ts | 19 ++++++++ .../repack/src/commands/rspack/Compiler.ts | 4 +- packages/repack/src/commands/rspack/start.ts | 39 +++++++++++------ packages/repack/src/commands/webpack/start.ts | 43 +++++++++++-------- 13 files changed, 130 insertions(+), 96 deletions(-) delete mode 100644 packages/dev-server/src/utils/parseSourceFilename.ts delete mode 100644 packages/repack/src/commands/common/parseFileUrl.ts create mode 100644 packages/repack/src/commands/common/parseUrl.ts create mode 100644 packages/repack/src/commands/common/resolveProjectPath.ts diff --git a/apps/tester-federation-v2/ios/Podfile.lock b/apps/tester-federation-v2/ios/Podfile.lock index 508c85b9d..9b2db54b4 100644 --- a/apps/tester-federation-v2/ios/Podfile.lock +++ b/apps/tester-federation-v2/ios/Podfile.lock @@ -1,6 +1,6 @@ PODS: - boost (1.84.0) - - callstack-repack (5.0.6): + - callstack-repack (5.1.1): - DoubleConversion - glog - hermes-engine @@ -2109,12 +2109,12 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - callstack-repack: 0f50dfdbd14d23303ce2d7a1740219910e97e1de + callstack-repack: dea0d9a2ed51c0d32c0008893224c224a82de370 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: abbac80c6f89e71a8c55c7e92ec015c8a9496753 fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd - glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8 + glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: c32f2e405098bc1ebe30630a051ddce6f21d3c2e JWTDecode: 2eed97c2fa46ccaf3049a787004eedf0be474a87 RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 diff --git a/packages/dev-server/src/createServer.ts b/packages/dev-server/src/createServer.ts index 3abc360e3..161a0ae8e 100644 --- a/packages/dev-server/src/createServer.ts +++ b/packages/dev-server/src/createServer.ts @@ -126,7 +126,7 @@ export async function createServer(config: Server.Config) { delegate, }); await instance.register(devtoolsPlugin, { - rootDir: options.rootDir, + delegate, }); await instance.register(symbolicatePlugin, { delegate, diff --git a/packages/dev-server/src/plugins/compiler/compilerPlugin.ts b/packages/dev-server/src/plugins/compiler/compilerPlugin.ts index a34fb091b..5555aeae4 100644 --- a/packages/dev-server/src/plugins/compiler/compilerPlugin.ts +++ b/packages/dev-server/src/plugins/compiler/compilerPlugin.ts @@ -21,10 +21,10 @@ async function compilerPlugin( }, }, handler: async (request, reply) => { - const filename = (request.params as { '*'?: string })['*']; + const filepath = (request.params as { '*'?: string })['*']; let { platform } = request.query as { platform?: string }; - if (!filename) { + if (!filepath) { // This technically should never happen - this route should not be called if file is missing. request.log.debug('File was not provided'); return reply.notFound('File was not provided'); @@ -49,12 +49,12 @@ async function compilerPlugin( try { const asset = await delegate.compiler.getAsset( - filename, + filepath, platform, sendProgress ); const mimeType = delegate.compiler.getMimeType( - filename, + filepath, platform, asset ); diff --git a/packages/dev-server/src/plugins/devtools/devtoolsPlugin.ts b/packages/dev-server/src/plugins/devtools/devtoolsPlugin.ts index 5fe718f6c..c6f6cc3b6 100644 --- a/packages/dev-server/src/plugins/devtools/devtoolsPlugin.ts +++ b/packages/dev-server/src/plugins/devtools/devtoolsPlugin.ts @@ -2,7 +2,7 @@ import type { FastifyInstance } from 'fastify'; import fastifyPlugin from 'fastify-plugin'; import launchEditor from 'launch-editor'; import open from 'open'; -import { parseSourceFilename } from '../../utils/parseSourceFilename.js'; +import type { Server } from '../../types.js'; interface OpenURLRequestBody { url: string; @@ -21,7 +21,7 @@ function parseRequestBody(body: unknown): T { async function devtoolsPlugin( instance: FastifyInstance, - { rootDir }: { rootDir: string } + { delegate }: { delegate: Server.Delegate } ) { // reference implementation in `@react-native-community/cli-server-api`: // https://github.com/react-native-community/cli/blob/46436a12478464752999d34ed86adf3212348007/packages/cli-server-api/src/openURLMiddleware.ts @@ -42,7 +42,8 @@ async function devtoolsPlugin( url: '/open-stack-frame', handler: async (request, reply) => { const body = parseRequestBody(request.body); - const filepath = parseSourceFilename(body.file, rootDir); + const filepath = + delegate.devTools?.resolveProjectPath(body.file) ?? body.file; launchEditor(`${filepath}:${body.lineNumber}`, process.env.REACT_EDITOR); reply.send('OK'); }, diff --git a/packages/dev-server/src/types.ts b/packages/dev-server/src/types.ts index 083d37c56..ab76392b3 100644 --- a/packages/dev-server/src/types.ts +++ b/packages/dev-server/src/types.ts @@ -77,6 +77,9 @@ export namespace Server { /** A compiler delegate. */ compiler: CompilerDelegate; + /** A DevTools delegate. */ + devTools?: DevToolsDelegate; + /** A symbolicator delegate. */ symbolicator: SymbolicatorDelegate; @@ -139,6 +142,19 @@ export namespace Server { onMessage: (log: any) => void; } + /** + * Delegate with implementation for dev tools functions. + */ + export interface DevToolsDelegate { + /** + * Resolve the project filepath with [projectRoot] prefix. + * + * @param filepath The filepath to resolve. + * @returns The resolved project path. + */ + resolveProjectPath: (filepath: string) => string; + } + /** * Delegate with implementation for messages used in route handlers. */ diff --git a/packages/dev-server/src/utils/parseSourceFilename.ts b/packages/dev-server/src/utils/parseSourceFilename.ts deleted file mode 100644 index cb059fab6..000000000 --- a/packages/dev-server/src/utils/parseSourceFilename.ts +++ /dev/null @@ -1,22 +0,0 @@ -import path from 'node:path'; - -export function parseSourceFilename(filename: string, rootDir: string) { - const prefix = filename.split('/')[0]; - let filepath = filename; - - // Handle [projectRoot] and [projectRoot^N] prefixes - const projectRootMatch = prefix.match(/^\[projectRoot(?:\^(\d+))?\]$/); - if (projectRootMatch) { - const upLevels = projectRootMatch[1]; - if (upLevels) { - // For [projectRoot^N], go up N levels from rootDir - const upPath = '../'.repeat(Number(upLevels)); - filepath = filepath.replace(`${prefix}/`, rootDir + '/' + upPath); - } else { - // For plain [projectRoot], just replace with rootDir - filepath = filepath.replace('[projectRoot]', rootDir); - } - } - - return path.resolve(filepath); -} diff --git a/packages/repack/src/commands/common/index.ts b/packages/repack/src/commands/common/index.ts index 2242a8f1a..af36e22e8 100644 --- a/packages/repack/src/commands/common/index.ts +++ b/packages/repack/src/commands/common/index.ts @@ -1,7 +1,8 @@ export * from './adaptFilenameToPlatform.js'; export * from './getMimeType.js'; +export * from './resolveProjectPath.js'; export * from './runAdbReverse.js'; -export * from './parseFileUrl.js'; +export * from './parseUrl.js'; export * from './resetPersistentCache.js'; export * from './setupInteractions.js'; export * from './setupStatsWriter.js'; diff --git a/packages/repack/src/commands/common/parseFileUrl.ts b/packages/repack/src/commands/common/parseFileUrl.ts deleted file mode 100644 index a059ce251..000000000 --- a/packages/repack/src/commands/common/parseFileUrl.ts +++ /dev/null @@ -1,29 +0,0 @@ -export function parseFileUrl(fileUrl: string, base?: string) { - const url = new URL(fileUrl, base); - const { pathname, searchParams } = url; - - let platform = searchParams.get('platform'); - let filename = pathname; - - if (!platform) { - const pathArray = pathname.split('/'); - const platformFromPath = pathArray[1]; - - if (platformFromPath === 'ios' || platformFromPath === 'android') { - platform = platformFromPath; - filename = pathArray.slice(2).join('/'); - } - } - - if (!platform) { - const [, platformOrName, name] = filename.split('.').reverse(); - if (name !== undefined) { - platform = platformOrName; - } - } - - return { - filename: filename.replace(/^\//, ''), - platform: platform || undefined, - }; -} diff --git a/packages/repack/src/commands/common/parseUrl.ts b/packages/repack/src/commands/common/parseUrl.ts new file mode 100644 index 000000000..4673c0eed --- /dev/null +++ b/packages/repack/src/commands/common/parseUrl.ts @@ -0,0 +1,28 @@ +export function parseUrl(url: string, platforms: string[], base = 'file:///') { + const { pathname, searchParams } = new URL(url, base); + + let path = pathname; + let platform = searchParams.get('platform'); + + if (!platform) { + const pathArray = pathname.split('/'); + const platformFromPath = pathArray[1]; + + if (platforms.includes(platformFromPath)) { + platform = platformFromPath; + path = pathArray.slice(2).join('/'); + } + } + + if (!platform) { + const [, platformOrName, name] = path.split('.').reverse(); + if (name !== undefined) { + platform = platformOrName; + } + } + + return { + resourcePath: path.replace(/^\//, ''), + platform: platform || undefined, + }; +} diff --git a/packages/repack/src/commands/common/resolveProjectPath.ts b/packages/repack/src/commands/common/resolveProjectPath.ts new file mode 100644 index 000000000..f5c293aa9 --- /dev/null +++ b/packages/repack/src/commands/common/resolveProjectPath.ts @@ -0,0 +1,19 @@ +import path from 'node:path'; + +const projectRootPattern = /^\[projectRoot(?:\^(\d+))?\]$/; + +function isProjectPath(filepath: string) { + const root = filepath.split('/')[0]; + return root.match(projectRootPattern); +} + +// Resolve [projectRoot] and [projectRoot^N] prefixes +export function resolveProjectPath(filepath: string, rootDir: string) { + const match = isProjectPath(filepath); + if (!match) return filepath; + + const [prefix, upLevels] = match; + const upPath = '../'.repeat(Number(upLevels ?? 0)); + const rootPath = path.join(rootDir, upPath); + return path.resolve(filepath.replace(prefix, rootPath)); +} diff --git a/packages/repack/src/commands/rspack/Compiler.ts b/packages/repack/src/commands/rspack/Compiler.ts index 50a007c9e..129b8789c 100644 --- a/packages/repack/src/commands/rspack/Compiler.ts +++ b/packages/repack/src/commands/rspack/Compiler.ts @@ -241,7 +241,9 @@ export class Compiler { } try { - const filePath = path.join(this.rootDir, filename); + const filePath = path.isAbsolute(filename) + ? filename + : path.join(this.rootDir, filename); const source = await fs.promises.readFile(filePath, 'utf8'); return source; } catch { diff --git a/packages/repack/src/commands/rspack/start.ts b/packages/repack/src/commands/rspack/start.ts index 2541a0cb3..db3043d4a 100644 --- a/packages/repack/src/commands/rspack/start.ts +++ b/packages/repack/src/commands/rspack/start.ts @@ -11,8 +11,9 @@ import { CLIError } from '../common/cliError.js'; import { makeCompilerConfig } from '../common/config/makeCompilerConfig.js'; import { getMimeType, - parseFileUrl, + parseUrl, resetPersistentCache, + resolveProjectPath, setupInteractions, } from '../common/index.js'; import { runAdbReverse } from '../common/index.js'; @@ -41,12 +42,14 @@ export async function start( throw new CLIError(`Unrecognized platform: ${args.platform}`); } + const platforms = args.platform ? [args.platform] : detectedPlatforms; + const configs = await makeCompilerConfig({ args: args, bundler: 'rspack', command: 'start', rootDir: cliConfig.root, - platforms: args.platform ? [args.platform] : detectedPlatforms, + platforms: platforms, reactNativePath: cliConfig.reactNativePath, }); @@ -132,24 +135,32 @@ export async function start( return { compiler: { - getAsset: (filename, platform) => { - const parsedUrl = parseFileUrl(filename, 'file:///'); - return compiler.getSource(parsedUrl.filename, platform); + getAsset: (url, platform) => { + const { resourcePath } = parseUrl(url, platforms); + return compiler.getSource(resourcePath, platform); + }, + getMimeType: (filename) => { + return getMimeType(filename); }, - getMimeType: (filename) => getMimeType(filename), - inferPlatform: (uri) => { - const { platform } = parseFileUrl(uri, 'file:///'); + inferPlatform: (url) => { + const { platform } = parseUrl(url, platforms); return platform; }, }, + devTools: { + resolveProjectPath: (filepath) => { + return resolveProjectPath(filepath, cliConfig.root); + }, + }, symbolicator: { - getSource: (fileUrl) => { - const { filename, platform } = parseFileUrl(fileUrl); - return compiler.getSource(filename, platform); + getSource: (url) => { + let { resourcePath, platform } = parseUrl(url, platforms); + resourcePath = resolveProjectPath(resourcePath, cliConfig.root); + return compiler.getSource(resourcePath, platform); }, - getSourceMap: (fileUrl) => { - const { filename, platform } = parseFileUrl(fileUrl); - return compiler.getSourceMap(filename, platform); + getSourceMap: (url) => { + const { resourcePath, platform } = parseUrl(url, platforms); + return compiler.getSourceMap(resourcePath, platform); }, shouldIncludeFrame: (frame) => { // If the frame points to internal bootstrap/module system logic, skip the code frame. diff --git a/packages/repack/src/commands/webpack/start.ts b/packages/repack/src/commands/webpack/start.ts index b488dea62..b97747574 100644 --- a/packages/repack/src/commands/webpack/start.ts +++ b/packages/repack/src/commands/webpack/start.ts @@ -13,8 +13,9 @@ import { CLIError } from '../common/cliError.js'; import { makeCompilerConfig } from '../common/config/makeCompilerConfig.js'; import { getMimeType, - parseFileUrl, + parseUrl, resetPersistentCache, + resolveProjectPath, runAdbReverse, setupInteractions, } from '../common/index.js'; @@ -43,12 +44,14 @@ export async function start( throw new CLIError(`Unrecognized platform: ${args.platform}`); } + const platforms = args.platform ? [args.platform] : detectedPlatforms; + const configs = await makeCompilerConfig({ args: args, bundler: 'webpack', command: 'start', rootDir: cliConfig.root, - platforms: args.platform ? [args.platform] : detectedPlatforms, + platforms: platforms, reactNativePath: cliConfig.reactNativePath, }); @@ -171,28 +174,32 @@ export async function start( return { compiler: { - getAsset: (filename, platform, sendProgress) => { - const parsedUrl = parseFileUrl(filename, 'file:///'); - return compiler.getSource( - parsedUrl.filename, - platform, - sendProgress - ); + getAsset: (url, platform, sendProgress) => { + const { resourcePath } = parseUrl(url, platforms); + return compiler.getSource(resourcePath, platform, sendProgress); + }, + getMimeType: (filename) => { + return getMimeType(filename); }, - getMimeType: (filename) => getMimeType(filename), - inferPlatform: (uri) => { - const { platform } = parseFileUrl(uri, 'file:///'); + inferPlatform: (url) => { + const { platform } = parseUrl(url, platforms); return platform; }, }, + devTools: { + resolveProjectPath: (filepath) => { + return resolveProjectPath(filepath, cliConfig.root); + }, + }, symbolicator: { - getSource: (fileUrl) => { - const { filename, platform } = parseFileUrl(fileUrl); - return compiler.getSource(filename, platform); + getSource: (url) => { + let { resourcePath, platform } = parseUrl(url, platforms); + resourcePath = resolveProjectPath(resourcePath, cliConfig.root); + return compiler.getSource(resourcePath, platform); }, - getSourceMap: (fileUrl) => { - const { filename, platform } = parseFileUrl(fileUrl); - return compiler.getSourceMap(filename, platform); + getSourceMap: (url) => { + const { resourcePath, platform } = parseUrl(url, platforms); + return compiler.getSourceMap(resourcePath, platform); }, shouldIncludeFrame: (frame) => { // If the frame points to internal bootstrap/module system logic, skip the code frame. From 2b3c2715f38f98ea9b58f21d37f80ac9af31a41a Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Fri, 13 Jun 2025 11:32:22 +0200 Subject: [PATCH 07/19] refactor: devtools plugin --- .../dev-server/src/plugins/devtools/devtoolsPlugin.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/dev-server/src/plugins/devtools/devtoolsPlugin.ts b/packages/dev-server/src/plugins/devtools/devtoolsPlugin.ts index c6f6cc3b6..7ffe06063 100644 --- a/packages/dev-server/src/plugins/devtools/devtoolsPlugin.ts +++ b/packages/dev-server/src/plugins/devtools/devtoolsPlugin.ts @@ -41,10 +41,11 @@ async function devtoolsPlugin( method: ['POST'], url: '/open-stack-frame', handler: async (request, reply) => { - const body = parseRequestBody(request.body); - const filepath = - delegate.devTools?.resolveProjectPath(body.file) ?? body.file; - launchEditor(`${filepath}:${body.lineNumber}`, process.env.REACT_EDITOR); + const { file, lineNumber } = parseRequestBody( + request.body + ); + const filepath = delegate.devTools?.resolveProjectPath(file) ?? file; + launchEditor(`${filepath}:${lineNumber}`, process.env.REACT_EDITOR); reply.send('OK'); }, }); From c7cd5751dc0a025cb1f7f41ea536a497086423a2 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 28 Jul 2025 16:02:15 +0200 Subject: [PATCH 08/19] wip --- .../repack/src/plugins/SourceMapPlugin.ts | 110 ++++++++++++------ .../repack/src/utils/getSwcLoaderOptions.ts | 6 + 2 files changed, 80 insertions(+), 36 deletions(-) diff --git a/packages/repack/src/plugins/SourceMapPlugin.ts b/packages/repack/src/plugins/SourceMapPlugin.ts index 3986c59b7..b91d430a2 100644 --- a/packages/repack/src/plugins/SourceMapPlugin.ts +++ b/packages/repack/src/plugins/SourceMapPlugin.ts @@ -1,8 +1,69 @@ import path from 'node:path'; -import type { Compiler as RspackCompiler } from '@rspack/core'; +import type { + Compiler as RspackCompiler, + SourceMapDevToolPluginOptions, +} from '@rspack/core'; import type { Compiler as WebpackCompiler } from 'webpack'; import { ConfigurationError } from './utils/ConfigurationError.js'; +type ModuleFilenameTemplate = + SourceMapDevToolPluginOptions['moduleFilenameTemplate']; +type ModuleFilenameTemplateFn = Exclude< + ModuleFilenameTemplate, + string | undefined +>; +type ModuleFilenameTemplateFnCtx = Parameters[0]; + +function devToolsmoduleFilenameTemplate( + namespace: string, + info: ModuleFilenameTemplateFnCtx +) { + // inlined modules + if (!info.identifier) { + return `${namespace}`; + } + + const [prefix, ...parts] = info.resourcePath.split('/'); + + // prefixed modules like React DevTools Backend + if (prefix !== '.' && prefix !== '..') { + const resourcePath = parts.filter((part) => part !== '..').join('/'); + return `webpack://${prefix}/${resourcePath}`; + } + + const hasValidAbsolutePath = path.isAbsolute(info.absoluteResourcePath); + + // project root + if (hasValidAbsolutePath && info.resourcePath.startsWith('./')) { + return `[projectRoot]${info.resourcePath.slice(1)}`; + } + + // outside of project root + if (hasValidAbsolutePath && info.resourcePath.startsWith('../')) { + const parts = info.resourcePath.split('/'); + const upLevel = parts.filter((part) => part === '..').length; + const restPath = parts.slice(parts.lastIndexOf('..') + 1).join('/'); + const rootRef = `[projectRoot^${upLevel}]`; + return `${rootRef}${restPath ? '/' + restPath : ''}`; + } + + return `[unknownOrigin]/${path.basename(info.identifier)}`; +} + +function defaultModuleFilenameTemplateHandler( + _: string, + info: ModuleFilenameTemplateFnCtx +) { + if (!info.absoluteResourcePath.startsWith('/')) { + // handle inlined modules + if (info.query || info.loaders || info.allLoaders) { + return `inlined-${info.hash}`; + } + } + // use absolute path for all other modules + return info.absoluteResourcePath; +} + interface SourceMapPluginConfig { platform?: string; } @@ -21,9 +82,17 @@ export class SourceMapPlugin { return; } - const host = compiler.options.devServer!.host; - const port = compiler.options.devServer!.port; - const namespace = `http://${host}:${port}`; + let moduleFilenameTemplateHandler: ModuleFilenameTemplateFn; + if (compiler.options.devServer) { + const host = compiler.options.devServer.host; + const port = compiler.options.devServer.port; + const namespace = `http://${host}:${port}`; + moduleFilenameTemplateHandler = (info: ModuleFilenameTemplateFnCtx) => + devToolsmoduleFilenameTemplate(namespace, info); + } else { + moduleFilenameTemplateHandler = (info: ModuleFilenameTemplateFnCtx) => + defaultModuleFilenameTemplateHandler('', info); + } const format = compiler.options.devtool; // disable builtin sourcemap generation @@ -62,38 +131,7 @@ export class SourceMapPlugin { new compiler.webpack.SourceMapDevToolPlugin({ test: /\.([cm]?jsx?|bundle)$/, filename: '[file].map', - moduleFilenameTemplate: (info) => { - // inlined modules - if (!info.identifier) { - return `${namespace}`; - } - - const [prefix, ...parts] = info.resourcePath.split('/'); - - // prefixed modules like React DevTools Backend - if (prefix !== '.' && prefix !== '..') { - const resourcePath = parts.filter((part) => part !== '..').join('/'); - return `webpack://${prefix}/${resourcePath}`; - } - - const hasValidAbsolutePath = path.isAbsolute(info.absoluteResourcePath); - - // project root - if (hasValidAbsolutePath && info.resourcePath.startsWith('./')) { - return `[projectRoot]${info.resourcePath.slice(1)}`; - } - - // outside of project root - if (hasValidAbsolutePath && info.resourcePath.startsWith('../')) { - const parts = info.resourcePath.split('/'); - const upLevel = parts.filter((part) => part === '..').length; - const restPath = parts.slice(parts.lastIndexOf('..') + 1).join('/'); - const rootRef = `[projectRoot^${upLevel}]`; - return `${rootRef}${restPath ? '/' + restPath : ''}`; - } - - return `[unknownOrigin]/${path.basename(info.identifier)}`; - }, + moduleFilenameTemplate: moduleFilenameTemplateHandler, fallbackModuleFilenameTemplate: devtoolFallbackModuleFilenameTemplate, append: hidden ? false diff --git a/packages/repack/src/utils/getSwcLoaderOptions.ts b/packages/repack/src/utils/getSwcLoaderOptions.ts index d337f8a31..acf85f8ec 100644 --- a/packages/repack/src/utils/getSwcLoaderOptions.ts +++ b/packages/repack/src/utils/getSwcLoaderOptions.ts @@ -80,6 +80,11 @@ function getModuleOptions( }; } +function getSourceMapOptions() { + // disable picking up sourcemaps from dependencies + return false; +} + /** * Interface for {@link getSwcLoaderOptions} options. */ @@ -161,5 +166,6 @@ export function getSwcLoaderOptions({ transform: getJSCTransformOptions(jsxRuntime, importSource), }, module: getModuleOptions(disableImportExportTransform, lazyImports), + sourceMaps: getSourceMapOptions(), }; } From c8b5e3d6041a8f9b38bf4e1a852e35dfa0b3d9a5 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 28 Jul 2025 17:23:57 +0200 Subject: [PATCH 09/19] chore: update podfile locks --- apps/tester-app/ios/Podfile.lock | 2 +- apps/tester-federation-v2/ios/Podfile.lock | 2 +- apps/tester-federation/ios/Podfile.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/tester-app/ios/Podfile.lock b/apps/tester-app/ios/Podfile.lock index 84ca4c1cd..3b6883c9a 100644 --- a/apps/tester-app/ios/Podfile.lock +++ b/apps/tester-app/ios/Podfile.lock @@ -2925,7 +2925,7 @@ SPEC CHECKSUMS: RNWorklets: 5e10b99988778b7a08a8aaeba9ad7f3e56787bf4 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6 - Yoga: 395b5d614cd7cbbfd76b05d01bd67230a6ad004e + Yoga: 0c4b7d2aacc910a1f702694fa86be830386f4ceb PODFILE CHECKSUM: 6d7cbe03444d5e87210979fb32a0eca299d758fe diff --git a/apps/tester-federation-v2/ios/Podfile.lock b/apps/tester-federation-v2/ios/Podfile.lock index 1a730b2e4..aa48b3bcf 100644 --- a/apps/tester-federation-v2/ios/Podfile.lock +++ b/apps/tester-federation-v2/ios/Podfile.lock @@ -2702,7 +2702,7 @@ SPEC CHECKSUMS: RNScreens: 75074e642b69b086813a943bdf63da7085fb2166 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6 - Yoga: 395b5d614cd7cbbfd76b05d01bd67230a6ad004e + Yoga: 0c4b7d2aacc910a1f702694fa86be830386f4ceb PODFILE CHECKSUM: 3d5c18eefbf70d38fbbfe81a262195cadac1f5dd diff --git a/apps/tester-federation/ios/Podfile.lock b/apps/tester-federation/ios/Podfile.lock index ca3b2d114..ceaee211d 100644 --- a/apps/tester-federation/ios/Podfile.lock +++ b/apps/tester-federation/ios/Podfile.lock @@ -2702,7 +2702,7 @@ SPEC CHECKSUMS: RNScreens: 75074e642b69b086813a943bdf63da7085fb2166 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6 - Yoga: 395b5d614cd7cbbfd76b05d01bd67230a6ad004e + Yoga: 0c4b7d2aacc910a1f702694fa86be830386f4ceb PODFILE CHECKSUM: 16a059e985a55bc49163512a311428a48f715334 From 47a16898e5da462ca9cdf3a961d0f6757e9d12d8 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 28 Jul 2025 17:25:53 +0200 Subject: [PATCH 10/19] chore: add changeset --- .changeset/evil-geckos-play.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/evil-geckos-play.md diff --git a/.changeset/evil-geckos-play.md b/.changeset/evil-geckos-play.md new file mode 100644 index 000000000..a7b7b1ee9 --- /dev/null +++ b/.changeset/evil-geckos-play.md @@ -0,0 +1,5 @@ +--- +"@callstack/repack": patch +--- + +Don't include 3rd party lib sourcemaps by default into the final sourcemaps From 2b464743231a1f432e702025a5b384620dea4862 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 28 Jul 2025 17:30:35 +0200 Subject: [PATCH 11/19] chore: no-package --- apps/tester-app/ios/Podfile.lock | 2 +- apps/tester-app/package.json | 4 ++-- apps/tester-federation-v2/package.json | 4 ++-- apps/tester-federation/package.json | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/tester-app/ios/Podfile.lock b/apps/tester-app/ios/Podfile.lock index 3b6883c9a..84ca4c1cd 100644 --- a/apps/tester-app/ios/Podfile.lock +++ b/apps/tester-app/ios/Podfile.lock @@ -2925,7 +2925,7 @@ SPEC CHECKSUMS: RNWorklets: 5e10b99988778b7a08a8aaeba9ad7f3e56787bf4 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6 - Yoga: 0c4b7d2aacc910a1f702694fa86be830386f4ceb + Yoga: 395b5d614cd7cbbfd76b05d01bd67230a6ad004e PODFILE CHECKSUM: 6d7cbe03444d5e87210979fb32a0eca299d758fe diff --git a/apps/tester-app/package.json b/apps/tester-app/package.json index 6e4ab80cf..799629896 100644 --- a/apps/tester-app/package.json +++ b/apps/tester-app/package.json @@ -3,9 +3,9 @@ "version": "0.0.1", "private": true, "scripts": { - "android": "react-native run-android --appId com.testerapp", + "android": "react-native run-android --appId com.testerapp --no-packager", "android:release": "node ./scripts/release.js android", - "ios": "react-native run-ios", + "ios": "react-native run-ios --no-packager", "ios:release": "node ./scripts/release.js ios", "pods": "(cd ios && bundle install && (bundle exec pod install || bundle exec pod update))", "pods:update": "(cd ios && bundle install && bundle exec pod update)", diff --git a/apps/tester-federation-v2/package.json b/apps/tester-federation-v2/package.json index 2c79549ca..bf49a7be8 100644 --- a/apps/tester-federation-v2/package.json +++ b/apps/tester-federation-v2/package.json @@ -3,8 +3,8 @@ "version": "0.0.1", "private": true, "scripts": { - "android": "react-native run-android --appId com.tester.federationV2", - "ios": "react-native run-ios", + "android": "react-native run-android --appId com.tester.federationV2 --no-packager", + "ios": "react-native run-ios --no-packager", "pods": "(cd ios && bundle install && (bundle exec pod install || bundle exec pod update))", "pods:update": "(cd ios && bundle install && bundle exec pod update)", "start:hostapp": "react-native webpack-start --config config.host-app.mts", diff --git a/apps/tester-federation/package.json b/apps/tester-federation/package.json index c7350564e..91b712a9a 100644 --- a/apps/tester-federation/package.json +++ b/apps/tester-federation/package.json @@ -3,8 +3,8 @@ "version": "0.0.1", "private": true, "scripts": { - "android": "react-native run-android --appId com.tester.federation", - "ios": "react-native run-ios", + "android": "react-native run-android --appId com.tester.federation --no-packager", + "ios": "react-native run-ios --no-packager", "pods": "(cd ios && bundle install && (bundle exec pod install || bundle exec pod update))", "pods:update": "(cd ios && bundle install && bundle exec pod update)", "start:hostapp": "react-native webpack-start --config config.host-app.mts", From 58cc6ba221cdae4623753728f3c7669cf88607f1 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 28 Jul 2025 18:01:38 +0200 Subject: [PATCH 12/19] refactor: use separate rules for node modules --- .../repack/src/utils/getJsTransformRules.ts | 42 +++++++++++++++++-- .../repack/src/utils/getSwcLoaderOptions.ts | 6 --- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/packages/repack/src/utils/getJsTransformRules.ts b/packages/repack/src/utils/getJsTransformRules.ts index 49f6f7817..480d34754 100644 --- a/packages/repack/src/utils/getJsTransformRules.ts +++ b/packages/repack/src/utils/getJsTransformRules.ts @@ -85,15 +85,51 @@ export function getJsTransformRules(options?: GetJsTransformRulesOptions) { oneOf: [ { test: /jsx?$/, - use: { loader: 'builtin:swc-loader', options: jsRules }, + include: /node_modules/, + use: { + loader: 'builtin:swc-loader', + options: { ...jsRules, sourceMaps: false }, + }, + }, + { + test: /jsx?$/, + exclude: /node_modules/, + use: { + loader: 'builtin:swc-loader', + options: { ...jsRules, sourceMaps: true }, + }, }, { test: /ts$/, - use: { loader: 'builtin:swc-loader', options: tsRules }, + include: /node_modules/, + use: { + loader: 'builtin:swc-loader', + options: { ...tsRules, sourceMaps: false }, + }, + }, + { + test: /ts$/, + exclude: /node_modules/, + use: { + loader: 'builtin:swc-loader', + options: { ...tsRules, sourceMaps: true }, + }, + }, + { + test: /tsx$/, + include: /node_modules/, + use: { + loader: 'builtin:swc-loader', + options: { ...tsxRules, sourceMaps: false }, + }, }, { test: /tsx$/, - use: { loader: 'builtin:swc-loader', options: tsxRules }, + exclude: /node_modules/, + use: { + loader: 'builtin:swc-loader', + options: { ...tsxRules, sourceMaps: true }, + }, }, ], }, diff --git a/packages/repack/src/utils/getSwcLoaderOptions.ts b/packages/repack/src/utils/getSwcLoaderOptions.ts index acf85f8ec..d337f8a31 100644 --- a/packages/repack/src/utils/getSwcLoaderOptions.ts +++ b/packages/repack/src/utils/getSwcLoaderOptions.ts @@ -80,11 +80,6 @@ function getModuleOptions( }; } -function getSourceMapOptions() { - // disable picking up sourcemaps from dependencies - return false; -} - /** * Interface for {@link getSwcLoaderOptions} options. */ @@ -166,6 +161,5 @@ export function getSwcLoaderOptions({ transform: getJSCTransformOptions(jsxRuntime, importSource), }, module: getModuleOptions(disableImportExportTransform, lazyImports), - sourceMaps: getSourceMapOptions(), }; } From 6ea0e89caf1feae44a52775a95bc1a3411d06b2d Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 28 Jul 2025 18:13:39 +0200 Subject: [PATCH 13/19] chore: update podfile locks --- apps/tester-federation-v2/ios/Podfile.lock | 2 +- apps/tester-federation/ios/Podfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/tester-federation-v2/ios/Podfile.lock b/apps/tester-federation-v2/ios/Podfile.lock index aa48b3bcf..1a730b2e4 100644 --- a/apps/tester-federation-v2/ios/Podfile.lock +++ b/apps/tester-federation-v2/ios/Podfile.lock @@ -2702,7 +2702,7 @@ SPEC CHECKSUMS: RNScreens: 75074e642b69b086813a943bdf63da7085fb2166 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6 - Yoga: 0c4b7d2aacc910a1f702694fa86be830386f4ceb + Yoga: 395b5d614cd7cbbfd76b05d01bd67230a6ad004e PODFILE CHECKSUM: 3d5c18eefbf70d38fbbfe81a262195cadac1f5dd diff --git a/apps/tester-federation/ios/Podfile.lock b/apps/tester-federation/ios/Podfile.lock index ceaee211d..ca3b2d114 100644 --- a/apps/tester-federation/ios/Podfile.lock +++ b/apps/tester-federation/ios/Podfile.lock @@ -2702,7 +2702,7 @@ SPEC CHECKSUMS: RNScreens: 75074e642b69b086813a943bdf63da7085fb2166 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6 - Yoga: 0c4b7d2aacc910a1f702694fa86be830386f4ceb + Yoga: 395b5d614cd7cbbfd76b05d01bd67230a6ad004e PODFILE CHECKSUM: 16a059e985a55bc49163512a311428a48f715334 From 64385613e4e41142bc030a5c1042a3aaf5cf9edc Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 28 Jul 2025 18:18:11 +0200 Subject: [PATCH 14/19] chore: add changeset --- .changeset/bitter-sheep-cry.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/bitter-sheep-cry.md diff --git a/.changeset/bitter-sheep-cry.md b/.changeset/bitter-sheep-cry.md new file mode 100644 index 000000000..814fc9ba5 --- /dev/null +++ b/.changeset/bitter-sheep-cry.md @@ -0,0 +1,6 @@ +--- +"@callstack/repack-dev-server": patch +"@callstack/repack": patch +--- + +Fix opening stack frame source file from LogBox From 24fdedb08bb2432ae9d82cf6003eaf5d9d6d18f1 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 28 Jul 2025 18:20:33 +0200 Subject: [PATCH 15/19] choere: add changeset --- .changeset/chatty-taxes-hug.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/chatty-taxes-hug.md diff --git a/.changeset/chatty-taxes-hug.md b/.changeset/chatty-taxes-hug.md new file mode 100644 index 000000000..b7556c979 --- /dev/null +++ b/.changeset/chatty-taxes-hug.md @@ -0,0 +1,5 @@ +--- +"@callstack/repack": minor +--- + +Handle displaying relative paths to sourcefiles in DevTools similarly to Metro From 28ac12cfbfbf6acf33ed04eb9335c28eb41120b7 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 28 Jul 2025 18:46:56 +0200 Subject: [PATCH 16/19] test: add resolveProjectPath tests --- .../__tests__/resolveProjectPath.test.ts | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 packages/repack/src/commands/common/__tests__/resolveProjectPath.test.ts diff --git a/packages/repack/src/commands/common/__tests__/resolveProjectPath.test.ts b/packages/repack/src/commands/common/__tests__/resolveProjectPath.test.ts new file mode 100644 index 000000000..fbb62dc78 --- /dev/null +++ b/packages/repack/src/commands/common/__tests__/resolveProjectPath.test.ts @@ -0,0 +1,51 @@ +import { resolveProjectPath } from '../resolveProjectPath.js'; + +describe('resolveProjectPath', () => { + const expectResolution = ( + input: string, + expected: string, + root = '/project/root' + ) => { + expect(resolveProjectPath(input, root)).toBe(expected); + }; + + it('should resolve [projectRoot] prefix correctly', () => { + expectResolution( + '[projectRoot]/src/index.js', + '/project/root/src/index.js' + ); + expectResolution( + '[projectRoot]/build/output.js', + '/apps/my-app/build/output.js', + '/apps/my-app' + ); + + expectResolution( + '[projectRoot]/special-file@2x.png', + '/project/root/special-file@2x.png' + ); + expectResolution( + '[projectRoot]/file with spaces.txt', + '/project/root/file with spaces.txt' + ); + }); + + it('should resolve [projectRoot^N] prefix with up-level navigation', () => { + expectResolution('[projectRoot^1]/src/index.js', '/project/src/index.js'); + expectResolution('[projectRoot^2]/shared/utils.js', '/shared/utils.js'); + expectResolution( + '[projectRoot^3]/global/config.json', + '/global/config.json' + ); + expectResolution( + '[projectRoot^2]/utils/helper.js', + '/deep/nested/utils/helper.js', + '/deep/nested/project/folder' + ); + expectResolution( + '[projectRoot^5]/very/deep/file.js', + '/a/very/deep/file.js', + '/a/b/c/d/e/f' + ); + }); +}); From 95c04730cddb59a57b41fd50bbbb2f3c559203e3 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 28 Jul 2025 18:52:54 +0200 Subject: [PATCH 17/19] refactor: rename --- .../__tests__/resolveProjectPath.test.ts | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/packages/repack/src/commands/common/__tests__/resolveProjectPath.test.ts b/packages/repack/src/commands/common/__tests__/resolveProjectPath.test.ts index fbb62dc78..89d2e06c2 100644 --- a/packages/repack/src/commands/common/__tests__/resolveProjectPath.test.ts +++ b/packages/repack/src/commands/common/__tests__/resolveProjectPath.test.ts @@ -1,7 +1,7 @@ import { resolveProjectPath } from '../resolveProjectPath.js'; describe('resolveProjectPath', () => { - const expectResolution = ( + const expectResolved = ( input: string, expected: string, root = '/project/root' @@ -10,39 +10,32 @@ describe('resolveProjectPath', () => { }; it('should resolve [projectRoot] prefix correctly', () => { - expectResolution( - '[projectRoot]/src/index.js', - '/project/root/src/index.js' - ); - expectResolution( + expectResolved('[projectRoot]/src/index.js', '/project/root/src/index.js'); + expectResolved( '[projectRoot]/build/output.js', '/apps/my-app/build/output.js', '/apps/my-app' ); - - expectResolution( + expectResolved( '[projectRoot]/special-file@2x.png', '/project/root/special-file@2x.png' ); - expectResolution( + expectResolved( '[projectRoot]/file with spaces.txt', '/project/root/file with spaces.txt' ); }); it('should resolve [projectRoot^N] prefix with up-level navigation', () => { - expectResolution('[projectRoot^1]/src/index.js', '/project/src/index.js'); - expectResolution('[projectRoot^2]/shared/utils.js', '/shared/utils.js'); - expectResolution( - '[projectRoot^3]/global/config.json', - '/global/config.json' - ); - expectResolution( + expectResolved('[projectRoot^1]/src/index.js', '/project/src/index.js'); + expectResolved('[projectRoot^2]/shared/utils.js', '/shared/utils.js'); + expectResolved('[projectRoot^3]/global/config.json', '/global/config.json'); + expectResolved( '[projectRoot^2]/utils/helper.js', '/deep/nested/utils/helper.js', '/deep/nested/project/folder' ); - expectResolution( + expectResolved( '[projectRoot^5]/very/deep/file.js', '/a/very/deep/file.js', '/a/b/c/d/e/f' From e28f0f586afa504d4fdc55747bb04e96d30ba5c7 Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 28 Jul 2025 19:00:15 +0200 Subject: [PATCH 18/19] test: add parseUrl tests --- .../common/__tests__/parseUrl.test.ts | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 packages/repack/src/commands/common/__tests__/parseUrl.test.ts diff --git a/packages/repack/src/commands/common/__tests__/parseUrl.test.ts b/packages/repack/src/commands/common/__tests__/parseUrl.test.ts new file mode 100644 index 000000000..6ad96c9ea --- /dev/null +++ b/packages/repack/src/commands/common/__tests__/parseUrl.test.ts @@ -0,0 +1,101 @@ +import { parseUrl } from '../parseUrl.js'; + +describe('parseUrl', () => { + const expectParsed = ( + url: string, + expected: { resourcePath: string; platform?: string }, + platformList = ['ios', 'android', 'web'] + ) => { + expect(parseUrl(url, platformList)).toEqual(expected); + }; + + it('should parse URLs with platform from query parameters', () => { + expectParsed('src/index.js?platform=ios', { + resourcePath: 'src/index.js', + platform: 'ios', + }); + expectParsed('components/Button.tsx?platform=android', { + resourcePath: 'components/Button.tsx', + platform: 'android', + }); + expectParsed('/absolute/path/file.js?platform=web', { + resourcePath: 'absolute/path/file.js', + platform: 'web', + }); + }); + + it('should parse URLs with platform from pathname', () => { + expectParsed('/ios/src/index.js', { + resourcePath: 'src/index.js', + platform: 'ios', + }); + expectParsed('/android/components/Button.tsx', { + resourcePath: 'components/Button.tsx', + platform: 'android', + }); + expectParsed('/web/utils/helper.js', { + resourcePath: 'utils/helper.js', + platform: 'web', + }); + }); + + it('should parse URLs with platform from file extension', () => { + expectParsed('src/index.ios.js', { + resourcePath: 'src/index.ios.js', + platform: 'ios', + }); + expectParsed('components/Button.android.tsx', { + resourcePath: 'components/Button.android.tsx', + platform: 'android', + }); + expectParsed('styles/theme.web.css', { + resourcePath: 'styles/theme.web.css', + platform: 'web', + }); + }); + + it('should handle URLs without platform detection', () => { + expectParsed('src/index.js', { + resourcePath: 'src/index.js', + platform: undefined, + }); + expectParsed('/components/Button.tsx', { + resourcePath: 'components/Button.tsx', + platform: undefined, + }); + expectParsed('utils/helper.unknown.js', { + resourcePath: 'utils/helper.unknown.js', + platform: undefined, + }); + }); + + it('should prioritize query parameter over pathname and extension', () => { + expectParsed('/android/src/index.ios.js?platform=web', { + resourcePath: 'android/src/index.ios.js', + platform: 'web', + }); + expectParsed('/ios/components/Button.android.tsx?platform=web', { + resourcePath: 'ios/components/Button.android.tsx', + platform: 'web', + }); + }); + + it('should work with different platform lists', () => { + expectParsed( + '/react-native/src/index.js', + { + resourcePath: 'src/index.js', + platform: 'react-native', + }, + ['react-native', 'macos'] + ); + expectParsed( + 'app.macos.js', + { + resourcePath: 'app.macos.js', + platform: 'macos', + }, + ['react-native', 'macos'] + ); + }); +}); From c28f2f63ddb96a8502a71d06346336c1fa026cad Mon Sep 17 00:00:00 2001 From: Jakub Romanczyk Date: Mon, 28 Jul 2025 19:00:38 +0200 Subject: [PATCH 19/19] fix: handle parseUrl edgecase --- packages/repack/src/commands/common/parseUrl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/repack/src/commands/common/parseUrl.ts b/packages/repack/src/commands/common/parseUrl.ts index 4673c0eed..06b840d89 100644 --- a/packages/repack/src/commands/common/parseUrl.ts +++ b/packages/repack/src/commands/common/parseUrl.ts @@ -16,7 +16,7 @@ export function parseUrl(url: string, platforms: string[], base = 'file:///') { if (!platform) { const [, platformOrName, name] = path.split('.').reverse(); - if (name !== undefined) { + if (name !== undefined && platforms.includes(platformOrName)) { platform = platformOrName; } }