{"action":"created","comment":{"url":"https://api.github.com/repos/Khan/engblog/pulls/comments/57600466","id":57600466,"diff_hunk":"@@ -0,0 +1,338 @@\n+title: \"Inline CSS at Khan Academy: Aphrodite\"\n+published_on: March 29, 2016\n+author: Jamie Wong\n+team: Infrastructure\n+...\n+\n+I won't ramble in this post about the maintenance woe that is CSS, as others have said it better in the past. In particular, Christopher \"vjeux\" Chedeau sparked much of this discussion in his talk [React: CSS in JS](http://blog.vjeux.com/2014/javascript/react-css-in-js-nationjs.html).\n+\n+Instead, I'd like to discuss [Aphrodite](https://github.com/Khan/aphrodite), the inline styling solution we've developed at Khan Academy, the constraints that led to it, and its use in production today.\n+\n+## Page weight and server-side rendering\n+\n+As part of an initiative to speed up Khan Academy's core content pages, we identified that the most important predictor of page load speed was the number of bytes needed before the content was rendered. From testing different bandwidth limitations and network latencies, we found that bandwidth was a bigger predictor of page speed, not latency. You can verify this yourself by trying loading various sites and using [custom networking throttling profiles](https://developer.chrome.com/devtools/docs/device-mode#network-conditions) in the Chrome Developer tools. So cutting bytes was our goal.\n+\n+To meet that goal, we opted to pursue server-side rendering.\n+\n+If you're unconvinced that server-side rendering yields a fast page load, here's a simple argument. We'll focus on initial page load, before any of your static assets are cached. Assume all the sizes mentioned here are measured after gzip.\n+\n+1. Let M be the byte size of the minimal HTML markup required to load your website, including script tags to load whatever client-side libraries you want.\n+\n+2. Let J be the byte size of the JavaScript needed to render your page (e.g. React + other libraries + the JavaScript UI components).\n+\n+3. Let S be the byte size of the server-side rendered markup for your page. This \n+   would be the contents of the `<body>`.\n+\n+4. Let C be the byte size of the CSS needed to render the components on the page.\n+\n+Now let's examine how many bytes the client has to download before the user can see the content on the page.\n+\n+In the server-side rendered case, it's M + S + C. In the client-side rendered case, it's M + J + C. So if you agree with the hypothesis that fewer bytes means a faster site, then if J > S, server-side rendering will be faster. The production build of React alone is 43KB after gzip, so if you can get your server-side rendered content markup below that, server-side rendering will get content to your users faster.\n+\n+You can read posts arriving at similar conclusions from [Tom Dale of Ember](http://tomdale.net/2015/02/youre-missing-the-point-of-server-side-rendered-javascript-apps/), [Dan Webb of Twitter](https://blog.twitter.com/2012/improving-performance-on-twittercom), [Spike Brehm of Airbnb](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/), and [Turadg Aleahmad of Coursera](https://building.coursera.org/blog/2015/08/18/why-and-how-coursera-does-isomorphic-javascript-a-fast-and-snappy-quiz/).\n+\n+## Constraints\n+\n+So let's return to the problem of CSS. \n+\n+Given a goal of reducing bytes until content was visible, we set forth to find a CSS solution that allowed us to colocate our CSS with our React components with the following constraints:\n+\n+1. We must be able to extract and deliver the CSS needed by our components when the component was server-side rendered.\n+\n+2. When the client-side re-renders the components in React to make them fully interactive, the page must not dramatically change. This meant correctly handling media queries, pseudo-selectors such as :hover and :visited all in CSS, and not via JavaScript-bound events. ([:visited can't be implemented in JavaScript anyway](https://developer.mozilla.org/en-US/docs/Web/CSS/Privacy_and_the_:visited_selector)). This disqualifies libraries such as [Radium](https://github.com/FormidableLabs/radium) which use component state to emulate :hover.\n+\n+3. We must deliver the absolute minimum amount of CSS necessary by only sending down CSS in use by components in the server-side rendered body. This rules out extracting all the CSS used in all of our components at compilation time, as much of that CSS would not be needed for the initial page load.\n+\n+4. As to avoid increasing the time to the site being fully interactive, any runtime added must be small (< 10KB after gzip).\n+\n+Unsatisfied with the existing options*, [Emily Eisenberg](https://github.com/xymostech) and I wrote [Aphrodite](https://github.com/Khan/aphrodite) to satisfy these constraints. Our primary use case is use with React components, so all of the examples that follow will be in React, though it's worth mentioning that Aphrodite does not depend on React in any way.\n+\n+*: [React Look](https://github.com/rofrischmann/react-look) has since come a long way. It is in many ways more featureful, though is a larger library to send over the wire. Many of the ideas appear to be quite similar. It also has support for React Native, which Aphrodite does not. React Look, as the name suggests, is coupled to React, which Aphrodite is not. If Aphrodite doesn't interest you, check out Look.\n+\n+## A simple example\n+\n+Here's how Aphrodite works. First, styles are defined via a syntax inspired by [React Native's StyleSheet](https://facebook.github.io/react-native/docs/stylesheet.html), like so:\n+\n+```\n+const styles = StyleSheet.create({\n+    red: {\n+        backgroundColor: 'red'\n+    },\n+    blue: {\n+        backgroundColor: 'blue'\n+\n+    },\n+    hover: {\n+        ':hover': {\n+            backgroundColor: 'red'\n+        }\n+    },\n+    small: {\n+        '@media (max-width: 600px)': {\n+            backgroundColor: 'red'\n+\n+        }\n+    }\n+});\n+```\n+\n+This, by itself, does not generate any CSS. CSS is only injected into the `<head>` of the page when these classes are *used*. The injection happens in Aphrodite's `css()` function. Here's an example of using `css()` in a React component:\n+\n+```\n+class App extends Component {\n+    render() {\n+        return <div>\n+            <span className={css(styles.red)}>\n+                This is red.\n+            </span>\n+            <span className={css(styles.hover)}>\n+                This turns red on hover.\n+            </span>\n+            <span className={css(styles.small)}>\n+                This turns red when the browser is less than 600px width.\n+            </span>\n+            <span className={css(styles.red, styles.blue)}>\n+                This is blue.\n+            </span>\n+            <span className={css(styles.blue, styles.small)}>\n+                This is blue and turns red when the browser is less than\n+                600px width.\n+            </span>\n+        </div>;\n+    }\n+}\n+```\n+\n+Only once this component is rendered will the CSS be injected into the page. The css function generates CSS to inject into the page and returns an associated CSS class name. On the client-side, it will automatically inject the CSS into the <head> of the page. On the server-side, it will buffer the CSS to be flushed as one big string into the HTML response.\n+\n+Let's look at a simple case:\n+\n+```\n+<span className={css(styles.red)}>\n+    This is red.\n+</span>\n+```\n+\n+The injected CSS for this span looks like this:\n+\n+```\n+.red_im3wl1{color:red !important;}\n+```\n+\n+And the span ends up looking like this in the DOM:\n+\n+```\n+<span class=\"red_im3wl1\">This should be red</span>\n+```\n+\n+## Avoiding class name collisions\n+\n+You may be wondering where the _im3wl1 comes from. Imagine we had two components, that each define a class with the style name red, like so:\n+\n+```\n+const Component1 = () => <span className={css(styles1.red)}>1</span>;\n+\n+const styles1 = StyleSheet.create({\n+    red: {\n+        backgroundColor: 'red'\n+    }\n+});\n+\n+const Component2 = () => <span className={css(styles2.red)}>2</span>;\n+\n+const styles2 = StyleSheet.create({\n+    red: {\n+        backgroundColor: '#cc1100'\n+    }\n+});\n+```\n+\n+It would be surprising behaviour if either one of those ended up using the other's style, so we need some way to differentiate between them. We initially solved this by appending a random number to the class name. This works great… unless you want server-side rendering to work well.\n+\n+## Deterministic rendering and data-react-checksum\n+\n+When you render a component server-side via ReactDOMServer.renderToString, out outputs HTML that might look something like this:\n+\n+```\n+<div id='container'\n+    data-reactid=\".1x73ayqlreo\"\n+    data-react-checksum=\"-1952287665\">...</div>\n+```\n+\n+From working with React keys, you've likely seen `data-reactid` before, but \n+what's this `data-react-checksum` business? \n+\n+When your re-render your React component on the client-side to bind all your \n+events and run the lifecycle starting with `componentDidMount`, \n+`ReactDOM.render`\n+will construct a virtual DOM using your component. It then takes a checksum of \n+that component tree and compares it with the one emitted by \n+`ReactDOMServer.renderToString`. If it's identical it will re-use the \n+server-rendered DOM tree and avoid needing to reflow the page or do any style \n+recalculations. If it's different, it will replace the entire DOM tree.\n+\n+This is not so great for performance, so we'd like to avoid that.\n+\n+If we generate random numbers that wind up in the HTML output of our component, then the checksum certainly won't match. So we can't use random numbers, but we still need something to avoid name collisions.\n+\n+We could use a simple global counter, but when would we reset that counter? Each time we render? If we do that, then we might still get name collisions between classes generated by two separate render calls.\n+\n+What we really want is a 1:1 mapping between the class name and the style declarations associated with that class name. Sounds like a job for hashing!\n+\n+We take a hash of the JSON stringified style declaration and use that as a suffix. On recommendation from our Dean of Infrastructure, Craig Silverstein, we used [MurmurHash](https://en.wikipedia.org/wiki/MurmurHash).\n+\n+It is possible that two components will each declare their own copy of styles with the same name and the same contents, like so:\n+\n+```\n+const Component1 = () => <span className={css(styles1.red)}>1</span>\n+\n+const styles1 = StyleSheet.create({\n+    red: {\n+        backgroundColor: 'red'\n+    }\n+});\n+\n+const Component2 = () => <span className={css(styles2.red)}>2</span>\n+\n+const styles2 = StyleSheet.create({\n+    red: {\n+        backgroundColor: 'red'\n+    }\n+});\n+```\n+\n+In this case, we get a class name collision on `red_im3wl1`, but that's okay, \n+because they reference the exact same properties and values, so there's no \n+surprising behaviour!\n+\n+## Deterministic precedence\n+","path":"src/posts/aphrodite-inline-css.md","position":209,"original_position":209,"commit_id":"19eefbc594eeed885403fb0bb4ab4ba591878acb","original_commit_id":"19eefbc594eeed885403fb0bb4ab4ba591878acb","user":{"login":"jaredly","id":112170,"avatar_url":"https://avatars.githubusercontent.com/u/112170?v=3","gravatar_id":"","url":"https://api.github.com/users/jaredly","html_url":"https://github.com/jaredly","followers_url":"https://api.github.com/users/jaredly/followers","following_url":"https://api.github.com/users/jaredly/following{/other_user}","gists_url":"https://api.github.com/users/jaredly/gists{/gist_id}","starred_url":"https://api.github.com/users/jaredly/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jaredly/subscriptions","organizations_url":"https://api.github.com/users/jaredly/orgs","repos_url":"https://api.github.com/users/jaredly/repos","events_url":"https://api.github.com/users/jaredly/events{/privacy}","received_events_url":"https://api.github.com/users/jaredly/received_events","type":"User","site_admin":false},"body":"Maybe add a line here like \"One of the things we really wanted to avoid was the unexpected and spooky-action-at-a-distance part of CSS that is precedence resolution. In a stylesheet, where there are two styles defined with equal specificity, the one that comes *later* in the stylesheet has precedence. In a hand-tuned stylesheet that can be confusing, but in an auto-generated one it can be disastrous!\"","created_at":"2016-03-28T17:28:02Z","updated_at":"2016-03-28T17:28:02Z","html_url":"https://github.com/Khan/engblog/pull/57#discussion_r57600466","pull_request_url":"https://api.github.com/repos/Khan/engblog/pulls/57","_links":{"self":{"href":"https://api.github.com/repos/Khan/engblog/pulls/comments/57600466"},"html":{"href":"https://github.com/Khan/engblog/pull/57#discussion_r57600466"},"pull_request":{"href":"https://api.github.com/repos/Khan/engblog/pulls/57"}}},"pull_request":{"url":"https://api.github.com/repos/Khan/engblog/pulls/57","id":64214375,"html_url":"https://github.com/Khan/engblog/pull/57","diff_url":"https://github.com/Khan/engblog/pull/57.diff","patch_url":"https://github.com/Khan/engblog/pull/57.patch","issue_url":"https://api.github.com/repos/Khan/engblog/issues/57","number":57,"state":"open","locked":false,"title":"Add post about Aphrodite","user":{"login":"jlfwong","id":150329,"avatar_url":"https://avatars.githubusercontent.com/u/150329?v=3","gravatar_id":"","url":"https://api.github.com/users/jlfwong","html_url":"https://github.com/jlfwong","followers_url":"https://api.github.com/users/jlfwong/followers","following_url":"https://api.github.com/users/jlfwong/following{/other_user}","gists_url":"https://api.github.com/users/jlfwong/gists{/gist_id}","starred_url":"https://api.github.com/users/jlfwong/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jlfwong/subscriptions","organizations_url":"https://api.github.com/users/jlfwong/orgs","repos_url":"https://api.github.com/users/jlfwong/repos","events_url":"https://api.github.com/users/jlfwong/events{/privacy}","received_events_url":"https://api.github.com/users/jlfwong/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-03-25T22:23:22Z","updated_at":"2016-03-28T17:28:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bb1e1c7f249cfe20a6a89797aa66d57d583f4197","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/Khan/engblog/pulls/57/commits","review_comments_url":"https://api.github.com/repos/Khan/engblog/pulls/57/comments","review_comment_url":"https://api.github.com/repos/Khan/engblog/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Khan/engblog/issues/57/comments","statuses_url":"https://api.github.com/repos/Khan/engblog/statuses/19eefbc594eeed885403fb0bb4ab4ba591878acb","head":{"label":"jlfwong:jlfwong-aphrodite-post","ref":"jlfwong-aphrodite-post","sha":"19eefbc594eeed885403fb0bb4ab4ba591878acb","user":{"login":"jlfwong","id":150329,"avatar_url":"https://avatars.githubusercontent.com/u/150329?v=3","gravatar_id":"","url":"https://api.github.com/users/jlfwong","html_url":"https://github.com/jlfwong","followers_url":"https://api.github.com/users/jlfwong/followers","following_url":"https://api.github.com/users/jlfwong/following{/other_user}","gists_url":"https://api.github.com/users/jlfwong/gists{/gist_id}","starred_url":"https://api.github.com/users/jlfwong/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jlfwong/subscriptions","organizations_url":"https://api.github.com/users/jlfwong/orgs","repos_url":"https://api.github.com/users/jlfwong/repos","events_url":"https://api.github.com/users/jlfwong/events{/privacy}","received_events_url":"https://api.github.com/users/jlfwong/received_events","type":"User","site_admin":false},"repo":{"id":54750780,"name":"engblog","full_name":"jlfwong/engblog","owner":{"login":"jlfwong","id":150329,"avatar_url":"https://avatars.githubusercontent.com/u/150329?v=3","gravatar_id":"","url":"https://api.github.com/users/jlfwong","html_url":"https://github.com/jlfwong","followers_url":"https://api.github.com/users/jlfwong/followers","following_url":"https://api.github.com/users/jlfwong/following{/other_user}","gists_url":"https://api.github.com/users/jlfwong/gists{/gist_id}","starred_url":"https://api.github.com/users/jlfwong/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jlfwong/subscriptions","organizations_url":"https://api.github.com/users/jlfwong/orgs","repos_url":"https://api.github.com/users/jlfwong/repos","events_url":"https://api.github.com/users/jlfwong/events{/privacy}","received_events_url":"https://api.github.com/users/jlfwong/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jlfwong/engblog","description":"KA Engineering blog.","fork":true,"url":"https://api.github.com/repos/jlfwong/engblog","forks_url":"https://api.github.com/repos/jlfwong/engblog/forks","keys_url":"https://api.github.com/repos/jlfwong/engblog/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jlfwong/engblog/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jlfwong/engblog/teams","hooks_url":"https://api.github.com/repos/jlfwong/engblog/hooks","issue_events_url":"https://api.github.com/repos/jlfwong/engblog/issues/events{/number}","events_url":"https://api.github.com/repos/jlfwong/engblog/events","assignees_url":"https://api.github.com/repos/jlfwong/engblog/assignees{/user}","branches_url":"https://api.github.com/repos/jlfwong/engblog/branches{/branch}","tags_url":"https://api.github.com/repos/jlfwong/engblog/tags","blobs_url":"https://api.github.com/repos/jlfwong/engblog/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jlfwong/engblog/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jlfwong/engblog/git/refs{/sha}","trees_url":"https://api.github.com/repos/jlfwong/engblog/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jlfwong/engblog/statuses/{sha}","languages_url":"https://api.github.com/repos/jlfwong/engblog/languages","stargazers_url":"https://api.github.com/repos/jlfwong/engblog/stargazers","contributors_url":"https://api.github.com/repos/jlfwong/engblog/contributors","subscribers_url":"https://api.github.com/repos/jlfwong/engblog/subscribers","subscription_url":"https://api.github.com/repos/jlfwong/engblog/subscription","commits_url":"https://api.github.com/repos/jlfwong/engblog/commits{/sha}","git_commits_url":"https://api.github.com/repos/jlfwong/engblog/git/commits{/sha}","comments_url":"https://api.github.com/repos/jlfwong/engblog/comments{/number}","issue_comment_url":"https://api.github.com/repos/jlfwong/engblog/issues/comments{/number}","contents_url":"https://api.github.com/repos/jlfwong/engblog/contents/{+path}","compare_url":"https://api.github.com/repos/jlfwong/engblog/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jlfwong/engblog/merges","archive_url":"https://api.github.com/repos/jlfwong/engblog/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jlfwong/engblog/downloads","issues_url":"https://api.github.com/repos/jlfwong/engblog/issues{/number}","pulls_url":"https://api.github.com/repos/jlfwong/engblog/pulls{/number}","milestones_url":"https://api.github.com/repos/jlfwong/engblog/milestones{/number}","notifications_url":"https://api.github.com/repos/jlfwong/engblog/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jlfwong/engblog/labels{/name}","releases_url":"https://api.github.com/repos/jlfwong/engblog/releases{/id}","deployments_url":"https://api.github.com/repos/jlfwong/engblog/deployments","created_at":"2016-03-25T22:20:38Z","updated_at":"2016-03-25T22:20:39Z","pushed_at":"2016-03-25T22:22:54Z","git_url":"git://github.com/jlfwong/engblog.git","ssh_url":"git@github.com:jlfwong/engblog.git","clone_url":"https://github.com/jlfwong/engblog.git","svn_url":"https://github.com/jlfwong/engblog","homepage":"http://engineering.khanacademy.org","size":24437,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"Khan:master","ref":"master","sha":"dc059e674bdb64d26d7e69374dbf0d32262a67ed","user":{"login":"Khan","id":15455,"avatar_url":"https://avatars.githubusercontent.com/u/15455?v=3","gravatar_id":"","url":"https://api.github.com/users/Khan","html_url":"https://github.com/Khan","followers_url":"https://api.github.com/users/Khan/followers","following_url":"https://api.github.com/users/Khan/following{/other_user}","gists_url":"https://api.github.com/users/Khan/gists{/gist_id}","starred_url":"https://api.github.com/users/Khan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Khan/subscriptions","organizations_url":"https://api.github.com/users/Khan/orgs","repos_url":"https://api.github.com/users/Khan/repos","events_url":"https://api.github.com/users/Khan/events{/privacy}","received_events_url":"https://api.github.com/users/Khan/received_events","type":"Organization","site_admin":false},"repo":{"id":35308364,"name":"engblog","full_name":"Khan/engblog","owner":{"login":"Khan","id":15455,"avatar_url":"https://avatars.githubusercontent.com/u/15455?v=3","gravatar_id":"","url":"https://api.github.com/users/Khan","html_url":"https://github.com/Khan","followers_url":"https://api.github.com/users/Khan/followers","following_url":"https://api.github.com/users/Khan/following{/other_user}","gists_url":"https://api.github.com/users/Khan/gists{/gist_id}","starred_url":"https://api.github.com/users/Khan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Khan/subscriptions","organizations_url":"https://api.github.com/users/Khan/orgs","repos_url":"https://api.github.com/users/Khan/repos","events_url":"https://api.github.com/users/Khan/events{/privacy}","received_events_url":"https://api.github.com/users/Khan/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Khan/engblog","description":"KA Engineering blog.","fork":false,"url":"https://api.github.com/repos/Khan/engblog","forks_url":"https://api.github.com/repos/Khan/engblog/forks","keys_url":"https://api.github.com/repos/Khan/engblog/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Khan/engblog/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Khan/engblog/teams","hooks_url":"https://api.github.com/repos/Khan/engblog/hooks","issue_events_url":"https://api.github.com/repos/Khan/engblog/issues/events{/number}","events_url":"https://api.github.com/repos/Khan/engblog/events","assignees_url":"https://api.github.com/repos/Khan/engblog/assignees{/user}","branches_url":"https://api.github.com/repos/Khan/engblog/branches{/branch}","tags_url":"https://api.github.com/repos/Khan/engblog/tags","blobs_url":"https://api.github.com/repos/Khan/engblog/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Khan/engblog/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Khan/engblog/git/refs{/sha}","trees_url":"https://api.github.com/repos/Khan/engblog/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Khan/engblog/statuses/{sha}","languages_url":"https://api.github.com/repos/Khan/engblog/languages","stargazers_url":"https://api.github.com/repos/Khan/engblog/stargazers","contributors_url":"https://api.github.com/repos/Khan/engblog/contributors","subscribers_url":"https://api.github.com/repos/Khan/engblog/subscribers","subscription_url":"https://api.github.com/repos/Khan/engblog/subscription","commits_url":"https://api.github.com/repos/Khan/engblog/commits{/sha}","git_commits_url":"https://api.github.com/repos/Khan/engblog/git/commits{/sha}","comments_url":"https://api.github.com/repos/Khan/engblog/comments{/number}","issue_comment_url":"https://api.github.com/repos/Khan/engblog/issues/comments{/number}","contents_url":"https://api.github.com/repos/Khan/engblog/contents/{+path}","compare_url":"https://api.github.com/repos/Khan/engblog/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Khan/engblog/merges","archive_url":"https://api.github.com/repos/Khan/engblog/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Khan/engblog/downloads","issues_url":"https://api.github.com/repos/Khan/engblog/issues{/number}","pulls_url":"https://api.github.com/repos/Khan/engblog/pulls{/number}","milestones_url":"https://api.github.com/repos/Khan/engblog/milestones{/number}","notifications_url":"https://api.github.com/repos/Khan/engblog/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Khan/engblog/labels{/name}","releases_url":"https://api.github.com/repos/Khan/engblog/releases{/id}","deployments_url":"https://api.github.com/repos/Khan/engblog/deployments","created_at":"2015-05-09T00:33:13Z","updated_at":"2016-01-24T22:11:36Z","pushed_at":"2016-03-25T22:23:23Z","git_url":"git://github.com/Khan/engblog.git","ssh_url":"git@github.com:Khan/engblog.git","clone_url":"https://github.com/Khan/engblog.git","svn_url":"https://github.com/Khan/engblog","homepage":"http://engineering.khanacademy.org","size":24440,"stargazers_count":7,"watchers_count":7,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":7,"mirror_url":null,"open_issues_count":20,"forks":7,"open_issues":20,"watchers":7,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Khan/engblog/pulls/57"},"html":{"href":"https://github.com/Khan/engblog/pull/57"},"issue":{"href":"https://api.github.com/repos/Khan/engblog/issues/57"},"comments":{"href":"https://api.github.com/repos/Khan/engblog/issues/57/comments"},"review_comments":{"href":"https://api.github.com/repos/Khan/engblog/pulls/57/comments"},"review_comment":{"href":"https://api.github.com/repos/Khan/engblog/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Khan/engblog/pulls/57/commits"},"statuses":{"href":"https://api.github.com/repos/Khan/engblog/statuses/19eefbc594eeed885403fb0bb4ab4ba591878acb"}}}}