Skip to content

Commit

Permalink
feat: use router to compose middlewares
Browse files Browse the repository at this point in the history
  • Loading branch information
liximomo authored Jul 19, 2021
1 parent b258d7b commit c9ee556
Show file tree
Hide file tree
Showing 28 changed files with 575 additions and 414 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,14 @@
- remove type `ISpecifier`
- remove helper `moduleExport`
- change `addAppExport(string, ISpecifier)` to `addAppExport(string, string)`


### router

https://docs.google.com/document/d/1g3KPpEdl9DILftI22qWKUQIVbAxtkoGKSTbEZAFjxDg/edit#


### app

- remove `parsedUrl` from `req`
- add `query` and `pathname` to `req`
2 changes: 1 addition & 1 deletion packages/router/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export { createRoutesFromArray } from './createRoutesFromArray';
export { matchPathname, matchStringify } from './matchPathname';
export { matchRoutes, IRouteBaseObject } from './matchRoutes';

export { pathToString } from './utils';
export { pathToString, parseQuery } from './utils';
export * from './types';
export * from './history';
export * from './router';
2 changes: 1 addition & 1 deletion packages/router/src/types/history.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ParsedQuery } from 'query-string';
import type History from '../history/base';

export { History };
export { History, ParsedQuery };

export type GlobalHistory = typeof window.history;

Expand Down
8 changes: 6 additions & 2 deletions packages/router/src/utils/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export function normalizeBase(base: string): string {
return base.replace(/\/$/, '');
}

export function parseQuery(queryStr: string) {
return qs.parse(queryStr);
}

