diff --git a/packages/create-expo-app/package.json b/packages/create-expo-app/package.json
index 80ed31cae0..7b0efbf82e 100644
--- a/packages/create-expo-app/package.json
+++ b/packages/create-expo-app/package.json
@@ -40,6 +40,7 @@
"@types/getenv": "^1.0.0",
"@types/minipass": "^3.3.5",
"@types/node": "^16.11.56",
+ "@types/node-fetch": "^2.5.8",
"@types/prompts": "2.0.14",
"@types/tar": "^6.1.2",
"arg": "^5.0.2",
diff --git a/packages/next-adapter/README.md b/packages/next-adapter/README.md
index 576fdc6986..4ff4a06f4f 100644
--- a/packages/next-adapter/README.md
+++ b/packages/next-adapter/README.md
@@ -3,53 +3,250 @@
👋 Welcome to
@expo/next-adapter
-
Adapter document and server for Next.js projects using Expo modules.
+Next.js plugin for using React Native modules
-
-
-
-
-
-
---
-> ⚠️ **Warning:** Support for Next.js is experimental. Please open an issue at [expo-cli/issues](https://github.com/expo/expo-cli/issues) if you encountered any problems.
-
-## [Documentation][docs]
-
-To learn more about Next.js usage with Expo, check out the docs here: [Using Next.js][docs]
-
-### Contributing to the docs
-
-- [Documentation for the master branch](https://github.com/expo/expo/blob/master/docs/pages/guides/using-nextjs.md)
-
-## License
-
-The Expo source code is made available under the [MIT license](LICENSE). Some of the dependencies are licensed differently, with the BSD license, for example.
-
-
-
----
-
-
-
-
-
-
-
-
-
-
-[docs]: https://docs.expo.dev/guides/using-nextjs/
-[nextjs]: https://nextjs.org/
-[next-docs]: https://nextjs.org/docs
-[custom-document]: https://nextjs.org/docs#custom-document
-[next-offline]: https://github.com/hanford/next-offline
-[next-pwa]: https://nextjs.org/features/progressive-web-apps
-[next-transpile-modules]: https://github.com/martpie/next-transpile-modules
+> ⚠️ **Warning:** Support for Next.js is unofficial and not a first-class Expo feature.
+
+## Setup
+
+### Dependencies
+
+Ensure you have `expo`, `next`, `@expo/next-adapter` installed in your project.
+
+### Transpilation
+
+Configure Next.js to transform language features:
+
+
+ Next.js with swc. (Recommended)
+
+ When using Next.js with SWC, you can configure the `babel.config.js` to only account for native.
+
+```js
+// babel.config.js
+module.exports = function (api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
+```
+
+You will also have to [force Next.js to use SWC](https://nextjs.org/docs/messages/swc-disabled) by adding the following to your `next.config.js`:
+
+```js
+// next.config.js
+module.exports = {
+ experimental: {
+ forceSwcTransforms: true,
+ },
+};
+```
+
+
+
+
+ Next.js with Babel. (Not recommended)
+
+ Adjust your `babel.config.js` to conditionally add `next/babel` when bundling with Webpack for web.
+
+```js
+// babel.config.js
+module.exports = function (api) {
+ // Detect web usage (this may change in the future if Next.js changes the loader)
+ const isWeb = api.caller(
+ caller =>
+ caller && (caller.name === 'babel-loader' || caller.name === 'next-babel-turbo-loader')
+ );
+ return {
+ presets: [
+ // Only use next in the browser, it'll break your native project
+ isWeb && require('next/babel'),
+ 'babel-preset-expo',
+ ].filter(Boolean),
+ };
+};
+```
+
+
+
+### Next.js configuration
+
+Add the following to your `next.config.js`:
+
+```js
+const { withExpo } = require('@expo/next-adapter');
+
+module.exports = withExpo({
+ // experimental.transpilePackages is a Next.js +13 feature.
+ // older versions can use next-transpile-modules
+ experimental: {
+ transpilePackages: [
+ 'react-native-web',
+ 'expo',
+ // Add more React Native / Expo packages here...
+ ],
+ },
+});
+```
+
+The fully qualified Next.js config may look like:
+
+```js
+const { withExpo } = require('@expo/next-adapter');
+
+/** @type {import('next').NextConfig} */
+const nextConfig = withExpo({
+ reactStrictMode: true,
+ swcMinify: true,
+ experimental: {
+ forceSwcTransforms: true,
+ transpilePackages: [
+ 'react-native-web',
+ 'expo',
+ // Add more React Native / Expo packages here...
+ ],
+ },
+});
+
+module.exports = nextConfig;
+```
+
+### React Native Web styling
+
+The package `react-native-web` builds on the assumption of reset CSS styles, here's how you reset styles in Next.js using the `pages/` directory.
+
+
+ Required pages/_document.js
file
+
+```js
+import { Children } from 'react';
+import Document, { Html, Head, Main, NextScript } from 'next/document';
+import { AppRegistry } from 'react-native';
+
+// Follows the setup for react-native-web:
+// https://necolas.github.io/react-native-web/docs/setup/#root-element
+// Plus additional React Native scroll and text parity styles for various
+// browsers.
+// Force Next-generated DOM elements to fill their parent's height
+const style = `
+html, body, #__next {
+ -webkit-overflow-scrolling: touch;
+}
+#__next {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+html {
+ scroll-behavior: smooth;
+ -webkit-text-size-adjust: 100%;
+}
+body {
+ /* Allows you to scroll below the viewport; default value is visible */
+ overflow-y: auto;
+ overscroll-behavior-y: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -ms-overflow-style: scrollbar;
+}
+`;
+
+export default class MyDocument extends Document {
+ static async getInitialProps({ renderPage }) {
+ AppRegistry.registerComponent('main', () => Main);
+ const { getStyleElement } = AppRegistry.getApplication('main');
+ const page = await renderPage();
+ const styles = [
+ ,
+ getStyleElement(),
+ ];
+ return { ...page, styles: Children.toArray(styles) };
+ }
+
+ render() {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
+```
+
+
+
+
+ Required pages/_app.js
file
+
+```js
+import Head from 'next/head';
+
+export default function App({ Component, pageProps }) {
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
+```
+
+
+
+## Transpiling modules
+
+By default, modules in the React Native ecosystem are not transpiled to run in web browsers. React Native relies on advanced caching in Metro to reload quickly. Next.js uses Webpack, which does not have the same level of caching, so by default, no node modules are transpiled. You will have to manually mark every module you want to transpile with the `experimental.transpilePackages` option in `next.config.js`:
+
+```js
+const { withExpo } = require('@expo/next-adapter');
+
+module.exports = withExpo({
+ experimental: {
+ transpilePackages: [
+ 'react-native-web',
+ 'expo',
+ // Add more React Native / Expo packages here...
+ ],
+ },
+});
+```
+
+## Notice
+
+Using Next.js for the web means you will be bundling with the Next.js Webpack config. This will lead to some core differences in how you develop your app vs your website.
+
+## Troubleshooting
+
+### Cannot use import statement outside a module
+
+Figure out which module has the import statement and add it to the `experimental.transpilePackages` option in `next.config.js`:
+
+```js
+const { withExpo } = require('@expo/next-adapter');
+
+module.exports = withExpo({
+ experimental: {
+ transpilePackages: [
+ 'react-native-web',
+ 'expo',
+ // Add failing package here, and restart the server...
+ ],
+ },
+});
+```
diff --git a/packages/next-adapter/babel/index.d.ts b/packages/next-adapter/babel/index.d.ts
deleted file mode 100644
index 0f800815dc..0000000000
--- a/packages/next-adapter/babel/index.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '../build/babel';
diff --git a/packages/next-adapter/babel/index.js b/packages/next-adapter/babel/index.js
deleted file mode 100644
index 6c08aacef4..0000000000
--- a/packages/next-adapter/babel/index.js
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = require('../build/babel');
diff --git a/packages/next-adapter/customize/index.d.ts b/packages/next-adapter/customize/index.d.ts
deleted file mode 100644
index eb11fee29f..0000000000
--- a/packages/next-adapter/customize/index.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from '../build/customize';
diff --git a/packages/next-adapter/customize/index.js b/packages/next-adapter/customize/index.js
deleted file mode 100644
index 3034d2cd11..0000000000
--- a/packages/next-adapter/customize/index.js
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = require('../build/customize');
diff --git a/packages/next-adapter/document.d.ts b/packages/next-adapter/document.d.ts
deleted file mode 100644
index 74b88d5a55..0000000000
--- a/packages/next-adapter/document.d.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-///
-import NextDocument from 'next/document';
-
-export declare const style: string;
-
-export declare function getInitialProps({ renderPage }: { renderPage: any }): Promise;
-
-export declare class Document extends NextDocument {
- static getInitialProps: typeof getInitialProps;
- render(): JSX.Element;
-}
-
-export default Document;
diff --git a/packages/next-adapter/document.js b/packages/next-adapter/document.js
deleted file mode 100644
index 6bd59880aa..0000000000
--- a/packages/next-adapter/document.js
+++ /dev/null
@@ -1,73 +0,0 @@
-// Based on https://github.com/zeit/next.js/tree/canary/examples/with-react-native-web
-// and https://github.com/expo/expo-cli/blob/main/packages/webpack-config/web-default/index.html
-import NextDocument, { Head, Html, Main, NextScript } from 'next/document';
-import * as React from 'react';
-import { AppRegistry } from 'react-native';
-
-export const style = `
-/**
- * Building on the RNWeb reset:
- * https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/exports/StyleSheet/initialRules.js
- */
-html, body, #__next {
- width: 100%;
- /* To smooth any scrolling behavior */
- -webkit-overflow-scrolling: touch;
- margin: 0px;
- padding: 0px;
- /* Allows content to fill the viewport and go beyond the bottom */
- min-height: 100%;
-}
-#__next {
- flex-shrink: 0;
- flex-basis: auto;
- flex-direction: column;
- flex-grow: 1;
- display: flex;
- flex: 1;
-}
-html {
- scroll-behavior: smooth;
- /* Prevent text size change on orientation change https://gist.github.com/tfausak/2222823#file-ios-8-web-app-html-L138 */
- -webkit-text-size-adjust: 100%;
- height: 100%;
-}
-body {
- display: flex;
- /* Allows you to scroll below the viewport; default value is visible */
- overflow-y: auto;
- overscroll-behavior-y: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- -ms-overflow-style: scrollbar;
-}
-`;
-
-export async function getInitialProps({ renderPage }) {
- AppRegistry.registerComponent('Main', () => Main);
- const { getStyleElement } = AppRegistry.getApplication('Main');
- const page = await renderPage();
- const styles = [, getStyleElement()];
- return { ...page, styles: React.Children.toArray(styles) };
-}
-
-export class Document extends NextDocument {
- render() {
- return (
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-Document.getInitialProps = getInitialProps;
-
-export default Document;
diff --git a/packages/next-adapter/package.json b/packages/next-adapter/package.json
index b9eb29a43f..1a8a278eb6 100644
--- a/packages/next-adapter/package.json
+++ b/packages/next-adapter/package.json
@@ -1,7 +1,7 @@
{
"name": "@expo/next-adapter",
"version": "4.0.13",
- "description": "Adapter for using Next.js with Expo modules",
+ "description": "Adapter for using Next.js to bundle Expo web projects",
"main": "build/index.js",
"types": "build/index.d.ts",
"scripts": {
@@ -12,18 +12,16 @@
"lint": "eslint ."
},
"bin": {
- "next-expo": "./build/cli/index.js"
+ "next-expo": "echo \"the next-expo command has been deprecated in favor of manual setup. See README for more.\" && exit 1"
},
"repository": {
"type": "git",
- "url": "https://github.com/expo/expo-cli.git",
+ "url": "git+https://github.com/expo/expo-cli.git",
"directory": "packages/next-adapter"
},
"keywords": [
"expo",
- "expo-web",
- "json",
- "next",
+ "next.js",
"react",
"react-native",
"react-dom",
@@ -35,35 +33,16 @@
},
"homepage": "https://github.com/expo/expo-cli/tree/main/packages/next-adapter#readme",
"files": [
- "babel",
- "customize",
- "template",
- "build",
- "document.js",
- "document.d.ts"
+ "build"
],
- "devDependencies": {
- "@types/fs-extra": "^9.0.1",
- "@types/node-fetch": "^2.5.8",
- "@types/prompts": "^2.0.6"
- },
"peerDependencies": {
- "next": "^11",
- "react": "^17",
- "react-native-web": "^0.17.0"
+ "next": "^13",
+ "expo": "^46",
+ "react": "^18",
+ "react-native-web": "~0.18.0",
+ "webpack": "^4.46.0 || ^5.74.0"
},
"publishConfig": {
"access": "public"
- },
- "dependencies": {
- "@expo/package-manager": "0.0.56",
- "@expo/webpack-config": "0.17.3",
- "babel-preset-expo": "^9.2.0",
- "chalk": "^4.0.0",
- "commander": "^4.0.1",
- "fs-extra": "9.0.0",
- "prompts": "^2.3.2",
- "resolve-from": "^5.0.0",
- "update-check": "^1.5.3"
}
}
diff --git a/packages/next-adapter/src/babel.ts b/packages/next-adapter/src/babel.ts
deleted file mode 100644
index ac8be726bc..0000000000
--- a/packages/next-adapter/src/babel.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-module.exports = function (api: any) {
- // Detect web usage (this may change in the future if Next.js changes the loader)
- const isWeb = api.caller(
- (caller?: { name: string }) =>
- caller && (caller.name === 'babel-loader' || caller.name === 'next-babel-turbo-loader')
- );
-
- return {
- presets: [
- // Only use next in the browser, it'll break your native project
- isWeb && require('next/babel'),
- [
- require('babel-preset-expo'),
- {
- web: { useTransformReactJSXExperimental: true },
- // Disable the `no-anonymous-default-export` plugin in babel-preset-expo
- // so users don't see duplicate warnings.
- 'no-anonymous-default-export': false,
- },
- ],
- ].filter(Boolean),
- };
-};
diff --git a/packages/next-adapter/src/cli/index.ts b/packages/next-adapter/src/cli/index.ts
deleted file mode 100644
index 444ea034c9..0000000000
--- a/packages/next-adapter/src/cli/index.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/usr/bin/env node
-import chalk from 'chalk';
-import { Command } from 'commander';
-import { resolve } from 'path';
-
-import { runAsync } from '../customize';
-import shouldUpdate from './update';
-
-let projectDirectory: string = '';
-
-const packageJson = () => require('@expo/next-adapter/package.json');
-
-const program = new Command(packageJson().name)
- .version(packageJson().version)
- .arguments('')
- .usage(`${chalk.green('')} [options]`)
- .description('Generate static Next.js files into your project.')
- .option('-c, --customize', 'Select template files you want to add to your project')
- .option('-f, --force', 'Allows replacing existing files')
- .action((inputProjectDirectory: string, options: any) => {
- projectDirectory = inputProjectDirectory;
- })
- .allowUnknownOption()
- .parse(process.argv);
-
-async function run() {
- if (typeof projectDirectory === 'string') {
- projectDirectory = projectDirectory.trim();
- }
-
- const resolvedProjectRoot = resolve(projectDirectory);
-
- runAsync({ projectRoot: resolvedProjectRoot, force: program.force, yes: !program.customize });
-}
-
-run()
- .then(shouldUpdate)
- .catch(async reason => {
- console.log();
- console.log('Aborting installation.');
- if (reason.command) {
- console.log(` ${chalk.magenta(reason.command)} has failed.`);
- } else {
- console.log(chalk.red('An unexpected error was encountered. Please report it as a bug:'));
- console.log(reason);
- }
- console.log();
-
- await shouldUpdate();
-
- process.exit(1);
- });
diff --git a/packages/next-adapter/src/cli/update.ts b/packages/next-adapter/src/cli/update.ts
deleted file mode 100644
index 0debb1a1c0..0000000000
--- a/packages/next-adapter/src/cli/update.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import chalk from 'chalk';
-import checkForUpdate from 'update-check';
-
-const packageJson = () => require('@expo/next-adapter/package.json');
-
-export default async function shouldUpdate() {
- const update = checkForUpdate(packageJson()).catch(() => null);
-
- try {
- const res = await update;
- if (res && res.latest) {
- const _packageJson = packageJson();
- console.log();
- console.log(chalk.yellow.bold(`A new version of \`${_packageJson.name}\` is available`));
- console.log('You can update by running: ' + chalk.cyan(`npm i -g ${_packageJson.name}`));
- console.log();
- }
- } catch {
- // ignore error
- }
-}
diff --git a/packages/next-adapter/src/customize/index.ts b/packages/next-adapter/src/customize/index.ts
deleted file mode 100644
index 2d679bb20a..0000000000
--- a/packages/next-adapter/src/customize/index.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import chalk from 'chalk';
-import prompts from 'prompts';
-
-import { manifest } from './manifest';
-
-export * from './manifest';
-
-function logReady() {
- console.log();
- console.log(
- chalk.reset(`\u203A Your Expo + Next.js project has all of the required customizations.`)
- );
- console.log(
- chalk.reset(`\u203A Start your project with \`${chalk.bold(`yarn next dev`)}\` to test it.`)
- );
- console.log();
-}
-
-async function runNonInteractiveFlowAsync(projectRoot: string): Promise {
- const customizations = manifest.filter(({ type }) => type === 'required');
- for (const customization of customizations) {
- if (await customization.onEnabledAsync({ projectRoot, force: false })) {
- await customization.onSelectAsync({ projectRoot, force: true });
- }
- }
- logReady();
-}
-
-export async function runAsync({
- projectRoot,
- force,
- yes: nonInteractive,
-}: {
- projectRoot: string;
- force: boolean;
- yes: boolean;
-}): Promise {
- if (nonInteractive) {
- await runNonInteractiveFlowAsync(projectRoot);
- return;
- }
-
- const values = [];
-
- for (const customization of manifest) {
- const enabled = await customization.onEnabledAsync({ projectRoot, force });
- values.push({
- title: customization.name,
- value: customization.name,
- // @ts-ignore: broken types
- disabled: !force && !enabled,
- message: force && !enabled ? chalk.red('This will overwrite the existing file') : '',
- });
- }
-
- if (!values.filter(({ disabled }) => !disabled).length) {
- logReady();
- console.log(
- chalk.dim(`\u203A To regenerate the files run: ${chalk.bold('next-expo --force')}`)
- );
- console.log();
- return;
- }
-
- const { answer } = await prompts({
- type: 'multiselect',
- name: 'answer',
- message: 'Which Next.js files would you like to generate?\n',
- hint: '- Space to select. Return to submit',
- // @ts-ignore: broken types
- warn: 'File exists, use --force to overwrite it.',
- limit: values.length,
- instructions: '',
- choices: values,
- });
-
- if (!answer) {
- console.log('\n\u203A Exiting...\n');
- return;
- }
-
- await Promise.all(
- answer
- .map((item: any) => {
- const customization = manifest.find(({ name }) => name === item);
- if (customization) return customization.onSelectAsync({ projectRoot, force });
- else throw new Error('failed to find customization matching: ' + item);
- })
- .filter(Boolean)
- );
-}
diff --git a/packages/next-adapter/src/customize/manifest.ts b/packages/next-adapter/src/customize/manifest.ts
deleted file mode 100644
index 5e2bc9e3d8..0000000000
--- a/packages/next-adapter/src/customize/manifest.ts
+++ /dev/null
@@ -1,287 +0,0 @@
-import { getPackageJson } from '@expo/config';
-import { createForProject } from '@expo/package-manager';
-import chalk from 'chalk';
-import fs from 'fs-extra';
-import path from 'path';
-import resolveFrom from 'resolve-from';
-
-type SelectMethod = (context: { projectRoot: string; force: boolean }) => Promise;
-type EnabledMethod = (context: { projectRoot: string; force: boolean }) => Promise;
-
-type CustomizeOption = {
- name: string;
- type: 'custom' | 'required' | 'extra';
- destinationPath: (projectRoot: string) => string;
- templatePath?: string;
- description: string;
- onSelectAsync: SelectMethod;
- onEnabledAsync: EnabledMethod;
-};
-
-const generatedTag = `@generated: @expo/next-adapter@${
- require('@expo/next-adapter/package.json').version
-}`;
-
-function createJSTag(): string {
- return `// ${generatedTag}`;
-}
-
-function createBashTag(): string {
- return `# ${generatedTag}`;
-}
-
-async function copyFileAsync(
- from: string,
- to: string,
- force: boolean,
- tag?: string
-): Promise {
- if (!force && (await fs.pathExists(to))) {
- throw new Error(`Cannot overwrite file at "${to}" without the \`force\` option`);
- }
- if (await fs.pathExists(from)) {
- if (tag) {
- const contents = await fs.readFile(from, 'utf8');
-
- await fs.ensureDir(path.dirname(to));
- await fs.writeFile(to, `${tag}\n${contents}`);
- } else {
- await fs.copy(from, to, { overwrite: true, recursive: true });
- }
- } else {
- throw new Error(`Expected template file for ${from} doesn't exist at path: ${to}`);
- }
-}
-
-async function projectHasLatestFileAsync(destinationPath: string, tag: string): Promise {
- if (await fs.pathExists(destinationPath)) {
- const contents = await fs.readFile(destinationPath, 'utf8');
- return contents.includes(tag);
- }
- return false;
-}
-
-const packageRoot = path.join(__dirname, '../../');
-
-function getDependencies(
- projectRoot: string
-): { dependencies: string[]; devDependencies: string[] } {
- const dependencies = ['react-native-web', 'next'].filter(
- dependency => !resolveFrom.silent(projectRoot, dependency)
- );
- const devDependencies = ['@expo/next-adapter', 'babel-preset-expo'].filter(
- dependency => !resolveFrom.silent(projectRoot, dependency)
- );
-
- return { dependencies, devDependencies };
-}
-
-export const manifest: CustomizeOption[] = [
- {
- name: 'Install Next.js dependencies',
- type: 'required',
- destinationPath: (projectRoot: string) => '',
- description: 'Ensure your project has all of the required dependencies',
- async onEnabledAsync({ projectRoot }): Promise {
- const { dependencies, devDependencies } = getDependencies(projectRoot);
- const all = [...dependencies, ...devDependencies];
- return !!all.length;
- },
- async onSelectAsync({ projectRoot }): Promise {
- const { dependencies, devDependencies } = getDependencies(projectRoot);
- const all = [...dependencies, ...devDependencies];
- if (!all.length) {
- console.log(
- chalk.magenta.dim(`\u203A All of the required dependencies are installed already`)
- );
- return;
- } else {
- console.log(chalk.magenta(`\u203A Installing the missing dependencies: ${all.join(', ')}`));
- }
-
- const packageManager = createForProject(projectRoot);
-
- if (dependencies.length) await packageManager.addAsync(...dependencies);
- if (devDependencies.length) await packageManager.addDevAsync(...devDependencies);
- },
- },
- {
- name: 'pages/index.js',
- type: 'required',
- destinationPath: projectRoot => path.resolve(projectRoot, './pages/index.js'),
- templatePath: path.resolve(packageRoot, 'template/pages/index.js'),
- description: 'the first page for your Next.js project.',
- async onEnabledAsync({ projectRoot }): Promise {
- const destinationPath = this.destinationPath(projectRoot);
- return !(await projectHasLatestFileAsync(destinationPath, createJSTag()));
- },
- async onSelectAsync({ projectRoot, force }): Promise {
- console.log(chalk.magenta(`\u203A Creating ${this.description}`));
-
- const destinationPath = this.destinationPath(projectRoot);
- await copyFileAsync(this.templatePath!, destinationPath, force, createJSTag());
- },
- },
- {
- name: 'pages/_document.js',
- type: 'required',
- destinationPath: projectRoot => path.resolve(projectRoot, './pages/_document.js'),
- templatePath: path.resolve(packageRoot, 'template/pages/_document.js'),
- description: 'a custom Next.js Document that ensures CSS-in-JS styles are setup.',
- async onEnabledAsync({ projectRoot }): Promise {
- const destinationPath = this.destinationPath(projectRoot);
- return !(await projectHasLatestFileAsync(destinationPath, createJSTag()));
- },
- async onSelectAsync({ projectRoot, force }): Promise {
- console.log(chalk.magenta(`\u203A Creating ${this.description}`));
- const destinationPath = this.destinationPath(projectRoot);
- await copyFileAsync(this.templatePath!, destinationPath, force, createJSTag());
- },
- },
- {
- name: 'babel.config.js',
- type: 'required',
- destinationPath: projectRoot => path.resolve(projectRoot, './babel.config.js'),
- templatePath: path.resolve(packageRoot, 'template/babel.config.js'),
- description: 'a universal Babel preset for loading projects in iOS, Android, and Next.js.',
- async onEnabledAsync({ projectRoot }): Promise {
- const destinationPath = this.destinationPath(projectRoot);
- return !(await projectHasLatestFileAsync(destinationPath, createJSTag()));
- },
- async onSelectAsync({ projectRoot, force }): Promise {
- console.log(chalk.magenta(`\u203A Creating ${this.description}`));
- const destinationPath = this.destinationPath(projectRoot);
- // TODO: Bacon: Handle the fact that this file will probably exist
- await copyFileAsync(this.templatePath!, destinationPath, force, createJSTag());
- },
- },
- {
- name: 'next.config.js',
- type: 'required',
- destinationPath: projectRoot => path.resolve(projectRoot, './next.config.js'),
- templatePath: path.resolve(packageRoot, 'template/next.config.js'),
- description: 'the Next.js config with Expo support.',
- async onEnabledAsync({ projectRoot }): Promise {
- const destinationPath = this.destinationPath(projectRoot);
- return !(await projectHasLatestFileAsync(destinationPath, createJSTag()));
- },
- async onSelectAsync({ projectRoot, force }): Promise {
- console.log(chalk.magenta(`\u203A Creating ${this.description}`));
- const destinationPath = this.destinationPath(projectRoot);
- await copyFileAsync(this.templatePath!, destinationPath, force, createJSTag());
- },
- },
- {
- name: 'Add build script',
- type: 'required',
- destinationPath: projectRoot => '',
- description: 'the build script required for deploying to now.',
- async onEnabledAsync({ projectRoot, force }): Promise {
- if (force) return true;
-
- const pkg = await readPackageJsonAsync(projectRoot);
-
- const hasNowBuildScript = pkg.scripts.build && pkg.scripts.build.trim() === 'next build';
-
- return !hasNowBuildScript;
- },
- async onSelectAsync({ projectRoot, force }): Promise {
- const pkg = await readPackageJsonAsync(projectRoot);
-
- if (!force && pkg.scripts.build) {
- console.warn(chalk.yellow(`\u203A A build script already exists.`));
- return;
- }
-
- pkg.scripts.build = 'next build';
-
- console.log(
- chalk.magenta(
- `\u203A Adding a build script to your \`${chalk.bold(
- `package.json`
- )}\` for deployment to now.`
- )
- );
-
- await fs.writeFile(path.resolve(projectRoot, 'package.json'), JSON.stringify(pkg, null, 2));
- },
- },
- {
- name: 'Update git ignore',
- type: 'required',
- destinationPath: projectRoot => path.resolve(projectRoot, '.gitignore'),
- templatePath: path.resolve(packageRoot, 'template/default-gitignore'),
- description: 'Ensure Next.js and Expo generated folders are ignored in .gitignore',
- async onEnabledAsync({ projectRoot }): Promise {
- const destinationPath = this.destinationPath(projectRoot);
- if (!(await fs.pathExists(destinationPath))) {
- return true;
- }
- const contents = await fs.readFile(destinationPath, 'utf8');
- return !contents.includes(createBashTag());
- },
- async onSelectAsync({ projectRoot, force }): Promise {
- const destinationPath = this.destinationPath(projectRoot);
-
- // Ensure a default expo .gitignore exists
- if (!(await fs.pathExists(destinationPath))) {
- console.log(
- chalk.magenta(
- `\u203A Creating a default .gitignore for a universal Expo project with Next.js support`
- )
- );
- await copyFileAsync(this.templatePath!, destinationPath, true, createBashTag());
- }
-
- // Ensure the .gitignore has the required fields
- let contents = await fs.readFile(destinationPath, 'utf8');
-
- if (contents.includes(createBashTag())) {
- console.warn(
- chalk.yellow('The .gitignore already appears to contain expo generated files')
- );
- return;
- }
-
- console.log(chalk.magenta(`\u203A Adding the generated folders to your .gitignore`));
-
- const ignore = [
- '',
- createBashTag(),
- '/.expo/*',
- '# Expo Web',
- '/web-build/*',
- '# Expo Native',
- '*.jks',
- '*.p8',
- '*.p12',
- '*.key',
- '*.mobileprovision',
- '*.orig.*',
- '# Next.js',
- '/.next/*',
- '/out/',
- '# Next.js production',
- '/build/',
- '# Next.js dependencies',
- '/.pnp',
- '.pnp.js',
- '# @end @expo/next-adapter',
- '',
- ];
-
- contents += ignore.join('\n');
- await fs.writeFile(destinationPath, contents);
- },
- },
-];
-
-async function readPackageJsonAsync(
- projectRoot: string
-): Promise<{ scripts: { build?: string }; [key: string]: any }> {
- const pkg = getPackageJson(projectRoot) as any;
-
- if (!pkg.scripts) pkg.scripts = {};
-
- return pkg;
-}
diff --git a/packages/next-adapter/src/index.ts b/packages/next-adapter/src/index.ts
index adff740dd4..b24c5ca78d 100644
--- a/packages/next-adapter/src/index.ts
+++ b/packages/next-adapter/src/index.ts
@@ -1 +1,52 @@
-export { default as withExpo } from './withExpo';
+import { Configuration, DefinePlugin } from 'webpack';
+
+export function withExpo(nextConfig: any = {}): any {
+ return {
+ ...nextConfig,
+ webpack(config: Configuration, options: any): Configuration {
+ // Mix in aliases
+ if (!config.resolve) {
+ config.resolve = {};
+ }
+
+ config.resolve.alias = {
+ ...(config.resolve.alias || {}),
+ // Alias direct react-native imports to react-native-web
+ 'react-native$': 'react-native-web',
+ // Alias internal react-native modules to react-native-web
+ 'react-native/Libraries/EventEmitter/RCTDeviceEventEmitter$':
+ 'react-native-web/dist/vendor/react-native/NativeEventEmitter/RCTDeviceEventEmitter',
+ 'react-native/Libraries/vendor/emitter/EventEmitter$':
+ 'react-native-web/dist/vendor/react-native/emitter/EventEmitter',
+ 'react-native/Libraries/EventEmitter/NativeEventEmitter$':
+ 'react-native-web/dist/vendor/react-native/NativeEventEmitter',
+ };
+
+ config.resolve.extensions = [
+ '.web.js',
+ '.web.jsx',
+ '.web.ts',
+ '.web.tsx',
+ ...(config.resolve?.extensions ?? []),
+ ];
+
+ if (!config.plugins) {
+ config.plugins = [];
+ }
+
+ // Expose __DEV__ from Metro.
+ config.plugins.push(
+ new DefinePlugin({
+ __DEV__: JSON.stringify(process.env.NODE_ENV !== 'production'),
+ })
+ );
+
+ // Execute the user-defined webpack config.
+ if (typeof nextConfig.webpack === 'function') {
+ return nextConfig.webpack(config, options);
+ }
+
+ return config;
+ },
+ };
+}
diff --git a/packages/next-adapter/src/withExpo.ts b/packages/next-adapter/src/withExpo.ts
deleted file mode 100644
index 106618164e..0000000000
--- a/packages/next-adapter/src/withExpo.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { getBareExtensions } from '@expo/config/paths';
-import { withUnimodules } from '@expo/webpack-config/addons';
-import { AnyConfiguration } from '@expo/webpack-config/webpack/types';
-
-export default function withExpo(nextConfig: any = {}): any {
- return {
- ...nextConfig,
- pageExtensions: getBareExtensions(['web']),
- webpack(config: AnyConfiguration, options: any): AnyConfiguration {
- // Prevent define plugin from overwriting Next.js environment.
- process.env.EXPO_WEBPACK_DEFINE_ENVIRONMENT_AS_KEYS = 'true';
-
- const webpack5 = (options.config || {}).webpack5;
-
- const expoConfig = withUnimodules(
- config,
- {
- projectRoot: nextConfig.projectRoot || process.cwd(),
- },
- {
- supportsFontLoading: false,
- webpack5: webpack5 !== false,
- }
- );
- // Use original public path
- (expoConfig.output || {}).publicPath = (config.output || {}).publicPath;
-
- // TODO: Bacon: use commonjs for RNW babel maybe...
- if (typeof nextConfig.webpack === 'function') {
- return nextConfig.webpack(expoConfig, options);
- }
-
- return expoConfig;
- },
- };
-}
diff --git a/packages/next-adapter/template/babel.config.js b/packages/next-adapter/template/babel.config.js
deleted file mode 100644
index fd9ef6dab5..0000000000
--- a/packages/next-adapter/template/babel.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-// Learn more: https://github.com/expo/expo/blob/master/docs/pages/versions/unversioned/guides/using-nextjs.md#shared-steps
-
-module.exports = { presets: ['@expo/next-adapter/babel'] };
diff --git a/packages/next-adapter/template/default-gitignore b/packages/next-adapter/template/default-gitignore
deleted file mode 100644
index 48801b3e3a..0000000000
--- a/packages/next-adapter/template/default-gitignore
+++ /dev/null
@@ -1,22 +0,0 @@
-node_modules/**/*
-.expo/*
-npm-debug.*
-*.jks
-*.p8
-*.p12
-*.key
-*.mobileprovision
-*.orig.*
-web-build/
-web-report/
-
-# debug
-yarn-debug.log*
-yarn-error.log*
-
-# macOS
-.DS_Store
-
-# misc
-.DS_Store
-.env*
diff --git a/packages/next-adapter/template/next.config.js b/packages/next-adapter/template/next.config.js
deleted file mode 100644
index 256a2a85ed..0000000000
--- a/packages/next-adapter/template/next.config.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// Learn more: https://github.com/expo/expo/blob/master/docs/pages/versions/unversioned/guides/using-nextjs.md#withexpo
-
-const { withExpo } = require('@expo/next-adapter');
-
-module.exports = withExpo({
- projectRoot: __dirname,
-});
diff --git a/packages/next-adapter/template/pages/_document.js b/packages/next-adapter/template/pages/_document.js
deleted file mode 100644
index 6370d31d68..0000000000
--- a/packages/next-adapter/template/pages/_document.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from '@expo/next-adapter/document';
diff --git a/packages/next-adapter/template/pages/index.js b/packages/next-adapter/template/pages/index.js
deleted file mode 100644
index 4f4d47ad50..0000000000
--- a/packages/next-adapter/template/pages/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import { StyleSheet, Text, View } from 'react-native';
-
-export default function App() {
- return (
-
- Welcome to Expo + Next.js 👋
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- },
- text: {
- fontSize: 16,
- },
-});
diff --git a/yarn.lock b/yarn.lock
index 97b6d940c1..2050381661 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -415,7 +415,7 @@
"@babel/helper-skip-transparent-expression-wrappers" "^7.16.0"
"@babel/plugin-proposal-optional-chaining" "^7.16.0"
-"@babel/plugin-proposal-async-generator-functions@^7.0.0", "@babel/plugin-proposal-async-generator-functions@^7.10.4", "@babel/plugin-proposal-async-generator-functions@^7.16.0":
+"@babel/plugin-proposal-async-generator-functions@^7.10.4", "@babel/plugin-proposal-async-generator-functions@^7.16.0":
version "7.19.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz#34f6f5174b688529342288cd264f80c9ea9fb4a7"
integrity sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==
@@ -913,7 +913,7 @@
"@babel/helper-module-transforms" "^7.16.0"
"@babel/helper-plugin-utils" "^7.14.5"
-"@babel/plugin-transform-named-capturing-groups-regex@^7.0.0", "@babel/plugin-transform-named-capturing-groups-regex@^7.10.4", "@babel/plugin-transform-named-capturing-groups-regex@^7.16.0":
+"@babel/plugin-transform-named-capturing-groups-regex@^7.10.4", "@babel/plugin-transform-named-capturing-groups-regex@^7.16.0":
version "7.19.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz#ec7455bab6cd8fb05c525a94876f435a48128888"
integrity sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==
@@ -5283,11 +5283,6 @@ babel-plugin-react-native-web@~0.17.1:
resolved "https://registry.yarnpkg.com/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.17.5.tgz#4bce51a20d21839f20506ef184bd5743a2c6d067"
integrity sha512-UWl0E9FGYVr5Gj7lbVc4DFy8pTgc6wIXBa0rDvPGxx3OmcKwcdvCfDn9mLuh7JesYfh+wLjp01fwPplMus7IPw==
-babel-plugin-react-native-web@~0.18.2:
- version "0.18.9"
- resolved "https://registry.yarnpkg.com/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.18.9.tgz#854c5e4979f52ae69fc3bb25df8b427a8ad372c7"
- integrity sha512-A9rrSfV98CFRS+ACgZorxaHH8gDrVyK2Nea8OHepY4Sv/Mf+vk8uvQq+tRUEBpHnUvd/qRDKIjFLbygecAt9VA==
-
babel-plugin-syntax-jsx@6.18.0, babel-plugin-syntax-jsx@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
@@ -5345,18 +5340,6 @@ babel-preset-current-node-syntax@^1.0.0:
"@babel/plugin-syntax-optional-chaining" "^7.8.3"
"@babel/plugin-syntax-top-level-await" "^7.8.3"
-babel-preset-expo@^9.2.0:
- version "9.2.0"
- resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-9.2.0.tgz#d01793e3a556065f103b3095fbbc959d52f08e88"
- integrity sha512-aM2htiNx0H49H+MWCp9+cKVSdcdNSn0tbE5Dln/GO1xna4ZlnA30clbfClcYJFUcZtW90IsYeZwQ/hj8zyWhNA==
- dependencies:
- "@babel/plugin-proposal-decorators" "^7.12.9"
- "@babel/plugin-transform-react-jsx" "^7.12.17"
- "@babel/preset-env" "^7.12.9"
- babel-plugin-module-resolver "^4.1.0"
- babel-plugin-react-native-web "~0.18.2"
- metro-react-native-babel-preset "~0.70.3"
-
babel-preset-expo@~9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-9.1.0.tgz#4cbac7d28618bb68bc9c2a0e7dccda7b207b61ab"
@@ -12847,51 +12830,6 @@ metro-react-native-babel-preset@~0.67.0:
"@babel/template" "^7.0.0"
react-refresh "^0.4.0"
-metro-react-native-babel-preset@~0.70.3:
- version "0.70.3"
- resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.70.3.tgz#1c77ec4544ecd5fb6c803e70b21284d7483e4842"
- integrity sha512-4Nxc1zEiHEu+GTdEMEsHnRgfaBkg8f/Td3+FcQ8NTSvs+xL3LBrQy6N07idWSQZHIdGFf+tTHvRfSIWLD8u8Tg==
- dependencies:
- "@babel/core" "^7.14.0"
- "@babel/plugin-proposal-async-generator-functions" "^7.0.0"
- "@babel/plugin-proposal-class-properties" "^7.0.0"
- "@babel/plugin-proposal-export-default-from" "^7.0.0"
- "@babel/plugin-proposal-nullish-coalescing-operator" "^7.0.0"
- "@babel/plugin-proposal-object-rest-spread" "^7.0.0"
- "@babel/plugin-proposal-optional-catch-binding" "^7.0.0"
- "@babel/plugin-proposal-optional-chaining" "^7.0.0"
- "@babel/plugin-syntax-dynamic-import" "^7.0.0"
- "@babel/plugin-syntax-export-default-from" "^7.0.0"
- "@babel/plugin-syntax-flow" "^7.2.0"
- "@babel/plugin-syntax-nullish-coalescing-operator" "^7.0.0"
- "@babel/plugin-syntax-optional-chaining" "^7.0.0"
- "@babel/plugin-transform-arrow-functions" "^7.0.0"
- "@babel/plugin-transform-async-to-generator" "^7.0.0"
- "@babel/plugin-transform-block-scoping" "^7.0.0"
- "@babel/plugin-transform-classes" "^7.0.0"
- "@babel/plugin-transform-computed-properties" "^7.0.0"
- "@babel/plugin-transform-destructuring" "^7.0.0"
- "@babel/plugin-transform-exponentiation-operator" "^7.0.0"
- "@babel/plugin-transform-flow-strip-types" "^7.0.0"
- "@babel/plugin-transform-function-name" "^7.0.0"
- "@babel/plugin-transform-literals" "^7.0.0"
- "@babel/plugin-transform-modules-commonjs" "^7.0.0"
- "@babel/plugin-transform-named-capturing-groups-regex" "^7.0.0"
- "@babel/plugin-transform-parameters" "^7.0.0"
- "@babel/plugin-transform-react-display-name" "^7.0.0"
- "@babel/plugin-transform-react-jsx" "^7.0.0"
- "@babel/plugin-transform-react-jsx-self" "^7.0.0"
- "@babel/plugin-transform-react-jsx-source" "^7.0.0"
- "@babel/plugin-transform-runtime" "^7.0.0"
- "@babel/plugin-transform-shorthand-properties" "^7.0.0"
- "@babel/plugin-transform-spread" "^7.0.0"
- "@babel/plugin-transform-sticky-regex" "^7.0.0"
- "@babel/plugin-transform-template-literals" "^7.0.0"
- "@babel/plugin-transform-typescript" "^7.5.0"
- "@babel/plugin-transform-unicode-regex" "^7.0.0"
- "@babel/template" "^7.0.0"
- react-refresh "^0.4.0"
-
metro-react-native-babel-transformer@0.64.0, metro-react-native-babel-transformer@^0.64.0:
version "0.64.0"
resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.64.0.tgz#eafef756972f20efdc51bd5361d55f8598355623"
@@ -18345,7 +18283,7 @@ update-check@1.5.3:
registry-auth-token "3.3.2"
registry-url "3.1.0"
-update-check@^1.5.3, update-check@^1.5.4:
+update-check@^1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/update-check/-/update-check-1.5.4.tgz#5b508e259558f1ad7dbc8b4b0457d4c9d28c8743"
integrity sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==