Skip to main content

调试服务器配置

配置结构

项目配置文件中的devServer是对webpack-dev-server配置的进一步抽象,它的结构如下:

type DevServerHttps = {proxy: boolean} & ({} | {client: true, serverOptions?: ServerOptions});

type RequestHandler = (req: IncomingMessage, res: ServerResponse, next: (err?: Error) => void) => void;

type Middleware = RequestHandler | {name?: string, path?: string, middleware: RequestHandler};

interface MiddlewareHook {
use: (route: string, fn: RequestHandler) => void;
get: (route: string, fn: RequestHandler) => void;
post: (route: string, fn: RequestHandler) => void;
put: (route: string, fn: RequestHandler) => void;
delete: (route: string, fn: RequestHandler) => void;
patch: (route: string, fn: RequestHandler) => void;
}

interface MiddlewareCustomization {
before: MiddlewareHook;
after: MiddlewareHook;
}

interface DevServerSettings {
// 是否以HTTPS协议代理请求及启动调试服务器
readonly https: DevServerHttps;
// 监听的端口
readonly port: number;
// 代理给后端的API请求的URL前缀
readonly apiPrefixes: string[];
// 重写部分请求URL,优先于apiPrefixes
readonly proxyRewrite: Record<string, string>;
// 默认的代理后端路径,可以被`--proxy-domain`命令行参数覆盖
readonly defaultProxyDomain: string;
// 是否启用热更新,其中`simple`只启用样式的更新,`all`则会加入组件的热更新
readonly hot: boolean;
// 服务启动后打开的页面
readonly openPage: string;
// 开辟Node服务器的参数
readonly serverOptions: ServerOptions;
// 对调试服务器追加一些配置或功能
readonly customizeMiddleware: (customization: MiddlewareCustomization) => void;
// 在最终调整配置,可以任意处理,原则上这个函数处理后的对象不会再被内部的逻辑修改
readonly finalize: (serverConfig: WebpackDevServerConfiguration, env: BuildEntry) => WebpackDevServerConfiguration | Promise<WebpackDevServerConfiguration>;
}

以上所有的配置均可以省略,其中port的默认值为8080

代理API

配置代理路径

