Skip to content

Commit

Permalink
chore(docs): Add "ESM and Gatsby" (#37934)
Browse files Browse the repository at this point in the history
  • Loading branch information
LekoArts authored Apr 13, 2023
1 parent 68510e1 commit e258553
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 134 deletions.
154 changes: 154 additions & 0 deletions docs/docs/how-to/custom-configuration/es-modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
---
title: ES Modules (ESM) and Gatsby
examples:
- label: Using MDX
href: "https://github.com/gatsbyjs/gatsby/tree/master/examples/using-mdx"
---

## Introduction

The ECMAScript module (ESM) format is the [official TC39 standard](https://tc39.es/ecma262/#sec-modules) for packaging JavaScript. For many years, [CommonJS (CJS)](https://nodejs.org/api/modules.html#modules-commonjs-modules) was the de facto standard in Node.js. You can author [`gatsby-config`](/docs/reference/config-files/gatsby-config/) and [`gatsby-node`](/docs/reference/config-files/gatsby-node/) in ESM syntax.

This feature was added in `gatsby@5.3.0`.

## Prerequisites

- A Gatsby project set up with `gatsby@5.3.0` or later. (Need help creating one? Follow the [Quick Start](/docs/quick-start/))

## Usage in Gatsby

Generally speaking you need to follow the official standard as explained in the [Node.js documentation](https://nodejs.org/api/esm.html).

### `gatsby-config`

Create a `gatsby-config.mjs` file. Here's an example `gatsby-config` using ESM syntax:

```js:title=gatsby-config.mjs
const config = {
siteMetadata: {
title: `ESM in Gatsby`,
},
}

export default config
```

### `gatsby-node`

Create a `gatsby-node.mjs` file and use any of the [Node APIs](/docs/reference/config-files/gatsby-node/) as usual. Here's an example `gatsby-node` using ESM syntax:

```js:title=gatsby-node.mjs
export const onPostBuild = () => {
console.log("Build is done!")
}
```

## Migrating from CommonJS to ES Modules

- Use `import`/`export` syntax instead of `require`/`module.exports`
- File extensions in imports are [mandatory](https://nodejs.org/api/esm.html#mandatory-file-extensions)
- You can replicate the `__dirname` call with `import.meta.url`:

```js
import { dirname } from "path"
import { fileURLToPath } from "url"

const __dirname = dirname(fileURLToPath(import.meta.url))
```
- You can replicate `require.resolve` with `createRequire`:
```js
import { createRequire } from "module"

const require = createRequire(import.meta.url)
```
The documents [Interopability with CommonJS](https://nodejs.org/api/esm.html#interoperability-with-commonjs) and [Differences between ES Modules and CommonJS](https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs) also apply to ESM in Gatsby.
Here's how you'd migrate a `gatsby-config.js` file to `gatsby-config.mjs`.
**Before:**
```js:title=gatsby-config.js
const { siteUrl } = require(`./defaults`)

module.exports = {
siteMetadata: {
title: `Using CJS`,
siteUrl,
},
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/content/posts/`,
},
},
{
resolve: require.resolve("./local-plugin-with-path"),
options: {},
},
{
resolve: `gatsby-plugin-mdx`,
options: {
mdxOptions: {
remarkPlugins: [require(`remark-gfm`)],
},
},
},
],
}
```
**After:**
```js:title=gatsby-config.mjs
import { createRequire } from "module"
import { dirname } from "path"
import { fileURLToPath } from "url"
import remarkGfm from "remark-gfm"
import { siteUrl } from "./defaults.mjs"

const __dirname = dirname(fileURLToPath(import.meta.url))
const require = createRequire(import.meta.url)

const config = {
siteMetadata: {
title: `Using ESM`,
siteUrl,
},
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/content/posts/`,
},
},
{
resolve: require.resolve("./local-plugin-with-path"),
options: {},
},
{
resolve: `gatsby-plugin-mdx`,
options: {
mdxOptions: {
remarkPlugins: [remarkGfm],
},
},
},
],
}

export default config
```
## Current limitations
- The TypeScript variants of `gatsby-config` and `gatsby-node` do **not** support ESM yet. We plan on adding support in a future minor release by using the `.mts` extension. If you have questions or suggestions about this, please go to our [ESM in Gatsby files](https://github.com/gatsbyjs/gatsby/discussions/37069) umbrella discussion.
However, you can use [Type Hinting](/docs/how-to/custom-configuration/typescript/#type-hinting-in-js-files) in the meantime.
The [ESM in Gatsby files](https://github.com/gatsbyjs/gatsby/discussions/37069) umbrella discussion is also the right place for any questions about the `.mjs` usage.
4 changes: 3 additions & 1 deletion docs/docs/reference/config-files/gatsby-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Gatsby Config API
---

The file `gatsby-config.js`/`gatsby-config.ts` defines your site's metadata, plugins, and other general configuration. This file should be in the root of your Gatsby site. You can author the file in JavaScript or [TypeScript](/docs/how-to/custom-configuration/typescript/#gatsby-configts).
The file `gatsby-config.js`/`gatsby-config.ts` defines your site's metadata, plugins, and other general configuration. This file should be in the root of your Gatsby site. You can author the file in JavaScript (CommonJS or [ES Modules (ESM)](/docs/how-to/custom-configuration/es-modules/) syntax) or [TypeScript](/docs/how-to/custom-configuration/typescript/#gatsby-configts).

If you created a Gatsby site with the `npm init gatsby` command, there should already be a sample configuration file in your site's directory.
_Note: There are many sample configs which may be helpful to reference in the different [Gatsby Example Websites](https://github.com/gatsbyjs/gatsby/tree/master/examples)._
Expand Down Expand Up @@ -39,6 +39,8 @@ module.exports = {

The [TypeScript and Gatsby documentation](/docs/how-to/custom-configuration/typescript/#gatsby-configts) shows how to set up a configuration file in TypeScript.

Read the [ES Modules (ESM) and Gatsby documentation](/docs/how-to/custom-configuration/es-modules/) if you don't want to use CommonJS syntax.

## Configuration options

Options available to set within `gatsby-config.js` include:
Expand Down
4 changes: 3 additions & 1 deletion docs/docs/reference/config-files/gatsby-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ apiCalls: NodeAPI

Gatsby gives plugins and site builders many APIs for building your site. Code in the file `gatsby-node.js`/`gatsby-node.ts` is run once in the process of building your site. You can use its APIs to create pages dynamically, add data into GraphQL, or respond to events during the build lifecycle. To use the [Gatsby Node APIs](/docs/reference/config-files/gatsby-node/), create a file named `gatsby-node.js`/`gatsby-node.ts` in the root of your site. Export any of the APIs you wish to use in this file.

You can author the file in JavaScript or [TypeScript](/docs/how-to/custom-configuration/typescript/#gatsby-nodets).
You can author the file in JavaScript (CommonJS or [ES Modules (ESM)](/docs/how-to/custom-configuration/es-modules/) syntax) or [TypeScript](/docs/how-to/custom-configuration/typescript/#gatsby-nodets).

Every Gatsby Node API gets passed a [set of helper functions](/docs/reference/config-files/node-api-helpers/). These let you access several methods like reporting, or perform actions like creating new pages.

Expand Down Expand Up @@ -51,6 +51,8 @@ exports.createPages = async ({ graphql, actions }) => {

The [TypeScript and Gatsby documentation](/docs/how-to/custom-configuration/typescript/#gatsby-nodets) shows how to set up a `gatsby-node` file in TypeScript.

Read the [ES Modules (ESM) and Gatsby documentation](/docs/how-to/custom-configuration/es-modules/) if you don't want to use CommonJS syntax.

## Async vs. sync work

If your plugin performs async operations (disk I/O, database access, calling remote APIs, etc.) you must either return a promise (explicitly using `Promise` API or implicitly using `async`/`await` syntax) or use the callback passed to the 3rd argument. Gatsby needs to know when plugins are finished as some APIs, to work correctly, require previous APIs to be complete first. See [Debugging Async Lifecycles](/docs/debugging-async-lifecycles/) for more info.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import remarkGfm from "remark-gfm"
import { dirname } from "path"
import { fileURLToPath } from "url"

const __dirname = dirname(fileURLToPath(import.meta.url))

/**
* @type {import('gatsby').GatsbyConfig}
*/
module.exports = {
const config = {
siteMetadata: {
title: `Using MDX example`,
description: `Kick off your next, great Gatsby project with MDX.`,
Expand All @@ -27,10 +33,7 @@ module.exports = {
resolve: `gatsby-plugin-mdx`,
options: {
mdxOptions: {
remarkPlugins: [
require(`remark-gfm`),
require(`remark-unwrap-images`),
],
remarkPlugins: [remarkGfm],
},
gatsbyRemarkPlugins: [
{
Expand All @@ -44,3 +47,5 @@ module.exports = {
},
],
}

export default config
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
const path = require("path")
const readingTime = require(`reading-time`)
const slugify = require(`@sindresorhus/slugify`)
const { compileMDXWithCustomOptions } = require(`gatsby-plugin-mdx`)
const { remarkHeadingsPlugin } = require(`./remark-headings-plugin`)
import path from "path"
import readingTime from "reading-time"
import slugify from "@sindresorhus/slugify"
import { compileMDXWithCustomOptions } from "gatsby-plugin-mdx"
import remarkHeadingsPlugin from "./remark-headings-plugin.mjs"

/**
* @type {import('gatsby').GatsbyNode['onCreateNode']}
*/
exports.onCreateNode = ({ node, actions }) => {
export const onCreateNode = ({ node, actions }) => {
const { createNodeField } = actions
if (node.internal.type === `Mdx`) {
createNodeField({
Expand All @@ -27,7 +27,7 @@ exports.onCreateNode = ({ node, actions }) => {
/**
* @type {import('gatsby').GatsbyNode['createSchemaCustomization']}
*/
exports.createSchemaCustomization = async ({ getNode, getNodesByType, pathPrefix, reporter, cache, actions, schema, store }) => {
export const createSchemaCustomization = async ({ getNode, getNodesByType, pathPrefix, reporter, cache, actions, schema, store }) => {
const { createTypes } = actions

const headingsResolver = schema.buildObjectType({
Expand Down Expand Up @@ -91,7 +91,7 @@ exports.createSchemaCustomization = async ({ getNode, getNodesByType, pathPrefix
/**
* @type {import('gatsby').GatsbyNode['createPages']}
*/
exports.createPages = async ({ graphql, actions, reporter }) => {
export const createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions

const result = await graphql(`
Expand Down
15 changes: 7 additions & 8 deletions examples/using-mdx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,21 @@
"version": "0.0.1",
"author": "LekoArts",
"dependencies": {
"@mdx-js/react": "^2.1.2",
"@nivo/core": "0.78.0",
"@nivo/line": "0.78.0",
"@sindresorhus/slugify": "^1",
"@mdx-js/react": "^2.3.0",
"@nivo/core": "^0.80.0",
"@nivo/line": "^0.80.0",
"@sindresorhus/slugify": "^2.2.0",
"gatsby": "next",
"gatsby-plugin-mdx": "next",
"gatsby-plugin-sharp": "next",
"gatsby-remark-images": "next",
"gatsby-source-filesystem": "next",
"mdast-util-to-string": "^2",
"mdast-util-to-string": "^3.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"reading-time": "^1.5.0",
"remark-gfm": "^1",
"remark-unwrap-images": "^2",
"unist-util-visit": "^2"
"remark-gfm": "^3.0.1",
"unist-util-visit": "^4.1.2"
},
"devDependencies": {
"prettier": "^2.7.1"
Expand Down
22 changes: 0 additions & 22 deletions examples/using-mdx/remark-headings-plugin.js

This file was deleted.

24 changes: 24 additions & 0 deletions examples/using-mdx/remark-headings-plugin.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { visit } from "unist-util-visit"
import { toString } from "mdast-util-to-string"

const transformer = (tree, file) => {
let headings = []

visit(tree, `heading`, heading => {
headings.push({
value: toString(heading),
depth: heading.depth,
})
})

const mdxFile = file
if (!mdxFile.data.meta) {
mdxFile.data.meta = {}
}

mdxFile.data.meta.headings = headings
}

const remarkHeadingsPlugin = () => transformer

export default remarkHeadingsPlugin
Loading

0 comments on commit e258553

Please sign in to comment.