Skip to content

Commit

Permalink
Merge branch 'fd-stable-vite-plugins' into fd-preset-virtual-routes
Browse files Browse the repository at this point in the history
  • Loading branch information
frandiox committed Apr 5, 2024
2 parents 18c63b7 + b83a485 commit 8efefad
Show file tree
Hide file tree
Showing 26 changed files with 457 additions and 167 deletions.
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"exports": {
"./package.json": "./package.json",
"./commands/hydrogen/init": {
"types": "./dist/commands/hydrogen/init.d.ts",
"types": "./dist/init.d.ts",
"default": "./dist/commands/hydrogen/init.js"
}
},
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/commands/hydrogen/dev-vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ import {getCliCommand} from '../../lib/shell.js';
import {findPort} from '../../lib/find-port.js';
import {logRequestLine} from '../../lib/mini-oxygen/common.js';

// @ts-ignore -- Module outside of the rootDir
// Do not import JS from here, only types
import type {OxygenApiOptions} from '~/mini-oxygen/vite/plugin.js';
// @ts-ignore -- Module outside of the rootDir
import type {HydrogenPluginOptions} from '~/hydrogen/vite/plugin.js';

export default class DevVite extends Command {
Expand Down
30 changes: 13 additions & 17 deletions packages/cli/src/lib/mini-oxygen/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
import colors from '@shopify/cli-kit/node/colors';
import {DEV_ROUTES} from '../request-events.js';
import {AbortError} from '@shopify/cli-kit/node/error';
import type {RequestHookInfo} from '@shopify/mini-oxygen';

// Default port used for debugging in VSCode and Chrome DevTools.
export const DEFAULT_INSPECTOR_PORT = 9229;
Expand All @@ -19,16 +20,11 @@ export function handleMiniOxygenImportFail(): never {
);
}

