├── .gitignore ├── README.md ├── package.json ├── patches └── mdx-deck+1.10.2.patch ├── src ├── Cover.js ├── Feedback.js ├── Img.js ├── InlineImg.js ├── Intro.js ├── Thanks.js ├── code │ └── NodeInterface.js ├── deck.mdx ├── img │ ├── ast.png │ ├── compilerParsing.png │ ├── compilerPipeline.png │ ├── compilerSteps.png │ ├── entria.png │ ├── entriaLogo.png │ ├── evergreen.png │ ├── explorer.png │ ├── github.png │ ├── graphql2ts.png │ ├── lisp.png │ ├── me.png │ ├── relayCompilerRepl.png │ ├── relaymodern.png │ ├── syntatic.png │ ├── tokenizer.png │ ├── tokens.png │ ├── traversal.gif │ ├── traverse.gif │ ├── twitter.png │ ├── useState.png │ └── visitor.png └── theme.js ├── static └── images │ └── evergreen.png └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.sublime-project 3 | *.sublime-workspace 4 | .idea/ 5 | 6 | lib-cov 7 | *.seed 8 | *.log 9 | *.csv 10 | *.dat 11 | *.out 12 | *.pid 13 | *.gz 14 | *.map 15 | 16 | pids 17 | logs 18 | results 19 | test-results 20 | 21 | node_modules 22 | npm-debug.log 23 | 24 | dump.rdb 25 | bundle.js 26 | 27 | build 28 | dist 29 | coverage 30 | .nyc_output 31 | .env 32 | 33 | graphql.*.json 34 | junit.xml 35 | 36 | .vs 37 | 38 | test/globalConfig.json 39 | distTs 40 | 41 | # Random things to ignore 42 | ignore/ 43 | package-lock.json 44 | /yarn-offline-cache 45 | .cache 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Practical GraphQL for Relay 2 | 3 | Slides of my talk about Practical GraphQL for Relay 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphl-relay", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "gh-pages": "^2.0.1", 8 | "mdx-code": "^1.1.3", 9 | "mdx-deck": "^1.6.7", 10 | "mdx-deck-code-surfer": "^0.5.5", 11 | "mdx-deck-live-code": "^1.0.0", 12 | "qrcode.react": "^0.9.3", 13 | "raw-loader": "^3.1.0", 14 | "rebass": "^3.1.1", 15 | "styled-components": "^4.3.2", 16 | "styled-system": "^5.0.15" 17 | }, 18 | "scripts": { 19 | "build": "mdx-deck build src/deck.mdx", 20 | "help": "mdx-deck", 21 | "image": "mdx-deck screenshot src/deck.mdx", 22 | "pdf": "mdx-deck pdf src/deck.mdx", 23 | "copy:static": "cp -r src/img dist", 24 | "publish:deck": "yarn build && yarn copy:static && gh-pages -d dist", 25 | "start": "mdx-deck src/deck.mdx", 26 | "postinstall": "patch-package" 27 | }, 28 | "devDependencies": { 29 | "patch-package": "^6.1.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /patches/mdx-deck+1.10.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/mdx-deck/lib/html.js b/node_modules/mdx-deck/lib/html.js 2 | index c4adec0..6cd8ed2 100644 3 | --- a/node_modules/mdx-deck/lib/html.js 4 | +++ b/node_modules/mdx-deck/lib/html.js 5 | @@ -28,7 +28,7 @@ const getApp = async opts => { 6 | config.target = 'node' 7 | config.externals = [ 8 | nodeExternals({ 9 | - whitelist: ['mdx-deck', 'mdx-deck/themes', 'mdx-deck/layouts'], 10 | + whitelist: ['mdx-deck', 'mdx-deck/themes', 'mdx-deck/layouts', 'mdx-deck-code-surfer'], 11 | }), 12 | ] 13 | 14 | -------------------------------------------------------------------------------- /src/Cover.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components' 3 | import { space, width } from 'styled-system'; 4 | 5 | export const Root = styled.div([], { 6 | width: '50vw', 7 | height: '70vh', 8 | }); 9 | 10 | const Img = styled.img` 11 | ${width} 12 | `; 13 | 14 | export const Center = styled.div` 15 | display: flex; 16 | flex: 1; 17 | flex-direction: column; 18 | align-items: center; 19 | justify-content: center; 20 | `; 21 | 22 | const Title = styled.span` 23 | font-size: 50px; 24 | ${space} 25 | `; 26 | 27 | const Subtitle = styled.span` 28 | font-size: 40px; 29 | color: #FDAA4C; 30 | ${space} 31 | `; 32 | 33 | const MeName = styled.span` 34 | font-size: 30px; 35 | color: #25D7FD; 36 | ${space} 37 | `; 38 | 39 | export const Cover = () => ( 40 | 41 |
42 | 43 | Practical GraphQL For Relay 44 | Understanding the Spec 45 | Sibelius Seraphini 46 |
47 |
48 | ); 49 | -------------------------------------------------------------------------------- /src/Feedback.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components' 3 | import { space, width, fontSize, color } from 'styled-system'; 4 | import QRCode from 'qrcode.react'; 5 | 6 | // Add styled-system functions to your component 7 | const Box = styled.div` 8 | ${space} 9 | ${width} 10 | ${fontSize} 11 | ${color} 12 | `; 13 | 14 | export const Root = styled.div([], { 15 | width: '50vw', 16 | height: '70vh', 17 | }); 18 | 19 | const SpaceBetween = styled.div` 20 | display: flex; 21 | flex: 1; 22 | flex-direction: row; 23 | align-items: center; 24 | justify-content: space-between; 25 | `; 26 | 27 | const FeedbackText = styled.span` 28 | font-size: 50px; 29 | color: #ffffff; 30 | `; 31 | 32 | const FeedbackLink = styled.a` 33 | font-size: 50px; 34 | color: #25D7FD; 35 | `; 36 | 37 | export const Center = styled.div` 38 | display: flex; 39 | flex: 1; 40 | flex-direction: row; 41 | align-items: center; 42 | justify-content: center; 43 | ${space} 44 | `; 45 | 46 | const EntriaLogo = styled.img` 47 | max-width: 600px; 48 | margin-top: 100px; 49 | margin-bottom: 50px; 50 | `; 51 | 52 | 53 | export const Feedback = () => ( 54 | 55 |
56 | Give me a Feedback: 57 |
58 |
59 | 63 |
64 |
65 | https://entria.feedback.house/sibelius 66 |
67 |
68 | ); 69 | -------------------------------------------------------------------------------- /src/Img.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components' 3 | import { width } from 'styled-system'; 4 | 5 | import { Root } from './Intro'; 6 | 7 | const StyledImg = styled.img` 8 | ${width} 9 | `; 10 | 11 | export const Center = styled.div` 12 | display: flex; 13 | flex: 1; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | `; 18 | 19 | export const Img = ({ src, ...props}) => ( 20 | 21 |
22 | 23 |
24 |
25 | ); 26 | -------------------------------------------------------------------------------- /src/InlineImg.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components' 3 | import { width } from 'styled-system'; 4 | 5 | 6 | const StyledImg = styled.img` 7 | ${width} 8 | `; 9 | 10 | export const Center = styled.div` 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | justify-content: center; 15 | `; 16 | 17 | export const InlineImg = ({ src, ...props}) => ( 18 |
19 | 20 |
21 | ); 22 | -------------------------------------------------------------------------------- /src/Intro.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components' 3 | import { Flex } from 'rebass'; 4 | 5 | const IconImage = styled.img` 6 | max-height: 60px; 7 | max-width: 60px; 8 | `; 9 | 10 | const Link = styled.a` 11 | text-decoration: none; 12 | color: white; 13 | `; 14 | 15 | const Me = styled.img` 16 | max-width: 150px; 17 | max-height: 150px; 18 | `; 19 | 20 | const MeName = styled.span` 21 | font-size: 50px; 22 | color: #25D7FD; 23 | margin-left: 60px; 24 | `; 25 | 26 | const Row = styled.div` 27 | display: flex; 28 | flex: 1; 29 | flex-direction: row; 30 | align-items: center; 31 | margin-bottom: 40px; 32 | `; 33 | 34 | export const Center = styled.div` 35 | display: flex; 36 | flex: 1; 37 | flex-direction: row; 38 | align-items: center; 39 | justify-content: center; 40 | `; 41 | 42 | const SpaceBetween = styled.div` 43 | display: flex; 44 | flex: 1; 45 | flex-direction: row; 46 | align-items: center; 47 | justify-content: space-between; 48 | `; 49 | 50 | export const Root = styled.div([], { 51 | // width: '50vw', 52 | height: '70vh', 53 | }); 54 | 55 | const Username = styled.span` 56 | font-size: 14px; 57 | margin-left: 20px; 58 | `; 59 | 60 | const EntriaLogo = styled.img` 61 | max-width: 600px; 62 | `; 63 | 64 | const SocialMediaLink = ({ src, link, username }) => ( 65 |
66 | 67 | 68 | 69 | {username} 70 | 71 | 72 |
73 | ); 74 | 75 | export const Intro = () => ( 76 | 77 | 78 | 79 | Sibelius Seraphini 80 | 81 | 82 | 87 | 92 | 93 |
94 | 95 |
96 | 97 | Abstract Engineer 98 | 99 |
100 | ); 101 | -------------------------------------------------------------------------------- /src/Thanks.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components' 3 | import { space, width, fontSize, color } from 'styled-system'; 4 | 5 | export const Root = styled.div([], { 6 | width: '50vw', 7 | height: '70vh', 8 | }); 9 | 10 | const SpaceBetween = styled.div` 11 | display: flex; 12 | flex: 1; 13 | flex-direction: row; 14 | align-items: center; 15 | justify-content: center; 16 | ${space} 17 | `; 18 | 19 | const ThanksText = styled.span` 20 | font-size: 50px; 21 | color: #ffffff; 22 | `; 23 | 24 | const HiringText = styled.span` 25 | font-size: 100px; 26 | color: #25D7FD; 27 | `; 28 | 29 | export const Center = styled.div` 30 | display: flex; 31 | flex: 1; 32 | flex-direction: row; 33 | align-items: center; 34 | justify-content: center; 35 | `; 36 | 37 | const EntriaLogo = styled.img` 38 | max-width: 600px; 39 | margin-top: 100px; 40 | `; 41 | 42 | export const Thanks = () => ( 43 | 44 |
45 | Thanks! 46 |
47 | 48 | We are hiring! 49 | 50 | 53 | Join Us 54 |
55 | 56 |
57 |
58 | ); 59 | -------------------------------------------------------------------------------- /src/code/NodeInterface.js: -------------------------------------------------------------------------------- 1 | import { fromGlobalId, nodeDefinitions } from 'graphql-relay'; 2 | import { GraphQLObjectType } from 'graphql'; 3 | 4 | import * as loaders from '../loader'; 5 | 6 | const registeredTypes = {}; 7 | 8 | export function registerType(type: GraphQLObjectType) { 9 | registeredTypes[type.name] = type; 10 | return type; 11 | } 12 | 13 | export const { nodeField, nodeInterface } = nodeDefinitions( 14 | (globalId, context) => { 15 | const { type, id } = fromGlobalId(globalId); 16 | 17 | const loader = (loaders)[`${type}Loader`]; 18 | 19 | return (loader && loader.load(context, id)) || null; 20 | }, 21 | object => registeredTypes[object.constructor.name] || null, 22 | ); 23 | -------------------------------------------------------------------------------- /src/deck.mdx: -------------------------------------------------------------------------------- 1 | import { Head, Image, Appear } from 'mdx-deck' 2 | import { Split, FullScreenCode } from 'mdx-deck/layouts' 3 | import { CodeSurfer } from "mdx-deck-code-surfer"; 4 | 5 | import { Cover } from './Cover'; 6 | import { Intro } from './Intro'; 7 | import { Img } from './Img'; 8 | import { InlineImg } from './InlineImg'; 9 | import { Thanks } from './Thanks'; 10 | import { Feedback } from './Feedback'; 11 | 12 | export { default as theme } from './theme' 13 | 14 | 15 | Practical GraphQL for Relay 16 | 17 | 18 | 19 | 20 | --- 21 | 22 | 23 | 24 | --- 25 | 26 | # Overview 27 | 28 | 47 | 48 | --- 49 | 50 | # Core assumptions 51 | 52 | 65 | 66 | --- 67 | 80 | --- 81 | 91 | --- 92 | 93 | # Object Identification 94 | 95 |
    96 | 97 |
  • 98 | Node Interface that has a non-null ID 99 |
  • 100 |
  • 101 | Global unique ID per node 102 |
  • 103 |
  • 104 | GlobalId = base64(type:id) 105 |
  • 106 |
  • 107 | All nodes that implements NodeInterface are refetchable 108 |
  • 109 |
    110 |
