Skip to content

Commit 218ac6f

Browse files
committed
Switch branch
0 parents  commit 218ac6f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+69440
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Alex Rintt
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.

README.md

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
## Features
2+
3+
- It does **NOT** support Incremental Builds (not tested at least).
4+
- It does **NOT** CMS Preview (not tested at least).
5+
- It does **SUPPORT** image optimizations.
6+
- It does **NOT** support gif optimizations, [simply because Gatsby does not](https://github.com/gatsbyjs/gatsby/issues/23678).
7+
- It **PARTIALLY SUPPORTS** the GraphQL Data Layer, if you use-case is not supported yet, feel free to check [how to create a subplugin](#how-to-create-a-subplugin).
8+
9+
## Installation
10+
11+
> **Warning** this is not npm hosted, so be careful if you want to depend on it, I recommend a fork instead a direct dependency.
12+
13+
This package is not published to npm yet, you need to install using [gitpkg](https://gitpkg.now.sh/).
14+
15+
```shell
16+
npm install https://gitpkg.now.sh/alexrintt/gatsby-source-github-graphql/packages/gatsby-source-github-graphql?master
17+
# or
18+
yarn add https://gitpkg.now.sh/alexrintt/gatsby-source-github-graphql/packages/gatsby-source-github-graphql?master
19+
```
20+
21+
## Usage
22+
23+
```js
24+
// gatsby-node.js
25+
module.exports = {
26+
// ...
27+
plugins: [
28+
{
29+
resolve: `gatsby-source-github-graphql`,
30+
// Required, GitHub only allow authenticated requests.
31+
// Your token is not shared across subplugins even if you specify a custom token to it.
32+
token: process.env.GITHUB_TOKEN,
33+
options: {
34+
plugins: [
35+
{
36+
resolve: `gatsby-source-github-graphql-discussions`,
37+
options: {
38+
owner: `<your-target-username>`,
39+
repo: `<your-target-user-repo>`
40+
},
41+
},
42+
{
43+
// You can duplicate the plugins to fetch data from multiple times from different sources.
44+
resolve: `gatsby-source-github-graphql-discussions`,
45+
options: {
46+
owner: `<your-another-target-username>`,
47+
repo: `<another-target-user-repo>`,
48+
// Optional, only if you want to override the token previously defined for this plugin instance in particular.
49+
token: process.env.SOME_ANOTHER_GITHUB_TOKEN
50+
},
51+
}
52+
]
53+
}
54+
}
55+
]
56+
}
57+
```
58+
59+
## Why does it exists
60+
61+
Because I'm building my blog that will be soon available at [alexrintt.io](https://alexrintt.io) and was searching for a plugin that fill these requirements:
62+
63+
- Fetch data from GitHub GraphQL API.
64+
- Supports Gatsby GraphQL Data Layer.
65+
- Supports image optimization.
66+
- Markdown compatible (or any other markup).
67+
68+
### Where these requirements come from?
69+
70+
- Fetch data from GitHub, well - this is my data source.
71+
- Supports Gatsby GraphQL Data Layer, this is the hard one which I kept in my requirements list for some reasons:
72+
1. I want to make total use of Gatsby GraphQL Data Layer (as we already have in CMS specific plugins like [gatsbyjs/gatsby/packages/gatsby-source-contentful](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-source-contentful) or [TryGhost/gatsby-source-ghost](https://github.com/TryGhost/gatsby-source-ghost/)).
73+
2. I want to apply any of Gatsby optimization and transformer plugins to any of the GitHub resource types (e.g repo, user, issue, discussion, file, etc.): is it a repository? optimize it's open graph image url; is it a user? optimize it's avatar url; is it a markdown file? mark it's contents as markdown media type to optimize using MarkdownRemark.
74+
3. From the previous point I also wanted to make it easy to extend and easy to replace, so I'll be able to extend when a use-case is missing but I'll also be able to replace in case of my use-case is different, e.g: if I've a plugin that optimize all GitHub issues as markdown files but instead I want to optimize as AsciiDoc files (or any custom processing), what should I do?
75+
- Supports image optimization, bandwidth bla-bla - this is also important but lets talk about this motherf [web.dev/optimize-cls](https://web.dev/optimize-cls/).
76+
- Markdown compatible (or any other markup), at this moment I'm using the discussions of a repository as markdown files to build a blog, but what if I want to switch in the future, or maybe change the processing rule or package?
77+
78+
### What I've tried before:
79+
80+
- [mosch/gatsby-source-github](https://github.com/mosch/gatsby-source-github/blob/master/src/gatsby-node.js) this unfortunately only supports fetching the file tree and the releases of a repository.
81+
- [ldd/gatsby-source-github-api](https://github.com/ldd/gatsby-source-github-api) which also doesn't support relationships. All nodes are the same type, which means there are no connection between data required; there are only flat nodes (of type `GithubData`).
82+
- [stevetweeddale/gatsby-source-git](https://github.com/stevetweeddale/gatsby-source-git) useful only if you pulling you repository markdown tree.
83+
- [gatsbyjs/gatsby/packages/gatsby-source-graphql](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-source-graphql) this also has known limitation:
84+
> This plugin has [known limitations](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-source-graphql#known-limitations), specifically in that 1. it does not support Incremental Builds, 2. CMS Preview, 3. image optimizations, 4. and lack of full support for the GraphQL data layer.
85+
86+
### Solution?
87+
88+
This monorepo is a Gatsby data source plugin + set of source subplugins which aims to provide a granular way to fetch typed and connected GitHub data chunks.
89+
90+
Technically saying:
91+
92+
- `coreplugin` is the actually Gatsby source plugin that is plugged directly into your `gatsby-config.js` and it's available under _/packages/gatsby-source-github-graphql_.
93+
- `subplugins` can be any Gatsby subplugin (under your Gatsby project at _/plugins/your-gatsby-plugin-that-will-be-used-as-subplugin_ or one of the already supported plugins at _/packages/gatsby-source-github-graphql-some-cool-usecase_ in this repo.
94+
- The core plugin request it's subplugins to fetch what data they want to `coreplugin.sourceNodes -> subplugins.sourceNodes`.
95+
- Then the core plugin connect the edges by creating the nodes by it's types `coreplugin.onCreateNodes`.
96+
- And finally the core plugin request it's subplugins again to create the schema customization through `subplugin.createSchemaCustomization`.
97+
98+
This is an answer and a question because I don't know if it's ok to create plugins in this way, I tried to copy/keep the same essence of [gatsbyjs/gatsby/packages/gatsby-transformer-remark](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-transformer-remark) but I'm not sure if it's sustentable, I did it only for personal use while trying to make it easy for me to extend in case of in the future adding some blog feature or modify an existing one.
99+
100+
But as always, do you have an idea or recommendation? just push it into the issues stack. Your use-case is not supported yet? feel free [to create a subplugin](#how-to-create-a-subplugin) and open a pull request.
101+
102+
## How to create a subplugin
103+
104+
Lets learn by example, the following section will create a subplugin which will fetch the \[viewer] or a given user from his \[login] and add it to the Gatsby GraphQL Data Layer.
105+
106+
### Defining your plugin options
107+
108+
Most plugins use options to customize their behavior, in our case we need to know the login, even though not required.
109+
110+
1. In your Gatsby project, create the plugin folder _plugins/gatsby-source-github-graphql-get-user_.
111+
112+
2. From now, we'll be working inside this folder.
113+
114+
3. Create a file called `gatsby-node.js`.
115+
116+
4. In this file lets specify which options we're expecting, in our case: the user \[login] which is not required since if it's omitted we will fetch the \[viewer] user (Gatsby uses [Joi](https://joi.dev/api/?v=17.6.1) for schema validation).
117+
118+
```js
119+
// plugins/gatsby-source-github-graphql-get-user/index.js
120+
121+
// Equivalent to [sourceNodes] gatsby API.
122+
module.exports.sourceNodes = async (gatsbyNodeApis, pluginOptions) => {
123+
// The [login] option we specified earlier in the [gatsby-node.js] file.
124+
// Remember we did not marked as required so it can be null or undefined.
125+
const { login } = pluginOptions;
126+
127+
// [githubSourcePlugin] was inserted by the core plugin and here lives all non-official (those provided by the core plugin not Gatsby) APIs.
128+
const { githubSourcePlugin } = gatsbyNodeApis;
129+
130+
// This [graphql] is from ocktokit/graphql.js package.
131+
// Even though is not possible to access the token directly,
132+
// you can make authenticated requests using this graphql client.
133+
// The authenticated user is defined in the [pluginOptions.token] or [yourSubpluginOptions.token].
134+
const { graphql } = githubSourcePlugin;
135+
136+
// Did not found an way to share fragments across subplugins to avoid repetition and lack of data so I did raw strings.
137+
// This is safe to insert in the query since it is package defined and has no user input.
138+
// You can use it or not. If you think it's not required (e.g you are fetching repositories of a user, you don't care about the user data itself) then just skip it for the user resolver.
139+
const { githubPlainResolverFields } = githubSourcePlugin;
140+
141+
// Always use this variable to define types.
142+
// Otherwise we will not be able to customize the types if a conflict between plugins node types happens.
143+
const { pluginNodeTypes } = githubSourcePlugin;
144+
145+
const userQuery = `
146+
query GetUser($login: String!) {
147+
user(login: $login) {
148+
${githubPlainResolverFields.USER}
149+
}
150+
}
151+
`;
152+
153+
const viewerQuery = `
154+
query GetViewer {
155+
viewer {
156+
${githubPlainResolverFields.USER}
157+
}
158+
}
159+
`;
160+
161+
// Wether or not we should fetch a user by its [login] option.
162+
const isCustomUser = typeof login === `string`;
163+
164+
// If there's a custom user, fetch through user query otherwise use the viewer query.
165+
const query = isCustomUser ? userQuery : viewerQuery;
166+
167+
// Same logic for the variables: custom user requires its [login]
168+
// But the [viewer] is resolved in the GitHub server through the provided token, so don't need variables.
169+
const variables = isCustomUser ? { login: login } : {};
170+
171+
// You can also add a query alias for [viewer] or [user] query.
172+
// But for simplicity lets extract both keys take the not-null one.
173+
const { user: customUser, viewer: viewerUser } = await graphql(
174+
query,
175+
variables
176+
);
177+
const user = customUser ?? viewerUser;
178+
179+
return {
180+
// Always define the key as data type and the value as an array of the data.
181+
[pluginNodeTypes.USER]: [user],
182+
};
183+
};
184+
185+
// The user avatarURL is optimized by default in the core plugin since it's a intrinsic use-case and it's available under the 'avatarUrlSharpOptimized' key.
186+
// But just for 'fun' lets create a custom key in the user node type to store a second optimized image URL (just for example purposes).
187+
module.exports.onCreateNode = async (
188+
{ node, githubSourcePlugin },
189+
pluginOptions
190+
) => {
191+
// [createFileNodeFrom] is new here and it's available only inside of [onCreateNode] function.
192+
// This function actually calls [createRemoteFileNode] from [gatsby-source-filesystem] and links to
193+
// its parent node, in this case our custom user, it's basically a helper function for image optimization.
194+
const { pluginNodeTypes, createFileNodeFrom } = githubSourcePlugin;
195+
196+
if (node.internal.type === pluginNodeTypes.USER) {
197+
if (`avatarUrl` in node) {
198+
await createFileNodeFrom({
199+
node,
200+
// Must be the key which stores the actually remote image URL, it's returned by the GitHub API.
201+
key: `avatarUrl`,
202+
// Important: this [fieldName] defines the key that our image will
203+
// be stored inside of the Gatsby reserved [fields] key.
204+
fieldName: `optimizedAvatarField`,
205+
});
206+
}
207+
}
208+
};
209+
210+
module.exports.createSchemaCustomization = (
211+
{ actions: { createTypes }, githubSourcePlugin },
212+
pluginOptions
213+
) => {
214+
const { pluginNodeTypes } = githubSourcePlugin;
215+
216+
// Now lets define that the User type will have the key
217+
// [optimizedAvatar] that should be linked from the previously created field [optimizedAvatarField].
218+
const userWithOptimizedAvatarTypeDef = `
219+
type ${pluginNodeTypes.USER} implements Node {
220+
optimizedAvatar: File @link(from: "fields.optimizedAvatarField")
221+
}
222+
`;
223+
224+
// Now call the API to actually create it.
225+
createTypes(userWithOptimizedAvatarTypeDef);
226+
};
227+
```
228+
229+
5. Create an empty `package.json` with the following contents or just run `npm init -y` or `yarn init -y`:
230+
231+
```js
232+
{
233+
"name": "gatsby-source-github-graphql-get-user",
234+
"version": "0.1.0",
235+
"main": "index.js",
236+
"license": "MIT"
237+
}
238+
```
239+
240+
6. Almost ready, lets move your working directory to your actual Gatsby project (not the plugins folder).
241+
- Remember: when you're using a plugin not from your _plugins/*_ folder you need to install it before (through npm or through directly git installations, see [installation section](#installation) for details).
242+
243+
7. Import your plugin inside the core plugin in your `gatsby-config.js`.
244+
245+
```js
246+
// gatsby-config.js
247+
module.exports = {
248+
plugins: [
249+
{
250+
resolve: `gatsby-source-github-graphql`,
251+
options: {
252+
token: process.env.GITHUB_TOKEN, // Do not forget to provide your token through the .env variable.
253+
plugins: [
254+
{
255+
resolve: `gatsby-source-github-graphql-get-user`,
256+
options: {
257+
// The option you marked as optional, lets provide it:
258+
login: `<your-github-username>` // Remember to try it without this option to see it working through the provided [token]!
259+
}
260+
}
261+
]
262+
}
263+
},
264+
]
265+
};
266+
```
267+
268+
8. Run `gatsby develop`.
269+
270+
9. Open your browser at `http://localhost:8000/___graphql` (or the URL your configured for Gatsby development server).
271+
272+
10. Run the following query:
273+
274+
```graphql
275+
query GetMyUser {
276+
# Regex because sometimes the username case can differ from the registered in the database.
277+
githubUser(login: { regex: "/<your-username-you-defined-at-gatsby-config>/i" }) {
278+
login
279+
name
280+
# The field you created through the plugin!
281+
optimizedAvatar
282+
}
283+
}
284+
```
285+
286+
A prinscreen of what it should looks like:
287+
288+
<img src="https://user-images.githubusercontent.com/51419598/196519795-2041eeb3-5d1b-438a-9012-720a6f71d24c.png">
289+
290+
11. Now keep hacking and use it to build your website/blog.
291+
292+
<samp>
293+
294+
<h2 align="center">
295+
Open Source
296+
</h2>
297+
<p align="center">
298+
<sub>Copyright © 2022-present, Alex Rintt.</sub>
299+
</p>
300+
<p align="center">Gatsby Source GitHub GraphQL <a href="/LICENSE">is MIT licensed 💖</a></p>
301+
<p align="center">
302+
<img src="https://user-images.githubusercontent.com/51419598/194058464-f67c7fb5-9066-49b5-aa94-cf34830708ad.png" width="35" />
303+
</p>
304+
305+
</samp>

lerna.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
3+
"useWorkspaces": true,
4+
"version": "0.0.0"
5+
}

0 commit comments

Comments
 (0)