Skip to content

Commit 4b5d285

Browse files
authored
refactor (#21)
1 parent 2eea733 commit 4b5d285

File tree

22 files changed

+145
-163
lines changed

22 files changed

+145
-163
lines changed

apps/bare-expo/android/gradle.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,8 @@ EXPO_ALLOW_GLIDE_LOGS=true
6565

6666
android.enableMinifyInReleaseBuilds=true
6767

68+
# Enable support for local modules in Expo CLI and Expo Modules Autolinking.
6869
expo.localModules.enabled=true
70+
71+
# List of directories watched for local modules.
6972
expo.localModules.watchedDirs=["../native-component-list/src/screens/LocalModules/localModulesExamples"]

apps/bare-expo/metro.config.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function findUpTSConfig(cwd) {
3737
return findUpTSConfig(parent);
3838
}
3939

40-
function findUpTSProjectRootOrAssert(dir) {
40+
function findUpTSProjectRootOrThrow(dir) {
4141
const tsProjectRoot = findUpTSConfig(dir);
4242
if (!tsProjectRoot) {
4343
throw new Error('Local modules watched dir needs to be inside a TS project with tsconfig.json');
@@ -64,15 +64,15 @@ config.resolver.resolveRequest = (context, moduleName, platform) => {
6464
localModuleFileExtension = '.view.js';
6565
}
6666
if (localModuleFileExtension) {
67-
const tsProjectRoot = findUpTSProjectRootOrAssert(path.dirname(context.originModulePath));
68-
const relativePathToOriginModule = path.relative(
67+
const tsProjectRoot = findUpTSProjectRootOrThrow(path.dirname(context.originModulePath));
68+
const modulePathRelativeToTSRoot = path.relative(
6969
tsProjectRoot,
7070
fs.realpathSync(path.dirname(context.originModulePath))
7171
);
7272

7373
const modulePath = path.resolve(
7474
localModulesModulesPath,
75-
relativePathToOriginModule,
75+
modulePathRelativeToTSRoot,
7676
moduleName.substring(0, moduleName.lastIndexOf('.')) + localModuleFileExtension
7777
);
7878

@@ -82,8 +82,7 @@ config.resolver.resolveRequest = (context, moduleName, platform) => {
8282
};
8383
}
8484

85-
const resolution = context.resolveRequest(context, moduleName, platform);
86-
return resolution;
85+
return context.resolveRequest(context, moduleName, platform);
8786
};
8887

8988
// When testing on MacOS we need to include the `react-native-macos/Libraries/Core/InitializeCore` as prepended global module

apps/native-component-list/src/screens/LocalModules/localModulesExamples/TestView.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
package localModulesExamples
32

43
import expo.modules.kotlin.modules.Module

apps/native-component-list/src/screens/LocalModules/localModulesExamples/TestView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class ExpoWebView: ExpoView, WKNavigationDelegate {
3131
webView.frame = bounds
3232
}
3333

34-
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
34+
func webView(_ webView: WKWebView, _: WKNavigation) {
3535
if let url = webView.url {
3636
onLoad([
3737
"url": url.absoluteString

packages/@expo/cli/src/localModules/generation.ts

Lines changed: 74 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { getConfig } from '@expo/config';
22
import { getPbxproj } from '@expo/config-plugins/build/ios/utils/Xcodeproj';
33
import Server from '@expo/metro/metro/Server';
44
import type MetroServer from '@expo/metro/metro/Server';
5-
import { assert as consoleAssert } from 'console';
65
import fs from 'fs';
76
import path from 'path';
87

@@ -26,7 +25,7 @@ function findUpTSConfig(cwd: string): string | null {
2625
return findUpTSConfig(parent);
2726
}
2827

29-
function findUpTSProjectRootOrAssert(dir: string): string {
28+
function findUpTSProjectRootOrThrow(dir: string): string {
3029
const tsProjectRoot = findUpTSConfig(dir);
3130
if (!tsProjectRoot) {
3231
throw new Error('Local modules watched dir needs to be inside a TS project with tsconfig.json');
@@ -71,7 +70,7 @@ function getMirrorDirectories(projectRoot: string): {
7170
};
7271
}
7372

74-
function createFreshMirrorDirectories(projectRoot: string) {
73+
function createFreshMirrorDirectories(projectRoot: string): void {
7574
const { localModulesModulesPath, localModulesTypesPath } = getMirrorDirectories(projectRoot);
7675

7776
if (fs.existsSync(localModulesModulesPath)) {
@@ -84,26 +83,26 @@ function createFreshMirrorDirectories(projectRoot: string) {
8483
fs.mkdirSync(localModulesTypesPath, { recursive: true });
8584
}
8685

87-
function trimExtension(fileName: string) {
86+
function trimExtension(fileName: string): string {
8887
return fileName.substring(0, fileName.lastIndexOf('.'));
8988
}
9089

9190
function typesAndLocalModulePathsForFile(
9291
projectRoot: string,
93-
watchedDirRoot: string,
92+
watchedDirRootAbolutePath: string,
9493
absoluteFilePath: string
95-
) {
96-
consoleAssert(!!absoluteFilePath);
97-
consoleAssert(path.isAbsolute(absoluteFilePath));
94+
): {
95+
moduleTypesFilePath: string;
96+
viewTypesFilePath: string;
97+
viewExportPath: string;
98+
moduleExportPath: string;
99+
moduleName: string;
100+
} {
98101
const { localModulesModulesPath, localModulesTypesPath } = getMirrorDirectories(projectRoot);
99-
const splitPath = absoluteFilePath.split('/');
100-
const fileName = splitPath.at(-1);
101-
if (!fileName) {
102-
throw new Error('Invalid absoluteFilePath provided.');
103-
}
102+
const fileName = path.basename(absoluteFilePath);
104103
const moduleName = trimExtension(fileName);
105104

106-
const watchedDirTSProjectRoot = findUpTSProjectRootOrAssert(watchedDirRoot);
105+
const watchedDirTSProjectRoot = findUpTSProjectRootOrThrow(watchedDirRootAbolutePath);
107106
const filePathRelativeToTSProjectRoot = path.relative(watchedDirTSProjectRoot, absoluteFilePath);
108107
const filePathRelativeToTSProjectRootWithoutExtension = trimExtension(
109108
filePathRelativeToTSProjectRoot
@@ -117,14 +116,14 @@ function typesAndLocalModulePathsForFile(
117116
localModulesTypesPath,
118117
filePathRelativeToTSProjectRootWithoutExtension + '.view.d.ts'
119118
);
120-
const viewExportPath = path.resolve(
121-
localModulesModulesPath,
122-
filePathRelativeToTSProjectRootWithoutExtension + '.view.js'
123-
);
124119
const moduleExportPath = path.resolve(
125120
localModulesModulesPath,
126121
filePathRelativeToTSProjectRootWithoutExtension + '.module.js'
127122
);
123+
const viewExportPath = path.resolve(
124+
localModulesModulesPath,
125+
filePathRelativeToTSProjectRootWithoutExtension + '.view.js'
126+
);
128127
return {
129128
moduleTypesFilePath,
130129
viewTypesFilePath,
@@ -148,7 +147,7 @@ function fileWatchedWithAnyNativeExtension(
148147
return false;
149148
}
150149

151-
export function updateXCodeProject(projectRoot: string) {
150+
export function updateXCodeProject(projectRoot: string): void {
152151
const pbxProject = getPbxproj(projectRoot);
153152
const mainGroupUUID = pbxProject.getFirstProject().firstProject.mainGroup;
154153
const mainTargetUUID = pbxProject.getFirstProject().firstProject.targets[0].value;
@@ -212,9 +211,12 @@ export function updateXCodeProject(projectRoot: string) {
212211
fs.writeFileSync(pbxProject.filepath, pbxProject.writeSync());
213212
}
214213

215-
function fileWatchedDirAncestor(projectRoot: string, filePathAbsolute: string): string | null {
214+
function getWatchedDirAncestorAbsolutePath(
215+
projectRoot: string,
216+
filePathAbsolute: string
217+
): string | null {
216218
const watchedDirs = getConfig(projectRoot).exp.localModules?.watchedDirs ?? [];
217-
const realRoot = fs.realpathSync(projectRoot);
219+
const realRoot = path.resolve(projectRoot);
218220
for (const dir of watchedDirs) {
219221
const dirPathAbsolute = path.resolve(realRoot, dir);
220222
if (filePathAbsolute.startsWith(dirPathAbsolute)) {
@@ -226,12 +228,12 @@ function fileWatchedDirAncestor(projectRoot: string, filePathAbsolute: string):
226228

227229
function onSourceFileCreated(
228230
projectRoot: string,
229-
watchedDirRoot: string,
231+
watchedDirRootAbolutePath: string,
230232
absoluteFilePath: string,
231233
filesWatched?: Set<string>
232-
) {
234+
): void {
233235
const { moduleTypesFilePath, viewTypesFilePath, viewExportPath, moduleExportPath, moduleName } =
234-
typesAndLocalModulePathsForFile(projectRoot, watchedDirRoot, absoluteFilePath);
236+
typesAndLocalModulePathsForFile(projectRoot, watchedDirRootAbolutePath, absoluteFilePath);
235237

236238
if (filesWatched && fileWatchedWithAnyNativeExtension(absoluteFilePath, filesWatched)) {
237239
filesWatched.add(absoluteFilePath);
@@ -265,12 +267,15 @@ export default _default`
265267
fs.writeFileSync(moduleTypesFilePath, 'const _default: any\nexport default _default');
266268
}
267269

268-
async function generateMirrorDirectories(projectRoot: string, filesWatched?: Set<string>) {
270+
async function generateMirrorDirectories(
271+
projectRoot: string,
272+
filesWatched?: Set<string>
273+
): Promise<void> {
269274
createFreshMirrorDirectories(projectRoot);
270275

271276
const generateExportsAndTypesForDirectory = async (
272277
absoluteDirPath: string,
273-
watchedDirRoot: string
278+
watchedDirRootAbolutePath: string
274279
) => {
275280
for (const glob of excludePathsGlobs(projectRoot)) {
276281
if (path.matchesGlob(absoluteDirPath, glob)) {
@@ -284,11 +289,16 @@ async function generateMirrorDirectories(projectRoot: string, filesWatched?: Set
284289
if (
285290
dirent.isFile() &&
286291
isValidLocalModuleFileName(dirent.name) &&
287-
absoluteDirentPath.startsWith(watchedDirRoot)
292+
absoluteDirentPath.startsWith(watchedDirRootAbolutePath)
288293
) {
289-
onSourceFileCreated(projectRoot, watchedDirRoot, absoluteDirentPath, filesWatched);
294+
onSourceFileCreated(
295+
projectRoot,
296+
watchedDirRootAbolutePath,
297+
absoluteDirentPath,
298+
filesWatched
299+
);
290300
} else if (dirent.isDirectory()) {
291-
await generateExportsAndTypesForDirectory(absoluteDirentPath, watchedDirRoot);
301+
await generateExportsAndTypesForDirectory(absoluteDirentPath, watchedDirRootAbolutePath);
292302
}
293303
}
294304
};
@@ -305,32 +315,25 @@ async function generateMirrorDirectories(projectRoot: string, filesWatched?: Set
305315
function excludePathsGlobs(projectRoot: string): string[] {
306316
return [
307317
path.resolve(projectRoot, '.expo'),
308-
path.resolve(projectRoot, '.expo', './**'),
309318
path.resolve(projectRoot, '.expo', './**/*'),
310319
path.resolve(projectRoot, 'node_modules'),
311-
path.resolve(projectRoot, 'node_modules', './**'),
312320
path.resolve(projectRoot, 'node_modules', './**/*'),
313321
path.resolve(projectRoot, 'localModules'),
314-
path.resolve(projectRoot, 'localModules', './**'),
315322
path.resolve(projectRoot, 'localModules', './**/*'),
316323
path.resolve(projectRoot, 'android'),
317-
path.resolve(projectRoot, 'android', './**'),
318324
path.resolve(projectRoot, 'android', './**/*'),
319325
path.resolve(projectRoot, 'ios'),
320-
path.resolve(projectRoot, 'ios', './**'),
321326
path.resolve(projectRoot, 'ios', './**/*'),
322327
path.resolve(projectRoot, 'modules'),
323-
path.resolve(projectRoot, 'modules', './**'),
324328
path.resolve(projectRoot, 'modules', './**/*'),
325329
];
326330
}
327331

328332
export async function startModuleGenerationAsync({
329333
projectRoot,
330334
metro,
331-
}: ModuleGenerationArguments) {
335+
}: ModuleGenerationArguments): Promise<void> {
332336
const dotExpoDir = ensureDotExpoProjectDirectoryInitialized(projectRoot);
333-
const { exp } = getConfig(projectRoot);
334337
const filesWatched = new Set<string>();
335338

336339
const isFileExcluded = (absolutePath: string) => {
@@ -345,21 +348,17 @@ export async function startModuleGenerationAsync({
345348
createFreshMirrorDirectories(projectRoot);
346349

347350
const removeFileAndEmptyDirectories = (absoluteFilePath: string) => {
348-
if (fs.lstatSync(absoluteFilePath).isSymbolicLink()) {
349-
fs.unlinkSync(absoluteFilePath);
350-
} else {
351-
fs.rmSync(absoluteFilePath);
352-
}
351+
fs.rmSync(absoluteFilePath);
353352
let dirNow: string = path.dirname(absoluteFilePath);
354353
while (fs.readdirSync(dirNow).length === 0 && dirNow !== dotExpoDir) {
355354
fs.rmdirSync(dirNow);
356355
dirNow = path.dirname(dirNow);
357356
}
358357
};
359358

360-
const onSourceFileRemoved = (absoluteFilePath: string, watchedDirRoot: string) => {
359+
const onSourceFileRemoved = (absoluteFilePath: string, watchedDirRootAbolutePath: string) => {
361360
const { moduleTypesFilePath, moduleExportPath, viewExportPath, viewTypesFilePath } =
362-
typesAndLocalModulePathsForFile(projectRoot, watchedDirRoot, absoluteFilePath);
361+
typesAndLocalModulePathsForFile(projectRoot, watchedDirRootAbolutePath, absoluteFilePath);
363362

364363
filesWatched.delete(absoluteFilePath);
365364
if (!fileWatchedWithAnyNativeExtension(absoluteFilePath, filesWatched)) {
@@ -370,55 +369,40 @@ export async function startModuleGenerationAsync({
370369
}
371370
};
372371

373-
const metroWatchKotlinAndSwiftFiles = async ({
374-
projectRoot,
375-
metro,
376-
eventTypes = ['add', 'delete'],
377-
}: {
378-
metro: MetroServer | null;
379-
projectRoot: string;
380-
eventTypes?: string[];
381-
}) => {
382-
const watcher = metro?.getBundler().getBundler().getWatcher();
383-
384-
const isWatchedFileEvent = (event: Event, watchedDirAncestor: string | null): boolean => {
385-
return (
386-
event.metadata?.type !== 'd' &&
387-
isValidLocalModuleFileName(path.basename(event.filePath)) &&
388-
!isFileExcluded(event.filePath) &&
389-
!!watchedDirAncestor
390-
);
391-
};
372+
const watcher = metro?.getBundler().getBundler().getWatcher();
373+
const eventTypes = ['add', 'delete', 'change'];
392374

393-
const listener = async ({ eventsQueue }: { eventsQueue: EventsQueue }) => {
394-
for (const event of eventsQueue) {
395-
const watchedDirAncestor = fileWatchedDirAncestor(
396-
projectRoot,
397-
fs.realpathSync(event.filePath)
398-
);
399-
if (
400-
eventTypes.includes(event.type) &&
401-
isWatchedFileEvent(event, watchedDirAncestor) &&
402-
!!watchedDirAncestor
403-
) {
404-
const { filePath } = event;
405-
if (event.type === 'add') {
406-
onSourceFileCreated(projectRoot, filePath, watchedDirAncestor, filesWatched);
407-
} else if (event.type === 'delete') {
408-
onSourceFileRemoved(filePath, watchedDirAncestor);
409-
}
375+
const isWatchedFileEvent = (event: Event, watchedDirAncestor: string | null): boolean => {
376+
return (
377+
event.metadata?.type !== 'd' &&
378+
isValidLocalModuleFileName(path.basename(event.filePath)) &&
379+
!isFileExcluded(event.filePath) &&
380+
!!watchedDirAncestor
381+
);
382+
};
383+
384+
const listener = async ({ eventsQueue }: { eventsQueue: EventsQueue }) => {
385+
for (const event of eventsQueue) {
386+
const watchedDirAncestor = getWatchedDirAncestorAbsolutePath(
387+
projectRoot,
388+
path.resolve(event.filePath)
389+
);
390+
if (
391+
eventTypes.includes(event.type) &&
392+
isWatchedFileEvent(event, watchedDirAncestor) &&
393+
!!watchedDirAncestor
394+
) {
395+
const { filePath } = event;
396+
if (event.type === 'add') {
397+
onSourceFileCreated(projectRoot, watchedDirAncestor, filePath, filesWatched);
398+
} else if (event.type === 'delete') {
399+
onSourceFileRemoved(filePath, watchedDirAncestor);
410400
}
411401
}
412-
};
413-
414-
watcher?.addListener('change', listener);
415-
416-
await generateMirrorDirectories(projectRoot, filesWatched);
402+
}
417403
};
418404

419-
metroWatchKotlinAndSwiftFiles({
420-
projectRoot,
421-
metro,
422-
eventTypes: ['add', 'delete', 'change'],
423-
});
405+
watcher?.addListener('change', listener);
406+
407+
await generateMirrorDirectories(projectRoot, filesWatched);
424408
}

packages/@expo/cli/src/start/server/metro/MetroBundlerDevServer.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,18 +1388,22 @@ export class MetroBundlerDevServer extends BundlerDevServer {
13881388
});
13891389
}
13901390

1391-
public async startTypeScriptServices() {
1391+
public async startTypeScriptServices(): Promise<any> {
13921392
const { projectRoot, metro } = this;
1393-
startTypescriptTypeGenerationAsync({
1393+
const startTypescriptTypeGenerationPromise = startTypescriptTypeGenerationAsync({
13941394
server: this.instance?.server,
13951395
metro: this.metro,
13961396
projectRoot: this.projectRoot,
13971397
});
13981398

13991399
const { exp } = getConfig(this.projectRoot);
14001400
if (exp.experiments?.localModules === true) {
1401-
startModuleGenerationAsync({ projectRoot, metro });
1401+
return Promise.all([
1402+
startTypescriptTypeGenerationPromise,
1403+
startModuleGenerationAsync({ projectRoot, metro }),
1404+
]);
14021405
}
1406+
return startTypescriptTypeGenerationPromise;
14031407
}
14041408

14051409
protected getConfigModuleIds(): string[] {

0 commit comments

Comments
 (0)