111 | 112 | --- 113 | 114 | 121 | 122 | --- 123 | 124 | 154 | --- 155 | 156 | # What is a Cursor? 157 | 158 | A cursor is a pointer to a "list"/"connection", it tells you where you are in a list of items 159 | 160 | --- 161 | 162 | # Forward Pagination 163 | 164 | first: 10, after: "mycurrentcursor" 165 | 166 | This tell GraphQL to return 10 items after "mycurrentcursor" cursor 167 | 168 | --- 169 | # Backward Pagination 170 | 171 | last: 10, before: "anothercursor" 172 | 173 | This tell GraphQL to return 10 items before "anotercursor" cursor 174 | 175 | --- 176 | 177 | # Mutations 178 | 179 | - It should return all necessary data to update your GraphQL client 180 | 181 | --- 182 | 183 | # Mutations that add a new Node/Edge to the Graph 184 | 185 | - It should return the new created edge to be add to existing connections 186 | 187 | --- 188 | 189 | 206 | --- 207 | 208 | # Mutations that edit an existing Node 209 | 210 | - It should return the edited node 211 | 212 | --- 213 | 214 | 229 | --- 230 | 231 | # Mutation that remove a Node 232 | 233 | - It should return the id of removed node 234 | 235 | --- 236 | 237 | 250 | --- 251 | 252 | # Pratical GraphQL Tips 253 | 254 | This is based on our experience working with GraphQL since their public release at 2015 255 | 256 | --- 257 | 258 | ## Avoid Viewer 259 | 260 | - Viewer was a "hacky" solution for Relay Classic where you couldn't do query on QueryType 261 | - We recommend use fields directly on QueryType instead, so we can avoid confusion 262 | 263 | --- 264 | 265 | ## Keep Logged User on GraphQL Context 266 | 267 | - As soon as you know who is logged user, add it to your GraphQL context so you can easily access them in any resolver 268 | - This is also useful to implement viewer can see (security) solutions on resolver based level 269 | 270 | --- 271 | 272 | ## Add a me field resolver 273 | 274 | - me field on QueryType that resolves to a UserType of the logged user 275 | - This makes it easy to find out who is logged in your app 276 | 277 | --- 278 | 279 | ## Each Node should have a load function 280 | 281 | - Each type should have an unique way to be resolved 282 | - This will make sure that every resolver is always resolving the correct data 283 | - This also make sure it is easy to add dataloader later on 284 | - UserLoader.load(context, id) 285 | 286 | --- 287 | 288 | ## Create a Dataloader generator per database/resource 289 | - Check [GraphQL Mongoose Loader](https://github.com/entria/graphql-mongoose-loader#mongoose-dataloader-batch) 290 | - This make sure all data is resolved in the same way with all optimizations 291 | 292 | --- 293 | 294 | ## Create one Connection per database/resource cursor 295 | - Check [GraphQL Mongoose Connection](https://github.com/entria/graphql-mongoose-loader#connection-from-mongoose-cursor) 296 | - This make sure all your connections handle cursor and pagination properly 297 | 298 | --- 299 | 300 | ## Follow Relay Mutation Input/Output Object pattern 301 | - Check [graphql-relay](https://github.com/graphql/graphql-relay-js) 302 | - With an Input Object type in your mutation it is easy to add more fields without breaking old clients 303 | - The same goes to Output Object type 304 | 305 | --- 306 | 307 | ## References 308 | 309 | - [Relay Docs about GraphQL Server Specification](https://relay.dev/docs/en/graphql-server-specification) 310 | - [Object Identification](https://relay.dev/graphql/objectidentification.htm) 311 | - [Cursor Connections](https://relay.dev/graphql/connections.htm) 312 | - [Relay Mutations](https://relay.dev/graphql/mutations.htm) 313 | 314 | --- 315 | 316 | ## References 317 | 318 | - [Relay Mutations Guide](https://relay.dev/docs/en/mutations) 319 | - [Entria Playground](https://github.com/entria/entria-fullstack) 320 | - [GraphQL Relay 2015](https://pt-br.reactjs.org/blog/2015/02/20/introducing-relay-and-graphql.html) 321 | - [Building the New Facebook](https://developers.facebook.com/videos/2019/building-the-new-facebookcom-with-react-graphql-and-relay/) 322 | - [GraphQL Mongoose](https://github.com/entria/graphql-mongoose-loader#mongoose-dataloader-batch) 323 | -------------------------------------------------------------------------------- /src/img/ast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/ast.png -------------------------------------------------------------------------------- /src/img/compilerParsing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/compilerParsing.png -------------------------------------------------------------------------------- /src/img/compilerPipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/compilerPipeline.png -------------------------------------------------------------------------------- /src/img/compilerSteps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/compilerSteps.png -------------------------------------------------------------------------------- /src/img/entria.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/entria.png -------------------------------------------------------------------------------- /src/img/entriaLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/entriaLogo.png -------------------------------------------------------------------------------- /src/img/evergreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/evergreen.png -------------------------------------------------------------------------------- /src/img/explorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/explorer.png -------------------------------------------------------------------------------- /src/img/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/github.png -------------------------------------------------------------------------------- /src/img/graphql2ts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/graphql2ts.png -------------------------------------------------------------------------------- /src/img/lisp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/lisp.png -------------------------------------------------------------------------------- /src/img/me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/me.png -------------------------------------------------------------------------------- /src/img/relayCompilerRepl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/relayCompilerRepl.png -------------------------------------------------------------------------------- /src/img/relaymodern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/relaymodern.png -------------------------------------------------------------------------------- /src/img/syntatic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/syntatic.png -------------------------------------------------------------------------------- /src/img/tokenizer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/tokenizer.png -------------------------------------------------------------------------------- /src/img/tokens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/tokens.png -------------------------------------------------------------------------------- /src/img/traversal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/traversal.gif -------------------------------------------------------------------------------- /src/img/traverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/traverse.gif -------------------------------------------------------------------------------- /src/img/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/twitter.png -------------------------------------------------------------------------------- /src/img/useState.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/useState.png -------------------------------------------------------------------------------- /src/img/visitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/src/img/visitor.png -------------------------------------------------------------------------------- /src/theme.js: -------------------------------------------------------------------------------- 1 | import { dark as theme } from 'mdx-deck/themes' 2 | 3 | console.log('dark: ', theme); 4 | 5 | export default { 6 | ...theme, 7 | 8 | colors: { 9 | ...theme.colors, 10 | background: '#272425', 11 | }, 12 | // Customize your presentation theme here. 13 | // 14 | // Read the docs for more info: 15 | // https://github.com/jxnblk/mdx-deck/blob/master/docs/theming.md 16 | // https://github.com/jxnblk/mdx-deck/blob/master/docs/themes.md 17 | 18 | } 19 | -------------------------------------------------------------------------------- /static/images/evergreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibelius/practical-graphql-relay/f369503801cb1ae4aadf6cf100df909cd43fec75/static/images/evergreen.png --------------------------------------------------------------------------------