export function pathToString({
pathname = '/',
search = '',
Expand Down Expand Up @@ -77,7 +81,7 @@ export function resolvePath(to: PathRecord, fromPathname = '/'): Path {
let searchIndex = to.indexOf('?');
if (searchIndex >= 0) {
parsedPath.search = to.substr(searchIndex);
parsedPath.query = qs.parse(parsedPath.search);
parsedPath.query = parseQuery(parsedPath.search);
to = to.substr(0, searchIndex);
}

Expand All @@ -96,7 +100,7 @@ export function resolvePath(to: PathRecord, fromPathname = '/'): Path {
});

if (parsedPath.search) {
parsedPath.query = qs.parse(parsedPath.search);
parsedPath.query = parseQuery(parsedPath.search);
} else {
parsedPath.search = qs.stringify(parsedPath.query);
}
Expand Down
1 change: 1 addition & 0 deletions packages/shuvi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"ejs": "3.1.5",
"fs-extra": "9.0.1",
"http-proxy-middleware": "1.0.6",
"launch-editor": "2.2.1",
"send": "0.17.1",
"webpack": "5.36.0",
"webpack-dev-middleware": "4.1.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { findPort } from 'shuvi-test-utils';
import got from 'got';
import { Server } from '../server';
import { Runtime } from '@shuvi/types';
import httpProxyMiddleware from '../httpProxyMiddleware';
import { applyHttpProxyMiddleware } from '../httpProxyMiddleware';
import { Server, IRequest, IResponse, INextFunc } from '../../server';

const host = 'localhost';

Expand All @@ -21,43 +20,25 @@ describe('server proxy test', () => {
beforeAll(async () => {
proxyTarget1 = new Server();
proxyTarget1
.use(
'/api',
(
req: Runtime.IIncomingMessage,
res: Runtime.IServerAppResponse,
next: Runtime.IServerAppNext
) => {
res.end('api1');
}
)
.use(
'/header',
(
req: Runtime.IIncomingMessage,
res: Runtime.IServerAppResponse,
next: Runtime.IServerAppNext
) => {
Object.keys(req.headers).forEach(header => {
const val = req.headers[header];
if (typeof val !== 'undefined') {
res.setHeader(header, val);
}
});
res.end('ok');
}
);
.use('/api', (req: IRequest, res: IResponse, next: INextFunc) => {
res.end('api1');
})
.use('/header', (req: IRequest, res: IResponse, next: INextFunc) => {
Object.keys(req.headers).forEach(header => {
const val = req.headers[header];
if (typeof val !== 'undefined') {
res.setHeader(header, val);
}
});
res.end('ok');
});
proxyTarget1Port = await findPort();
await proxyTarget1.listen(proxyTarget1Port);

proxyTarget2 = new Server();
proxyTarget2.use(
'/api',
(
req: Runtime.IIncomingMessage,
res: Runtime.IServerAppResponse,
next: Runtime.IServerAppNext
) => {
(req: IRequest, res: IResponse, next: INextFunc) => {
res.end('api2');
}
);
Expand All @@ -71,7 +52,7 @@ describe('server proxy test', () => {

test('object options', async () => {
server = new Server();
httpProxyMiddleware(server, {
applyHttpProxyMiddleware(server, {
'/api': `http://${host}:${proxyTarget1Port}`,
'/server1/header': {
target: `http://${host}:${proxyTarget1Port}`,
Expand All @@ -87,11 +68,7 @@ describe('server proxy test', () => {
});
server.use(
'/noproxy',
(
req: Runtime.IIncomingMessage,
res: Runtime.IServerAppResponse,
next: Runtime.IServerAppNext
) => {
(req: IRequest, res: IResponse, next: INextFunc) => {
res.end('no proxy');
}
);
Expand All @@ -113,7 +90,7 @@ describe('server proxy test', () => {

test('array options', async () => {
server = new Server();
httpProxyMiddleware(server, [
applyHttpProxyMiddleware(server, [
{
context: '/api',
target: `http://${host}:${proxyTarget1Port}`
Expand All @@ -134,11 +111,7 @@ describe('server proxy test', () => {
]);
server.use(
'/noproxy',
(
req: Runtime.IIncomingMessage,
res: Runtime.IServerAppResponse,
next: Runtime.IServerAppNext
) => {
(req: IRequest, res: IResponse, next: INextFunc) => {
res.end('no proxy');
}
);
Expand Down
4 changes: 2 additions & 2 deletions packages/shuvi/src/lib/devMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { APIHooks } from '@shuvi/types';
import { createLaunchEditorMiddleware } from '@shuvi/toolpack/lib/utils/errorOverlayMiddleware';
import { createLaunchEditorMiddleware } from './launchEditorMiddleware';
import WebpackDevMiddleware from 'webpack-dev-middleware';
import { WebpackHotMiddleware } from './hotMiddleware';
import { Api } from '../api';
Expand Down Expand Up @@ -47,7 +47,7 @@ export async function getDevMiddleware({

const apply = () => {
api.server.use(webpackDevMiddleware);
api.server.use(webpackHotMiddleware.middleware);
api.server.use(webpackHotMiddleware.middleware as any);
api.server.use(
createLaunchEditorMiddleware(DEV_HOT_LAUNCH_EDITOR_ENDPOINT)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { createProxyMiddleware } from 'http-proxy-middleware';
import {
IServerProxyConfig,
IServerProxyConfigItem,
Runtime
} from '@shuvi/types';
import { Server } from './server';
Options as ProxyOptions,
Filter as ProxyFilter,
createProxyMiddleware
} from 'http-proxy-middleware';
import { Server } from '../server/server';
import { IMiddlewareHandler } from '../server/serverTypes';

export interface IProxyConfigItem extends ProxyOptions {
context?: ProxyFilter;
}

export type IProxyConfig =
| Record<string, string | Omit<IProxyConfigItem, 'context'>>
| IProxyConfigItem[];

function mergeDefaultProxyOptions(
config: Partial<IServerProxyConfigItem>
): IServerProxyConfigItem {
config: Partial<IProxyConfigItem>
): IProxyConfigItem {
return {
logLevel: 'silent',
secure: false,
Expand All @@ -18,10 +26,8 @@ function mergeDefaultProxyOptions(
...config
};
}
function normalizeProxyConfig(
proxyConfig: IServerProxyConfig
): IServerProxyConfigItem[] {
const res: IServerProxyConfigItem[] = [];
function normalizeProxyConfig(proxyConfig: IProxyConfig): IProxyConfigItem[] {
const res: IProxyConfigItem[] = [];

if (Array.isArray(proxyConfig)) {
proxyConfig.forEach(item => res.push(mergeDefaultProxyOptions(item)));
Expand All @@ -45,20 +51,13 @@ function normalizeProxyConfig(
return res;
}

export default function httpProxyMiddleware(
server: Server,
proxy: IServerProxyConfig
) {
export function applyHttpProxyMiddleware(server: Server, proxy: IProxyConfig) {
const proxyOptions = normalizeProxyConfig(proxy);
proxyOptions.forEach(({ context, ...opts }) => {
if (context) {
server.use(
createProxyMiddleware(context, opts) as Runtime.IServerMiddlewareHandler
);
server.use(createProxyMiddleware(context, opts) as IMiddlewareHandler);
} else {
server.use(
createProxyMiddleware(opts) as Runtime.IServerMiddlewareHandler
);
server.use(createProxyMiddleware(opts) as IMiddlewareHandler);
}
});
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import launchEditor from 'launch-editor';
import { Runtime } from '@shuvi/types';
import { IRequestHandlerWithNext } from '../server';

function getSourcePath(source: string) {
// Webpack prefixes certain source paths with this path
Expand All @@ -25,14 +25,10 @@ function getSourcePath(source: string) {

export function createLaunchEditorMiddleware(
launchEditorEndpoint: string
): Runtime.IServerMiddlewareHandler {
return function launchEditorMiddleware(
req: Runtime.IIncomingMessage,
res: Runtime.IServerAppResponse,
next: Runtime.IServerAppNext
) {
): IRequestHandlerWithNext {
return function launchEditorMiddleware(req, res, next) {
if (req.url.startsWith(launchEditorEndpoint)) {
const { query } = req.parsedUrl;
const { query } = req;
const lineNumber = parseInt(query.lineNumber as string, 10) || 1;
const colNumber = parseInt(query.colNumber as string, 10) || 1;
launchEditor(
Expand Down
12 changes: 4 additions & 8 deletions packages/shuvi/src/lib/onDemandRouteManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ModuleReplacePlugin from '@shuvi/toolpack/lib/webpack/plugins/module-replace-plugin';
import { Runtime } from '@shuvi/types';
import { IRequestHandlerWithNext } from '../server';
import { DevMiddleware } from './devMiddleware';
import { ROUTE_RESOURCE_QUERYSTRING } from '../constants';
import { Api } from '../api/api';
Expand All @@ -13,13 +13,9 @@ export class OnDemandRouteManager {
this._api = api;
}

getServerMiddleware(): Runtime.IServerAsyncMiddlewareHandler {
return async (
req,
res,
next
) => {
const pathname = req.parsedUrl.pathname!;
getServerMiddleware(): IRequestHandlerWithNext {
return async (req, res, next) => {
const pathname = req.pathname;
if (!pathname.startsWith(this._api.assetPublicPath)) {
return next();
}
Expand Down
20 changes: 0 additions & 20 deletions packages/shuvi/src/lib/sendHtml.ts

This file was deleted.

41 changes: 15 additions & 26 deletions packages/shuvi/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Runtime } from '@shuvi/types';
import { IncomingMessage, ServerResponse } from 'http';

export function acceptsHtml(
header: string,
Expand All @@ -14,32 +14,21 @@ export function acceptsHtml(
return false;
}

export function dedupe<T extends Record<string, any>, K extends keyof T>(
bundles: T[],
prop: K
): T[] {
const files = new Set();
const kept = [];
export function sendHTML(
req: IncomingMessage,
res: ServerResponse,
html: string
) {
if (res.writableEnded || res.headersSent) return;

for (const bundle of bundles) {
if (files.has(bundle[prop])) continue;
files.add(bundle[prop]);
kept.push(bundle);
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
}
return kept;
}
const buffer = Buffer.from(html);
res.setHeader('Content-Length', buffer.length);

export function asyncMiddlewareWarp(fn: Runtime.IServerAsyncMiddlewareHandler) {
return function asyncUtilWrap (req, res, next
) {
const fnReturn = fn(req, res, next)
return Promise.resolve(fnReturn).catch(next)
} as Runtime.NextHandleFunction
}
// ctx.body will set ctx.status to 200, if ctx.status is not set before
if (!res.statusCode) res.statusCode = 200;

export const asyncCall =
typeof setImmediate === 'function'
? setImmediate
: function (fn: (...args: any[]) => any) {
process.nextTick(fn.bind.apply(fn, arguments as any));
};
res.end(req.method === 'HEAD' ? null : buffer);
}
Loading

0 comments on commit c9ee556

Please sign in to comment.