';
55 |
56 | // hydrate graphql state
57 | const scriptString = '';
58 |
59 | const finalHTML = rawHTML
60 | .replace(appString, `
${rendered}
`)
61 | .replace(
62 | scriptString,
63 | ``,
69 | );
70 |
71 | res.end(finalHTML);
72 | } catch (e) {
73 | console.error(e);
74 | res.writeHead(500);
75 | res.end();
76 | }
77 | };
78 |
--------------------------------------------------------------------------------
/app/GraphqlHooks.bs.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | import * as Block from "bs-platform/lib/es6/block.js";
4 | import * as Curry from "bs-platform/lib/es6/curry.js";
5 | import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js";
6 | import * as Caml_option from "bs-platform/lib/es6/caml_option.js";
7 | import * as GraphqlHooks from "graphql-hooks";
8 | import * as GraphqlHooksMemcache from "graphql-hooks-memcache";
9 |
10 | var MemCache = { };
11 |
12 | function createMemCache(initialState) {
13 | return GraphqlHooksMemcache.default(initialState);
14 | }
15 |
16 | var Client = { };
17 |
18 | function createClient(url, cache) {
19 | return new GraphqlHooks.GraphQLClient({
20 | url: url,
21 | cache: cache
22 | });
23 | }
24 |
25 | var provider = GraphqlHooks.ClientContext.Provider;
26 |
27 | var Provider = {
28 | provider: provider
29 | };
30 |
31 | function extractErrorMessage(clientRequestError) {
32 | if (clientRequestError !== undefined) {
33 | var match = clientRequestError;
34 | var match$1 = match.fetchError;
35 | if (match$1 !== undefined) {
36 | return match$1.message;
37 | } else {
38 | var match$2 = match.httpError;
39 | if (match$2 !== undefined) {
40 | return match$2.body;
41 | } else {
42 | var match$3 = match.graphQLErrors;
43 | if (match$3 !== undefined) {
44 | return Belt_Array.reduce(match$3, "", (function (currentMsg, error) {
45 | return currentMsg + (", " + error.message);
46 | }));
47 | } else {
48 | return ;
49 | }
50 | }
51 | }
52 | }
53 |
54 | }
55 |
56 | function useQuery(query) {
57 | var result = GraphqlHooks.useQuery(query.query);
58 | var match = result.loading;
59 | var match$1 = extractErrorMessage(result.error);
60 | var match$2 = result.data;
61 | if (match) {
62 | return /* Loading */0;
63 | } else if (match$1 !== undefined) {
64 | if (match$2 !== undefined) {
65 | return /* Error */Block.__(0, ["Something went wrong"]);
66 | } else {
67 | return /* Error */Block.__(0, [match$1]);
68 | }
69 | } else if (match$2 !== undefined) {
70 | return /* Data */Block.__(1, [Curry._1(query.parse, Caml_option.valFromOption(match$2))]);
71 | } else {
72 | return /* Error */Block.__(0, ["Something went wrong"]);
73 | }
74 | }
75 |
76 | export {
77 | MemCache ,
78 | createMemCache ,
79 | Client ,
80 | createClient ,
81 | Provider ,
82 | extractErrorMessage ,
83 | useQuery ,
84 |
85 | }
86 | /* provider Not a pure module */
87 |
--------------------------------------------------------------------------------
/app/GraphqlHooks.re:
--------------------------------------------------------------------------------
1 | module MemCache = {
2 | type t;
3 |
4 | type config = {initialState: Js.Json.t};
5 |
6 | type conf = Js.Json.t;
7 |
8 | [@bs.module "graphql-hooks-memcache"]
9 | external _createMemCache: config => t = "default";
10 | };
11 |
12 | let createMemCache = (~initialState) => {
13 | let config = {
14 | initialState;
15 | };
16 | MemCache._createMemCache(config);
17 | };
18 |
19 | module Client = {
20 | type t;
21 |
22 | type config = {
23 | url: string,
24 | cache: MemCache.t,
25 | };
26 |
27 | [@bs.module "graphql-hooks"] [@bs.new]
28 | external _createClient: config => t = "GraphQLClient";
29 | };
30 |
31 | let createClient = (~url: string, ~cache: MemCache.t) => {
32 | open Client;
33 | let config = {url, cache};
34 | Client._createClient(config);
35 | };
36 |
37 | [@bs.val] [@bs.module "graphql-hooks"]
38 | external context: React.Context.t(Client.t) = "ClientContext";
39 |
40 | module Provider = {
41 | let provider = React.Context.provider(context);
42 |
43 | [@react.component] [@bs.module "graphql-hooks"] [@bs.scope "ClientContext"]
44 | external make: (~value: Client.t, ~children: React.element) => React.element =
45 | "Provider";
46 | };
47 |
48 | type error = {message: string};
49 |
50 | type httpError = {
51 | status: int,
52 | statusText: string,
53 | body: string,
54 | };
55 |
56 | type clientRequestError = {
57 | fetchError: option(error),
58 | httpError: option(httpError),
59 | graphQLErrors: option(array(error)),
60 | };
61 |
62 | type clientRequestResult('any) = {
63 | loading: bool,
64 | cacheHit: bool,
65 | error: option(clientRequestError),
66 | data: 'any,
67 | };
68 |
69 | type queryResponse('a) =
70 | | Loading
71 | | Error(string)
72 | | Data('a);
73 |
74 | [@bs.module "graphql-hooks"]
75 | external _useQuery: string => clientRequestResult('any) = "useQuery";
76 |
77 | let extractErrorMessage = (~clientRequestError) => {
78 | switch (clientRequestError) {
79 | | Some({fetchError: Some(fetchError), _}) => Some(fetchError.message)
80 | | Some({httpError: Some(httpError), _}) => Some(httpError.body)
81 | | Some({graphQLErrors: Some(graphQLErrors), _}) =>
82 | graphQLErrors
83 | ->Belt.Array.reduce("", (currentMsg, error) =>
84 | currentMsg ++ ", " ++ error.message
85 | )
86 | ->Some
87 | | _ => None
88 | };
89 | };
90 |
91 | let useQuery = (~query) => {
92 | let result = _useQuery(query##query);
93 | switch (
94 | result.loading,
95 | extractErrorMessage(~clientRequestError=result.error),
96 | result.data,
97 | ) {
98 | | (true, _, _) => Loading
99 | | (false, None, Some(response)) => Data(response |> query##parse)
100 | | (false, Some(message), None) => Error(message)
101 | | _ => Error("Something went wrong")
102 | };
103 | };
104 |
--------------------------------------------------------------------------------
/packages/api/StoryData.bs.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | import * as $$Array from "bs-platform/lib/es6/array.js";
4 | import * as Belt_Array from "bs-platform/lib/es6/belt_Array.js";
5 | import * as Caml_int32 from "bs-platform/lib/es6/caml_int32.js";
6 | import * as Json_decode from "@glennsl/bs-json/src/Json_decode.bs.js";
7 |
8 | require('isomorphic-fetch')
9 | ;
10 |
11 | var apiBaseUrl = "https://hacker-news.firebaseio.com";
12 |
13 | function topStoriesUrl(param) {
14 | return "" + (String(apiBaseUrl) + "/v0/topstories.json");
15 | }
16 |
17 | function storyUrl(id) {
18 | return "" + (String(apiBaseUrl) + ("/v0/item/" + (String(id) + ".json")));
19 | }
20 |
21 | function idsArray(json) {
22 | return Json_decode.array(Json_decode.$$int, json);
23 | }
24 |
25 | function story(json) {
26 | return {
27 | by: Json_decode.field("by", Json_decode.string, json),
28 | descendants: Json_decode.optional((function (param) {
29 | return Json_decode.field("descendants", Json_decode.$$int, param);
30 | }), json),
31 | id: Json_decode.field("id", Json_decode.$$int, json),
32 | score: Json_decode.field("score", Json_decode.$$int, json),
33 | time: Json_decode.field("time", Json_decode.$$int, json),
34 | title: Json_decode.field("title", Json_decode.string, json),
35 | url: Json_decode.optional((function (param) {
36 | return Json_decode.field("url", Json_decode.string, param);
37 | }), json)
38 | };
39 | }
40 |
41 | function stories(json) {
42 | return Json_decode.array(story, json);
43 | }
44 |
45 | var Decode = {
46 | idsArray: idsArray,
47 | story: story,
48 | stories: stories
49 | };
50 |
51 | function getStory(id) {
52 | return fetch(storyUrl(id)).then((function (prim) {
53 | return prim.json();
54 | })).then((function (json) {
55 | return Promise.resolve(story(json));
56 | }));
57 | }
58 |
59 | function sliced(array, page) {
60 | var arg = Caml_int32.imul(page, 25);
61 | return (function (param) {
62 | return Belt_Array.slice(param, arg, 25);
63 | })(array);
64 | }
65 |
66 | function getStoriesForIds(ids) {
67 | return Promise.all($$Array.map(getStory, ids)).then((function (res) {
68 | return Promise.resolve(res);
69 | }));
70 | }
71 |
72 | function getTopStories(page) {
73 | return fetch(topStoriesUrl(/* () */0)).then((function (prim) {
74 | return prim.json();
75 | })).then((function (json) {
76 | return Promise.resolve(sliced(Json_decode.array(Json_decode.$$int, json), page));
77 | })).then(getStoriesForIds);
78 | }
79 |
80 | var perPage = 25;
81 |
82 | export {
83 | apiBaseUrl ,
84 | topStoriesUrl ,
85 | storyUrl ,
86 | Decode ,
87 | getStory ,
88 | perPage ,
89 | sliced ,
90 | getStoriesForIds ,
91 | getTopStories ,
92 |
93 | }
94 | /* Not a pure module */
95 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## hackerz
2 |
3 | [View the application](https://hackerz.now.sh)
4 |
5 | Ultra high performance progressive web application built with React + Reason (hooks, react ppx 3), GraphQL (api and client) and rollup.
6 |
7 | [](https://percy.io)
8 |
9 | ## Features
10 |
11 | - Progressive web app
12 | - offline
13 | - install prompts on supported platforms
14 | - Server side rendering (including prefetching graphql queries for the current route)
15 | - GraphQL (client using graphql-hooks)
16 | - GrappQL (server, using reason-graphql)
17 | - Rollup (dual bundling, module and nomodule)
18 | - Now.sh 2.x
19 | - Reason React (hooks, react ppx 3)
20 | - Yarn (monorepo with workspaces)
21 | - Routing (including ssr support, static routing)
22 |
23 | ## Things to know
24 |
25 | - A production build is deployed from a merge to master
26 | - A staging build is deployed from a PR against master
27 |
28 | ## Setting the project up locally
29 |
30 | First of all make sure you are using node `8.11.3` and latest yarn, you can always have a look at the `engines` section of the `package.json`.
31 |
32 | ```sh
33 | $ yarn (install)
34 | $ yarn dev
35 | ```
36 |
37 | After doing this, you'll have a server with hot-reloading (ui only) running at [http://localhost:8004](http://localhost:8004)
38 |
39 | You can also start in production.
40 |
41 | ```sh
42 | $ yarn start
43 | ```
44 |
45 | After doing this, you'll have a server running at [http://localhost:8004](http://localhost:8004)
46 |
47 | ## If working on the graphql server and want hot reloading
48 |
49 | ```sh
50 | $ yarn dev-graphql
51 | ```
52 |
53 | After doing this, you'll have a server with hot-reloading running at [http://localhost:3000](http://localhost:3000)
54 |
55 | ## When changing the graphql server schema
56 |
57 | ```sh
58 | $ yarn send-introspection-query http://localhost:8004/api/graphql
59 | ```
60 |
61 | ## Run tests and friends
62 |
63 | We don't want to use snapshots, we use also use [react-testing-library](https://github.com/testing-library/react-testing-library) to avoid having to use enzyme and to enforce best test practices.
64 |
65 | ```sh
66 | $ yarn lint
67 | $ yarn build
68 | $ yarn test
69 | ```
70 |
71 | or
72 |
73 | ```sh
74 | $ yarn ci
75 | ```
76 |
77 | ## End to end tests
78 |
79 | The end to end test go fetch latest news from the server and expect it to be found inside the homepage. Please check `e2e/basic.test.js` for more details.
80 |
81 | ```sh
82 | $ yarn e2e
83 | ```
84 |
85 | ## Storybook
86 |
87 | This is where we list all our components (comes with hot reloading)
88 |
89 | ```sh
90 | $ yarn storybook
91 | ```
92 |
93 | After doing this, you'll have a showcase page running at [http://localhost:6006](http://localhost:6006)
94 |
95 | ## CI
96 |
97 | We are using [Github Actions](https://help.github.com/en/articles/about-github-actions).
98 |
99 | ### Useful commands
100 |
101 | ```sh
102 | # force a deploy
103 | $ now
104 |
105 | # check all running instances
106 | $ now ls
107 |
108 | # check logs for a given instance
109 | $ now logs hackerz.now.sh --all
110 | ```
111 |
--------------------------------------------------------------------------------
/packages/api/GraphqlSchema.bs.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | import * as Curry from "bs-platform/lib/es6/curry.js";
4 | import * as Future from "reason-future/src/Future.bs.js";
5 | import * as Caml_obj from "bs-platform/lib/es6/caml_obj.js";
6 | import * as FutureJs from "reason-future/src/FutureJs.bs.js";
7 | import * as Belt_List from "bs-platform/lib/es6/belt_List.js";
8 | import * as GraphqlFuture from "reason-graphql/src/variations/GraphqlFuture.bs.js";
9 | import * as CamlinternalLazy from "bs-platform/lib/es6/camlinternalLazy.js";
10 | import * as StoryData$Hackerz from "./StoryData.bs.js";
11 |
12 | var storyTypeLazy = Caml_obj.caml_lazy_make((function (param) {
13 | return Curry._4(GraphqlFuture.Schema.obj, undefined, undefined, (function (param) {
14 | return /* :: */[
15 | Curry._6(GraphqlFuture.Schema.field, undefined, undefined, /* [] */0, (function (_ctx, story) {
16 | return story.id;
17 | }), "id", Curry._1(GraphqlFuture.Schema.nonnull, GraphqlFuture.Schema.$$int)),
18 | /* :: */[
19 | Curry._6(GraphqlFuture.Schema.field, undefined, undefined, /* [] */0, (function (_ctx, story) {
20 | return story.title;
21 | }), "title", Curry._1(GraphqlFuture.Schema.nonnull, GraphqlFuture.Schema.string)),
22 | /* [] */0
23 | ]
24 | ];
25 | }), "story");
26 | }));
27 |
28 | var storyType = CamlinternalLazy.force(storyTypeLazy);
29 |
30 | function handleJsPromiseError(prim) {
31 | return String(prim);
32 | }
33 |
34 | var query = Curry._1(GraphqlFuture.Schema.query, /* :: */[
35 | Curry._6(GraphqlFuture.Schema.async_field, undefined, undefined, /* :: */[
36 | Curry._3(GraphqlFuture.Schema.Arg.arg, undefined, "id", Curry._1(GraphqlFuture.Schema.Arg.nonnull, GraphqlFuture.Schema.Arg.$$int)),
37 | /* [] */0
38 | ], (function (_ctx, param, id) {
39 | return Future.mapOk(FutureJs.fromPromise(StoryData$Hackerz.getStory(id), handleJsPromiseError), (function (result) {
40 | return result;
41 | }));
42 | }), "story", storyType),
43 | /* :: */[
44 | Curry._6(GraphqlFuture.Schema.async_field, undefined, undefined, /* :: */[
45 | Curry._3(GraphqlFuture.Schema.Arg.arg, undefined, "page", Curry._1(GraphqlFuture.Schema.Arg.nonnull, GraphqlFuture.Schema.Arg.$$int)),
46 | /* [] */0
47 | ], (function (_ctx, param, page) {
48 | return Future.mapOk(FutureJs.fromPromise(StoryData$Hackerz.getTopStories(page), handleJsPromiseError), (function (result) {
49 | return Belt_List.fromArray(result);
50 | }));
51 | }), "topStories", Curry._1(GraphqlFuture.Schema.list, Curry._1(GraphqlFuture.Schema.nonnull, storyType))),
52 | /* [] */0
53 | ]
54 | ]);
55 |
56 | var schema = Curry._2(GraphqlFuture.Schema.create, undefined, query);
57 |
58 | export {
59 | storyTypeLazy ,
60 | storyType ,
61 | handleJsPromiseError ,
62 | query ,
63 | schema ,
64 |
65 | }
66 | /* storyType Not a pure module */
67 |
--------------------------------------------------------------------------------
/graphql_schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "__schema": {
3 | "directives": [],
4 | "types": [
5 | {
6 | "possibleTypes": null,
7 | "enumValues": null,
8 | "interfaces": null,
9 | "inputFields": null,
10 | "fields": null,
11 | "description": null,
12 | "name": "Int",
13 | "kind": "SCALAR"
14 | },
15 | {
16 | "possibleTypes": null,
17 | "enumValues": null,
18 | "interfaces": null,
19 | "inputFields": null,
20 | "fields": null,
21 | "description": null,
22 | "name": "String",
23 | "kind": "SCALAR"
24 | },
25 | {
26 | "possibleTypes": null,
27 | "enumValues": null,
28 | "interfaces": [],
29 | "inputFields": null,
30 | "fields": [
31 | {
32 | "deprecationReason": null,
33 | "isDeprecated": false,
34 | "type": {
35 | "ofType": {
36 | "ofType": null,
37 | "name": "Int",
38 | "kind": "SCALAR"
39 | },
40 | "name": null,
41 | "kind": "NON_NULL"
42 | },
43 | "args": [],
44 | "description": null,
45 | "name": "id"
46 | },
47 | {
48 | "deprecationReason": null,
49 | "isDeprecated": false,
50 | "type": {
51 | "ofType": {
52 | "ofType": null,
53 | "name": "String",
54 | "kind": "SCALAR"
55 | },
56 | "name": null,
57 | "kind": "NON_NULL"
58 | },
59 | "args": [],
60 | "description": null,
61 | "name": "title"
62 | }
63 | ],
64 | "description": null,
65 | "name": "story",
66 | "kind": "OBJECT"
67 | },
68 | {
69 | "possibleTypes": null,
70 | "enumValues": null,
71 | "interfaces": [],
72 | "inputFields": null,
73 | "fields": [
74 | {
75 | "deprecationReason": null,
76 | "isDeprecated": false,
77 | "type": {
78 | "ofType": null,
79 | "name": "story",
80 | "kind": "OBJECT"
81 | },
82 | "args": [
83 | {
84 | "defaultValue": null,
85 | "type": {
86 | "ofType": {
87 | "ofType": null,
88 | "name": "Int",
89 | "kind": "SCALAR"
90 | },
91 | "name": null,
92 | "kind": "NON_NULL"
93 | },
94 | "description": null,
95 | "name": "id"
96 | }
97 | ],
98 | "description": null,
99 | "name": "story"
100 | },
101 | {
102 | "deprecationReason": null,
103 | "isDeprecated": false,
104 | "type": {
105 | "ofType": {
106 | "ofType": {
107 | "ofType": null,
108 | "name": "story",
109 | "kind": "OBJECT"
110 | },
111 | "name": null,
112 | "kind": "NON_NULL"
113 | },
114 | "name": null,
115 | "kind": "LIST"
116 | },
117 | "args": [
118 | {
119 | "defaultValue": null,
120 | "type": {
121 | "ofType": {
122 | "ofType": null,
123 | "name": "Int",
124 | "kind": "SCALAR"
125 | },
126 | "name": null,
127 | "kind": "NON_NULL"
128 | },
129 | "description": null,
130 | "name": "page"
131 | }
132 | ],
133 | "description": null,
134 | "name": "topStories"
135 | }
136 | ],
137 | "description": null,
138 | "name": "Query",
139 | "kind": "OBJECT"
140 | }
141 | ],
142 | "subscriptionType": null,
143 | "mutationType": null,
144 | "queryType": {
145 | "name": "Query"
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hackerz",
3 | "private": true,
4 | "workspaces": {
5 | "packages": [
6 | "packages/*"
7 | ]
8 | },
9 | "engines": {
10 | "node": "12.x"
11 | },
12 | "config": {
13 | "publicDir": "dist"
14 | },
15 | "scripts": {
16 | "clean": "rimraf dist && bsb -clean-world",
17 | "dev": "run-p -c dev:*",
18 | "dev:reason": "bsb -make-world -w",
19 | "dev:rollup": "cross-env NODE_ENV=development rollup -c -w",
20 | "dev:server": "now-lambda",
21 | "dev-graphql": "run-p -c dev-graphql:*",
22 | "dev-graphql:reason": "bsb -make-world -w",
23 | "dev-graphql:api": "yarn --cwd packages/api dev",
24 | "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md,html,graphql}\"",
25 | "build": "yarn clean && yarn build:reason && yarn create-bundles",
26 | "build:reason": "bsb -make-world",
27 | "create-bundles": "cross-env NODE_ENV=production rollup -c",
28 | "start": "yarn build && now-lambda",
29 | "test": "yarn build:reason && jest --config jest.json",
30 | "test-watch": "run-p -c test-watch:*",
31 | "test-watch:reason": "yarn dev:reason",
32 | "test-watch:jest": "jest --config jest.json --watch",
33 | "lint": "run-p -c lint:*",
34 | "lint:css": "stylelint '**/*.css'",
35 | "lint:ts": "eslint '**/*.js{,x}'",
36 | "ci": "yarn lint && yarn test",
37 | "deploy": "scripts/deploy-ci.sh",
38 | "deploy:production": "now --token $NOW_TOKEN --target production",
39 | "deploy:staging": "now --token $NOW_TOKEN --target staging",
40 | "e2e": "yarn build && yarn --cwd packages/e2e jest",
41 | "storybook": "run-p -c storybook:*",
42 | "storybook:reason": "yarn dev:reason",
43 | "storybook:start": "start-storybook -p 6006",
44 | "build-storybook": "yarn build:reason && build-storybook",
45 | "snapshot-ui": "build-storybook && percy-storybook --widths=320,1280",
46 | "update-schema": "get-graphql-schema http://localhost:3000/api/graphql -j > graphql_schema.json"
47 | },
48 | "dependencies": {
49 | "@glennsl/bs-json": "5.0.2",
50 | "bs-fetch": "0.6.1",
51 | "bs-let": "0.1.16",
52 | "core-js": "3.6.5",
53 | "graphql-hooks": "5.0.0",
54 | "graphql-hooks-memcache": "2.0.0",
55 | "isomorphic-fetch": "3.0.0",
56 | "react": "16.14.0",
57 | "react-dom": "16.14.0",
58 | "reason-future": "2.5.0",
59 | "reason-graphql": "0.6.1",
60 | "reason-react": "0.8.0"
61 | },
62 | "devDependencies": {
63 | "@babel/core": "7.12.0",
64 | "@babel/polyfill": "7.11.5",
65 | "@babel/preset-env": "7.12.0",
66 | "@babel/preset-react": "7.10.4",
67 | "@baransu/graphql_ppx_re": "0.7.1",
68 | "@percy-io/percy-storybook": "2.1.0",
69 | "@storybook/addon-actions": "6.0.26",
70 | "@storybook/addon-centered": "5.3.21",
71 | "@storybook/addon-links": "6.0.26",
72 | "@storybook/addons": "6.0.26",
73 | "@storybook/react": "6.0.26",
74 | "@testing-library/jest-dom": "5.11.4",
75 | "@testing-library/react": "11.1.0",
76 | "autoprefixer": "9.8.6",
77 | "babel-jest": "26.5.2",
78 | "babel-loader": "8.1.0",
79 | "bs-platform": "7.3.2",
80 | "cross-env": "7.0.2",
81 | "eslint": "7.11.0",
82 | "eslint-config-prettier": "6.12.0",
83 | "eslint-config-synacor": "3.0.5",
84 | "eslint-plugin-jest": "24.1.0",
85 | "gentype": "3.36.0",
86 | "get-graphql-schema": "2.1.2",
87 | "husky": "4.3.0",
88 | "identity-obj-proxy": "3.0.0",
89 | "jest": "26.5.3",
90 | "jest-fetch-mock": "3.0.3",
91 | "jest-haste-map": "26.5.2",
92 | "jest-resolve": "26.5.2",
93 | "lint-staged": "10.4.0",
94 | "now": "16.7.3",
95 | "now-lambda-runner": "4.0.0",
96 | "npm-run-all": "4.1.5",
97 | "postcss-modules": "3.2.2",
98 | "prettier": "2.1.2",
99 | "rimraf": "3.0.2",
100 | "rollup": "2.30.0",
101 | "rollup-plugin-babel": "4.4.0",
102 | "rollup-plugin-commonjs": "10.1.0",
103 | "rollup-plugin-copy-assets": "2.0.1",
104 | "rollup-plugin-filesize": "9.0.2",
105 | "rollup-plugin-json": "4.0.0",
106 | "rollup-plugin-livereload": "2.0.0",
107 | "rollup-plugin-node-resolve": "5.2.0",
108 | "rollup-plugin-postcss": "3.1.8",
109 | "rollup-plugin-progress": "1.1.2",
110 | "rollup-plugin-replace": "2.2.0",
111 | "rollup-plugin-terser": "7.0.2",
112 | "rollup-plugin-workbox-build": "0.2.0",
113 | "stylelint": "13.7.2",
114 | "stylelint-config-recommended": "3.0.0",
115 | "stylelint-config-standard": "20.0.0"
116 | },
117 | "prettier": {
118 | "singleQuote": true,
119 | "trailingComma": "all",
120 | "bracketSpacing": true
121 | },
122 | "lint-staged": {
123 | "*.{js,json,css,md,html}": [
124 | "yarn format",
125 | "git add"
126 | ]
127 | },
128 | "husky": {
129 | "hooks": {
130 | "pre-commit": "lint-staged",
131 | "pre-push": "yarn lint",
132 | "post-commit": "git update-index -g"
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/app/pages/Home.bs.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | import * as $$Array from "bs-platform/lib/es6/array.js";
4 | import * as React from "react";
5 | import * as Js_exn from "bs-platform/lib/es6/js_exn.js";
6 | import * as Js_dict from "bs-platform/lib/es6/js_dict.js";
7 | import * as Js_json from "bs-platform/lib/es6/js_json.js";
8 | import * as Js_option from "bs-platform/lib/es6/js_option.js";
9 | import * as HomeCss from "./Home.css";
10 | import * as Caml_option from "bs-platform/lib/es6/caml_option.js";
11 | import * as Link$Hackerz from "../components/Link.bs.js";
12 | import * as GraphqlHooks$Hackerz from "../GraphqlHooks.bs.js";
13 |
14 | var css = HomeCss;
15 |
16 | function ste(prim) {
17 | return prim;
18 | }
19 |
20 | var ppx_printed_query = "query {\ntopStories(page: 0) {\nid \ntitle \n}\n\n}\n";
21 |
22 | function parse(value) {
23 | var value$1 = Js_option.getExn(Js_json.decodeObject(value));
24 | var match = Js_dict.get(value$1, "topStories");
25 | var tmp;
26 | if (match !== undefined) {
27 | var value$2 = Caml_option.valFromOption(match);
28 | var match$1 = Js_json.decodeNull(value$2);
29 | tmp = match$1 !== undefined ? undefined : Js_option.getExn(Js_json.decodeArray(value$2)).map((function (value) {
30 | var value$1 = Js_option.getExn(Js_json.decodeObject(value));
31 | var match = Js_dict.get(value$1, "id");
32 | var tmp;
33 | if (match !== undefined) {
34 | var value$2 = Caml_option.valFromOption(match);
35 | var match$1 = Js_json.decodeNumber(value$2);
36 | tmp = match$1 !== undefined ? match$1 | 0 : Js_exn.raiseError("graphql_ppx: Expected int, got " + JSON.stringify(value$2));
37 | } else {
38 | tmp = Js_exn.raiseError("graphql_ppx: Field id on type story is missing");
39 | }
40 | var match$2 = Js_dict.get(value$1, "title");
41 | var tmp$1;
42 | if (match$2 !== undefined) {
43 | var value$3 = Caml_option.valFromOption(match$2);
44 | var match$3 = Js_json.decodeString(value$3);
45 | tmp$1 = match$3 !== undefined ? match$3 : Js_exn.raiseError("graphql_ppx: Expected string, got " + JSON.stringify(value$3));
46 | } else {
47 | tmp$1 = Js_exn.raiseError("graphql_ppx: Field title on type story is missing");
48 | }
49 | return {
50 | id: tmp,
51 | title: tmp$1
52 | };
53 | }));
54 | } else {
55 | tmp = undefined;
56 | }
57 | return {
58 | topStories: tmp
59 | };
60 | }
61 |
62 | function make(param) {
63 | return {
64 | query: ppx_printed_query,
65 | variables: null,
66 | parse: parse
67 | };
68 | }
69 |
70 | function makeWithVariables(param) {
71 | return {
72 | query: ppx_printed_query,
73 | variables: null,
74 | parse: parse
75 | };
76 | }
77 |
78 | function makeVariables(param) {
79 | return null;
80 | }
81 |
82 | function definition_002(graphql_ppx_use_json_variables_fn) {
83 | return 0;
84 | }
85 |
86 | var definition = /* tuple */[
87 | parse,
88 | ppx_printed_query,
89 | definition_002
90 | ];
91 |
92 | function ret_type(f) {
93 | return { };
94 | }
95 |
96 | var MT_Ret = { };
97 |
98 | var TopStoriesQuery = {
99 | ppx_printed_query: ppx_printed_query,
100 | query: ppx_printed_query,
101 | parse: parse,
102 | make: make,
103 | makeWithVariables: makeWithVariables,
104 | makeVariables: makeVariables,
105 | definition: definition,
106 | ret_type: ret_type,
107 | MT_Ret: MT_Ret
108 | };
109 |
110 | var query = make(/* () */0);
111 |
112 | function Home(Props) {
113 | var result = GraphqlHooks$Hackerz.useQuery(query);
114 | var queryResult;
115 | if (typeof result === "number") {
116 | queryResult = React.createElement("div", undefined, "Loading");
117 | } else if (result.tag) {
118 | var match = result[0].topStories;
119 | queryResult = match !== undefined ? React.createElement("ul", undefined, $$Array.map((function (story) {
120 | return React.createElement("li", {
121 | key: String(story.id)
122 | }, story.title);
123 | }), match)) : "No stories found";
124 | } else {
125 | queryResult = React.createElement("div", undefined, result[0]);
126 | }
127 | return React.createElement(React.Fragment, undefined, React.createElement("div", {
128 | className: css.foo
129 | }, "HELLO"), queryResult, React.createElement(Link$Hackerz.make, {
130 | href: "/more",
131 | children: "See some more"
132 | }));
133 | }
134 |
135 | var make$1 = Home;
136 |
137 | export {
138 | css ,
139 | ste ,
140 | TopStoriesQuery ,
141 | query ,
142 | make$1 as make,
143 |
144 | }
145 | /* css Not a pure module */
146 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import filesize from 'rollup-plugin-filesize';
3 | import nodeResolve from 'rollup-plugin-node-resolve';
4 | import progress from 'rollup-plugin-progress';
5 | import commonjs from 'rollup-plugin-commonjs';
6 | import replace from 'rollup-plugin-replace';
7 | import { terser } from 'rollup-plugin-terser';
8 | import livereload from 'rollup-plugin-livereload';
9 | import copy from 'rollup-plugin-copy-assets';
10 | import postcss from 'rollup-plugin-postcss';
11 | import workbox from 'rollup-plugin-workbox-build';
12 | import json from 'rollup-plugin-json';
13 | const pkg = require('./package.json');
14 |
15 | const namedExports = {
16 | 'node_modules/react/index.js': [
17 | 'Children',
18 | 'Component',
19 | 'PropTypes',
20 | 'createElement',
21 | 'isValidElement',
22 | 'Fragment',
23 | 'useState',
24 | 'useEffect',
25 | 'useContext',
26 | 'useLayoutEffect',
27 | 'useMemo',
28 | 'useRef',
29 | 'useReducer',
30 | 'render',
31 | 'hydrate',
32 | ],
33 | 'node_modules/react-dom/index.js': ['render', 'hydrate'],
34 | 'node_modules/react-dom/server.js': [
35 | 'renderToString',
36 | 'renderToStaticMarkup',
37 | ],
38 | 'node_modules/body-parser/index.js': ['json'],
39 | };
40 |
41 | // `npm run build` -> `production` is true
42 | // `npm run dev` -> `production` is false
43 | const production = !process.env.ROLLUP_WATCH;
44 |
45 | // Module config for