Skip to content

Commit

Permalink
feat: expose devServer.open API
Browse files Browse the repository at this point in the history
  • Loading branch information
chenjiahan committed Sep 16, 2024
1 parent 37a147d commit 048d433
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 84 deletions.
1 change: 0 additions & 1 deletion packages/core/src/createRsbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ async function applyDefaultPlugins(
import('./plugins/splitChunks').then(({ pluginSplitChunks }) =>
pluginSplitChunks(),
),
import('./plugins/open').then(({ pluginOpen }) => pluginOpen()),
import('./plugins/inlineChunk').then(({ pluginInlineChunk }) =>
pluginInlineChunk(),
),
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/plugins/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import {
} from '../constants';
import { formatPublicPath, getFilename } from '../helpers';
import { getCssExtractPlugin } from '../pluginHelper';
import { replacePortPlaceholder } from '../server/open';
import type {
NormalizedEnvironmentConfig,
RsbuildContext,
RsbuildPlugin,
} from '../types';
import { isUseCssExtract } from './css';
import { replacePortPlaceholder } from './open';

function getPublicPath({
isProd,
Expand Down
17 changes: 16 additions & 1 deletion packages/core/src/plugins/server.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
import fs from 'node:fs';
import { isAbsolute, join } from 'node:path';
import { normalizePublicDirs } from '../config';
import type { RsbuildPlugin } from '../types';
import { open } from '../server/open';
import type { OnAfterStartDevServerFn, RsbuildPlugin } from '../types';

// For Rsbuild Server Config
export const pluginServer = (): RsbuildPlugin => ({
name: 'rsbuild:server',

setup(api) {
const onStartServer: OnAfterStartDevServerFn = async ({ port, routes }) => {
const config = api.getNormalizedConfig();
if (config.server.open) {
open({
https: api.context.devServer?.https,
port,
routes,
config,
});
}
};

api.onAfterStartDevServer(onStartServer);
api.onAfterStartProdServer(onStartServer);
api.onBeforeBuild(async ({ isFirstCompile }) => {
if (!isFirstCompile) {
return;
Expand Down
14 changes: 14 additions & 0 deletions packages/core/src/server/devServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
} from './helper';
import { createHttpServer } from './httpServer';
import { notFoundMiddleware } from './middlewares';
import { open } from './open';
import { onBeforeRestartServer } from './restart';
import { setupWatchFiles } from './watchFiles';

Expand Down Expand Up @@ -78,6 +79,10 @@ export type RsbuildDevServer = {
* Print the server URLs.
*/
printUrls: () => void;
/**
* Open URL in the browser after starting the server.
*/
open: () => Promise<void>;
};

const formatDevConfig = (config: NormalizedDevConfig, port: number) => {
Expand Down Expand Up @@ -333,6 +338,15 @@ export async function createDevServer<
await Promise.all([devMiddlewares.close(), fileWatcher?.close()]);
},
printUrls,
open: async () => {
return open({
https,
port,
routes,
config,
clearCache: true,
});
},
};

logger.debug('create dev server done');
Expand Down
140 changes: 68 additions & 72 deletions packages/core/src/plugins/open.ts → packages/core/src/server/open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { promisify } from 'node:util';
import { STATIC_PATH } from '../constants';
import { canParse, castArray } from '../helpers';
import { logger } from '../logger';
import type { NormalizedConfig, Routes, RsbuildPlugin } from '../types';
import type { NormalizedConfig, Routes } from '../types';

const execAsync = promisify(exec);

Expand Down Expand Up @@ -37,7 +37,7 @@ const getTargetBrowser = async () => {
* Copyright (c) 2015-present, Facebook, Inc.
* https://github.com/facebook/create-react-app/blob/master/LICENSE
*/
export async function openBrowser(url: string): Promise<boolean> {
async function openBrowser(url: string): Promise<boolean> {
// If we're on OS X, the user hasn't specifically
// requested a different browser, we can try opening
// a Chromium browser with AppleScript. This lets us reuse an
Expand Down Expand Up @@ -80,6 +80,12 @@ export async function openBrowser(url: string): Promise<boolean> {
}
}

let openedURLs: string[] = [];

const clearOpenedURLs = () => {
openedURLs = [];
};

export const replacePortPlaceholder = (url: string, port: number): string =>
url.replace(/<port>/g, String(port));

Expand All @@ -98,17 +104,12 @@ export function resolveUrl(str: string, base: string): string {
}
}

const openedURLs: string[] = [];

const normalizeOpenConfig = (
config: NormalizedConfig,
): { targets?: string[]; before?: () => Promise<void> | void } => {
): { targets: string[]; before?: () => Promise<void> | void } => {
const { open } = config.server;

if (open === false) {
return {};
}
if (open === true) {
if (typeof open === 'boolean') {
return { targets: [] };
}
if (typeof open === 'string') {
Expand All @@ -124,67 +125,62 @@ const normalizeOpenConfig = (
};
};

export function pluginOpen(): RsbuildPlugin {
return {
name: 'rsbuild:open',
setup(api) {
const onStartServer = async (params: {
port: number;
routes: Routes;
}) => {
const { port, routes } = params;
const config = api.getNormalizedConfig();
const { https } = api.context.devServer || {};
const { targets, before } = normalizeOpenConfig(config);

// Skip open in codesandbox. After being bundled, the `open` package will
// try to call system xdg-open, which will cause an error on codesandbox.
// https://github.com/codesandbox/codesandbox-client/issues/6642
const isCodesandbox = process.env.CSB === 'true';
const shouldOpen = targets !== undefined && !isCodesandbox;

if (!shouldOpen) {
return;
}

const urls: string[] = [];
const protocol = https ? 'https' : 'http';
const baseUrl = `${protocol}://localhost:${port}`;

if (!targets.length) {
if (routes.length) {
// auto open the first one
urls.push(`${baseUrl}${routes[0].pathname}`);
}
} else {
urls.push(
...targets.map((target) =>
resolveUrl(replacePortPlaceholder(target, port), baseUrl),
),
);
}

const openUrls = () => {
for (const url of urls) {
/**
* If a URL has been opened in current process, we will not open it again.
* It can prevent opening the same URL multiple times.
*/
if (!openedURLs.includes(url)) {
openBrowser(url);
openedURLs.push(url);
}
}
};

if (before) {
await before();
}
openUrls();
};

api.onAfterStartDevServer(onStartServer);
api.onAfterStartProdServer(onStartServer);
},
};
export async function open({
https,
port,
routes,
config,
clearCache,
}: {
https?: boolean;
port: number;
routes: Routes;
config: NormalizedConfig;
clearCache?: boolean;
}): Promise<void> {
const { targets, before } = normalizeOpenConfig(config);

// Skip open in codesandbox. After being bundled, the `open` package will
// try to call system xdg-open, which will cause an error on codesandbox.
// https://github.com/codesandbox/codesandbox-client/issues/6642
const isCodesandbox = process.env.CSB === 'true';
if (isCodesandbox) {
return;
}

if (clearCache) {
clearOpenedURLs();
}

const urls: string[] = [];
const protocol = https ? 'https' : 'http';
const baseUrl = `${protocol}://localhost:${port}`;

if (!targets.length) {
if (routes.length) {
// auto open the first one
urls.push(`${baseUrl}${routes[0].pathname}`);
}
} else {
urls.push(
...targets.map((target) =>
resolveUrl(replacePortPlaceholder(target, port), baseUrl),
),
);
}

if (before) {
await before();
}

for (const url of urls) {
/**
* If an URL has been opened in current process, we will not open it again.
* It can prevent opening the same URL multiple times.
*/
if (!openedURLs.includes(url)) {
openBrowser(url);
openedURLs.push(url);
}
}
}
7 changes: 0 additions & 7 deletions packages/core/tests/__snapshots__/environments.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,6 @@ exports[`environment config > should print environment config when inspect confi
"rsbuild:swc",
"rsbuild:externals",
"rsbuild:split-chunks",
"rsbuild:open",
"rsbuild:inline-chunk",
"rsbuild:rsdoctor",
"rsbuild:resource-hints",
Expand Down Expand Up @@ -513,7 +512,6 @@ exports[`environment config > should print environment config when inspect confi
"rsbuild:swc",
"rsbuild:externals",
"rsbuild:split-chunks",
"rsbuild:open",
"rsbuild:inline-chunk",
"rsbuild:rsdoctor",
"rsbuild:resource-hints",
Expand Down Expand Up @@ -688,7 +686,6 @@ exports[`environment config > should support modify environment config by api.mo
"rsbuild:swc",
"rsbuild:externals",
"rsbuild:split-chunks",
"rsbuild:open",
"rsbuild:inline-chunk",
"rsbuild:rsdoctor",
"rsbuild:resource-hints",
Expand Down Expand Up @@ -846,7 +843,6 @@ exports[`environment config > should support modify environment config by api.mo
"rsbuild:swc",
"rsbuild:externals",
"rsbuild:split-chunks",
"rsbuild:open",
"rsbuild:inline-chunk",
"rsbuild:rsdoctor",
"rsbuild:resource-hints",
Expand Down Expand Up @@ -1005,7 +1001,6 @@ exports[`environment config > should support modify environment config by api.mo
"rsbuild:swc",
"rsbuild:externals",
"rsbuild:split-chunks",
"rsbuild:open",
"rsbuild:inline-chunk",
"rsbuild:rsdoctor",
"rsbuild:resource-hints",
Expand Down Expand Up @@ -1167,7 +1162,6 @@ exports[`environment config > should support modify single environment config by
"rsbuild:swc",
"rsbuild:externals",
"rsbuild:split-chunks",
"rsbuild:open",
"rsbuild:inline-chunk",
"rsbuild:rsdoctor",
"rsbuild:resource-hints",
Expand Down Expand Up @@ -1325,7 +1319,6 @@ exports[`environment config > should support modify single environment config by
"rsbuild:swc",
"rsbuild:externals",
"rsbuild:split-chunks",
"rsbuild:open",
"rsbuild:inline-chunk",
"rsbuild:rsdoctor",
"rsbuild:resource-hints",
Expand Down
1 change: 0 additions & 1 deletion packages/core/tests/__snapshots__/inspect.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ exports[`inspectConfig > should print plugin names when inspect config 1`] = `
"rsbuild:swc",
"rsbuild:externals",
"rsbuild:split-chunks",
"rsbuild:open",
"rsbuild:inline-chunk",
"rsbuild:rsdoctor",
"rsbuild:resource-hints",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/tests/open.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { replacePortPlaceholder, resolveUrl } from '../src/plugins/open';
import { replacePortPlaceholder, resolveUrl } from '../src/server/open';

describe('plugin-open', () => {
it('#replacePortPlaceholder - should replace port number correctly', () => {
Expand Down
4 changes: 4 additions & 0 deletions website/docs/en/api/javascript-api/instance.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,10 @@ type RsbuildDevServer = {
* Print the server URLs.
*/
printUrls: () => void;
/**
* Open URL in the browser after starting the server.
*/
open: () => Promise<void>;
};

type CreateDevServerOptions = {
Expand Down
4 changes: 4 additions & 0 deletions website/docs/zh/api/javascript-api/instance.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,10 @@ type RsbuildDevServer = {
* 打印 server URLs
*/
printUrls: () => void;
/**
* 启动服务器后,在浏览器中打开 URL
*/
open: () => Promise<void>;
};

type CreateDevServerOptions = {
Expand Down

0 comments on commit 048d433

Please sign in to comment.