Files
colanode/apps/mobile/metro.config.js
Ylber Gashi 8d3020e6e5 Unify mobile data access hooks with shared packages/ui (#363)
Mobile duplicated useLiveQuery, useQuery, useMutation, and buildQueryClient
from packages/ui with the only difference being the executor mechanism
(appService.mediator vs window.colanode). This consolidates them by:

- Adding a DOM-free core-api module to packages/ui with getColanode() and
  getEventBus() helpers that access globalThis, avoiding Window/DOM types
- Updating shared hooks and buildQueryClient to use these helpers instead
  of window.colanode/window.eventBus directly
- Installing a globalThis.colanode bridge in mobile's _layout.tsx that
  delegates to appService.mediator (matching the web/desktop pattern)
- Migrating all 41 mobile files to import hooks from @colanode/ui
- Deleting 4 duplicate mobile files (3 hooks + query-client)
- Adding Metro resolver override to force react/react-native resolution
  from the mobile app's node_modules (packages/ui ships a separate
  web-only React 19.2.4 that conflicts with mobile's RN-compatible 19.1.0)

Also standardizes useQuery to use buildQueryKey instead of inline sha256.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 19:11:18 +01:00

81 lines
2.5 KiB
JavaScript

const { getDefaultConfig } = require('expo/metro-config');
const path = require('path');
const config = getDefaultConfig(__dirname);
// Allow .db and .html files to be imported as assets
config.resolver.assetExts = [...config.resolver.assetExts, 'db', 'html'];
// Modules that are never used at runtime on mobile — mock with empty export.
const EMPTY_MOCK_MODULES = [
'@tiptap/core',
'@tiptap/pm',
];
// Modules that need a functional mock (used by yjs/lib0 at runtime).
const CUSTOM_MOCKS = {
'isomorphic-webcrypto': path.resolve(
__dirname,
'src/mocks/isomorphic-webcrypto.js'
),
};
const emptyModule = path.resolve(__dirname, 'src/mocks/empty-module.js');
const originalResolveRequest = config.resolver.resolveRequest;
// Files containing syntax Hermes cannot compile (e.g. dynamic import with
// webpack magic comments). Matched against the resolved file path.
const BLOCKED_FILE_PATTERNS = [
/kysely[/\\]dist[/\\].*file-migration-provider/,
];
config.resolver.resolveRequest = (context, moduleName, platform) => {
// packages/ui ships its own web-only React (different version). Force react
// and react-native to always resolve from the mobile app's node_modules so
// there is exactly one React instance in the bundle.
if (moduleName === 'react' || moduleName.startsWith('react/') ||
moduleName === 'react-native' || moduleName.startsWith('react-native/')) {
const resolved = require.resolve(moduleName, { paths: [__dirname] });
return { filePath: resolved, type: 'sourceFile' };
}
// Empty mocks for browser-only modules
if (
EMPTY_MOCK_MODULES.some(
(m) => moduleName === m || moduleName.startsWith(m + '/')
)
) {
return {
filePath: emptyModule,
type: 'sourceFile',
};
}
// Custom mocks for modules that need partial functionality
for (const [prefix, mockPath] of Object.entries(CUSTOM_MOCKS)) {
if (moduleName === prefix || moduleName.startsWith(prefix + '/')) {
return {
filePath: mockPath,
type: 'sourceFile',
};
}
}
// Resolve normally first, then check if the result should be blocked
const resolution = originalResolveRequest
? originalResolveRequest(context, moduleName, platform)
: context.resolveRequest(context, moduleName, platform);
if (
resolution?.type === 'sourceFile' &&
BLOCKED_FILE_PATTERNS.some((re) => re.test(resolution.filePath))
) {
return { filePath: emptyModule, type: 'sourceFile' };
}
return resolution;
};
module.exports = config;