export function logRequestLine(
// Minimal overlap between Fetch, Miniflare@2 and Miniflare@3 request types.
request: Pick<Request, 'method' | 'url'> & {
headers: {get: (key: string) => string | null};
},
{
responseStatus = 200,
durationMs = 0,
}: {responseStatus?: number; durationMs?: number} = {},
): void {
export function logRequestLine({
request,
response,
meta,
}: RequestHookInfo): void {
try {
const url = new URL(request.url);
if (DEV_ROUTES.has(url.pathname) || url.pathname === '/favicon.ico') return;
Expand All @@ -46,26 +42,26 @@ export function logRequestLine(
}

const colorizeStatus =
responseStatus < 300
response.status < 300
? outputToken.green
: responseStatus < 400
: response.status < 400
? outputToken.cyan
: outputToken.errorText;

outputInfo(
outputContent`${request.method.padStart(6)} ${colorizeStatus(
String(responseStatus),
String(response.status),
)} ${outputToken.italic(type.padEnd(7, ' '))} ${route} ${
durationMs > 0 ? colors.dim(` ${durationMs}ms`) : ''
meta.durationMs > 0 ? colors.dim(` ${meta.durationMs}ms`) : ''
}${info ? ' ' + colors.dim(info) : ''}${
request.headers.get('purpose') === 'prefetch'
request.headers['purpose'] === 'prefetch'
? outputToken.italic(colors.dim(' prefetch'))
: ''
}`,
);
} catch {
if (request && responseStatus) {
outputInfo(`${request.method} ${responseStatus} ${request.url}`);
if (request && response?.status) {
outputInfo(`${request.method} ${response.status} ${request.url}`);
}
}
}
21 changes: 18 additions & 3 deletions packages/cli/src/lib/mini-oxygen/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,24 @@ export async function startNodeServer({
() => defaultDispatcher(request),
);

logRequestLine(request, {
responseStatus: response.status,
durationMs: startTimeMs > 0 ? Date.now() - startTimeMs : 0,
const endTimeMs = Date.now();

logRequestLine({
request: {
url: request.url,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
},
response: {
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
},
meta: {
startTimeMs,
endTimeMs,
durationMs: startTimeMs > 0 ? endTimeMs - startTimeMs : 0,
},
});

return response;
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/lib/mini-oxygen/workerd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ export async function startWorkerdServer({

const miniOxygen = createMiniOxygen({
debug,
logRequestLine,
port: appPort,
host: 'localhost',
liveReload: watch,
requestHook: logRequestLine,
inspectorPort: publicInspectorPort,
inspectWorkerName: mainWorkerName,
assets: {port: assetsPort, directory: buildPathClient},
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/lib/request-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ export function setConstructors(constructors: {

export const DEV_ROUTES = new Set([
'/graphiql',
'/graphiql/customer-account.schema.json',
'/subrequest-profiler',
'/__vite_warmup',
'/debug-network-server',
]);

type RequestEvent = {
Expand Down
70 changes: 70 additions & 0 deletions packages/cli/src/lib/virtual-routes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {describe, it, expect} from 'vitest';
import {fileURLToPath} from 'node:url';
import path from 'node:path';
import type {RemixConfig} from '@remix-run/dev/dist/config.js';
import {
addVirtualRoutes,
VIRTUAL_ROOT,
VIRTUAL_ROUTES_DIR,
} from './virtual-routes.js';

const appDirectory = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'virtual-test',
);

describe('virtual routes', () => {
it('adds virtual routes', async () => {
const config = {
appDirectory,
routes: {},
} as RemixConfig;

await addVirtualRoutes(config);

expect(config.routes[VIRTUAL_ROOT]).toMatchObject({
path: '',
id: VIRTUAL_ROOT,
file: expect.stringContaining('virtual-routes/virtual-root.jsx'),
});

expect(config.routes[VIRTUAL_ROUTES_DIR + '/index']).toMatchObject({
parentId: VIRTUAL_ROOT,
path: undefined,
file: expect.stringContaining(VIRTUAL_ROUTES_DIR + '/index.tsx'),
});

expect(config.routes[VIRTUAL_ROUTES_DIR + '/graphiql']).toMatchObject({
parentId: VIRTUAL_ROOT,
path: 'graphiql',
file: expect.stringContaining(VIRTUAL_ROUTES_DIR + '/graphiql.tsx'),
});
});

it('skips existing routes', async () => {
const existingIndexRoute = {
id: 'routes/index',
index: true,
parentId: 'root',
path: undefined,
file: 'user-app/routes/index.tsx',
};

const config = {
appDirectory,
routes: {
[existingIndexRoute.id]: existingIndexRoute,
},
} as unknown as RemixConfig;

await addVirtualRoutes(config);

expect(config.routes[existingIndexRoute.id]).toMatchObject(
existingIndexRoute,
);

expect(config.routes[VIRTUAL_ROUTES_DIR + '/index']).toBeFalsy();

expect(Object.values(config.routes).length).toBeGreaterThan(2);
});
});
5 changes: 4 additions & 1 deletion packages/cli/src/lib/virtual-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ type MinimalRemixConfig = {
export async function addVirtualRoutes<T extends MinimalRemixConfig>(
config: T,
): Promise<T> {
const distPath = process.env.SHOPIFY_UNIT_TEST
? fileURLToPath(new URL('../../../hydrogen/src/vite', import.meta.url))
: fileURLToPath(new URL('..', import.meta.url));

const userRouteList = Object.values(config.routes);
const distPath = fileURLToPath(new URL('..', import.meta.url));
const virtualRoutesPath = joinPath(distPath, VIRTUAL_ROUTES_DIR);

for (const absoluteFilePath of await glob(
Expand Down
1 change: 0 additions & 1 deletion packages/cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"module": "NodeNext",
"moduleResolution": "NodeNext",
"jsx": "react-jsx",
"rootDir": "src",
"noUncheckedIndexedAccess": true,
"types": ["@shopify/oxygen-workers-types", "node", "@remix-run/dev"],
"outDir": "dist",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const outDir = 'dist';
export default defineConfig([
{
...commonConfig,
entry: ['src/**/*.ts'],
entry: ['src/**/*.ts', '!src/**/*.test.ts'],
outDir,
// Generate types only for the exposed entry points
dts: {entry: ['src/commands/hydrogen/init.ts']},
Expand Down
2 changes: 0 additions & 2 deletions packages/hydrogen/src/cache/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/// <reference types="@shopify/remix-oxygen" />

import {hashKey} from '../utils/hash.js';
import {
CacheShort,
Expand Down
8 changes: 8 additions & 0 deletions packages/hydrogen/src/hydrogen.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
SessionData,
FlashSessionData,
} from '@remix-run/server-runtime';
import type {RequestEventPayload} from './vite/request-events';

export interface HydrogenSessionData {
customerAccount: {
Expand All @@ -29,3 +30,10 @@ export interface HydrogenSession<
SessionStorage<HydrogenSessionData & Data, FlashData>['commitSession']
>;
}

declare global {
var __H2O_LOG_EVENT: undefined | ((event: RequestEventPayload) => void);
var __remix_devServerHooks:
| undefined
| {getCriticalCss: (...args: unknown[]) => any};
}
62 changes: 60 additions & 2 deletions packages/hydrogen/src/vite/hydrogen-middleware.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,71 @@
import type {ViteDevServer} from 'vite';
import crypto from 'node:crypto';
import {createRequire} from 'node:module';
import {createReadStream} from 'node:fs';
import {clearHistory, streamRequestEvents} from './request-events.js';
import {
clearHistory,
emitRequestEvent,
streamRequestEvents,
} from './request-events.js';
import type {HydrogenPluginOptions} from './types.js';

export type HydrogenMiddlewareOptions = HydrogenPluginOptions & {
isOxygen?: boolean;
};

export function setupHydrogenMiddleware(
viteDevServer: ViteDevServer,
options: HydrogenPluginOptions,
options: HydrogenMiddlewareOptions,
) {
if (!options.isOxygen) {
// If Oxygen is not present, we are probably running
// on Node.js, so we can setup the global functions directly.
globalThis.__H2O_LOG_EVENT = (data) => {
emitRequestEvent(data, viteDevServer.config.root);
};

viteDevServer.middlewares.use(function (req, res, next) {
// Filter out dev requests
if (!/^\/__vite_/.test(req.url || '')) {
// Hydrogen requires a unique request ID for each request
// to track the request lifecycle. This is added by Oxygen
// normally but we can add it here for development in Node.
req.headers['request-id'] ??= crypto.randomUUID();

const startTimeMs = Date.now();
let endTimeMs = 0;

res.once('pipe', () => {
endTimeMs = Date.now();
});

res.once('close', () => {
emitRequestEvent(
{
__fromVite: true,
eventType: 'request',
url: req.url!,
requestId: req.headers['request-id'] as string,
purpose: req.headers['purpose'] as string,
startTime: startTimeMs,
endTime: endTimeMs || Date.now(),
responseInit: {
status: res.statusCode,
statusText: res.statusMessage,
headers: Object.entries(
res.getHeaders() as Record<string, string>,
),
},
},
viteDevServer.config.root,
);
});
}

next();
});
}

if (options.disableVirtualRoutes) return;

viteDevServer.middlewares.use(
Expand Down
Loading

0 comments on commit 8efefad

Please sign in to comment.