xref: /expo/docs/next.config.js (revision cfc00a9f)
1/* eslint-disable import/order */
2const { copySync, removeSync } = require('fs-extra');
3const merge = require('lodash/merge');
4const { join } = require('path');
5const semver = require('semver');
6const { ESBuildMinifyPlugin } = require('esbuild-loader');
7const { info: logInfo } = require('next/dist/build/output/log');
8
9const navigation = require('./constants/navigation');
10const { VERSIONS } = require('./constants/versions');
11const { version, betaVersion } = require('./package.json');
12
13// To generate a sitemap, we need context about the supported versions and navigational data
14const createSitemap = require('./scripts/create-sitemap');
15
16// Determine if we are using esbuild for MDX transpiling
17const enableEsbuild = !!process.env.USE_ESBUILD;
18logInfo(
19  enableEsbuild
20    ? 'Using esbuild for MDX files, USE_ESBUILD set to true'
21    : 'Using babel for MDX files, USE_ESBUILD not set'
22);
23
24// Prepare the latest version by copying the actual exact latest version
25const vLatest = join('pages', 'versions', `v${version}/`);
26const latest = join('pages', 'versions', 'latest/');
27removeSync(latest);
28copySync(vLatest, latest);
29logInfo(`Copied latest Expo SDK version from v${version}`);
30
31/** @type {import('next').NextConfig}  */
32module.exports = {
33  trailingSlash: true,
34  pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
35  // Next 11 does not support ESLint v8, enable it when we upgrade to 12
36  eslint: { ignoreDuringBuilds: true },
37  // Keep using webpack 4, webpack 5 causes some issues. See: https://github.com/expo/expo/pull/12794
38  webpack5: false,
39  webpack: (config, options) => {
40    // Add preval support for `constants/*` only and move it to the `.next/preval` cache.
41    // It's to prevent over-usage and separate the cache to allow manually invalidation.
42    // See: https://github.com/kentcdodds/babel-plugin-preval/issues/19
43    config.module.rules.push({
44      test: /.jsx?$/,
45      include: [join(__dirname, 'constants')],
46      use: merge({}, options.defaultLoaders.babel, {
47        options: {
48          // Keep this path in sync with package.json and other scripts that clear the cache
49          cacheDirectory: '.next/preval',
50          plugins: ['preval'],
51        },
52      }),
53    });
54
55    // Add support for MDX with our custom loader and esbuild
56    config.module.rules.push({
57      test: /.mdx?$/, // load both .md and .mdx files
58      use: [
59        !enableEsbuild
60          ? options.defaultLoaders.babel
61          : {
62              loader: 'esbuild-loader',
63              options: {
64                loader: 'tsx',
65                target: 'es2017',
66              },
67            },
68        {
69          loader: '@mdx-js/loader',
70          options: {
71            remarkPlugins: [
72              [require('remark-frontmatter'), ['yaml']],
73              require('./mdx-plugins/remark-export-yaml'),
74              require('./mdx-plugins/remark-export-headings'),
75              require('./mdx-plugins/remark-link-rewrite'),
76            ],
77            rehypePlugins: [require('rehype-slug')],
78          },
79        },
80      ],
81    });
82
83    // Fix inline or browser MDX usage: https://mdxjs.com/getting-started/webpack#running-mdx-in-the-browser
84    // Webpack 4
85    config.node = { fs: 'empty' };
86    // Webpack 5
87    // config.resolve.fallback = { fs: false, path: require.resolve('path-browserify') };
88
89    // Add the esbuild plugin only when using esbuild
90    if (enableEsbuild) {
91      config.optimization.minimizer = [
92        new ESBuildMinifyPlugin({
93          target: 'es2017',
94        }),
95      ];
96    }
97
98    return config;
99  },
100  // Create a map of all pages to export
101  async exportPathMap(defaultPathMap, { dev, outDir }) {
102    if (dev) {
103      return defaultPathMap;
104    }
105    const pathMap = Object.assign(
106      ...Object.entries(defaultPathMap).map(([pathname, page]) => {
107        if (pathname.match(/\/v[1-9][^/]*$/)) {
108          // ends in "/v<version>"
109          pathname += '/index.html'; // TODO: find out why we need to do this
110        }
111        if (pathname.match(/unversioned/)) {
112          return {};
113        } else {
114          // hide versions greater than the package.json version number
115          const versionMatch = pathname.match(/\/v(\d\d\.\d\.\d)\//);
116          if (
117            versionMatch &&
118            versionMatch[1] &&
119            semver.gt(versionMatch[1], betaVersion || version)
120          ) {
121            return {};
122          }
123          return { [pathname]: page };
124        }
125      })
126    );
127
128    const sitemapEntries = createSitemap({
129      pathMap,
130      domain: `https://docs.expo.dev`,
131      output: join(outDir, `sitemap.xml`),
132      // Some of the search engines only track the first N items from the sitemap,
133      // this makes sure our starting and general guides are first, and API index last (in order from new to old)
134      pathsPriority: [
135        ...navigation.startingDirectories,
136        ...navigation.generalDirectories,
137        ...navigation.easDirectories,
138        ...VERSIONS.map(version => `versions/${version}`),
139      ],
140      // Some of our pages are "hidden" and should not be added to the sitemap
141      pathsHidden: navigation.previewDirectories,
142    });
143    logInfo(`�� Generated sitemap with ${sitemapEntries.length} entries`);
144
145    return pathMap;
146  },
147  async headers() {
148    const cacheHeaders = [{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }];
149    return [{ source: '/_next/static/:static*', headers: cacheHeaders }];
150  },
151};
152