在大部分的应用中,后端的所有API都集中在一个域名上,很少见到不同的API请求需要代理到不同域名的情况。基于这一考虑,reSKRipt简化了API代理的配置,主要用到以下的属性:

  • apiPrefixes:一个URL的前缀数组,在这数组中声明的URL前缀的请求都会被代理到后端。
  • defaultProxyDomain:代理的后端地址,这个地址不需要包含协议部分(如http://https://),只需要声明域名(或IP)和端口。

比如在产品中,以/api/rest为前缀的请求都是后端的API,后端的地址为my-app.dev,测试环境暴露在端口8788上,使用HTTP协议,那么可以使用如下的配置:

export default configure(
'webpack',
{
devServer: {
apiPrefixes: ['/api', '/rest'],
defaultProxyDomain: 'my-app.dev:8788',
},
}
);

启用HTTPS

https属性用于配置代理后端接口以及响应前端请求时是否使用HTTPS协议,它的取值如下:

  • proxy属性为true时,代理后端接口启用HTTPS协议。
  • client属性为true时,响应前端请求使用HTTPS协议,你需要通过serverOptions属性提供https.createServer需要的选项。

如果仅需要代理后端的API时使用HTTPS协议:

export default configure(
'webpack',
{
devServer: {
https: {
proxy: true,
},
},
}
);

如果需要响应前端请求时使用HTTPS协议:

export default configure(
'webpack',
{
devServer: {
https: {
client: true,
serverOptions: {
key: './path/to/server.key',
cert: './path/to/server.crt',
},
},
},
}
);

如果你已经有系统级的证书可用于localhost,则可以不传递serverOptions参数。

本地网络代理

如果你在本地设置有网络代理,且通过代理才能连接到后端,那么你需要根据https配置是否打开,在环境变量中声明http_proxyhttps_proxyreSKRipt会自动读取这2个环境变量,并将其作为代理请求时的中间节点。

相反,如果本地配置的网络代理影响了将请求代理到后端,那么你需要清除掉相应的环境变量:

unset http_proxy
unset https_proxy

当清除或修改环境变量后,你需要重启调试服务器才会生效。

监听第三方代码变更

note

仅针对webpack引擎。

默认情况下,skr dev不会监听node_modules下的代码的变化。我们有理由相信这些代码并不会频繁变更,而大量的监听会影响调试服务器的性能和内存占用。

如果你正好在调试第三方的包,需要主动去修改包的源码来确认问题,那么你可能不希望每一次修改都要重启服务。此时你可以通过devServer.finalize来控制第三方包的监听:

export default configure(
'webpack',
{
devServer: {
finalize: devServerConfig => {
// 监听所有文件,当然你也可以写得更精确一些
devServerConfig.static.watch.ignored = undefined;
return devServerConfig;
},
},
}
);

具体参考webpack的说明来实现。

关于finalize详细配置在下文中单独说明。

更换代理目标

在有些团队中,不同的开发人员、版本会需要将后端API请求代理到不同的机器或域名、IP上去。但我们不希望每个人都修改一份自己的项目配置文件并引起合并冲突等问题。

为此,在skr dev的命令行上,我们支持--proxy-domain参数来覆盖defaultProxyDomain这一配置:

skr dev --proxy-domain=my-local-app.dev:8988

多后端代理配置

对于更复杂的应用,有可能后端在开发环境中使用多服务且没有一个统一的入口,所以不同的接口路径需要代理到不同的后端上。

对于此类情况,你可以使用proxyRewrite配置。配置中的转发目标如果包含协议部分,则使用指定的协议,仅包含路径的话则根据devServer.https配置选择使用HTTP或HTTPS转发,例如以下的配置:

export default configure(
'webpack',
{
devServer: {
defaultProxyDomain: 'my-app.dev:8788',
proxyRewrite: {
'/api/user': 'user-app.dev:8786',
'/api/inventory': 'inventory-app.dev:8787',
'/api/order': 'https://order-app.dev:8789',
},
},
}
);

是述配置表达了如下的转发规则:

  • 当请求的URL前缀为/api/user时,请求将代理到user-app.dev:8786下。
  • 当请求的URL前缀为/api/inventory时,请求将代理到user-app.dev:8786下。
  • 当请求的URL前缀为/api/order时,请求将代理到order-app.dev:8789下,且无论devServer.https配置如何,都将使用HTTPS协议转发。
  • 其它请求都代理到my-app.dev:8788下。

比如请求的路径为/api/user/list?page=1,则目标的URL为user-app.dev:8786/list?page=1需要注意的是,在proxyRewrite中配置的前缀不会变成代理后URL的一部分

关于热更新

热更新在实际的开发调试中有着非常强的效率提升效果,reSKRipt内置集成了热更新相关的逻辑,简化了它的对外配置,devServer.hot被设计为一个boolean类型:

  • hot: true:尽可能地启用不同类型资源的热更新,包括使用react-refresh进行组件热更新。
  • hot: false:完全关闭热更新。
note

当你使用--mode=production启动调试服务器时,会始终关闭热更新以保持与生产环境尽可能的一致。

自定义中间件

如果你希望简单地在调试服务器上增加一些路由处理,可以使用customizeMiddleware配置。这个配置是一个函数,它会传入这样的一个对象:

{
before: MiddlewareHook;
after: MiddlewareHook;
}

其中MiddlewareHook是一个经过封装、便于使用的对象,它很像一个express的路由,你可以通过挂在上面的getpostputdeletepatch等函数注册一个路由处理。例如你需要增加一个路由返回健康检查:

import {configure} from '@reskript/settings';

export default configure(
'webpack' // 或'vite'
{
devServer: {
customizeMiddleware: ({before}) => {
before.get(
'/ok',
(req, res) => {
res.end('OK');
},
);
},
},
}
);

在传入的参数中,before上挂载的路由将应用在默认的中间件之前,after的则在默认中间件之后。值得注意的是historyApiFallback是包含在默认中间件内的,因此如果你要注册一个路由,则建议放到before之中。

扩展配置

如果你需要最终再对webpack引擎下的webpack-dev-server的配置,或者vite引擎下的server配置做自己的调整,可以使用devServer.finalize配置。这个配置是一个函数,第一个参数是服务器的配置对象(在webpack引擎下是devServer配置项,在vite引擎下是server配置项),第二个参数是BuildEntry对象。

在上文中你已经看到如何利用这一配置实现对第三方代码的变更的监听。再比如你想要给调试服务器增加一个/version的路由,访问时返回当前产品的版本号,也是可以通过扩展来实现的:

import fs from 'node:fs';
import {configure} from '@reskript/settings';

const pacakgeInfo = JSON.parse(fs.readFileSync('./package.json'), 'utf-8');

export default configure(
'webpack',
{
devServer: {
finalize: devServerConfig => {
// 记得调用之前已经有的配置,不要太暴力覆盖
const {setupMiddlewares} = devServerConfig;
devServerConfig.setupMiddlewares = (middlewares, devServer) => {
const returnValue = setupMiddlewares?.(devServer) ?? middlewares;
devServer.app.get(
'/version',
(req, res) => {
res.status(200).type('html').end(`${packageInfo.name}@${packageInfo.version}`);
}
);
return returnValue;
};
return devServerConfig;
},
},
}
);

关于devServer.setupMiddlewares可以参考官方文档