Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion @app/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"graphql": "^16.9.0",
"lodash": "^4.17.21",
"net": "^1.0.2",
"next": "^13.2.3",
"next": "^14.2.35",
"nprogress": "^0.2.0",
"rc-field-form": "~1.27.4",
"react": "^18.2.0",
Expand Down
42 changes: 42 additions & 0 deletions @app/client/src/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,48 @@ if (!process.env.ROOT_URL) {
return {
poweredByHeader: false,
distDir: `../.next`,
// more @ant-design/* and rc-* may need to be added here depending on
// usage. `bundlePagesRouterDependencies` may be able to help with this
// config bloat in next.js 15
// https://nextjs.org/docs/15/pages/api-reference/config/next-config-js/bundlePagesRouterDependencies
transpilePackages: [
"@ant-design/icons",
"@ant-design/icons-svg",
"rc-cascader",
"rc-checkbox",
"rc-collapse",
"rc-dialog",
"rc-drawer",
"rc-dropdown",
"rc-field-form",
"rc-image",
"rc-input",
"rc-input-number",
"rc-mentions",
"rc-menu",
"rc-motion",
"rc-notification",
"rc-overflow",
"rc-pagination",
"rc-picker",
"rc-progress",
"rc-rate",
"rc-resize-observer",
"rc-segmented",
"rc-select",
"rc-slider",
"rc-steps",
"rc-switch",
"rc-table",
"rc-tabs",
"rc-textarea",
"rc-tooltip",
"rc-tree",
"rc-tree-select",
"rc-upload",
"rc-util",
"rc-virtual-list",
],
trailingSlash: false,
webpack(config, { webpack, dev, isServer }) {
const makeSafe = (externals) => {
Expand Down
25 changes: 10 additions & 15 deletions @app/client/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,13 @@ import "nprogress/nprogress.css";
import "../styles.css";

import { ApolloClient, ApolloProvider } from "@apollo/client";
import { withApollo } from "@app/lib";
import { setGraphileApp, withApollo } from "@app/lib";
import { ConfigProvider, notification } from "antd";
import App from "next/app";
import Router from "next/router";
import NProgress from "nprogress";
import * as React from "react";

declare global {
interface Window {
__GRAPHILE_APP__: {
ROOT_URL?: string;
T_AND_C_URL?: string;
};
}
}

NProgress.configure({
showSpinner: false,
});
Expand All @@ -29,10 +20,12 @@ if (typeof window !== "undefined") {
throw new Error("Cannot read from __NEXT_DATA__ element");
}
const data = JSON.parse(nextDataEl.textContent);
window.__GRAPHILE_APP__ = {
ROOT_URL: data.query.ROOT_URL,
T_AND_C_URL: data.query.T_AND_C_URL,
};
if (!data.props?.graphileApp) {
throw new Error(
"Cannot find property props.graphileApp in __NEXT_DATA__. Was it returned correctly from MyApp.getInitialProps()?"
);
}
setGraphileApp(data.props.graphileApp);

Router.events.on("routeChangeStart", () => {
NProgress.start();
Expand Down Expand Up @@ -65,7 +58,9 @@ class MyApp extends App<{ apollo: ApolloClient<any> }> {
pageProps = await Component.getInitialProps(ctx);
}

return { pageProps };
const graphileApp = ctx.req?.graphileApp;

return { pageProps, graphileApp };
}

render() {
Expand Down
2 changes: 1 addition & 1 deletion @app/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@apollo/client": "3.6.10",
"@app/graphql": "0.0.0",
"antd": "5.3.3",
"next": "^13.2.3",
"next": "^14.2.35",
"react": "^18.2.0",
"tslib": "^2.5.0"
},
Expand Down
2 changes: 1 addition & 1 deletion @app/lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"grafast": "^1.0.0",
"graphql": "^16.9.0",
"graphql-ws": "^5.11.3",
"next": "^13.2.3",
"next": "^14.2.35",
"next-with-apollo": "^5.3.0",
"rc-field-form": "^1.27.4",
"react": "^18.2.0",
Expand Down
32 changes: 32 additions & 0 deletions @app/lib/src/graphileApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export interface GraphileApp {
CSRF_TOKEN: string;
ROOT_URL: string;
T_AND_C_URL?: string;
}

type GraphileAppWindow = Window & {
__GRAPHILE_APP__?: GraphileApp;
};

export function setGraphileApp(incoming: GraphileApp): void {
const existing = (window as GraphileAppWindow).__GRAPHILE_APP__;
if (existing) {
if (
existing.CSRF_TOKEN === incoming.CSRF_TOKEN &&
existing.ROOT_URL === incoming.ROOT_URL &&
existing.T_AND_C_URL === incoming.T_AND_C_URL
) {
return;
} else {
throw new Error("window.__GRAPHILE_APP__ has already been set.");
}
}
(window as GraphileAppWindow).__GRAPHILE_APP__ = incoming;
}

export function getGraphileApp(): GraphileApp {
const graphileApp = (window as GraphileAppWindow).__GRAPHILE_APP__;
if (!graphileApp)
throw new Error("window.__GRAPHILE_APP__ has not been set.");
return graphileApp;
}
1 change: 1 addition & 0 deletions @app/lib/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./errors";
export * from "./forms";
export * from "./graphileApp";
export * from "./passwords";
export * from "./withApollo";
9 changes: 4 additions & 5 deletions @app/lib/src/withApollo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Client, createClient } from "graphql-ws";
import { withApollo as withApolloBase } from "next-with-apollo";

import { GraphileApolloLink } from "./GraphileApolloLink";
import { getGraphileApp } from "./graphileApp";

let wsClient: Client | null = null;

Expand Down Expand Up @@ -98,12 +99,10 @@ function makeClientSideLink(ROOT_URL: string) {
throw new Error("Must only makeClientSideLink once");
}
_rootURL = ROOT_URL;
const nextDataEl = document.getElementById("__NEXT_DATA__");
if (!nextDataEl || !nextDataEl.textContent) {
throw new Error("Cannot read from __NEXT_DATA__ element");
const { CSRF_TOKEN } = getGraphileApp();
if (!CSRF_TOKEN) {
throw new Error("Cannot read CSRF_TOKEN");
}
const data = JSON.parse(nextDataEl.textContent);
const CSRF_TOKEN = data.query.CSRF_TOKEN;
const httpLink = new HttpLink({
uri: `${ROOT_URL}/graphql`,
credentials: "same-origin",
Expand Down
3 changes: 2 additions & 1 deletion @app/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"helmet": "^6.0.1",
"lodash": "^4.17.21",
"morgan": "^1.10.0",
"next": "^13.2.3",
"next": "^14.2.35",
"passport": "^0.6.0",
"passport-github2": "^0.1.12",
"pg": "^8.9.0",
Expand All @@ -48,6 +48,7 @@
"tslib": "^2.5.0"
},
"devDependencies": {
"@app/lib": "0.0.0",
"@types/node": "^18.14.2",
"cross-env": "^7.0.3",
"graphql": "^16.9.0",
Expand Down
70 changes: 37 additions & 33 deletions @app/server/src/middleware/installSSR.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { GraphileApp } from "@app/lib" with {
"resolution-mode": "import",
};
import { Express } from "express";
import { createServer } from "http";
import next from "next";
import { parse } from "url";

import { getUpgradeHandlers } from "../app";

Expand All @@ -20,8 +22,10 @@ export default async function installSSR(app: Express) {
// Don't specify 'conf' key

// Trick Next.js into adding its upgrade handler here, so we can extract
// it. Calling `getUpgradeHandler()` is insufficient because that doesn't
// handle the assets.
// it. Calling `getUpgradeHandler()` is insufficient here: in Next
// custom-server mode it follows the inherited server handleUpgrade path,
// while dev HMR uses the upgrade handler returned by getRequestHandlers()
// and wired onto httpServer.
httpServer: fakeHttpServer,
});
const handlerPromise = (async () => {
Expand All @@ -35,36 +39,36 @@ export default async function installSSR(app: Express) {
});
app.get("*", async (req, res) => {
const handler = await handlerPromise;
const parsedUrl = parse(req.url, true);
handler(req, res, {
...parsedUrl,
query: {
...parsedUrl.query,
CSRF_TOKEN: req.csrfToken(),
// See 'next.config.js':
ROOT_URL: process.env.ROOT_URL || "http://localhost:5678",
T_AND_C_URL: process.env.T_AND_C_URL,
},
});
const graphileApp: GraphileApp = {
CSRF_TOKEN: req.csrfToken(),
ROOT_URL: process.env.ROOT_URL || "http://localhost:5678",
...(process.env.T_AND_C_URL && { T_AND_C_URL: process.env.T_AND_C_URL }),
};
(req as any).graphileApp = graphileApp;
handler(req, res);
});

// Now handle websockets
if (!(nextApp as any).getServer) {
console.warn(
`Our Next.js workaround for getting the upgrade handler without giving Next.js dominion over all websockets might no longer work - nextApp.getServer (private API) is no more.`
);
} else {
await (nextApp as any).getServer();
}
const nextJsUpgradeHandler = fakeHttpServer.listeners("upgrade")[0] as any;
if (nextJsUpgradeHandler) {
const upgradeHandlers = getUpgradeHandlers(app);
upgradeHandlers.push({
name: "Next.js",
check(req) {
return req.url?.includes("/_next/") ?? false;
},
upgrade: nextJsUpgradeHandler,
});
}
// Next.js wires its custom-server websocket listener lazily the first time
// its request handler runs. Register our dispatcher entry now, then read the
// listener from the fake server when a matching upgrade arrives.
let nextJsUpgradeHandler: ReturnType<typeof fakeHttpServer.listeners>[number];
const upgradeHandlers = getUpgradeHandlers(app);
upgradeHandlers.push({
name: "Next.js",
check(req) {
if (req.url == null) return false;
return req.url.includes("/_next/");
},
upgrade(req, socket, head) {
nextJsUpgradeHandler ??= fakeHttpServer.listeners("upgrade")[0];
if (typeof nextJsUpgradeHandler === "function") {
nextJsUpgradeHandler(req, socket, head);
} else {
console.error(
`Next.js websocket upgrade handler was not installed before an upgrade request for ${req.url ?? "<unknown URL>"}.`
);
socket.destroy();
}
},
});
}
2 changes: 1 addition & 1 deletion @app/server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"target": "es2018"
},
"include": ["src"],
"references": [{ "path": "../config" }],
"references": [{ "path": "../config" }, { "path": "../lib" }],
"ts-node": {
"compilerOptions": {
"rootDir": null
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"lint:fix": "yarn eslint --fix . && yarn prettier:all --write",
"eslint": "eslint --ext .js,.jsx,.ts,.tsx,.graphql",
"prettier:all": "prettier --ignore-path .eslintignore \"**/*.{js,jsx,ts,tsx,graphql,md}\"",
"depcheck": "yarn workspaces foreach --verbose --topological --exclude ROOT --exclude docker-helpers exec depcheck --ignores=\"@app/config,@app/client,tslib,webpack,babel-plugin-import,source-map-support,@graphql-codegen/*,*eslint*,@typescript-eslint/*,graphql-toolkit,net,tls,dayjs,@types/jest,babel-jest,jest,mock-req,mock-res,nodemon,ts-jest,ts-loader,ts-node,update-dotenv,mkdirp,@types/helmet,helmet\" --ignore-dirs=\".next\"",
"depcheck": "yarn workspaces foreach --verbose --topological --exclude ROOT --exclude docker-helpers exec depcheck --ignores=\"@app/config,@app/client,@app/lib,tslib,webpack,babel-plugin-import,source-map-support,@graphql-codegen/*,*eslint*,@typescript-eslint/*,graphql-toolkit,net,tls,dayjs,@types/jest,babel-jest,jest,mock-req,mock-res,nodemon,ts-jest,ts-loader,ts-node,update-dotenv,mkdirp,@types/helmet,helmet\" --ignore-dirs=\".next\"",
"dev": "yarn && yarn workspaces foreach --verbose --topological --exclude ROOT --exclude docker-helpers run codegen && tsc -b && concurrently --kill-others --names \"TSC,WATCH,RUN,TEST\" --prefix \"({name})\" --prefix-colors \"yellow.bold,yellow.bold,cyan.bold,greenBright.bold\" \"tsc -b --watch --preserveWatchOutput\" \"yarn workspaces foreach --verbose --parallel --interlaced --exclude ROOT --exclude docker-helpers run watch\" \"yarn workspaces foreach --verbose --parallel --interlaced --exclude ROOT --exclude docker-helpers run dev\" \"yarn test:watch --delay 10\"",
"build": "yarn workspaces foreach --verbose --topological --exclude ROOT --exclude docker-helpers run build",
"clean": "node ./scripts/clean.js",
Expand Down
Loading
Loading