Skip to content

Commit

Permalink
add routes support (experimental)
Browse files Browse the repository at this point in the history
  • Loading branch information
FredKSchott committed Nov 27, 2020
1 parent 3a157b5 commit efb30b4
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 11 deletions.
44 changes: 36 additions & 8 deletions snowpack/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import http from 'http';
import HttpProxy from 'http-proxy';
import http2 from 'http2';
import https from 'https';
import {isBinaryFile} from 'isbinaryfile';
import * as colors from 'kleur/colors';
import mime from 'mime-types';
import os from 'os';
Expand All @@ -52,8 +53,8 @@ import {
wrapImportProxy,
} from '../build/build-import-proxy';
import {buildFile as _buildFile, getInputsFromOutput} from '../build/build-pipeline';
import {createImportResolver} from '../build/import-resolver';
import {getUrlForFile} from '../build/file-urls';
import {createImportResolver} from '../build/import-resolver';
import {EsmHmrEngine} from '../hmr-server-engine';
import {logger} from '../logger';
import {
Expand All @@ -65,10 +66,11 @@ import {matchDynamicImportValue} from '../scan-imports';
import {
CommandOptions,
ImportMap,
SnowpackBuildMap,
LoadResult,
SnowpackDevServer,
OnFileChangeCallback,
RouteConfigObject,
SnowpackBuildMap,
SnowpackDevServer,
} from '../types/snowpack';
import {
BUILD_CACHE,
Expand All @@ -90,7 +92,6 @@ import {
} from '../util';
import {getInstallTargets, run as installRunner} from './install';
import {getPort, getServerInfoMessage, paintDashboard, paintEvent} from './paint';
import {isBinaryFile} from 'isbinaryfile';

interface FoundFile {
fileLoc: string;
Expand Down Expand Up @@ -590,7 +591,7 @@ export async function startDevServer(commandOptions: CommandOptions): Promise<Sn
return null;
}

async function getFileFromRoute(reqPath: string): Promise<FoundFile | null> {
async function getFileFromLazyUrl(reqPath: string): Promise<FoundFile | null> {
for (const [mountKey, mountEntry] of Object.entries(config.mount)) {
let requestedFile: string;
if (mountEntry.url === '/') {
Expand Down Expand Up @@ -634,7 +635,10 @@ export async function startDevServer(commandOptions: CommandOptions): Promise<Sn

let foundFile = await getFileFromUrl(reqPath);
if (!foundFile && isRoute) {
foundFile = (await getFileFromRoute(reqPath)) || (await getFileFromFallback());
foundFile =
(await getFileFromLazyUrl(reqPath)) ||
// @deprecated: to be removed in v3
(await getFileFromFallback());
}

if (!foundFile) {
Expand Down Expand Up @@ -1125,6 +1129,21 @@ export async function startDevServer(commandOptions: CommandOptions): Promise<Sn
*/
const knownETags = new Map<string, string>();

function matchRoute(reqUrl: string): RouteConfigObject | null {
let reqPath = decodeURI(url.parse(reqUrl).pathname!);
const reqExt = path.extname(reqPath);
const isRoute = !reqExt || reqExt.toLowerCase() === '.html';
for (const route of config.experiments.routes) {
if (route.match === 'routes' && !isRoute) {
continue;
}
if (route._srcRegex.test(reqPath)) {
return route;
}
}
return null;
}

/**
* Fully handle the response for a given request. This is used internally for
* every response that the dev server sends, but it can also be used via the
Expand All @@ -1135,8 +1154,17 @@ export async function startDevServer(commandOptions: CommandOptions): Promise<Sn
res: http.ServerResponse,
{handleError}: {handleError?: boolean} = {},
) {
const reqUrl = req.url!;
// Check if a configured proxy matches the request.
let reqUrl = req.url!;
const matchedRoute = matchRoute(reqUrl);
// If a route is matched, rewrite the URL or call the route function
if (matchedRoute) {
if (typeof matchedRoute.dest === 'string') {
reqUrl = matchedRoute.dest;
} else {
return matchedRoute.dest(req, res);
}
}
// @deprecated: to be removed in v3
const requestProxy = getRequestProxy(reqUrl);
if (requestProxy) {
return requestProxy(req, res);
Expand Down
28 changes: 26 additions & 2 deletions snowpack/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import {isPlainObject} from 'is-plain-object';
import {validate, ValidatorResult} from 'jsonschema';
import os from 'os';
import path from 'path';
import yargs from 'yargs-parser';
import url from 'url';

import yargs from 'yargs-parser';
import {logger} from './logger';
import {esbuildPlugin} from './plugins/plugin-esbuild';
import {
Expand All @@ -23,6 +22,7 @@ import {
PluginOptimizeOptions,
Proxy,
ProxyOptions,
RouteConfigObject,
SnowpackConfig,
SnowpackPlugin,
SnowpackUserConfig,
Expand Down Expand Up @@ -72,6 +72,7 @@ const DEFAULT_CONFIG: SnowpackUserConfig = {
files: ['__tests__/**/*', '**/*.@(spec|test).*'],
},
experiments: {
routes: [],
ssr: false,
},
};
Expand Down Expand Up @@ -180,6 +181,7 @@ const configSchema = {
properties: {
ssr: {type: 'boolean'},
app: {},
routes: {},
},
},
proxy: {
Expand Down Expand Up @@ -568,6 +570,27 @@ function normalizeMount(config: SnowpackConfig, cwd: string) {
return normalizedMount;
}

function normalizeRoutes(routes: RouteConfigObject[]): RouteConfigObject[] {
return routes.map(({src, dest, match}, i) => {
// Normalize
if (typeof dest === 'string') {
dest = addLeadingSlash(dest);
}
if (!src.startsWith('^')) {
src = '^' + src;
}
if (!src.endsWith('$')) {
src = src + '$';
}
// Validate
try {
return {src, dest, match: match || 'all', _srcRegex: new RegExp(src)};
} catch (err) {
throw new Error(`config.routes[${i}].src: invalid regular expression syntax "${src}"`);
}
});
}

function normalizeAlias(config: SnowpackConfig, cwd: string, createMountAlias: boolean) {
const cleanAlias: Record<string, string> = config.alias || {};
if (createMountAlias) {
Expand Down Expand Up @@ -634,6 +657,7 @@ function normalizeConfig(_config: SnowpackUserConfig): SnowpackConfig {
config.proxy = normalizeProxies(config.proxy as any);
config.mount = normalizeMount(config, cwd);
config.alias = normalizeAlias(config, cwd, isLegacyMountConfig);
config.experiments.routes = normalizeRoutes(config.experiments.routes);
if (config.experiments.optimize) {
config.experiments.optimize = {
entrypoints: config.experiments.optimize.entrypoints ?? 'auto',
Expand Down
15 changes: 14 additions & 1 deletion snowpack/src/types/snowpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,20 @@ export interface OptimizeOptions {
target: 'es2020' | 'es2019' | 'es2018' | 'es2017';
}

export interface RouteConfigObject {
src: string;
dest: string | ((req: http.IncomingMessage, res: http.ServerResponse) => void);
match: 'routes' | 'all';
_srcRegex: RegExp;
}

// interface this library uses internally
export interface SnowpackConfig {
install: string[];
extends?: string;
exclude: string[];
knownEntrypoints: string[];
proxy: Proxy[];
webDependencies: Record<string, string>;
mount: Record<string, MountEntry>;
alias: Record<string, string>;
scripts: Record<string, string>;
Expand Down Expand Up @@ -225,18 +232,23 @@ export interface SnowpackConfig {
testOptions: {
files: string[];
};
// @deprecated - Use experiments.routes instead
proxy: Proxy[];
/** EXPERIMENTAL - This section is experimental and not yet finalized. May change across minor versions. */
experiments: {
/** (EXPERIMENTAL) If true, "snowpack build" should build your site for SSR. */
ssr: boolean;
/** (EXPERIMENTAL) Custom request handler for the dev server. */
/** @deprecated: Use experiments.routes or integrate Snowpack's JS API into your own server instead. */
app?: (
req: http.IncomingMessage,
res: http.ServerResponse,
next: (err?: Error) => void,
) => unknown;
/** (EXPERIMENTAL) Optimize your site for production. */
optimize?: OptimizeOptions;
/** (EXPERIMENTAL) Configure routes during development. */
routes: RouteConfigObject[];
};
_extensionMap: Record<string, string>;
}
Expand All @@ -259,6 +271,7 @@ export type SnowpackUserConfig = {
ssr?: SnowpackConfig['experiments']['ssr'];
app?: SnowpackConfig['experiments']['app'];
optimize?: Partial<SnowpackConfig['experiments']['optimize']>;
routes?: Pick<RouteConfigObject, 'src' | 'dest' | 'match'>[];
};
};

Expand Down

0 comments on commit efb30b4

Please sign in to comment.