gatsbyjs / gatsby

The best React-based framework with performance, scalability and security built in.
https://www.gatsbyjs.com
MIT License
55.28k stars 10.31k forks source link

Is it possible to write the gatsby config in TypeScript? #1457

Closed pspeter3 closed 6 years ago

pspeter3 commented 7 years ago

Assuming I have ts-node, it would be nice to write the config file in TypeScript.

KyleAMathews commented 7 years ago

It'd be cool to add support for this. Perhaps the existing Typescript plugin can just add require('ts-node/register')

pspeter3 commented 7 years ago

That would be a good solution.

KyleAMathews commented 6 years ago

Edit: This comment was written years ago when Typescript was much less popular. Most of the framework code is now written in Typescript. We're very open to community PRs adding better support for Typescript to core files.


Closing this as probably won't ever support it in core.

clarkdave commented 6 years ago

Edit: Before you attempt the solution below, please update to 4.9 (https://www.gatsbyjs.com/docs/reference/release-notes/v4.9/#support-for-typescript-in-gatsby-config-and-gatsby-node) and use the built-in way of using TypeScript files.

Quick note for anyone looking to do this - it's very straightforward. Just add ts-node yourself and add the hook in gatsby-node.js before loading your TS files:

require('source-map-support').install()
require('ts-node').register({
  compilerOptions: {
    module: 'commonjs',
    target: 'es2017',
  },
})

// typescript files
exports.createPages = require('./lib/createPages')
exports.onCreateNode = require('./lib/onCreateNode')

There aren't any TS typings for the gatsby node API, but it's pretty easy to create some to cover your own app's surface area and provide some safety. Full example: https://gist.github.com/clarkdave/53cc050fa58d9a70418f8a76982dd6c8#file-types-ts

jonnybot0 commented 5 years ago

Small follow-up to @clarkdave's most helpful comment; ts-node automagically loads tsconfig.json, so if you've already got one in your project, you needn't import it when you register ts-node. You should be able to just call require('ts-node').register().

jonnybot0 commented 5 years ago

So, since the above, I've been trying to start a fresh project utilizing this approach. I'm now having newfound problems. Whenever gatsby-node.js tries to load the file, I get an error like this:

  TSError: ⨯ Unable to compile TypeScript:
  src/util/createPages.ts(6,33): error TS7006: Parameter '_a' implicitly has an 'any' type.
  src/util/createPages.ts(11,50): error TS7006: Parameter 'result' implicitly has an 'any' type.
  src/util/createPages.ts(16,57): error TS7006: Parameter '_a' implicitly has an 'any' type.

Will update when I find a solution, but wanted to leave this here in case anyone else was having the same trouble & pulling their hair out.

The createPages.ts file I'm using definitely doesn't have any functions with a parameter called _a. Any functions in the file that do use any have explicitly declared the parameter type using : any. tsc compiles the file just fine, too, so it's only ts-node that seems to have the problem. I thought perhaps this was just part of ts-node's normal behaviour, but even changing my registration call to require('ts-node').register({files: true}) hasn't helped.

Full error:

success open and validate gatsby-configs — 0.241 s success load plugins — 0.120 s error gatsby-node.js returned an error

TSError: ⨯ Unable to compile TypeScript: src/util/createPages.ts(6,33): error TS7006: Parameter '_a' implicitly has an 'any' type. src/util/createPages.ts(11,50): error TS7006: Parameter 'result' implicitly has an 'any' type. src/util/createPages.ts(16,57): error TS7006: Parameter '_a' implicitly has an 'any' type.

  • index.ts:261 createTSError [new-adaptavist-docs]/[ts-node]/src/index.ts:261:12

  • index.ts:367 getOutput [new-adaptavist-docs]/[ts-node]/src/index.ts:367:40

  • index.ts:558 Object.compile [new-adaptavist-docs]/[ts-node]/src/index.ts:558:11

  • index.ts:439 Module.m._compile [new-adaptavist-docs]/[ts-node]/src/index.ts:439:43

  • index.ts:439 Module.m._compile [new-adaptavist-docs]/[ts-node]/src/index.ts:439:23

  • index.ts:442 require.extensions.(anonymous function) [new-adaptavist-docs]/[ts-node]/src/index.ts:442:12

  • index.ts:442 Object.require.extensions.(anonymous function) [as .ts] [new-adaptavist-docs]/[ts-node]/src/index.ts:442:12

  • v8-compile-cache.js:159 require [new-adaptavist-docs]/[v8-compile-cache]/v8-compile-cache.js:159:20

  • gatsby-node.js:4 Object. /Users/jonny/work/new-adaptavist-docs/gatsby-node.js:4:23

  • v8-compile-cache.js:178 Module._compile [new-adaptavist-docs]/[v8-compile-cache]/v8-compile-cache.js:178:30

  • v8-compile-cache.js:159 require [new-adaptavist-docs]/[v8-compile-cache]/v8-compile-cache.js:159:20

  • api-runner-node.js:61 require [new-adaptavist-docs]/[gatsby]/src/utils/api-runner-node.js:61:22

success onPreInit — 1.235 s success delete html and css files from previous builds — 0.010 s success initialize cache — 0.007 s success copy gatsby files — 0.056 s

It does appear that the un-typed parameter of _a actually exists in the compiled version of my lib/createNode.ts file; that is, it looks like ts-node is trying parse the resultant js file from an initial compile.

jonnybot0 commented 5 years ago

🤦‍♂️ It was as simple as this: I was calling require('ts-node').register({files: true}) in both gatsby-config.js and gatsby-node.js. That accounts for the second attempt to compile an already compiled file.

Once I simply made it require ts-node once in gatsby-config.js, it worked.

jonnybot0 commented 5 years ago

Pardon the spam... trying to decide the most appropriate place to leave this comment. If you've read this far looking for answers to the above problem, just skip this note! Everything you need to know is above. :)

I've been toying around with gatsby themes. I created a theme, and installed it into my project via yarn (that is, as a dependency in my package.json). My theme used the above solution for pulling typescript files into gatsby-config.js. While that works inside the theme's repo, it throws errors when trying to require the theme. The initial error is a bit confusing:

error ENOENT: no such file or directory, scandir 'adaptavist-docs-gatsby-theme'

  Error: ENOENT: no such file or directory, scandir 'my-docs-gatsby-theme'

error UNHANDLED REJECTION

  Error: ENOENT: no such file or directory, scandir 'my-docs-gatsby-theme'

error Command failed with exit code 1.

my-docs-gatsby-theme is the name of my custom theme. Debugging into the gatsby code, I found that the problem came when gatsby was trying to require the theme's gatsby-config.js file. The real, underlying error was this:

/path/to/my/project/node_modules/my-docs-gatsby-theme/siteMap.ts:1
(function (exports, require, module, __filename, __dirname) { import {SiteMap} from "./index";
                                                              ^^^^^^

SyntaxError: Unexpected token import
    at NativeCompileCache._moduleCompile (/path/to/my/project/node_modules/v8-compile-cache/v8-compile-cache.js:226:18)
    at Module._compile (/path/to/my/project/node_modules/v8-compile-cache/v8-compile-cache.js:172:36)
    at Module._extensions..js (module.js:663:10)
    at Object.require.extensions.(anonymous function) [as .ts] (/path/to/my/project/node_modules/ts-node/src/index.ts:431:14)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Module.require (module.js:596:17)
    at require (/path/to/my/project/node_modules/v8-compile-cache/v8-compile-cache.js:159:20)
    at Object.<anonymous> (/path/to/my/project/node_modules/my-docs-gatsby-theme/gatsby-config.js:2:19)
    at Module._compile (/path/to/my/project/node_modules/v8-compile-cache/v8-compile-cache.js:178:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Module.require (module.js:596:17)

The typescript file I was importing had an import statement at the top. That's fine for typescript, not so fine for a vanilla node JS loader. I'm not sure why registering ts-node doesn't help in this case.

Only workaround I can think of is to compile my typescript files into JS for redistribution.

mdluo commented 4 years ago

I found a perfect (almost) solution: use gatsby-plugin-typegen to generate types from GraphQL schema so that there will be:

In both gatsby-node configs and tsx components.

Example: https://github.com/mdluo/blog-gatsby/commit/68eb2693a67fbafc869fd55272d04bc3e96f4021

image

image

ecklf commented 4 years ago

@mdluo I really like the solution you have come up with but it only seems to generate type definitions when using the useStaticQuery hook for me. I am also exporting the graphql schema in createPages.ts as in your example commit. Do you mind to explain how you got that working?

Solved: got my files outside of src folder (which the plugin scans). https://github.com/cometkim/gatsby-plugin-typegen/blob/master/gatsby-node.ts#L19

assainov commented 4 years ago

@clarkdave 's solution is awesome. I actually took it a bit further and converted all Gatsby api files to TypeScript. There are multiple ways to achieve that.

Check out my blogpost: Converting Gatsby Config and Node API to TypeScript

Ieuanoh commented 4 years ago

I was able to fix the issue raised by @jonnybot0 regarding the unexpected token in an import statement by moving where the ts-node().register was called.

I had this error as a result of an import into gatsby-config when calling register in gatsby-node:

(function (exports, require, module, __filename, __dirname) { import { LanguageCode } from "types/localisation"
                                                                       ^
  SyntaxError: Unexpected token {

Calling register() in gatsby-config instead of gatsby-node fixed this without having to do any extra transpiling.

mrmartineau commented 4 years ago

FWIW I achieved this by moving any Gatsby config files, that I wanted written in TypeScript, to my src directory (e.g. ./src/gatsby). I then just created a prebuild step to compile those files with tsc (TypeScript's compiler). gatsby-plugin-ts was used instead of the gatsby-plugin-typescript because it checks your types while it compiles and autogenerates your Graphql schema too.

The prebuild step runs tsc using a different tsconfig.json (tsconfig.gatsby.json), like so:

"scripts": {
    "build:gatsby": "tsc --project tsconfig.gatsby.json",
    "prebuild": "yarn clean && yarn build:gatsby",
    "build": "gatsby build",
   ...
}

gatsby-plugin-ts requires some specific tsconfig changes which I needed because there was an issue with some Graphql queries being added to the compiled code. Below you can see what I used, which I took directly from the gatsby-plugin-ts readme

"compilerOptions": {
  "target": "ES2018",    /* or at least ES2015 */
  "module": "ESNext",    /* or at least ES2015 */
  "lib": ["dom"],             /* <-- required! */
  "jsx": "preserve",          /* <-- required! */
  "moduleResolution": "node", /* <-- required! */
  /* other options... */
}

My full tsconfig.gatsby.json looks like this:

{
  "compilerOptions": {
    "allowJs": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "lib": ["dom", "dom.iterable", "esnext"],
    "module": "esnext",
    "moduleResolution": "node",
    "noEmit": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": false,
    "target": "es5"
    "module": "commonjs",
    "outDir": ".gatsby",
    "target": "es2017",
    "isolatedModules": false,
    "noEmit": false
  },
  "include": ["src/gatsby/**/*.ts"]
}

If you see the "outDir": ".gatsby", that is where the code is compiled to. I added this directory to my .gitignore file. What you will notice is the output from running tsc in this way does not bundle the files, it just converts them to .js file in CommonJS format, so if you are referencing other files that aren't .ts, you may have to use path.resolve to ensure you get the correct relative path.

Here's an example of that:

createPage({
  path: `/${node.node_locale.toLowerCase()}/${BLOG_PATH}/${node.slug}`,
  component: nodePath.resolve('./src/templates/Article/Article.tsx'),
  context: {
    id: node.contentful_id,
    locale: node.node_locale,
  },
})

gatsby-config.js is still needed by Gatsby, but now all it does is require the newly compiled file that tsc generates:

/**
 * DO NOT EDIT THIS FILE DIRECTLY
 * Edit the source file at `./src/gatsby/gatsby-config.ts`
 */
module.exports = require('./.gatsby/src/gatsby/gatsby-config')

I do the same for gatsby-node.js, and you can see the folder structure here:

❯ exa -T -L 2
.
├── createPages
│  ├── createContentfulArticlePages.ts
│  ├── createContentfulLegalPages.ts
│  └── createContentfulMarketingPages.ts
├── createPages.ts
├── gatsby-config.ts
├── gatsby-node.ts
├── paths.ts
└── README.md

Hope this helps some people. Let me know if it can be improved.

dandv commented 4 years ago

This gist by @JohnAlbin worked great as of the current Gatsby version, and was very easy to follow and set up. No changes were required in the build or develop commands.

antoinerousseau commented 4 years ago

If you want I made a TypeScript starter (including Gatsby node hooks and config):

https://github.com/antoinerousseau/gatsby-starter-antoine

openscript commented 3 years ago

Unfortunately some of the solutions to have TypeScript config files break with Gatsby v3 including gatsby-plugin-ts-config(https://github.com/Js-Brecht/gatsby-plugin-ts-config/issues/29).

I would love to see Gatsby having full fledged TypeScript support, also for the config files. I guess the best would be if it's somehow integrated into gatsby-plugin-typescript. It seems to be the right place for it. What would be the necessary steps?

dvakatsiienko commented 3 years ago

Totally supporting @openscript on having core-supported .ts - .tsx file extension support for gatsby-*.* files!

dustinlacewell commented 3 years ago

Come on!

samwightt commented 3 years ago

It's really embarrassing that Gatsby's Typescript support is half-assed like this. Everything inside the src directory works perfectly fine, but gatsby-* don't for reasons that aren't understandable to beginners. This should really be re-opened until its resolved.

KyleAMathews commented 3 years ago

This issue was closed years ago when Typescript was much less popular. We're very open to PRs adding better support for Typescript to core files. If you'd like to discuss how to help make that happen, please open a new issue and let's make it happen.

qhj commented 3 years ago

I used this way. It works fine during yarn develop but two lines of warning occurs when running yarn build:

<w> [webpack.cache.PackFileCacheStrategy] Caching failed for pack: Error: Can't resolve 'path/to/my-gatsby-project/gatsby-node.js' in 'path/to/my-gatsby-project'

gatsby-config.js:

require('ts-node').register()

module.exports = require('./gatsby-config.ts')

project files:

$ exa -T -L 1
.
├── content
├── gatsby-browser.ts
├── gatsby-config.js
├── gatsby-config.ts
├── gatsby-node.ts
├── node_modules
├── package.json
├── public
├── README.md
├── src
└── yarn.lock
alvis commented 3 years ago

@qhj The error you faced is due to this https://github.com/gatsbyjs/gatsby/blob/3651e932c507bb783e4385820a9ce1065843a908/packages/gatsby/src/utils/webpack.config.js#L798-L813

gatsby tries to cache gatsby-node.js with an assumption that gatsby-node must be in a .js extension. In this case, obviously, not.

qhj commented 3 years ago

@alvis I get it. Thanks for your reply.

roffelsaurus commented 3 years ago

@qhj The error you faced is due to this https://github.com/gatsbyjs/gatsby/blob/3651e932c507bb783e4385820a9ce1065843a908/packages/gatsby/src/utils/webpack.config.js#L798-L813

gatsby tries to cache gatsby-node.js with an assumption that gatsby-node must be in a .js extension. In this case, obviously, not.

This is an unfortunate hardcoding which conflicts with the ts-node hack me and many use. However, I avoided it by changing just this file to .js.

What is not mentioned on this issue is that it's not really the core files which is interesting to get typescript support for; it's plugins and other code in the build pipeline which use Gatsby APIs. They can grow quite complex and need defs to be maintainable.

rynoV commented 2 years ago

I used this way. It works fine during yarn develop but two lines of warning occurs when running yarn build:

<w> [webpack.cache.PackFileCacheStrategy] Caching failed for pack: Error: Can't resolve 'path/to/my-gatsby-project/gatsby-node.js' in 'path/to/my-gatsby-project'

gatsby-config.js:

require('ts-node').register()

module.exports = require('./gatsby-config.ts')

For those using this setup running into this issue, I kept my gatsby-node.ts as it was and added a gatsby-node.js with just the line:

module.exports = require('./gatsby-node.ts')

and this seems to have gotten rid of the warning. Although now I get

<w> [webpack.cache.PackFileCacheStrategy] Serializing big strings (1888kiB) impacts deserialization performance (consider using Buffer instead and decode when needed)
fzyzcjy commented 2 years ago

By the way, I wonder why there is require('source-map-support').install()? How is it related to the topic? Thanks

decanTyme commented 2 years ago

For anyone wondering how they could use the GatsbyConfig interface without rewriting to TS or using any other configuration/deps just for intellisense, just paste this at the top of your gatsby-config.js:

/** @type {import("gatsby").GatsbyConfig} */
LekoArts commented 2 years ago

Please go to https://github.com/gatsbyjs/gatsby/discussions/34613 and you can use TypeScript with Gatsby. By the time you're reading this it's also possible that the feature already was shipped in Gatsby 4. If that is the case the RFC will be marked as done.

LekoArts commented 2 years ago

Future googlers: This is available in 4.9. https://www.gatsbyjs.com/docs/reference/release-notes/v4.9/#support-for-typescript-in-gatsby-config-and-gatsby-node