├── .gitignore
├── LICENSE
├── README.md
└── relay-entrypoints
├── .babelrc
├── .gitignore
├── extractQueriesAndModules.js
├── js-resource-loader.js
├── package-lock.json
├── package.json
├── public
└── .gitkeep
├── queries.json
├── react-env.d.ts
├── relay.config.js
├── src
├── EntryPointConfig.ts
├── ErrorBoundary.ts
├── Home.entrypoint.ts
├── Home.tsx
├── JSResource.ts
├── RelayEnvironment.ts
├── Repository.entrypoint.ts
├── Repository.route.ts
├── Repository.tsx
├── Root.entrypoint.ts
├── Root.route.ts
├── Root.tsx
├── RouteConfig.ts
├── Router.tsx
├── ServerEntry.ts
├── Shell.tsx
├── __generated__
│ └── RepositoryQuery.graphql.ts
└── nullThrows.ts
├── tsconfig.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Relay Tools
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # typescript-relayjs-examples
2 | RelayJS typescript examples
3 |
--------------------------------------------------------------------------------
/relay-entrypoints/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "relay",
4 | "@babel/plugin-proposal-optional-chaining",
5 | "@babel/plugin-proposal-nullish-coalescing-operator"
6 | ],
7 | "presets": [
8 | "@babel/preset-typescript",
9 | [
10 | "@babel/preset-react",
11 | {
12 | "runtime": "automatic",
13 | "development": true
14 | }
15 | ]
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/relay-entrypoints/.gitignore:
--------------------------------------------------------------------------------
1 | out
--------------------------------------------------------------------------------
/relay-entrypoints/extractQueriesAndModules.js:
--------------------------------------------------------------------------------
1 | const server = require("./out/server/server");
2 |
3 | const { queries, modules } = server.getQueriesAndModulesForUrl(
4 | "/facebook/relay"
5 | );
6 | console.log(queries, modules);
7 |
--------------------------------------------------------------------------------
/relay-entrypoints/js-resource-loader.js:
--------------------------------------------------------------------------------
1 | const babel = require("@babel/core");
2 |
3 | module.exports = function jsResourceLoader(content, map, meta) {
4 | function babelPlugin({ types: t }) {
5 | const jsResourceAbsoluteModulePath = "./JSResource";
6 | const jsrIdent = t.identifier("JSResource");
7 | let program = null;
8 | let imported = false;
9 | function addImport() {
10 | if (!imported) {
11 | imported = true;
12 | const importDefaultSpecifier = t.importDefaultSpecifier(jsrIdent);
13 | const importDeclaration = t.importDeclaration(
14 | [importDefaultSpecifier],
15 | t.stringLiteral(jsResourceAbsoluteModulePath)
16 | );
17 | program.unshiftContainer("body", importDeclaration);
18 | }
19 | }
20 |
21 | function requireResolveWeak(modulePath) {
22 | return t.callExpression(
23 | t.memberExpression(
24 | t.identifier("require"),
25 | t.identifier("resolveWeak")
26 | ),
27 | [modulePath]
28 | );
29 | }
30 |
31 | const processed = [];
32 |
33 | return {
34 | name: "ast-transform", // not required
35 | visitor: {
36 | Program(path) {
37 | program = path;
38 | },
39 | Import(path) {
40 | const fn = path.getFunctionParent();
41 | if (processed.includes(path.node)) return;
42 | processed.push(path.node);
43 | if (fn && t.isCallExpression(path.parent)) {
44 | const callExpression = path.parent;
45 | const modulePath = callExpression.arguments[0];
46 | t.assertStringLiteral(modulePath);
47 | fn.replaceWith(
48 | t.callExpression(t.identifier("JSResource"), [
49 | requireResolveWeak(modulePath),
50 | fn.node,
51 | ])
52 | );
53 | addImport();
54 | }
55 | },
56 | },
57 | };
58 | }
59 | return babel.transformSync(content, {
60 | plugins: [babelPlugin],
61 | }).code;
62 | };
63 |
--------------------------------------------------------------------------------
/relay-entrypoints/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "start": "webpack-dev-server --config webpack.config.js",
4 | "build": "webpack --config webpack.config.js"
5 | },
6 | "dependencies": {
7 | "body-parser": "^1.19.0",
8 | "history": "^5.0.0",
9 | "react": "0.0.0-experimental-94c0244ba",
10 | "react-dom": "0.0.0-experimental-94c0244ba",
11 | "react-relay": "0.0.0-experimental-183bdd28",
12 | "react-router": "^5.2.0",
13 | "react-router-config": "^5.1.1",
14 | "relay-runtime": "^10.0.1"
15 | },
16 | "devDependencies": {
17 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4",
18 | "@babel/plugin-proposal-optional-chaining": "^7.11.0",
19 | "@babel/preset-react": "^7.10.4",
20 | "@babel/preset-typescript": "^7.10.4",
21 | "@octokit/graphql-schema": "^8.28.0",
22 | "@types/history": "^4.7.8",
23 | "@types/react": "^16.9.49",
24 | "@types/react-dom": "^16.9.8",
25 | "@types/react-relay": "^7.0.9",
26 | "@types/react-router": "^5.1.8",
27 | "@types/react-router-config": "^5.0.1",
28 | "@types/relay-compiler": "^8.0.0",
29 | "@types/relay-runtime": "^10.0.3",
30 | "@types/webpack": "^4.41.22",
31 | "@types/webpack-env": "^1.15.3",
32 | "babel-loader": "^8.1.0",
33 | "babel-plugin-relay": "^10.0.1",
34 | "html-webpack-plugin": "^4.4.1",
35 | "relay-compiler": "^10.0.1",
36 | "relay-compiler-language-typescript": "^13.0.1",
37 | "relay-config": "^10.0.1",
38 | "typescript": "^4.0.2",
39 | "webpack": "^4.44.2",
40 | "webpack-cli": "^3.3.12",
41 | "webpack-dev-server": "^3.11.0"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/relay-entrypoints/public/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/relay-tools/typescript-relayjs-examples/6e5b36c2fad2b6eaef7a39c040c60485f093dffe/relay-entrypoints/public/.gitkeep
--------------------------------------------------------------------------------
/relay-entrypoints/queries.json:
--------------------------------------------------------------------------------
1 | {
2 | "8aa70460f97b04dea4549c71282be858": "query RepositoryQuery(\n $owner: String!\n $name: String!\n) {\n repository(owner: $owner, name: $name) {\n name\n id\n }\n}\n",
3 | "4a45f359f7fe4a783fae695058a98919": "query RepositoryQuery(\n $owner: String!\n $name: String!\n) {\n repository(owner: $owner, name: $name) {\n nameWithOwner\n id\n }\n}\n"
4 | }
--------------------------------------------------------------------------------
/relay-entrypoints/react-env.d.ts:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | ///
4 | ///
5 |
--------------------------------------------------------------------------------
/relay-entrypoints/relay.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | customScalars: {},
3 | schema: "./node_modules/@octokit/graphql-schema/schema.graphql",
4 | language: "typescript",
5 | src: "./src",
6 | persistOutput: "./queries.json",
7 | };
8 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/EntryPointConfig.ts:
--------------------------------------------------------------------------------
1 | /** @format */
2 | import type { EntryPoint } from "react-relay/lib/relay-experimental/EntryPointTypes";
3 |
4 | export function createEntryPoint
(
5 | config: EntryPoint
6 | ): EntryPoint
{
7 | return {
8 | name: config.root.getModuleId(),
9 | root: config.root,
10 | getPreloadProps: config.getPreloadProps,
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/ErrorBoundary.ts:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import { Component, ReactNode } from "react";
4 |
5 | type Props = {
6 | children: ReactNode;
7 | renderError: (error: Error | null, retry: () => void) => ReactNode;
8 | };
9 |
10 | type State = {
11 | hasError: boolean;
12 | error: Error | null;
13 | };
14 |
15 | export default class ErrorBoundary extends Component {
16 | static getDerivedStateFromError(error: Error) {
17 | // Update state so the next render will show the fallback UI.
18 | return { hasError: true, error };
19 | }
20 |
21 | constructor(props: Props) {
22 | super(props);
23 | this.state = {
24 | hasError: false,
25 | error: null,
26 | };
27 | }
28 |
29 | componentDidCatch(error: Error) {
30 | console.error(error);
31 | }
32 |
33 | render() {
34 | return this.state.hasError
35 | ? this.props.renderError(this.state.error, () => {
36 | this.setState({ hasError: false, error: null });
37 | })
38 | : this.props.children;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/Home.entrypoint.ts:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import { createEntryPoint } from "./EntryPointConfig";
4 |
5 | export default createEntryPoint({
6 | root: () => import("./Home"),
7 | getPreloadProps() {
8 | return {};
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/Home.tsx:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | const Home = () => {
4 | return (
5 |
6 |
Home!
7 |
8 | );
9 | };
10 |
11 | export default Home;
12 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/JSResource.ts:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | class JSResourceImpl {
4 | private _loader: () => Promise;
5 | private _result: T | null;
6 | private _error: unknown | null;
7 | private _promise: Promise | null;
8 | private _moduleId: string | number;
9 | constructor(moduleId: string | number, loader: () => Promise) {
10 | this._loader = loader;
11 | this._result = null;
12 | this._error = null;
13 | this._promise = null;
14 | this._moduleId = moduleId;
15 | this._populateIfLoaded();
16 | }
17 |
18 | load() {
19 | console.log("load!!");
20 | this._populateIfLoaded();
21 | let promise = this._promise;
22 | if (promise == null) {
23 | promise = this._loader().then(
24 | (result: any) => {
25 | this._result = result.default;
26 | return result.default;
27 | },
28 | (error) => {
29 | this._error = error;
30 | throw error;
31 | }
32 | );
33 |
34 | this._promise = promise;
35 | }
36 |
37 | return promise;
38 | }
39 |
40 | get() {
41 | if (this._result != null) {
42 | console.log("loaded");
43 | return this._result;
44 | }
45 | }
46 |
47 | getModuleIfRequired() {
48 | return this.get();
49 | }
50 |
51 | read() {
52 | this._populateIfLoaded();
53 | if (this._result != null) {
54 | return this._result;
55 | } else if (this._error != null) {
56 | throw this._error;
57 | } else {
58 | throw this._promise;
59 | }
60 | }
61 | getModuleId(): string {
62 | return this._moduleId.toString();
63 | }
64 |
65 | _populateIfLoaded() {
66 | const moduleIsLoaded = __webpack_modules__.hasOwnProperty(this._moduleId);
67 | if (this._result == null || this._promise == null) {
68 | if (moduleIsLoaded) {
69 | const m = __webpack_require__(this._moduleId);
70 | this._promise = Promise.resolve(m.default);
71 | this._result = m.default;
72 | }
73 | }
74 | }
75 | }
76 |
77 | const resourceMap: Map> = new Map();
78 |
79 | export type JSResource = {
80 | getModuleIfRequired(): T | null;
81 | load(): Promise;
82 | };
83 |
84 | export default function JSResource(
85 | moduleId: string | number,
86 | loader: () => Promise
87 | ): JSResource {
88 | let resource = resourceMap.get(moduleId);
89 | if (resource == null) {
90 | resource = new JSResourceImpl(moduleId, loader);
91 | resourceMap.set(moduleId, resource);
92 | }
93 | return resource as JSResource;
94 | }
95 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/RelayEnvironment.ts:
--------------------------------------------------------------------------------
1 | import { Environment, Network, Store, RecordSource } from "relay-runtime";
2 |
3 | const network = Network.create((request, variables, cacheConfig) => {
4 | return fetch("/api/graphql", {
5 | headers: {
6 | "content-type": "application/json",
7 | accept: "application/json",
8 | },
9 | method: "POST",
10 | body: JSON.stringify({
11 | query: request.id ?? request.text,
12 | variables,
13 | }),
14 | })
15 | .then((response) => response.json())
16 | .then((payload) =>
17 | Array.isArray(payload.errors) ? Promise.reject(payload) : payload
18 | );
19 | });
20 |
21 | const RelayEnvironment = new Environment({
22 | network,
23 | store: new Store(new RecordSource(), { gcReleaseBufferSize: 10 }),
24 | });
25 |
26 | export { RelayEnvironment };
27 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/Repository.entrypoint.ts:
--------------------------------------------------------------------------------
1 | import { createEntryPoint } from "./EntryPointConfig";
2 | import RepositoryQuery from "./__generated__/RepositoryQuery.graphql";
3 |
4 | export default createEntryPoint<{ owner: string; name: string }>({
5 | root: () => import("./Repository"),
6 | getPreloadProps(params) {
7 | return {
8 | queries: {
9 | repositoryQuery: {
10 | parameters: RepositoryQuery,
11 | variables: {
12 | owner: params.owner,
13 | name: params.name,
14 | },
15 | },
16 | },
17 | };
18 | },
19 | });
20 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/Repository.route.ts:
--------------------------------------------------------------------------------
1 | import { createRoute } from "./RouteConfig";
2 | import RootRoute from "./Root.route";
3 | import RepositoryEntrypoint from "./Repository.entrypoint";
4 |
5 | export default createRoute({
6 | parent: RootRoute,
7 | path: "/:owner/:name",
8 | exact: true,
9 | entryPoint: RepositoryEntrypoint,
10 | });
11 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/Repository.tsx:
--------------------------------------------------------------------------------
1 | import { PreloadedQuery } from "react-relay/lib/relay-experimental/EntryPointTypes";
2 | import { usePreloadedQuery, graphql } from "react-relay/hooks";
3 | import { RepositoryQuery } from "./__generated__/RepositoryQuery.graphql";
4 |
5 | export default function Repository(props: {
6 | queries: { repositoryQuery: PreloadedQuery };
7 | }) {
8 | const data = usePreloadedQuery(
9 | graphql`
10 | query RepositoryQuery($owner: String!, $name: String!) {
11 | repository(owner: $owner, name: $name) {
12 | nameWithOwner
13 | }
14 | }
15 | `,
16 | props.queries.repositoryQuery
17 | );
18 |
19 | return {data.repository?.nameWithOwner}
;
20 | }
21 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/Root.entrypoint.ts:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import { createEntryPoint } from "./EntryPointConfig";
4 |
5 | export default createEntryPoint({
6 | root: () => import("./Root"),
7 | getPreloadProps() {
8 | return {};
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/Root.route.ts:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import { createRoute } from "./RouteConfig";
4 | import RootEntryPoint from "./Root.entrypoint";
5 | export default createRoute({
6 | entryPoint: RootEntryPoint,
7 | });
8 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/Root.tsx:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import { ReactNode, Suspense } from "react";
4 |
5 | function Fallback() {
6 | return (
7 |
10 | );
11 | }
12 |
13 | export default function Root(props: { props: { children: ReactNode } }) {
14 | console.log(props);
15 |
16 | return (
17 | }>{props.props.children ?? null}
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/RouteConfig.ts:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import type { EntryPoint } from "react-relay/lib/relay-experimental/EntryPointTypes";
4 |
5 | export function createRoute(routeConfig: {
6 | parent?: Route;
7 | path?: string;
8 | exact?: boolean;
9 | entryPoint: EntryPoint;
10 | }): Route {
11 | return routeConfig;
12 | }
13 |
14 | type Route = Readonly<{
15 | parent?: Route;
16 | path?: string;
17 | exact?: boolean;
18 | entryPoint: EntryPoint;
19 | }>;
20 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/Router.tsx:
--------------------------------------------------------------------------------
1 | /** @format */
2 | import { Suspense } from "react";
3 | import { EntryPoint } from "react-relay/lib/relay-experimental/EntryPointTypes";
4 | import { createBrowserHistory, Location } from "history";
5 | import { matchRoutes, MatchedRoute } from "react-router-config";
6 |
7 | import {
8 | // @ts-expect-error
9 | loadEntryPoint,
10 | EntryPointContainer,
11 | RelayEnvironmentProvider,
12 | } from "react-relay/hooks";
13 |
14 | import ErrorBoundary from "./ErrorBoundary";
15 | import { RelayEnvironment } from "./RelayEnvironment";
16 | const context = require.context(".", true, /\.route\.ts$/);
17 |
18 | const routes = context.keys().map((moduleId) => {
19 | const module = context(moduleId);
20 | return module.default;
21 | });
22 |
23 | const environmentProvider = {
24 | getEnvironment() {
25 | return RelayEnvironment;
26 | },
27 | };
28 |
29 | type Route = {
30 | path?: string;
31 | exact?: boolean;
32 | entryPoint: EntryPoint<{}, {}>;
33 | routes: Route[];
34 | };
35 |
36 | const routeConfig: Route[] = [];
37 |
38 | for (const route of routes) {
39 | if (route.parent) {
40 | let foundParent = false;
41 | for (const possibleParent of routes) {
42 | if (possibleParent === route.parent) {
43 | possibleParent.routes = possibleParent.routes || [];
44 | possibleParent.routes.push(route);
45 | foundParent = true;
46 | break;
47 | }
48 | }
49 | if (!foundParent) {
50 | throw new Error("Unknown route parent");
51 | }
52 | } else {
53 | routeConfig.push(route);
54 | }
55 | }
56 |
57 | console.log(routeConfig);
58 |
59 | function createRouter() {
60 | const history = createBrowserHistory();
61 | const initialMatches = matchRoute(routeConfig, history.location);
62 | const initialEntries = prepareMatches(initialMatches);
63 | let currentEntry = {
64 | location: history.location,
65 | entries: initialEntries,
66 | };
67 | console.log(currentEntry);
68 |
69 | return {
70 | currentEntry,
71 | };
72 | }
73 |
74 | /**
75 | * Match the current location to the corresponding route entry.
76 | */
77 | function matchRoute(routes: Route[], location: Location) {
78 | const pathname = location.pathname;
79 | const matchedRoutes = matchRoutes(routes, pathname);
80 | if (!Array.isArray(matchedRoutes) || matchedRoutes.length === 0) {
81 | throw new Error("No route for " + pathname);
82 | }
83 | return matchedRoutes;
84 | }
85 |
86 | function prepareMatches(matches: MatchedRoute<{}>[]) {
87 | return matches.map(({ match, route }) => {
88 | const entryPoint = loadEntryPoint(
89 | environmentProvider,
90 | route.entryPoint,
91 | match.params
92 | );
93 | return { entryPoint, match };
94 | });
95 | }
96 |
97 | const router = createRouter();
98 |
99 | function Router() {
100 | const { currentEntry } = router;
101 |
102 | const reversedEntries = currentEntry.entries.slice().reverse();
103 | const firstEntry = reversedEntries[0];
104 |
105 | let routeComponent = (
106 |
110 | );
111 |
112 | for (let ii = 1; ii < reversedEntries.length; ii++) {
113 | const nextItem = reversedEntries[ii];
114 | routeComponent = (
115 |
119 | );
120 | }
121 |
122 | return (
123 |
124 | "Error"}>
125 | {routeComponent}
126 |
127 |
128 | );
129 | }
130 |
131 | export default Router;
132 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/ServerEntry.ts:
--------------------------------------------------------------------------------
1 | import { matchRoutes, MatchedRoute } from "react-router-config";
2 | import { EntryPoint } from "react-relay/lib/relay-experimental/EntryPointTypes";
3 | import { Location, parsePath } from "history";
4 | const context = require.context(".", true, /\.route\.ts$/);
5 |
6 | const routes = context.keys().map((moduleId) => {
7 | const module = context(moduleId);
8 | return module.default;
9 | });
10 |
11 | type Route = {
12 | path?: string;
13 | exact?: boolean;
14 | entryPoint: EntryPoint<{}, {}>;
15 | routes: Route[];
16 | };
17 |
18 | const routeConfig: Route[] = [];
19 |
20 | for (const route of routes) {
21 | if (route.parent) {
22 | let foundParent = false;
23 | for (const possibleParent of routes) {
24 | if (possibleParent === route.parent) {
25 | possibleParent.routes = possibleParent.routes || [];
26 | possibleParent.routes.push(route);
27 | foundParent = true;
28 | break;
29 | }
30 | }
31 | if (!foundParent) {
32 | throw new Error("Unknown route parent");
33 | }
34 | } else {
35 | routeConfig.push(route);
36 | }
37 | }
38 |
39 | /**
40 | * Match the current location to the corresponding route entry.
41 | */
42 | function matchRoute(routes: Route[], pathname: string) {
43 | const matchedRoutes = matchRoutes(routes, pathname);
44 | if (!Array.isArray(matchedRoutes) || matchedRoutes.length === 0) {
45 | throw new Error("No route for " + pathname);
46 | }
47 | return matchedRoutes;
48 | }
49 |
50 | export function getQueriesAndModulesForUrl(url: string) {
51 | const partialPath = parsePath(url);
52 |
53 | const matches = matchRoute(routeConfig, partialPath.pathname!);
54 |
55 | const modules = [];
56 | const queries = [];
57 |
58 | const entryPoints = [];
59 | while (matches.length !== 0) {
60 | const matched = matches.pop();
61 | const route = matched?.route;
62 | const match = matched?.match;
63 | entryPoints.push(route?.entryPoint);
64 | while (entryPoints.length !== 0) {
65 | const entryPoint = entryPoints.pop();
66 | modules.push(entryPoint.root.getModuleId());
67 | const props = entryPoint.getPreloadProps(match?.params);
68 | if (props.queries) {
69 | for (const queryname of Object.keys(props.queries)) {
70 | if (props.queries.hasOwnProperty(queryname)) {
71 | const query = props.queries[queryname];
72 | queries.push(query);
73 | }
74 | }
75 | }
76 |
77 | if (props.entryPoints) {
78 | for (const subEntryPointName of Object.keys(props.entryPoints)) {
79 | if (props.entryPoints.hasOwnProperty(subEntryPointName)) {
80 | const subEntryPoint = props.entryPoints[subEntryPointName];
81 | entryPoints.push(subEntryPoint);
82 | }
83 | }
84 | }
85 | }
86 | }
87 |
88 | return { modules, queries };
89 | }
90 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/Shell.tsx:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import { unstable_createRoot } from "react-dom";
4 | import { nullThrows } from "./nullThrows";
5 | import Router from "./Router";
6 |
7 | const node = document.getElementById("app");
8 | nullThrows(node, "no app node found");
9 | const root = unstable_createRoot(node);
10 |
11 | root.render();
12 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/__generated__/RepositoryQuery.graphql.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @ts-nocheck
4 | /* @relayHash 4a45f359f7fe4a783fae695058a98919 */
5 |
6 | import { ConcreteRequest } from "relay-runtime";
7 | export type RepositoryQueryVariables = {
8 | owner: string;
9 | name: string;
10 | };
11 | export type RepositoryQueryResponse = {
12 | readonly repository: {
13 | readonly nameWithOwner: string;
14 | } | null;
15 | };
16 | export type RepositoryQuery = {
17 | readonly response: RepositoryQueryResponse;
18 | readonly variables: RepositoryQueryVariables;
19 | };
20 |
21 |
22 |
23 | /*
24 | query RepositoryQuery(
25 | $owner: String!
26 | $name: String!
27 | ) {
28 | repository(owner: $owner, name: $name) {
29 | nameWithOwner
30 | id
31 | }
32 | }
33 | */
34 |
35 | const node: ConcreteRequest = (function(){
36 | var v0 = {
37 | "defaultValue": null,
38 | "kind": "LocalArgument",
39 | "name": "name"
40 | },
41 | v1 = {
42 | "defaultValue": null,
43 | "kind": "LocalArgument",
44 | "name": "owner"
45 | },
46 | v2 = [
47 | {
48 | "kind": "Variable",
49 | "name": "name",
50 | "variableName": "name"
51 | },
52 | {
53 | "kind": "Variable",
54 | "name": "owner",
55 | "variableName": "owner"
56 | }
57 | ],
58 | v3 = {
59 | "alias": null,
60 | "args": null,
61 | "kind": "ScalarField",
62 | "name": "nameWithOwner",
63 | "storageKey": null
64 | };
65 | return {
66 | "fragment": {
67 | "argumentDefinitions": [
68 | (v0/*: any*/),
69 | (v1/*: any*/)
70 | ],
71 | "kind": "Fragment",
72 | "metadata": null,
73 | "name": "RepositoryQuery",
74 | "selections": [
75 | {
76 | "alias": null,
77 | "args": (v2/*: any*/),
78 | "concreteType": "Repository",
79 | "kind": "LinkedField",
80 | "name": "repository",
81 | "plural": false,
82 | "selections": [
83 | (v3/*: any*/)
84 | ],
85 | "storageKey": null
86 | }
87 | ],
88 | "type": "Query",
89 | "abstractKey": null
90 | },
91 | "kind": "Request",
92 | "operation": {
93 | "argumentDefinitions": [
94 | (v1/*: any*/),
95 | (v0/*: any*/)
96 | ],
97 | "kind": "Operation",
98 | "name": "RepositoryQuery",
99 | "selections": [
100 | {
101 | "alias": null,
102 | "args": (v2/*: any*/),
103 | "concreteType": "Repository",
104 | "kind": "LinkedField",
105 | "name": "repository",
106 | "plural": false,
107 | "selections": [
108 | (v3/*: any*/),
109 | {
110 | "alias": null,
111 | "args": null,
112 | "kind": "ScalarField",
113 | "name": "id",
114 | "storageKey": null
115 | }
116 | ],
117 | "storageKey": null
118 | }
119 | ]
120 | },
121 | "params": {
122 | "id": "4a45f359f7fe4a783fae695058a98919",
123 | "metadata": {},
124 | "name": "RepositoryQuery",
125 | "operationKind": "query",
126 | "text": null
127 | }
128 | };
129 | })();
130 | (node as any).hash = 'eec3c75ce7b55fdc389904b17ee1ba63';
131 | export default node;
132 |
--------------------------------------------------------------------------------
/relay-entrypoints/src/nullThrows.ts:
--------------------------------------------------------------------------------
1 | export function nullThrows(
2 | thing: T | null | undefined,
3 | message: string
4 | ): asserts thing is T {
5 | if (thing == null) {
6 | throw new Error(message);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/relay-entrypoints/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "jsx": "preserve"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/relay-entrypoints/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require("webpack");
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 | const https = require("https");
4 | const fs = require("fs");
5 | const bodyParser = require("body-parser");
6 |
7 | module.exports = [
8 | {
9 | mode: "development",
10 | entry: "./src/Shell.tsx",
11 | resolve: {
12 | extensions: [".ts", ".tsx", ".js"],
13 | },
14 | output: {
15 | publicPath: "/",
16 | path: __dirname + "/out/client",
17 | },
18 | devServer: {
19 | contentBase: __dirname + "/public",
20 | port: 8731,
21 | historyApiFallback: true,
22 | before: (app, server, compiler) => {
23 | app.use(bodyParser.json());
24 | },
25 | after: (app, server, compiler) => {
26 | app.post("/api/graphql", (req, res) => {
27 | if (req.header("content-type") === "application/json") {
28 | const body = req.body;
29 | fs.readFile("./queries.json", (err, data) => {
30 | const queries = JSON.parse(data);
31 | const postData = JSON.stringify({
32 | query: queries[body.query] || body.query,
33 | variables: body.variables,
34 | });
35 | const options = {
36 | hostname: "api.github.com",
37 | //port: 443,
38 | path: "/graphql",
39 | method: "POST",
40 | headers: {
41 | "User-Agent": req.header("user-agent"),
42 | Authorization:
43 | "Bearer " + process.env.REACT_APP_GITHUB_AUTH_TOKEN,
44 | "Content-Type": "application/json",
45 | "Content-Length": Buffer.byteLength(postData),
46 | },
47 | };
48 |
49 | const apiReq = https.request(options, (githubResponse) => {
50 | res.status(githubResponse.statusCode);
51 | githubResponse.setEncoding("utf8");
52 |
53 | githubResponse.on("data", (chunk) => {
54 | res.write(chunk);
55 | });
56 | githubResponse.on("end", () => {
57 | res.end();
58 | });
59 | });
60 |
61 | apiReq.on("error", (error) => {
62 | res.status(500).send({ message: error.message });
63 | });
64 | apiReq.write(postData);
65 | apiReq.end();
66 | });
67 | } else {
68 | res.status(415).end();
69 | }
70 | });
71 | },
72 | },
73 | module: {
74 | rules: [
75 | {
76 | test: /\.entrypoint\.ts$/,
77 | use: [
78 | {
79 | loader: "./js-resource-loader",
80 | },
81 | ],
82 | },
83 | {
84 | test: /\.(tsx?)$/,
85 | exclude: /node_modules/,
86 | use: [
87 | {
88 | loader: "babel-loader",
89 | options: {
90 | // extends: path.resolve(__dirname, "../babel.config.js")
91 | },
92 | },
93 | ],
94 | },
95 | ],
96 | },
97 | plugins: [
98 | //new JSResourcePlugin(),
99 | new HtmlWebpackPlugin({
100 | inject: false,
101 | templateContent: ({ htmlWebpackPlugin }) => `
102 |
103 |
104 | ${htmlWebpackPlugin.tags.headTags}
105 |
106 |
107 |
108 | ${htmlWebpackPlugin.tags.bodyTags.slice(0, 1)}
109 |
110 |
111 | ${htmlWebpackPlugin.tags.bodyTags.slice(1)}
112 |
113 |
114 | `,
115 | }),
116 | ],
117 | optimization: {
118 | runtimeChunk: {
119 | name: (entrypoint) => `runtime~${entrypoint.name}`,
120 | },
121 | },
122 | },
123 | {
124 | target: "node",
125 | mode: "development",
126 | entry: { server: "./src/ServerEntry.ts" },
127 | resolve: {
128 | extensions: [".ts", ".tsx", ".js"],
129 | },
130 | output: {
131 | libraryTarget: "commonjs",
132 | publicPath: "/",
133 | path: __dirname + "/out/server",
134 | },
135 | module: {
136 | rules: [
137 | {
138 | test: /\.entrypoint\.ts$/,
139 | use: [
140 | {
141 | loader: "./js-resource-loader",
142 | },
143 | ],
144 | },
145 | {
146 | test: /\.(tsx?)$/,
147 | exclude: /node_modules/,
148 | use: [
149 | {
150 | loader: "babel-loader",
151 | options: {
152 | // extends: path.resolve(__dirname, "../babel.config.js")
153 | },
154 | },
155 | ],
156 | },
157 | ],
158 | },
159 | },
160 | ];
161 |
--------------------------------------------------------------------------------