;
110 | };
111 | "
112 | `)
113 |
114 | expect(mockReporter.warn).not.toHaveBeenCalled()
115 | })
116 |
117 | it('calls `reporter.warn` when `loadDocuments` rejects', async () => {
118 | loadDocuments.mockReturnValueOnce(Promise.reject(new Error('test error')))
119 |
120 | const mockReporter = {
121 | warn: jest.fn(),
122 | }
123 |
124 | const generateFromSchema = await generateWithConfig({
125 | directory: './example-directory',
126 | documentPaths: ['./src/**/*.{ts,tsx}'],
127 | fileName: 'example-types.ts',
128 | reporter: mockReporter,
129 | codegenPlugins: [],
130 | codegenConfig: {},
131 | })
132 |
133 | const mockSchema = buildSchema(`
134 | type Query {
135 | example: String
136 | }
137 | `)
138 |
139 | await generateFromSchema(mockSchema)
140 |
141 | expect(mockReporter.warn).toMatchInlineSnapshot(`
142 | [MockFunction] {
143 | "calls": Array [
144 | Array [
145 | "[gatsby-plugin-graphql-codegen] test error",
146 | ],
147 | ],
148 | "results": Array [
149 | Object {
150 | "type": "return",
151 | "value": undefined,
152 | },
153 | ],
154 | }
155 | `)
156 | })
157 |
158 | describe('mapCodegenPlugins', () => {
159 | const fakePlugin = () => {}
160 |
161 | it('add new plugin', () => {
162 | const { pluginMap, plugins } = mapCodegenPlugins({
163 | codegenPlugins: [
164 | {
165 | resolve: fakePlugin,
166 | options: {
167 | hey: false,
168 | },
169 | },
170 | ],
171 | defaultPlugins: {
172 | pluginMap: {},
173 | plugins: [],
174 | },
175 | })
176 |
177 | const identifier = 'codegen-plugin-0'
178 |
179 | expect(plugins).toHaveLength(1)
180 | expect(plugins).toContainEqual({ [identifier]: { hey: false } })
181 | expect(pluginMap).toHaveProperty(identifier)
182 | expect(pluginMap[identifier].plugin).toBe(fakePlugin)
183 | })
184 |
185 | it('update default plugins', () => {
186 | const { pluginMap, plugins } = mapCodegenPlugins({
187 | codegenPlugins: [
188 | {
189 | resolve: 'typescript',
190 | options: {
191 | hey: false,
192 | },
193 | },
194 | ],
195 | defaultPlugins: {
196 | pluginMap: {
197 | typescript: {
198 | plugin: fakePlugin,
199 | },
200 | },
201 | plugins: [{ typescript: { hey: true } }],
202 | },
203 | })
204 |
205 | const identifier = 'typescript'
206 |
207 | expect(plugins).toHaveLength(1)
208 | expect(plugins).toContainEqual({ [identifier]: { hey: false } })
209 | expect(pluginMap).toHaveProperty(identifier)
210 | expect(pluginMap[identifier].plugin).toBe(fakePlugin)
211 | })
212 |
213 | it('add new plugin and update default plugin', () => {
214 | const { pluginMap, plugins } = mapCodegenPlugins({
215 | codegenPlugins: [
216 | {
217 | resolve: 'typescript',
218 | options: {
219 | hey: false,
220 | },
221 | },
222 | {
223 | resolve: fakePlugin,
224 | options: {
225 | how: 1,
226 | },
227 | },
228 | ],
229 | defaultPlugins: {
230 | pluginMap: {
231 | typescript: {
232 | plugin: fakePlugin,
233 | },
234 | },
235 | plugins: [{ typescript: { hey: true } }],
236 | },
237 | })
238 |
239 | expect(plugins).toHaveLength(2)
240 | expect(plugins).toContainEqual({ typescript: { hey: false } })
241 | expect(plugins).toContainEqual({ 'codegen-plugin-1': { how: 1 } })
242 | expect(pluginMap).toHaveProperty('typescript')
243 | expect(pluginMap).toHaveProperty('codegen-plugin-1')
244 | })
245 | })
246 |
--------------------------------------------------------------------------------
/packages/gatsby-plugin-graphql-codegen/readme.md:
--------------------------------------------------------------------------------
1 | # Gatsby Typescript Graphql Codegen
2 |
3 | Automatic type generation for your graphql queries via [`graphql-code-generator`](https://github.com/dotansimha/graphql-code-generator)
4 |
5 |
6 | ## Installation
7 |
8 | ```
9 | yarn add typescript gatsby-plugin-graphql-codegen
10 | ```
11 |
12 | Add this to your gatsby config like any other plugins:
13 | ```js
14 | // gatsby-config.js
15 | module.exports = {
16 | plugins: [
17 | `gatsby-plugin-graphql-codegen`,
18 | ]
19 | }
20 | ```
21 |
22 | ### Options
23 |
24 | |key | default | value |
25 | |---|---|---|
26 | |options.codegen| `true` | enable / disable generating definitions for graphql queries|
27 | |options.documentPaths| ['./src/**/*.{ts,tsx}',
'./.cache/fragments/*.js',
'./node_modules/gatsby-*/**/*.js'] | The paths to files containing graphql queries.
⚠️ The default paths will be overwritten by the `documentPaths` you pass in, so please make sure to include *all* necessary paths ⚠️
28 | |options.fileName| `graphql-type.ts` | path to the generated file. By default, it's placed at the project root directory & it should not be placed into `src`, since this will create an infinite loop|
29 | |options.codegenDelay| `200` | amount of delay from file change to codegen|
30 | |options.pluckConfig| { globalGqlIdentifierName: "graphql", modules: [ { name: 'gatsby', identifier: 'graphql' } ] } | options passed to [graphql-tag-pluck](https://github.com/ardatan/graphql-toolkit/tree/master/packages/graphql-tag-pluck) when extracting queries and fragments from documents |
31 | |options.failOnError (^2.5.0)| `process.env.NODE_ENV === 'production'` | Throw error if the codegen fails. By default only apply to production builds.
32 | |options.codegenConfig (^2.7.0)| `{}` | Add config directly to `graphql-codegen`. These key-value config will be applied to every `graphql-codegen` plugins. See [graphql-codegen docs on the config field](https://graphql-code-generator.com/docs/getting-started/config-field) |
33 | |options.codegenPlugins (^2.7.0)| `[]` | Add additional plugins to `graphql-codegen`. We use the same format as Gatsby's. See example usage below.
34 | |options.additionalSchemas (^2.6.0)| []
| array of additional schemas (other than the schema used by gatsby queries) for which types should be generated for. This is useful when you use client-side queries (e.g. with apollo-client) where you are querying another schema/endpoint |
35 |
36 | #### Additional Schema Options (for `options.additionalSchemas`)
37 | |key | default | value |
38 | |---|---|---|
39 | |key| - | an unique key used internally by this plugin to identify different schemas|
40 | |fileName| graphql-types-${key}.ts | path to the generated file for this schema. By default, it's placed at the project root directory & it should not be placed into `src`, since this will create an infinite loop |
41 | |documentPaths| value of `options.documentPaths` | The paths to files containing graphql queries. See also `options.documentPaths` |
42 | |pluckConfig| - | options passed to [graphql-tag-pluck](https://github.com/ardatan/graphql-toolkit/tree/master/packages/graphql-tag-pluck) when extracting queries and fragments from documents |
43 | |schema| - | additional schema to process. Can either be an url, a path to a local schema definition (both `.json` and `.graphql` are supported) or an inline definition. See also https://github.com/ardatan/graphql-toolkit#-schema-loading |
44 | |codegenConfig (^2.7.0)| `{}` | See `codegenConfig` above
45 | |codegenPlugin (^2.7.0)| `{}` | See `codegenPlugin` above
46 |
47 | ## Example Setups
48 |
49 | ### Normal Usecase
50 | Set it & forget it
51 |
52 | ```js
53 | exports.default = {
54 | plugins: [
55 | `gatsby-plugin-graphql-codegen`,
56 | ]
57 | }
58 | ```
59 |
60 | ### Custom Filename & Location
61 |
62 | ```js
63 | exports.default = {
64 | plugins: [{
65 | resolve: `gatsby-plugin-graphql-codegen`,
66 | options: {
67 | fileName: `./gatsby-graphql.ts`,
68 | }
69 | }]
70 | }
71 | ```
72 |
73 | ### Gatsby-node.ts
74 | You have queries in your gatsby-node? We can take care of that. The experience is not 100% right now, but that'll change soon!
75 |
76 | ```js
77 | exports.default = {
78 | plugins: [{
79 | resolve: `gatsby-plugin-graphql-codegen`,
80 | options: {
81 | fileName: `./gatsby-graphql.ts`,
82 | documentPaths: [
83 | './src/**/*.{ts,tsx}',
84 | './node_modules/gatsby-*/**/*.js',
85 | './gatsby-node.ts',
86 | ],
87 | }
88 | }]
89 | }
90 | ```
91 |
92 | ### Customize Graphql Codegen
93 | You want to pass additional config to `graphql-codegen`:
94 |
95 | ```js
96 | // additional plugins
97 | import { plugin as resolverPlugin } from '@graphql-codegen/typescript-resolvers'
98 |
99 | exports.default = {
100 | plugins: [{
101 | resolve: `gatsby-plugin-graphql-codegen`,
102 | options: {
103 | codegenConfig: {
104 | // key-value configs that will be applied to every plugins.
105 | // Note: The example below is completely unnecessary, just a demonstration.
106 | typesPrefix: 'Hi' // -> import { HiImageQuery } from '../../graphql-types'
107 | },
108 | codegenPlugins: [{
109 | // built-in plugin.
110 | // Use `typescript` for `@graphql-codegen/typescript`
111 | // and `operations` for `@graphql-codegen/typescript-operations`
112 | resolve: 'typescript',
113 | options: {
114 | namingConvention: `lower-case#lowerCase`,
115 | }
116 | },{
117 | // additional plugin
118 | resolve: resolverPlugin,
119 | options: {
120 | typesPrefix: 'I'
121 | }
122 | }]
123 | }
124 | }]
125 | }
126 | ```
127 |
128 | ### Dual-Schema Setup
129 | If you use `graphql` on the client side, this is for you.
130 |
131 | ```js
132 | exports.default = {
133 | plugins: [{
134 | resolve: `gatsby-plugin-graphql-codegen`,
135 | options: {
136 | additionalSchemas: [{
137 | key: 'pokemon',
138 | fileName: './graphql-pokemon.ts',
139 | schema: 'https://graphql-pokemon.now.sh/',
140 | pluckConfig: {
141 | // config to ensure only queries using the `gql` tag are used for this schema
142 | globalGqlIdentifierName: 'gql',
143 | modules: [
144 | {
145 | name: 'graphql-tag',
146 | identifier: 'gql',
147 | },
148 | ],
149 | },
150 | }],
151 | }
152 | }]
153 | }
154 | ```
155 |
156 | ## Code generation
157 |
158 | By default, this plugin will build typing for your queries automatically to `graphql-types.d.ts` on every edit. Please note that the definition file **should not** be placed inside `src` since this triggers a never ending loop during development.
159 |
160 | In order to take advantage of the generated code, user needs to name their query:
161 |
162 | ```js
163 | // src/pages/index.tsx
164 |
165 | export const pageQuery = graphql`
166 | - query {
167 | + query BlogIndex {
168 | site {
169 | siteMetadata {
170 | title
171 | }
172 | }
173 | ...
174 | ```
175 |
176 | ...and import it from the generated type file:
177 |
178 | ```js
179 | // src/pages/index.tsx
180 |
181 | import { PageProps } from "gatsby";
182 | import { BlogIndexQuery } from '../graphqlTypes'
183 |
184 | const BlogIndex: React.FC> = ({ data, location }) => {
185 | ...
186 | }
187 | ```
188 |
--------------------------------------------------------------------------------
/packages/gatsby-plugin-ts/readme.md:
--------------------------------------------------------------------------------
1 | # Gatsby Typescript Plugin
2 |
3 | An alternative to the official typescript plugin, with [`ts-loader`](https://github.com/TypeStrong/ts-loader) & automatic type generation for your graphql queries (using [`graphql-code-generator`](https://github.com/dotansimha/graphql-code-generator))
4 |
5 | ---
6 | ## Installation
7 |
8 | ```
9 | yarn add typescript gatsby-plugin-ts
10 | ```
11 |
12 | Add this to your gatsby config like any other plugins:
13 | ```js
14 | // gatsby-config.js
15 | module.exports = {
16 | plugins: [
17 | `gatsby-plugin-ts`,
18 | ]
19 | }
20 | ```
21 |
22 | ---
23 |
24 | Unlike the official plugin, you'd have to bring your own `tsconfig.json`.
25 |
26 | ```bash
27 | # generate a tsconfig if you have none
28 | tsc --init
29 | ```
30 |
31 | In order for this plugin to work right, you'd need to set your compile options like the following:
32 |
33 | ```js
34 | "compilerOptions": {
35 | "target": "ES2018", /* or at least ES2015 */
36 | "module": "ESNext", /* or at least ES2015 */
37 | "lib": ["dom"], /* <-- required! */
38 | "jsx": "preserve", /* <-- required! */
39 | "moduleResolution": "node", /* <-- required! */
40 |
41 | /* for mixed ts/js codebase */
42 | "allowJs": true,
43 | "outDir": "./build" /* this won't be used by ts-loader */
44 | /* other options... */
45 | }
46 |
47 | ```
48 |
49 | ### Options
50 |
51 | |key | default | value |
52 | |---|---|---|
53 | |**typecheck options**|||
54 | |options.tsLoader| `{}` | option to be passed into `ts-loader`. `transpileOnly` is always `true`, since typechecking is handled by `fork-ts-checker-webpack-plugin`. [See ts-loader docs](https://github.com/TypeStrong/ts-loader#options) for more |
55 | |options.alwaysCheck | `false` | ⚠️deprecated
By default type checking is disabled in production mode (during `gatsby build`). Set this to `true` to enable type checking in production as well |
56 | |options.typeCheck | `true` | Enable / disable type checking with `fork-ts-checker-webpack-plugin`. |
57 | |options.forkTsCheckerPlugin | `{}` | Options that'll be passed to `fork-ts-checker-webpack-plugin`. For all options, please see [their docs](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#options)
58 | |**codegen options**|||
59 | |options.codegen| `true` | enable / disable generating definitions for graphql queries|
60 | |options.documentPaths| ['./src/**/*.{ts,tsx}',
'./.cache/fragments/*.js',
'./node_modules/gatsby-*/**/*.js'] | The paths to files containing graphql queries.
⚠️ The default paths will be overwritten by the `documentPaths` you pass in, so please make sure to include *all* necessary paths ⚠️
61 | |options.fileName| `graphql-type.ts` | path to the generated file. By default, it's placed at the project root directory & it should not be placed into `src`, since this will create an infinite loop|
62 | |options.codegenDelay| `200` | amount of delay from file change to codegen|
63 | |options.pluckConfig| { globalGqlIdentifierName: "graphql", modules: [ { name: 'gatsby', identifier: 'graphql' } ] } | options passed to [graphql-tag-pluck](https://github.com/ardatan/graphql-toolkit/tree/master/packages/graphql-tag-pluck) when extracting queries and fragments from documents |
64 | |options.failOnError (2.5.0)| `process.env.NODE_ENV === 'production'` | Throw error if the codegen fails. By default only apply to production builds.
65 | |options.codegenConfig (^2.7.0)| `{}` | Add config directly to `graphql-codegen`. These key-value config will be applied to every `graphql-codegen` plugins. See [graphql-codegen docs on the config field](https://graphql-code-generator.com/docs/getting-started/config-field) |
66 | |options.codegenPlugins (^2.7.0)| `[]` | Add additional plugins to `graphql-codegen`. We use the same format as Gatsby's. See example usage below.
67 | |options.additionalSchemas (^2.6.0)| []
| array of additional schemas (other than the schema used by gatsby queries) for which types should be generated for. This is useful when you use client-side queries (e.g. with apollo-client) where you are querying another schema/endpoint |
68 |
69 | ## Example Setup
70 |
71 | ### Basic
72 |
73 | ```js
74 | module.exports = {
75 | plugins: [
76 | `gatsby-plugin-ts`,
77 | ]
78 | }
79 | ```
80 |
81 | ### Custom Output Path
82 |
83 | ```js
84 | module.exports = {
85 | plugins: [
86 | {
87 | resolve: `gatsby-plugin-ts`,
88 | options: {
89 | fileName: `gen/graphql-types.ts`,
90 | }
91 | }
92 | ]
93 | }
94 | ```
95 |
96 | ### I need to change everything
97 |
98 | ```js
99 | // gatsby-config.js
100 | {
101 | resolve: `gatsby-plugin-ts`,
102 | options: {
103 | tsLoader: {
104 | logLevel: 'warn',
105 | },
106 | forkTsCheckerPlugin: {
107 | eslint: true,
108 | },
109 | fileName: `types/graphql-types.ts`,
110 | codegen: true,
111 | codegenDelay: 250,
112 | typeCheck: false,
113 | pluckConfig: {
114 | // this is the default config
115 | globalGqlIdentifierName: 'graphql',
116 | modules: [
117 | { name: 'gatsby', identifier: 'graphql' },
118 | ],
119 | },
120 | additionalSchemas: [
121 | {
122 | key: 'example',
123 | fileName: 'graphql-types-example.ts',
124 | schema: 'https://example.com/graphql',
125 | pluckConfig: {
126 | // config to ensure only queries using the `gql` tag are used for this schema
127 | globalGqlIdentifierName: 'gql',
128 | modules: [
129 | {
130 | name: 'graphql-tag',
131 | identifier: 'gql',
132 | },
133 | ],
134 | },
135 | }
136 | ],
137 | },
138 | }
139 | ```
140 |
141 | ### Gatsby files
142 |
143 | - `gatsby-config` has to be a `.js` file
144 | - `gatsby-node` is run directly by `node`, so it has to be a .js file as well. It is a shame, because in a complicated Gatsby app it is where a lot of logic live & will benefit from ts. As a work around, it can be built with `tsc` independently, in a script in `package.json` or somehow in gatsby's pre-init hook.
145 | - Gatsby's global variable like `__PATH_PREFIX__` can be handled by declaring this code somewhere:
146 |
147 | ```ts
148 | // src/global.d.ts
149 | declare const __PATH_PREFIX__: string
150 | ```
151 |
152 | ## Code generation
153 |
154 | By default, this plugin will build typing for your queries automatically to `graphql-types.d.ts` on every edit. Please note that the definition file **should not** be placed inside `src` since this triggers a never ending loop during development.
155 |
156 | In order to take advantage of the generated code, user needs to name their query:
157 |
158 | ```js
159 | // src/pages/index.tsx
160 |
161 | export const pageQuery = graphql`
162 | - query {
163 | + query BlogIndex {
164 | site {
165 | siteMetadata {
166 | title
167 | }
168 | }
169 | ...
170 | ```
171 |
172 | ...and import it from the generated type file:
173 |
174 | ```js
175 | // src/pages/index.tsx
176 |
177 | import { BlogIndexQuery } from '../graphqlTypes'
178 |
179 | interface IBlogIndexProps {
180 | data: BlogIndexQuery;
181 | location: Location;
182 | }
183 |
184 | const BlogIndex: React.FC = ({ data, location }) => {
185 | ...
186 | }
187 | ```
188 |
189 | ## Disable type checking in production
190 |
191 | Previously this plugin disable type checking in production by default, which can be changed by setting `alwaysCheck` to `true`. Since 2.0.0 it no longer does this. If you want to preseve the previous behavior, please set the `typeCheck` option like below:
192 |
193 | ```js
194 | {
195 | resolve: 'gatsby-plugin-ts',
196 | options: {
197 | // Disable type checking in production
198 | typeCheck: process.env.NODE_ENV !== 'production',
199 | }
200 | }
201 | ```
--------------------------------------------------------------------------------
/packages/gatsby-plugin-graphql-codegen/src/gatsby-node.test.js:
--------------------------------------------------------------------------------
1 | import { onPostBootstrap } from './gatsby-node'
2 | import { generateWithConfig } from './graphql-codegen.config'
3 |
4 | jest.mock('./graphql-codegen.config', () => ({ generateWithConfig: jest.fn() }))
5 |
6 | const delay = (milliseconds) =>
7 | new Promise((resolve) => setTimeout(resolve, milliseconds))
8 |
9 | it('early returns if the codegen option is false', async () => {
10 | const mockGetState = jest.fn()
11 | const mockGatsbyArgs = {
12 | store: {
13 | getState: mockGetState,
14 | },
15 | }
16 |
17 | const pluginOptions = {
18 | plugins: [],
19 | codegen: false,
20 | }
21 |
22 | await onPostBootstrap(mockGatsbyArgs, pluginOptions)
23 |
24 | expect(mockGetState).not.toHaveBeenCalled()
25 | })
26 |
27 | it('calls `generateWithConfig` from `graphql-codegen.config.ts`', async () => {
28 | const mockGatsbyArgs = {
29 | store: {
30 | getState: () => ({
31 | schema: 'mock-schema',
32 | program: { directory: 'mock-directory' },
33 | }),
34 | subscribe: jest.fn(),
35 | },
36 | reporter: {
37 | info: jest.fn(),
38 | panic: jest.fn(),
39 | },
40 | }
41 |
42 | const pluginOptions = {
43 | documentPaths: ['./example-document-paths'],
44 | fileName: './example-filename.ts',
45 | plugins: [],
46 | }
47 |
48 | const mockGenerateFromSchema = jest.fn()
49 | generateWithConfig.mockReturnValueOnce(mockGenerateFromSchema)
50 |
51 | await onPostBootstrap(mockGatsbyArgs, pluginOptions)
52 |
53 | expect(generateWithConfig).toHaveBeenCalledTimes(1)
54 | expect(generateWithConfig.mock.calls[0][0]).toMatchInlineSnapshot(`
55 | Object {
56 | "codegenConfig": Object {},
57 | "codegenPlugins": Array [],
58 | "directory": "mock-directory",
59 | "documentPaths": Array [
60 | "./example-document-paths",
61 | ],
62 | "fileName": "./example-filename.ts",
63 | "key": "default-gatsby-schema",
64 | "pluckConfig": Object {
65 | "globalGqlIdentifierName": "graphql",
66 | "modules": Array [
67 | Object {
68 | "identifier": "graphql",
69 | "name": "gatsby",
70 | },
71 | ],
72 | },
73 | "reporter": Object {
74 | "info": [MockFunction] {
75 | "calls": Array [
76 | Array [
77 | "[gatsby-plugin-graphql-codegen] definition for queries of schema default-gatsby-schema has been updated at ./example-filename.ts",
78 | ],
79 | ],
80 | "results": Array [
81 | Object {
82 | "type": "return",
83 | "value": undefined,
84 | },
85 | ],
86 | },
87 | "panic": [MockFunction],
88 | },
89 | "schema": "mock-schema",
90 | }
91 | `)
92 |
93 | expect(mockGenerateFromSchema).toMatchInlineSnapshot(`
94 | [MockFunction] {
95 | "calls": Array [
96 | Array [
97 | "mock-schema",
98 | ],
99 | ],
100 | "results": Array [
101 | Object {
102 | "type": "return",
103 | "value": undefined,
104 | },
105 | ],
106 | }
107 | `)
108 | })
109 |
110 | it('calls `reporter.warn` if `generateWithConfig` throws', async () => {
111 | const mockGatsbyArgs = {
112 | store: {
113 | getState: () => ({
114 | schema: 'mock-schema',
115 | program: { directory: 'mock-directory' },
116 | }),
117 | subscribe: jest.fn(),
118 | },
119 | reporter: {
120 | info: jest.fn(),
121 | warn: jest.fn(),
122 | },
123 | }
124 |
125 | const pluginOptions = {
126 | documentPaths: ['./example-document-paths'],
127 | fileName: './example-filename.ts',
128 | plugins: [],
129 | }
130 |
131 | const mockGenerateFromSchema = () => {
132 | throw new Error('mock error')
133 | }
134 | generateWithConfig.mockReturnValueOnce(mockGenerateFromSchema)
135 |
136 | await onPostBootstrap(mockGatsbyArgs, pluginOptions)
137 |
138 | expect(mockGatsbyArgs.reporter.warn).toMatchInlineSnapshot(`
139 | [MockFunction] {
140 | "calls": Array [
141 | Array [
142 | [Error: mock error],
143 | ],
144 | ],
145 | "results": Array [
146 | Object {
147 | "type": "return",
148 | "value": undefined,
149 | },
150 | ],
151 | }
152 | `)
153 | })
154 |
155 | it('subscribes to the store and debounces the `build` function', async () => {
156 | // this variable is assigned when the `.subscribe` function is called.
157 | // it allows us to invoke the listener and mock "notify" its state
158 | let notify
159 |
160 | const codegenDelay = 50
161 |
162 | const mockState = {
163 | schema: 'mock-schema',
164 | program: { directory: 'mock-directory' },
165 | // this type has to be either `QUERY_EXTRACTED` or `REPLACE_STATIC_QUERY`
166 | lastAction: { type: 'QUERY_EXTRACTED' },
167 | }
168 |
169 | const mockGatsbyArgs = {
170 | store: {
171 | getState: () => mockState,
172 | subscribe: (listener) => {
173 | notify = listener
174 | },
175 | },
176 | reporter: {
177 | info: jest.fn(),
178 | warn: jest.fn(),
179 | },
180 | }
181 |
182 | const pluginOptions = {
183 | documentPaths: ['./example-document-paths'],
184 | fileName: './example-filename.ts',
185 | codegenDelay,
186 | plugins: [],
187 | }
188 |
189 | const mockGenerateFromSchema = jest.fn()
190 | generateWithConfig.mockReturnValue(mockGenerateFromSchema)
191 |
192 | await onPostBootstrap(mockGatsbyArgs, pluginOptions)
193 | expect(mockGenerateFromSchema).toHaveBeenCalledTimes(1)
194 |
195 | // notify the subscriber 5 times
196 | for (let i = 0; i < 5; i += 1) {
197 | notify()
198 | }
199 |
200 | // wait 2x the amount of the codegen delay to ensure everything has flushed
201 | await delay(codegenDelay * 2)
202 | // because of the debounce, we should expect it to have only been called
203 | // twice instead of 6 times
204 | expect(mockGenerateFromSchema).toHaveBeenCalledTimes(2)
205 |
206 | expect(mockGatsbyArgs.reporter.info).toHaveBeenCalled()
207 | expect(mockGatsbyArgs.reporter.warn).not.toHaveBeenCalled()
208 | })
209 |
210 | it("doesn't call build if the `lastAction.type` isn't 'REPLACE_STATIC_QUERY' or 'QUERY_EXTRACTED'", async () => {
211 | // this variable is assigned when the `.subscribe` function is called.
212 | // it allows us to invoke the listener and mock "notify" its state
213 | let notify
214 |
215 | const codegenDelay = 50
216 |
217 | const mockState = {
218 | schema: 'mock-schema',
219 | program: { directory: 'mock-directory' },
220 | lastAction: { type: 'NOT_THE_CORRECT_ACTION' },
221 | }
222 |
223 | const mockGatsbyArgs = {
224 | store: {
225 | getState: () => mockState,
226 | subscribe: (listener) => {
227 | notify = listener
228 | },
229 | },
230 | reporter: {
231 | info: jest.fn(),
232 | panic: jest.fn(),
233 | },
234 | }
235 |
236 | const pluginOptions = {
237 | documentPaths: ['./example-document-paths'],
238 | fileName: './example-filename.ts',
239 | codegenDelay,
240 | plugins: [],
241 | }
242 |
243 | const mockGenerateFromSchema = jest.fn()
244 | generateWithConfig.mockReturnValue(mockGenerateFromSchema)
245 |
246 | await onPostBootstrap(mockGatsbyArgs, pluginOptions)
247 | expect(mockGenerateFromSchema).toHaveBeenCalledTimes(1)
248 |
249 | // notify the subscriber 5 times
250 | for (let i = 0; i < 5; i += 1) {
251 | notify()
252 | }
253 |
254 | // wait 2x the amount of the codegen delay to ensure everything has flushed
255 | await delay(codegenDelay * 2)
256 | // because the lastAction.type above isn't the 'REPLACE_STATIC_QUERY' or
257 | // 'QUERY_EXTRACTED', this will only be called once
258 | expect(mockGenerateFromSchema).toHaveBeenCalledTimes(1)
259 | })
260 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Gatsby Typescript
2 |
3 | An alternative to the official typescript plugin, with [`ts-loader`](https://github.com/TypeStrong/ts-loader) & automatic type generation for your graphql queries (using [`graphql-code-generator`](https://github.com/dotansimha/graphql-code-generator))
4 |
5 | ---
6 |
7 | > Hi there 👋
8 | > Are you just looking for a way to generate graphql types for your graphql queries?
9 | >
10 | > `gatsby-plugin-graphql-codegen` is what you want. However, other maintainers and I haven't been able to keep this repo up to shape. Please have a look at @cometkim's [graphql-plugin-typegen](https://github.com/cometkim/gatsby-plugin-typegen) which does almost the same thing & better maintained.
11 | > Still, ideas & PRs are all welcomed. If you'd like to help maintain this project, please feel free to reach out. Thank you, have a great day!
12 |
13 | ---
14 |
15 |
16 | This monorepo houses 2 packages:
17 |
18 | | npm package | Description | Docs |
19 | |---|---|---|
20 | | gatsby-plugin-ts | alternative typescript support with `ts-loader` & automated graphql codegen via `graphql-code-generator` | [`docs`](./packages/gatsby-plugin-ts/readme.md)
21 | | gatsby-plugin-graphql-codegen | automated graphql codegen via `graphql-code-generator` | [`docs`](./packages/gatsby-plugin-graphql-codegen/readme.md)
22 |
23 | ---
24 |
25 | Quick links: [Acknowledgement](https://github.com/d4rekanguok/gatsby-typescript#acknowledgment) • [General Q&A](https://github.com/d4rekanguok/gatsby-typescript#general-qa) • [Contribute](https://github.com/d4rekanguok/gatsby-typescript#contribute-to-this-repo)
26 |
27 | ---
28 |
29 | ## Acknowledgment
30 |
31 | Special thanks to the contributors, who have improved this project with code, bug reports & suggestions:
32 |
33 | ### Code
34 |
35 | [@ricokahler (maintainer)](https://github.com/ricokahler),
36 |
37 | [@kije](https://github.com/kije),
38 |
39 | [@Js-Brecht](https://github.com/Js-Brecht),
40 |
41 | [@Kerumen](https://github.com/Kerumen),
42 |
43 | [@olee](https://github.com/olee),
44 |
45 | [@Tielem](https://github.com/Tielem)
46 |
47 | ### Bugs & Suggestions
48 |
49 | [@frantic1048](https://github.com/frantic1048),
50 |
51 | [@cometkim](https://github.com/cometkim),
52 |
53 | [@FrobtheBuilder](https://github.com/FrobtheBuilder),
54 |
55 | [@aswinckr](https://github.com/aswinckr),
56 |
57 | [@mshick](https://github.com/mshick),
58 |
59 | [@joewood](https://github.com/joewood),
60 |
61 | [@Jdruwe](https://github.com/Jdruwe)
62 |
63 |
64 |
65 | Do you want to send a PR? [see this section](https://github.com/d4rekanguok/gatsby-typescript#contribute-to-this-repo)
66 |
67 |
68 |
69 | ### Powered By
70 |
71 | This project is built upon these awesome projects:
72 |
73 | - TypeStrong projects:
74 | - [TS Loader](https://github.com/TypeStrong/ts-loader)
75 | - [Fork TS Checker Webpack Plugin](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin)
76 |
77 | - [The Guild](https://the-guild.dev/)'s projects:
78 | - [Graphql Code Generator](https://github.com/dotansimha/graphql-code-generator)
79 | - [Graphql Toolkit](https://github.com/ardatan/graphql-toolkit)
80 |
81 | And of course, [Gatsbyjs](https://github.com/gatsbyjs/gatsby) and [Typescript](https://github.com/microsoft/typescript)
82 |
83 | ---
84 |
85 | ## General Q&A
86 |
87 | Here's a list of common questions I've seen since releasing this project. If you have a question that's not here, please don't hesitate to open an issue!
88 |
89 |
90 |
91 | Why use gatsby-plugin-ts?
92 |
93 |
94 | Gatsby use `babel-preset-typescript` which strips type information out of your code without doing typecheck. `gatsby-plugin-ts` use `ts-loader`, so you don't have to (1) worry about the [caveats](https://babeljs.io/docs/en/babel-plugin-transform-typescript#caveats) of `babel-preset-typescript` or (2) use an IDE / code editor that can typecheck for you.
95 |
96 | It also generate typings for your graphql queries, make it easier to strengthen your code.
97 |
98 | If you're already using something like VSCode and/or don't want to do typecheck in production, you can toggle off the typecheck option.
99 |
100 |
101 |
102 |
103 | What's the difference between gatsby-plugin-ts and gatsby-plugin-graphql-codegen?
104 |
105 |
106 | Originally belong to the same plugin, the codegen portion was extracted to `gatsby-plugin-graphql-codegen` so it can be used with the official typescript plugin. If you are already using `gatsby-plugin-ts`, you don't need `gatsby-plugin-graphql-codegen`.
107 |
108 |
109 |
110 |
111 | Should I check the generated type definition into git?
112 |
113 |
114 | It's up to your preference.
115 |
116 |
117 |
118 |
119 | What is up with all the Maybe<T>?
120 |
121 |
122 | It's due to Gatsby internal. There's an effort to [make typing more strict here](https://github.com/gatsbyjs/gatsby/issues/20069).
123 |
124 | You also may find the new optional chaining & nullish coalescing operator in typescript 3.7 helpful to deal with this.
125 |
126 |
127 |
128 |
129 | Can I turn off type checking and/or type generating?
130 |
131 |
132 | Yes! You can also use node env to determine whether to enable these features.
133 |
134 | ```js
135 | // gatsby-config.js
136 | {
137 | resolve: `gatsby-plugin-ts`,
138 | options: {
139 | codegen: false,
140 | typeCheck: process.env.NODE_ENV === 'development',
141 | }
142 | },
143 | ```
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 | My graphql queries returns null
152 |
153 |
154 | Gatsby extract graphql queries statically and it only understand queries inside template literal. It's possible that tsc is transpiling your template literal to string concat quivalent. Check your `tsconfig.json` & make sure you have a setting similar to this:
155 |
156 | ```js
157 | "compilerOptions": {
158 | "target": "ES2018", /* or at least ES2015 */
159 | "module": "ESNext", /* or at least ES2015 */
160 | "lib": ["dom"], /* <-- required! */
161 | "jsx": "preserve", /* <-- required! */
162 | "moduleResolution": "node", /* <-- required! */
163 | /* other options... */
164 | }
165 | ```
166 |
167 |
168 |
169 |
170 |
171 | What if I have a mixed ts/js codebase?
172 |
173 |
174 | You'd have to update your `tsconfig` with the below options:
175 |
176 | ```json
177 | "allowJs": true,
178 | "outDir": "./build"
179 | ```
180 |
181 | The `outDir` option won't be used by ts-loader, but you may need it to satisfy vscode.
182 |
183 |
184 |
185 |
186 |
187 | Babel doesn't understand the new optional chaining & nullish coalescing syntax even though my IDE shows no errors
188 |
189 |
190 | If you are using `gatsby-plugin-ts`, before you go off and install a bunch of babel plugins like a lot of tutorials suggest, check if your compilation `target` in `tsconfig.json` is too high (`ESNext` or `ES2019`).
191 |
192 | With these targets, tsc will leave the new syntax as-is, which babel might not understand. Downgrade them to `ES2018` should fix the issue; also make sure _your_ IDE's typescript version is the same as the one listed in your `package.json` dependency.
193 |
194 |
195 |
196 |
197 |
198 | Can I write `gatsby-node` in typescript & have its queries typing generated as well?
199 |
200 |
201 | Yes, but it's not easy at the moment. We're working on it; stay tuned!
202 |
203 |
204 |
205 |
206 |
207 | Typechecking causes `gatsby develop` to crash.
208 |
209 |
210 | We're trying to pin down why this happens, please share your experience in [#36](https://github.com/d4rekanguok/gatsby-typescript/issues/36)
211 |
212 |
213 |
214 | ### Common warning & errors
215 | Gatsby recently moved plugins' fragments from `.cache` to `node_modules`. We currently support both paths, but sometimes it may cause conflict warnings & errors:
216 |
217 |
218 |
219 | `warning: Unable to find any GraphQL type definitions for the following pointers ...`
220 |
221 |
222 | If you are annoyed by this warning, set the `documentPaths` options as below:
223 |
224 | ```js
225 | // gatsby-config.js
226 | {
227 | resolve: 'gatsby-plugin-graphql-codegen',
228 | options: {
229 | documentPaths: [
230 | './src/**/*.{ts,tsx}',
231 | './node_modules/gatsby-*/**/*.js',
232 | ],
233 | }
234 | },
235 | ```
236 |
237 | ~~We will remove the `.cache/fragments` path and bump gatsby peer dependency version in a later release.~~
238 |
239 | **Update**: Since 2.4.0, we've removed `.cache/fragments` & bump up gatsby peer dep.
240 |
241 |
242 |
243 |
244 |
245 | Duplicate identifier error: Duplicate identifier 'GatsbyImageSharpFixedFragment'
246 |
247 |
248 | If you see this error please run a `gatsby clean` to remove fragments in `.cache`, or set the `documentPaths` options as below:
249 |
250 | ```js
251 | // gatsby-config.js
252 | {
253 | resolve: 'gatsby-plugin-graphql-codegen',
254 | options: {
255 | documentPaths: [
256 | './src/**/*.{ts,tsx}',
257 | './node_modules/gatsby-*/**/*.js',
258 | ],
259 | }
260 | },
261 | ```
262 |
263 |
264 |
265 |
266 | Missing definition Unknown identifier 'GatsbyImageSharpFixedFragment' in a yarn/lerna monorepo
267 |
268 |
269 | Are you using a monorepo? It's possible that the missing fragment's plugin is 'hoisted' (moved to workspace root's `node_modules`). A simple fix is use a `nohoist` config, supported by both lerna & yarn. Here's an example with yarn workspace, where `gatsby-transformer-sharp` is always installed in its project's `node_modules`.
270 |
271 | in your root's `package.json`
272 | ```json
273 | "workspaces": {
274 | "packages": ["packages/*"],
275 | "nohoist": [
276 | "**/gatsby-transformer-sharp"
277 | ]
278 | }
279 | ```
280 |
281 |
282 |
283 | ## Contribute to this repo
284 |
285 | All PRs / issues are welcomed.
286 |
287 | Steps to run in development:
288 |
289 | ```bash
290 | # 0
291 | git clone https://github.com/d4rekanguok/gatsby-typescript.git && cd gatsby-typescript
292 |
293 | # 1 Install deps
294 | yarn
295 |
296 | # 2 Hook up dependencies
297 | yarn bootstrap
298 |
299 | # 3 Build binaries
300 | lerna run build
301 |
302 | # 4 Run test
303 | yarn test
304 | ```
305 |
306 | You can test your code against the starters inside the repo. Don't forget to checkout the changes in those repo before sending a PR. Alternatively, use [yalc](https://github.com/whitecolor/yalc) to test the plugins in your own Gatsby project:
307 |
308 | ```bash
309 | # 1 Install yalc
310 | npm i yalc -G
311 |
312 | # 2 cd into, say, gatsby-plugin-ts
313 | cd packages/gatsby-plugin-ts
314 |
315 | # 3 Publish to yalc
316 | yalc publish
317 |
318 | # 4 cd into your gatsby project
319 | cd ../../my-gatsby-project
320 |
321 | # 5 Install yalc & re-install deps
322 | npm uninstall gatsby-plugin-ts && yalc add gatsby-plugin-ts
323 |
324 | npm install
325 |
326 | # 6 Subsequent update
327 | yalc update
328 |
329 | # 7 Done? remove yalc deps
330 | yalc remove --all
331 | ```
332 |
--------------------------------------------------------------------------------
/packages/gatsby-starter-ts/src/components/layout.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-family: sans-serif;
3 | -ms-text-size-adjust: 100%;
4 | -webkit-text-size-adjust: 100%;
5 | }
6 | body {
7 | margin: 0;
8 | -webkit-font-smoothing: antialiased;
9 | -moz-osx-font-smoothing: grayscale;
10 | }
11 | article,
12 | aside,
13 | details,
14 | figcaption,
15 | figure,
16 | footer,
17 | header,
18 | main,
19 | menu,
20 | nav,
21 | section,
22 | summary {
23 | display: block;
24 | }
25 | audio,
26 | canvas,
27 | progress,
28 | video {
29 | display: inline-block;
30 | }
31 | audio:not([controls]) {
32 | display: none;
33 | height: 0;
34 | }
35 | progress {
36 | vertical-align: baseline;
37 | }
38 | [hidden],
39 | template {
40 | display: none;
41 | }
42 | a {
43 | background-color: transparent;
44 | -webkit-text-decoration-skip: objects;
45 | }
46 | a:active,
47 | a:hover {
48 | outline-width: 0;
49 | }
50 | abbr[title] {
51 | border-bottom: none;
52 | text-decoration: underline;
53 | text-decoration: underline dotted;
54 | }
55 | b,
56 | strong {
57 | font-weight: inherit;
58 | font-weight: bolder;
59 | }
60 | dfn {
61 | font-style: italic;
62 | }
63 | h1 {
64 | font-size: 2em;
65 | margin: 0.67em 0;
66 | }
67 | mark {
68 | background-color: #ff0;
69 | color: #000;
70 | }
71 | small {
72 | font-size: 80%;
73 | }
74 | sub,
75 | sup {
76 | font-size: 75%;
77 | line-height: 0;
78 | position: relative;
79 | vertical-align: baseline;
80 | }
81 | sub {
82 | bottom: -0.25em;
83 | }
84 | sup {
85 | top: -0.5em;
86 | }
87 | img {
88 | border-style: none;
89 | }
90 | svg:not(:root) {
91 | overflow: hidden;
92 | }
93 | code,
94 | kbd,
95 | pre,
96 | samp {
97 | font-family: monospace, monospace;
98 | font-size: 1em;
99 | }
100 | figure {
101 | margin: 1em 40px;
102 | }
103 | hr {
104 | box-sizing: content-box;
105 | height: 0;
106 | overflow: visible;
107 | }
108 | button,
109 | input,
110 | optgroup,
111 | select,
112 | textarea {
113 | font: inherit;
114 | margin: 0;
115 | }
116 | optgroup {
117 | font-weight: 700;
118 | }
119 | button,
120 | input {
121 | overflow: visible;
122 | }
123 | button,
124 | select {
125 | text-transform: none;
126 | }
127 | [type="reset"],
128 | [type="submit"],
129 | button,
130 | html [type="button"] {
131 | -webkit-appearance: button;
132 | }
133 | [type="button"]::-moz-focus-inner,
134 | [type="reset"]::-moz-focus-inner,
135 | [type="submit"]::-moz-focus-inner,
136 | button::-moz-focus-inner {
137 | border-style: none;
138 | padding: 0;
139 | }
140 | [type="button"]:-moz-focusring,
141 | [type="reset"]:-moz-focusring,
142 | [type="submit"]:-moz-focusring,
143 | button:-moz-focusring {
144 | outline: 1px dotted ButtonText;
145 | }
146 | fieldset {
147 | border: 1px solid silver;
148 | margin: 0 2px;
149 | padding: 0.35em 0.625em 0.75em;
150 | }
151 | legend {
152 | box-sizing: border-box;
153 | color: inherit;
154 | display: table;
155 | max-width: 100%;
156 | padding: 0;
157 | white-space: normal;
158 | }
159 | textarea {
160 | overflow: auto;
161 | }
162 | [type="checkbox"],
163 | [type="radio"] {
164 | box-sizing: border-box;
165 | padding: 0;
166 | }
167 | [type="number"]::-webkit-inner-spin-button,
168 | [type="number"]::-webkit-outer-spin-button {
169 | height: auto;
170 | }
171 | [type="search"] {
172 | -webkit-appearance: textfield;
173 | outline-offset: -2px;
174 | }
175 | [type="search"]::-webkit-search-cancel-button,
176 | [type="search"]::-webkit-search-decoration {
177 | -webkit-appearance: none;
178 | }
179 | ::-webkit-input-placeholder {
180 | color: inherit;
181 | opacity: 0.54;
182 | }
183 | ::-webkit-file-upload-button {
184 | -webkit-appearance: button;
185 | font: inherit;
186 | }
187 | html {
188 | font: 112.5%/1.45em georgia, serif;
189 | box-sizing: border-box;
190 | overflow-y: scroll;
191 | }
192 | * {
193 | box-sizing: inherit;
194 | }
195 | *:before {
196 | box-sizing: inherit;
197 | }
198 | *:after {
199 | box-sizing: inherit;
200 | }
201 | body {
202 | color: hsla(0, 0%, 0%, 0.8);
203 | font-family: georgia, serif;
204 | font-weight: normal;
205 | word-wrap: break-word;
206 | font-kerning: normal;
207 | -moz-font-feature-settings: "kern", "liga", "clig", "calt";
208 | -ms-font-feature-settings: "kern", "liga", "clig", "calt";
209 | -webkit-font-feature-settings: "kern", "liga", "clig", "calt";
210 | font-feature-settings: "kern", "liga", "clig", "calt";
211 | }
212 | img {
213 | max-width: 100%;
214 | margin-left: 0;
215 | margin-right: 0;
216 | margin-top: 0;
217 | padding-bottom: 0;
218 | padding-left: 0;
219 | padding-right: 0;
220 | padding-top: 0;
221 | margin-bottom: 1.45rem;
222 | }
223 | h1 {
224 | margin-left: 0;
225 | margin-right: 0;
226 | margin-top: 0;
227 | padding-bottom: 0;
228 | padding-left: 0;
229 | padding-right: 0;
230 | padding-top: 0;
231 | margin-bottom: 1.45rem;
232 | color: inherit;
233 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
234 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
235 | font-weight: bold;
236 | text-rendering: optimizeLegibility;
237 | font-size: 2.25rem;
238 | line-height: 1.1;
239 | }
240 | h2 {
241 | margin-left: 0;
242 | margin-right: 0;
243 | margin-top: 0;
244 | padding-bottom: 0;
245 | padding-left: 0;
246 | padding-right: 0;
247 | padding-top: 0;
248 | margin-bottom: 1.45rem;
249 | color: inherit;
250 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
251 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
252 | font-weight: bold;
253 | text-rendering: optimizeLegibility;
254 | font-size: 1.62671rem;
255 | line-height: 1.1;
256 | }
257 | h3 {
258 | margin-left: 0;
259 | margin-right: 0;
260 | margin-top: 0;
261 | padding-bottom: 0;
262 | padding-left: 0;
263 | padding-right: 0;
264 | padding-top: 0;
265 | margin-bottom: 1.45rem;
266 | color: inherit;
267 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
268 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
269 | font-weight: bold;
270 | text-rendering: optimizeLegibility;
271 | font-size: 1.38316rem;
272 | line-height: 1.1;
273 | }
274 | h4 {
275 | margin-left: 0;
276 | margin-right: 0;
277 | margin-top: 0;
278 | padding-bottom: 0;
279 | padding-left: 0;
280 | padding-right: 0;
281 | padding-top: 0;
282 | margin-bottom: 1.45rem;
283 | color: inherit;
284 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
285 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
286 | font-weight: bold;
287 | text-rendering: optimizeLegibility;
288 | font-size: 1rem;
289 | line-height: 1.1;
290 | }
291 | h5 {
292 | margin-left: 0;
293 | margin-right: 0;
294 | margin-top: 0;
295 | padding-bottom: 0;
296 | padding-left: 0;
297 | padding-right: 0;
298 | padding-top: 0;
299 | margin-bottom: 1.45rem;
300 | color: inherit;
301 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
302 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
303 | font-weight: bold;
304 | text-rendering: optimizeLegibility;
305 | font-size: 0.85028rem;
306 | line-height: 1.1;
307 | }
308 | h6 {
309 | margin-left: 0;
310 | margin-right: 0;
311 | margin-top: 0;
312 | padding-bottom: 0;
313 | padding-left: 0;
314 | padding-right: 0;
315 | padding-top: 0;
316 | margin-bottom: 1.45rem;
317 | color: inherit;
318 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
319 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
320 | font-weight: bold;
321 | text-rendering: optimizeLegibility;
322 | font-size: 0.78405rem;
323 | line-height: 1.1;
324 | }
325 | hgroup {
326 | margin-left: 0;
327 | margin-right: 0;
328 | margin-top: 0;
329 | padding-bottom: 0;
330 | padding-left: 0;
331 | padding-right: 0;
332 | padding-top: 0;
333 | margin-bottom: 1.45rem;
334 | }
335 | ul {
336 | margin-left: 1.45rem;
337 | margin-right: 0;
338 | margin-top: 0;
339 | padding-bottom: 0;
340 | padding-left: 0;
341 | padding-right: 0;
342 | padding-top: 0;
343 | margin-bottom: 1.45rem;
344 | list-style-position: outside;
345 | list-style-image: none;
346 | }
347 | ol {
348 | margin-left: 1.45rem;
349 | margin-right: 0;
350 | margin-top: 0;
351 | padding-bottom: 0;
352 | padding-left: 0;
353 | padding-right: 0;
354 | padding-top: 0;
355 | margin-bottom: 1.45rem;
356 | list-style-position: outside;
357 | list-style-image: none;
358 | }
359 | dl {
360 | margin-left: 0;
361 | margin-right: 0;
362 | margin-top: 0;
363 | padding-bottom: 0;
364 | padding-left: 0;
365 | padding-right: 0;
366 | padding-top: 0;
367 | margin-bottom: 1.45rem;
368 | }
369 | dd {
370 | margin-left: 0;
371 | margin-right: 0;
372 | margin-top: 0;
373 | padding-bottom: 0;
374 | padding-left: 0;
375 | padding-right: 0;
376 | padding-top: 0;
377 | margin-bottom: 1.45rem;
378 | }
379 | p {
380 | margin-left: 0;
381 | margin-right: 0;
382 | margin-top: 0;
383 | padding-bottom: 0;
384 | padding-left: 0;
385 | padding-right: 0;
386 | padding-top: 0;
387 | margin-bottom: 1.45rem;
388 | }
389 | figure {
390 | margin-left: 0;
391 | margin-right: 0;
392 | margin-top: 0;
393 | padding-bottom: 0;
394 | padding-left: 0;
395 | padding-right: 0;
396 | padding-top: 0;
397 | margin-bottom: 1.45rem;
398 | }
399 | pre {
400 | margin-left: 0;
401 | margin-right: 0;
402 | margin-top: 0;
403 | margin-bottom: 1.45rem;
404 | font-size: 0.85rem;
405 | line-height: 1.42;
406 | background: hsla(0, 0%, 0%, 0.04);
407 | border-radius: 3px;
408 | overflow: auto;
409 | word-wrap: normal;
410 | padding: 1.45rem;
411 | }
412 | table {
413 | margin-left: 0;
414 | margin-right: 0;
415 | margin-top: 0;
416 | padding-bottom: 0;
417 | padding-left: 0;
418 | padding-right: 0;
419 | padding-top: 0;
420 | margin-bottom: 1.45rem;
421 | font-size: 1rem;
422 | line-height: 1.45rem;
423 | border-collapse: collapse;
424 | width: 100%;
425 | }
426 | fieldset {
427 | margin-left: 0;
428 | margin-right: 0;
429 | margin-top: 0;
430 | padding-bottom: 0;
431 | padding-left: 0;
432 | padding-right: 0;
433 | padding-top: 0;
434 | margin-bottom: 1.45rem;
435 | }
436 | blockquote {
437 | margin-left: 1.45rem;
438 | margin-right: 1.45rem;
439 | margin-top: 0;
440 | padding-bottom: 0;
441 | padding-left: 0;
442 | padding-right: 0;
443 | padding-top: 0;
444 | margin-bottom: 1.45rem;
445 | }
446 | form {
447 | margin-left: 0;
448 | margin-right: 0;
449 | margin-top: 0;
450 | padding-bottom: 0;
451 | padding-left: 0;
452 | padding-right: 0;
453 | padding-top: 0;
454 | margin-bottom: 1.45rem;
455 | }
456 | noscript {
457 | margin-left: 0;
458 | margin-right: 0;
459 | margin-top: 0;
460 | padding-bottom: 0;
461 | padding-left: 0;
462 | padding-right: 0;
463 | padding-top: 0;
464 | margin-bottom: 1.45rem;
465 | }
466 | iframe {
467 | margin-left: 0;
468 | margin-right: 0;
469 | margin-top: 0;
470 | padding-bottom: 0;
471 | padding-left: 0;
472 | padding-right: 0;
473 | padding-top: 0;
474 | margin-bottom: 1.45rem;
475 | }
476 | hr {
477 | margin-left: 0;
478 | margin-right: 0;
479 | margin-top: 0;
480 | padding-bottom: 0;
481 | padding-left: 0;
482 | padding-right: 0;
483 | padding-top: 0;
484 | margin-bottom: calc(1.45rem - 1px);
485 | background: hsla(0, 0%, 0%, 0.2);
486 | border: none;
487 | height: 1px;
488 | }
489 | address {
490 | margin-left: 0;
491 | margin-right: 0;
492 | margin-top: 0;
493 | padding-bottom: 0;
494 | padding-left: 0;
495 | padding-right: 0;
496 | padding-top: 0;
497 | margin-bottom: 1.45rem;
498 | }
499 | b {
500 | font-weight: bold;
501 | }
502 | strong {
503 | font-weight: bold;
504 | }
505 | dt {
506 | font-weight: bold;
507 | }
508 | th {
509 | font-weight: bold;
510 | }
511 | li {
512 | margin-bottom: calc(1.45rem / 2);
513 | }
514 | ol li {
515 | padding-left: 0;
516 | }
517 | ul li {
518 | padding-left: 0;
519 | }
520 | li > ol {
521 | margin-left: 1.45rem;
522 | margin-bottom: calc(1.45rem / 2);
523 | margin-top: calc(1.45rem / 2);
524 | }
525 | li > ul {
526 | margin-left: 1.45rem;
527 | margin-bottom: calc(1.45rem / 2);
528 | margin-top: calc(1.45rem / 2);
529 | }
530 | blockquote *:last-child {
531 | margin-bottom: 0;
532 | }
533 | li *:last-child {
534 | margin-bottom: 0;
535 | }
536 | p *:last-child {
537 | margin-bottom: 0;
538 | }
539 | li > p {
540 | margin-bottom: calc(1.45rem / 2);
541 | }
542 | code {
543 | font-size: 0.85rem;
544 | line-height: 1.45rem;
545 | }
546 | kbd {
547 | font-size: 0.85rem;
548 | line-height: 1.45rem;
549 | }
550 | samp {
551 | font-size: 0.85rem;
552 | line-height: 1.45rem;
553 | }
554 | abbr {
555 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
556 | cursor: help;
557 | }
558 | acronym {
559 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
560 | cursor: help;
561 | }
562 | abbr[title] {
563 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
564 | cursor: help;
565 | text-decoration: none;
566 | }
567 | thead {
568 | text-align: left;
569 | }
570 | td,
571 | th {
572 | text-align: left;
573 | border-bottom: 1px solid hsla(0, 0%, 0%, 0.12);
574 | font-feature-settings: "tnum";
575 | -moz-font-feature-settings: "tnum";
576 | -ms-font-feature-settings: "tnum";
577 | -webkit-font-feature-settings: "tnum";
578 | padding-left: 0.96667rem;
579 | padding-right: 0.96667rem;
580 | padding-top: 0.725rem;
581 | padding-bottom: calc(0.725rem - 1px);
582 | }
583 | th:first-child,
584 | td:first-child {
585 | padding-left: 0;
586 | }
587 | th:last-child,
588 | td:last-child {
589 | padding-right: 0;
590 | }
591 | tt,
592 | code {
593 | background-color: hsla(0, 0%, 0%, 0.04);
594 | border-radius: 3px;
595 | font-family: "SFMono-Regular", Consolas, "Roboto Mono", "Droid Sans Mono",
596 | "Liberation Mono", Menlo, Courier, monospace;
597 | padding: 0;
598 | padding-top: 0.2em;
599 | padding-bottom: 0.2em;
600 | }
601 | pre code {
602 | background: none;
603 | line-height: 1.42;
604 | }
605 | code:before,
606 | code:after,
607 | tt:before,
608 | tt:after {
609 | letter-spacing: -0.2em;
610 | content: " ";
611 | }
612 | pre code:before,
613 | pre code:after,
614 | pre tt:before,
615 | pre tt:after {
616 | content: "";
617 | }
618 | @media only screen and (max-width: 480px) {
619 | html {
620 | font-size: 100%;
621 | }
622 | }
623 |
--------------------------------------------------------------------------------