Skip to content

Commit

Permalink
feat: initialize before serve
Browse files Browse the repository at this point in the history
- add initialize phase to bundler
  - setup watcher
- print initialize indicator
  • Loading branch information
leegeunhyeok committed Oct 21, 2023
1 parent f8e753d commit 8993689
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 99 deletions.
4 changes: 2 additions & 2 deletions packages/cli/lib/commands/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export const bundle: Command = async (argv) => {
logger.debug('bundle options');
printDebugOptions(bundleOptions);

const bundler = new ReactNativeEsbuildBundler(
const bundler = await new ReactNativeEsbuildBundler(
bundleOptions.entry ? path.dirname(bundleOptions.entry) : process.cwd(),
);
).initialize();

// @TODO: remove registerPlugin and add plugin presets (plugin preset builder)
if (bundleOptions.platform !== 'web') {
Expand Down
20 changes: 11 additions & 9 deletions packages/cli/lib/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { logger } from '../shared';
import type { Command } from '../types';
import { serveArgvSchema } from '../schema';

// eslint-disable-next-line @typescript-eslint/require-await -- no async task in serve command
export const serve: Command = async (argv): Promise<void> => {
const serveArgv = serveArgvSchema.parse(argv);
const serveOptions = {
Expand All @@ -30,12 +29,15 @@ export const serve: Command = async (argv): Promise<void> => {
logger.debug('bundle options');
printDebugOptions(bundleOptions);

new ReactNativeWebServer(serveOptions, bundleOptions)
.setup((bundler) => {
bundler
.registerPlugin(createSvgTransformPlugin())
.registerPlugin(createReactNativeRuntimeTransformPlugin())
.registerPlugin(createReactNativeWebPlugin());
})
.listen();
const server = await new ReactNativeWebServer(
serveOptions,
bundleOptions,
).initialize((bundler) => {
bundler
.registerPlugin(createSvgTransformPlugin())
.registerPlugin(createReactNativeRuntimeTransformPlugin())
.registerPlugin(createReactNativeWebPlugin());
});

await server.listen();
};
16 changes: 9 additions & 7 deletions packages/cli/lib/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ export const start: Command = async (argv) => {
logger.debug('start options');
printDebugOptions({ entry, ...serveOptions });

const server = new ReactNativeAppServer(serveOptions).setup((bundler) => {
bundler
.registerPlugin(createAssetRegisterPlugin())
.registerPlugin(createSvgTransformPlugin())
.registerPlugin(createReactNativeRuntimeTransformPlugin());
});
const server = await new ReactNativeAppServer(serveOptions).initialize(
(bundler) => {
bundler
.registerPlugin(createAssetRegisterPlugin())
.registerPlugin(createSvgTransformPlugin())
.registerPlugin(createReactNativeRuntimeTransformPlugin());
},
);

server.listen(() => {
await server.listen(() => {
if (
enableInteractiveMode((keyName) => {
switch (keyName) {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { logger } from './shared';
(async () => {
const argv = await cli();
const options = baseArgvSchema.parse(argv);
ReactNativeEsbuildBundler.initialize(options.config);
ReactNativeEsbuildBundler.bootstrap(options.config);
ReactNativeEsbuildBundler.setGlobalLogLevel(
options.verbose ? LogLevel.Trace : LogLevel.Info,
);
Expand Down
8 changes: 7 additions & 1 deletion packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@
```ts
import { ReactNativeEsbuildBundler } from '@react-native-esbuild/core';

// Must be called first
ReactNativeEsbuildBundler.bootstrap();

const bundler = new ReactNativeEsbuildBundler();

bundler
.registerPlugin(/* call EsbuildPluginFactory */)
.registerPlugin(/* call EsbuildPluginFactory */)
.registerPlugin(/* call EsbuildPluginFactory */);

// initialize bundler
await bundler.initialize();

// using `esbuild.build()` (write output to file system)
await bundler.bundle(bundleOptions);

// using `esbuildContext.watch()` (in-memory output for dev-server)
// using `esbuild.context()` (in-memory output for dev-server)
await bundler.getBundle(bundleOptions);
```
70 changes: 54 additions & 16 deletions packages/core/lib/bundler/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import esbuild, {
type ServeResult,
} from 'esbuild';
import invariant from 'invariant';
import ora from 'ora';
import { getGlobalVariables } from '@react-native-esbuild/internal';
import {
setEnvironment,
Expand All @@ -19,6 +20,7 @@ import { FileSystemWatcher } from '../watcher';
import { logger } from '../shared';
import type {
Config,
BundlerInitializeOptions,
BuildTask,
BuildStatus,
BundleMode,
Expand All @@ -42,7 +44,10 @@ import {
} from './helpers';
import { BundlerEventEmitter } from './events';
import { createBuildStatusPlugin, createMetafilePlugin } from './plugins';
import { ReactNativeEsbuildError, ReactNativeEsbuildErrorCode } from './errors';
import {
ReactNativeEsbuildError,
ReactNativeEsbuildErrorCode as ErrorCode,
} from './errors';
import { printLogo, printVersion } from './logo';

export class ReactNativeEsbuildBundler extends BundlerEventEmitter {
Expand All @@ -52,15 +57,16 @@ export class ReactNativeEsbuildBundler extends BundlerEventEmitter {
changed: null,
},
};
private initialized = false;
private config: Config;
private appLogger = new Logger('app', LogLevel.Trace);
private buildTasks = new Map<number, BuildTask>();
private plugins: ReturnType<EsbuildPluginFactory<unknown>>[] = [];

/**
* must be initialized first at the entry point
* must be bootstrapped first at the entry point
*/
public static initialize(configFilePath?: string): void {
public static bootstrap(configFilePath?: string): void {
printLogo();
printVersion();
const config = loadConfig(configFilePath);
Expand Down Expand Up @@ -125,9 +131,9 @@ export class ReactNativeEsbuildBundler extends BundlerEventEmitter {
this.config.reporter?.(event);
}

private setupWatcher(): void {
logger.debug('setup watcher');
FileSystemWatcher.getInstance()
private startWatcher(): Promise<void> {
logger.debug('starting watcher');
return FileSystemWatcher.getInstance()
.setHandler((event, changedFile, stats) => {
if (this.buildTasks.size > 0 && event === 'change') {
ReactNativeEsbuildBundler.shared.watcher = {
Expand Down Expand Up @@ -240,11 +246,20 @@ export class ReactNativeEsbuildBundler extends BundlerEventEmitter {
return getIdByOptions(bundleOptions);
}

private throwIfNotInitialized(): void {
if (this.initialized) return;

throw new ReactNativeEsbuildError(
'bundler not initialized',
ErrorCode.NotInitialized,
);
}

private assertBuildTask(task?: BuildTask): asserts task is BuildTask {
if (task) return;
throw new ReactNativeEsbuildError(
'unable to get build task',
ReactNativeEsbuildErrorCode.InvalidTask,
ErrorCode.InvalidTask,
);
}

Expand All @@ -254,7 +269,7 @@ export class ReactNativeEsbuildBundler extends BundlerEventEmitter {
if (handler) return;
throw new ReactNativeEsbuildError(
'invalid task handler',
ReactNativeEsbuildErrorCode.InvalidTask,
ErrorCode.InvalidTask,
);
}

Expand Down Expand Up @@ -300,7 +315,7 @@ export class ReactNativeEsbuildBundler extends BundlerEventEmitter {
if (!data.success) {
throw new ReactNativeEsbuildError(
'build failed',
ReactNativeEsbuildErrorCode.BuildFailure,
ErrorCode.BuildFailure,
);
}

Expand Down Expand Up @@ -347,7 +362,6 @@ export class ReactNativeEsbuildBundler extends BundlerEventEmitter {

if (!this.buildTasks.has(targetTaskId)) {
logger.debug(`bundle task not registered (id: ${targetTaskId})`);
this.setupWatcher();
const buildOptions = await this.getBuildOptionsForBundler(
'watch',
bundleOptions,
Expand Down Expand Up @@ -394,15 +408,37 @@ export class ReactNativeEsbuildBundler extends BundlerEventEmitter {
});
}

registerPlugin(plugin: ReturnType<EsbuildPluginFactory<unknown>>): this {
public async initialize(options?: BundlerInitializeOptions): Promise<this> {
if (this.initialized) {
logger.warn('bundler already initialized');
return this;
}
const spinner = ora({ discardStdin: false }).start(
'Bundler initializing...',
);

if (options?.watcherEnabled) {
await this.startWatcher();
}

this.initialized = true;
spinner.stop();

return this;
}

public registerPlugin(
plugin: ReturnType<EsbuildPluginFactory<unknown>>,
): this {
this.plugins.push(plugin);
return this;
}

async bundle(
public async bundle(
bundleOptions: Partial<BundleOptions>,
additionalData?: BundlerAdditionalData,
): Promise<BuildResult> {
this.throwIfNotInitialized();
const buildOptions = await this.getBuildOptionsForBundler(
'bundle',
combineWithDefaultBundleOptions(bundleOptions),
Expand All @@ -411,10 +447,11 @@ export class ReactNativeEsbuildBundler extends BundlerEventEmitter {
return esbuild.build(buildOptions);
}

async serve(
public async serve(
bundleOptions: Partial<BundleOptions>,
additionalData?: BundlerAdditionalData,
): Promise<ServeResult> {
this.throwIfNotInitialized();
if (bundleOptions.platform !== 'web') {
throw new ReactNativeEsbuildError(
'serve mode is only available on web platform',
Expand All @@ -432,10 +469,11 @@ export class ReactNativeEsbuildBundler extends BundlerEventEmitter {
});
}

async getBundle(
public async getBundle(
bundleOptions: BundleRequestOptions,
additionalData?: BundlerAdditionalData,
): Promise<BundleResult> {
this.throwIfNotInitialized();
const buildTask = await this.getOrCreateBundleTask(
combineWithDefaultBundleOptions(bundleOptions),
additionalData,
Expand All @@ -444,11 +482,11 @@ export class ReactNativeEsbuildBundler extends BundlerEventEmitter {
return buildTask.handler.task;
}

getConfig(): Config {
public getConfig(): Config {
return this.config;
}

getRoot(): string {
public getRoot(): string {
return this.root;
}
}
1 change: 1 addition & 0 deletions packages/core/lib/bundler/errors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum ReactNativeEsbuildErrorCode {
NotInitialized = 'NotInitialized',
BuildFailure = 'BuildFailure',
InvalidTask = 'InvalidTask',
Unknown = 'Unknown',
Expand Down
5 changes: 4 additions & 1 deletion packages/core/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,11 @@ interface CustomTransformRuleBase<T> {
export type BabelTransformRule = CustomTransformRuleBase<BabelTransformOptions>;
export type SwcTransformRule = CustomTransformRuleBase<SwcTransformOptions>;

export interface BundlerInitializeOptions {
watcherEnabled?: boolean;
}

export type BundleMode = 'bundle' | 'watch';
export type InternalCaller = 'dev-server';

export interface BuildTask {
context: BuildContext;
Expand Down
Loading

2 comments on commit 8993689

@vercel
Copy link

@vercel vercel bot commented on 8993689 Oct 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

St.
Category Percentage Covered / Total
🔴 Statements 15.52% 356/2294
🔴 Branches 18.3% 140/765
🔴 Functions 10.75% 70/651
🔴 Lines 14.79% 313/2116

Test suite run success

85 tests passing in 12 suites.

Report generated by 🧪jest coverage report action from 8993689

Please sign in to comment.