diff --git a/.eslintignore b/.eslintignore index 6fabdb2f64..73eeb62509 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,5 @@ node_modules test lib -demos \ No newline at end of file +demos +plugin/src/templates/edge \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 189f0bc925..167c33e0ad 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -15,6 +15,9 @@ module.exports = { 'node/exports-style': 0, 'node/global-require': 0, 'node/prefer-global/process': 0, + // Allow a single word inline so that it can do language tags for syntax highlighting + // ['error', { ignorePattern: /^ (\w+) $/ }], + 'no-inline-comments': 0, 'no-magic-numbers': 0, 'no-param-reassign': 0, 'no-promise-executor-return': 0, diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bc36c6921f..7c26e63e54 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,12 +27,11 @@ jobs: - uses: actions/checkout@v2 - name: Installing with latest Node.js uses: actions/setup-node@v2 - if: "${{ matrix.node-version != '*' }}" with: node-version: '*' check-latest: true - name: NPM Install - run: npm ci + run: npm install - name: Switching to Node.js ${{ matrix.node-version }} to run tests uses: actions/setup-node@v2 if: "${{ matrix.node-version != '*' }}" @@ -64,12 +63,11 @@ jobs: - uses: actions/checkout@v2 - name: Installing with latest Node.js uses: actions/setup-node@v2 - if: "${{ matrix.node-version != '*' }}" with: node-version: '*' check-latest: true - name: NPM Install - run: npm ci + run: npm install - name: Install Next.js Canary run: npm install -D next@canary --legacy-peer-deps - name: Switching to Node.js ${{ matrix.node-version }} to run tests diff --git a/.prettierignore b/.prettierignore index 7d2a1afdcd..8c51766a32 100644 --- a/.prettierignore +++ b/.prettierignore @@ -20,4 +20,5 @@ node_modules # Test lib tsconfig.json -demos/nx-next-monorepo-demo \ No newline at end of file +demos/nx-next-monorepo-demo +plugin/src/templates/edge \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..85b75ad0ca --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "deno.enablePaths": [ + "plugin/src/templates/edge", + "demos/middleware/.netlify/edge-functions", + "demos/server-components/.netlify/edge-functions", + ], + "deno.unstable": true, + "deno.importMap": "demos/server-components/.netlify/edge-functions-import-map.json" +} \ No newline at end of file diff --git a/demos/base-path/package.json b/demos/base-path/package.json index 15b4a5f13f..caabba1ac6 100644 --- a/demos/base-path/package.json +++ b/demos/base-path/package.json @@ -4,6 +4,9 @@ "description": "", "devDependencies": { "@netlify/plugin-nextjs": "*", + "@types/fs-extra": "^9.0.13", + "@types/jest": "^27.4.1", + "@types/node": "^17.0.25", "husky": "^7.0.4", "if-env": "^1.0.4", "npm-run-all": "^4.1.5", diff --git a/demos/default/package.json b/demos/default/package.json index 73163ef535..d5bdebc7ee 100644 --- a/demos/default/package.json +++ b/demos/default/package.json @@ -4,7 +4,7 @@ "private": true, "description": "", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "build": "next build" }, "repository": { "type": "git", @@ -18,10 +18,15 @@ "homepage": "https://github.com/netlify/netlify-plugin-nextjs#readme", "dependencies": { "@reach/dialog": "^0.16.2", - "@reach/visually-hidden": "^0.16.0" + "@reach/visually-hidden": "^0.16.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "devDependencies": { "@netlify/plugin-nextjs": "*", + "@types/fs-extra": "^9.0.13", + "@types/jest": "^27.4.1", + "@types/node": "^17.0.25", "husky": "^7.0.4", "if-env": "^1.0.4", "npm-run-all": "^4.1.5", diff --git a/demos/middleware/.gitignore b/demos/middleware/.gitignore new file mode 100644 index 0000000000..55175ef867 --- /dev/null +++ b/demos/middleware/.gitignore @@ -0,0 +1,32 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel diff --git a/demos/middleware/README.md b/demos/middleware/README.md new file mode 100644 index 0000000000..76ef3ae6fd --- /dev/null +++ b/demos/middleware/README.md @@ -0,0 +1,29 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with +[`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. + +[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on +[http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. + +The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as +[API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. diff --git a/demos/middleware/netlify.toml b/demos/middleware/netlify.toml new file mode 100644 index 0000000000..0a5d2ad069 --- /dev/null +++ b/demos/middleware/netlify.toml @@ -0,0 +1,22 @@ +[build] +command = "npm run build" +publish = ".next" + +[environment] +NEXT_USE_NETLIFY_EDGE = "true" + +[[plugins]] +package = "../plugin-wrapper/" + +# This is a fake plugin, that makes it run npm install +[[plugins]] +package = "@netlify/plugin-local-install-core" + +[functions] +included_files = [ + "!node_modules/sharp/vendor/8.12.2/darwin-*/**/*", + "!node_modules/sharp/build/Release/sharp-darwin-*" +] + +[dev] +framework = "#static" diff --git a/demos/middleware/next-env.d.ts b/demos/middleware/next-env.d.ts new file mode 100644 index 0000000000..4f11a03dc6 --- /dev/null +++ b/demos/middleware/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/demos/middleware/next.config.js b/demos/middleware/next.config.js new file mode 100644 index 0000000000..f99fac3940 --- /dev/null +++ b/demos/middleware/next.config.js @@ -0,0 +1,11 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + eslint: { + // Warning: This allows production builds to successfully complete even if + // your project has ESLint errors. + ignoreDuringBuilds: true, + }, +} + +module.exports = nextConfig diff --git a/demos/middleware/package.json b/demos/middleware/package.json new file mode 100644 index 0000000000..ca57cce327 --- /dev/null +++ b/demos/middleware/package.json @@ -0,0 +1,26 @@ +{ + "name": "middleware", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "ntl": "ntl-internal" + }, + "dependencies": { + "next": "^12.1.5-canary.2", + "react": "18.0.0", + "react-dom": "18.0.0" + }, + "devDependencies": { + "@netlify/plugin-nextjs": "*", + "@types/fs-extra": "^9.0.13", + "@types/jest": "^27.4.1", + "@types/node": "^17.0.25", + "@types/react": "^17.0.43", + "husky": "^7.0.4", + "npm-run-all": "^4.1.5", + "typescript": "^4.6.3" + } +} diff --git a/demos/middleware/pages/_app.js b/demos/middleware/pages/_app.js new file mode 100644 index 0000000000..1e1cec9242 --- /dev/null +++ b/demos/middleware/pages/_app.js @@ -0,0 +1,7 @@ +import '../styles/globals.css' + +function MyApp({ Component, pageProps }) { + return +} + +export default MyApp diff --git a/demos/middleware/pages/api/hello.js b/demos/middleware/pages/api/hello.js new file mode 100644 index 0000000000..df63de88fa --- /dev/null +++ b/demos/middleware/pages/api/hello.js @@ -0,0 +1,5 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction + +export default function handler(req, res) { + res.status(200).json({ name: 'John Doe' }) +} diff --git a/demos/middleware/pages/index.js b/demos/middleware/pages/index.js new file mode 100644 index 0000000000..dc4b640352 --- /dev/null +++ b/demos/middleware/pages/index.js @@ -0,0 +1,69 @@ +import Head from 'next/head' +import Image from 'next/image' +import styles from '../styles/Home.module.css' + +export default function Home() { + return ( +
+ + Create Next App + + + + +
+

+ Welcome to Next.js! +

+ +

+ Get started by editing{' '} + pages/index.js +

+ + +
+ + +
+ ) +} diff --git a/demos/middleware/pages/shows/[id].js b/demos/middleware/pages/shows/[id].js new file mode 100644 index 0000000000..e9dd5e1979 --- /dev/null +++ b/demos/middleware/pages/shows/[id].js @@ -0,0 +1,55 @@ +import Error from 'next/error' +import Link from 'next/link' + +const Show = ({ errorCode, show, env }) => { + // If show item was not found, render 404 page + if (errorCode) { + return + } + + // Otherwise, render show + return ( +
+

+ This page uses getInitialProps() to fetch the show with the ID provided + in the URL: /shows/:id +
+ Refresh the page to see server-side rendering in action. +
+ You can also try changing the ID to any other number between 1-10000. + Env: {env} +

+
+ +

Show #{show.id}

+

{show.name}

+ +
+ + + Go back home + +
+ ) +} + +export const getServerSideProps = async ({ params, req }) => { + // The ID to render + const { id } = params + console.log(req.headers) + const res = await fetch(`https://api.tvmaze.com/shows/${id}`) + const data = await res.json() + + // Set error code if show item could not be found + const errorCode = res.status > 200 ? res.status : false + + return { + props: { + errorCode, + show: data, + env: process.env.HELLO_WORLD || null, + }, + } +} + +export default Show diff --git a/demos/middleware/pages/shows/_middleware.ts b/demos/middleware/pages/shows/_middleware.ts new file mode 100644 index 0000000000..98d8ed4256 --- /dev/null +++ b/demos/middleware/pages/shows/_middleware.ts @@ -0,0 +1,13 @@ +import { NextResponse } from 'next/server' +import type { NextFetchEvent, NextRequest } from 'next/server' + +export async function middleware(req: NextRequest, ev: NextFetchEvent) { + const response = NextResponse.next() + + // Set custom header + response.headers.set('x-modified-edge', 'true') + response.headers.set('x-is-deno', 'Deno' in globalThis ? 'true' : 'false') + + // Return response + return response +} diff --git a/demos/middleware/pages/shows/index.js b/demos/middleware/pages/shows/index.js new file mode 100644 index 0000000000..bf0af734b3 --- /dev/null +++ b/demos/middleware/pages/shows/index.js @@ -0,0 +1,17 @@ +import Error from 'next/error' +import Link from 'next/link' + +const Show = () => { + return ( +
+

Add a number between 1 and 10000 to the URL

+
+ + + Go back home + +
+ ) +} + +export default Show diff --git a/demos/middleware/pages/shows/rewriteme/_middleware.ts b/demos/middleware/pages/shows/rewriteme/_middleware.ts new file mode 100644 index 0000000000..6893967a39 --- /dev/null +++ b/demos/middleware/pages/shows/rewriteme/_middleware.ts @@ -0,0 +1,10 @@ +import { NextResponse } from 'next/server' +import { NextFetchEvent, NextRequest } from 'next/server' + +export function middleware(req: NextRequest, ev: NextFetchEvent) { + const url = req.nextUrl.clone() + url.pathname = '/shows/100' + const res = NextResponse.rewrite(url) + res.headers.set('x-modified-in-rewrite', 'true') + return res +} diff --git a/demos/middleware/public/favicon.ico b/demos/middleware/public/favicon.ico new file mode 100644 index 0000000000..718d6fea48 Binary files /dev/null and b/demos/middleware/public/favicon.ico differ diff --git a/demos/middleware/styles/Home.module.css b/demos/middleware/styles/Home.module.css new file mode 100644 index 0000000000..32a57d52f3 --- /dev/null +++ b/demos/middleware/styles/Home.module.css @@ -0,0 +1,116 @@ +.container { + padding: 0 2rem; +} + +.main { + min-height: 100vh; + padding: 4rem 0; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.footer { + display: flex; + flex: 1; + padding: 2rem 0; + border-top: 1px solid #eaeaea; + justify-content: center; + align-items: center; +} + +.footer a { + display: flex; + justify-content: center; + align-items: center; + flex-grow: 1; +} + +.title a { + color: #0070f3; + text-decoration: none; +} + +.title a:hover, +.title a:focus, +.title a:active { + text-decoration: underline; +} + +.title { + margin: 0; + line-height: 1.15; + font-size: 4rem; +} + +.title, +.description { + text-align: center; +} + +.description { + margin: 4rem 0; + line-height: 1.5; + font-size: 1.5rem; +} + +.code { + background: #fafafa; + border-radius: 5px; + padding: 0.75rem; + font-size: 1.1rem; + font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, + Bitstream Vera Sans Mono, Courier New, monospace; +} + +.grid { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + max-width: 800px; +} + +.card { + margin: 1rem; + padding: 1.5rem; + text-align: left; + color: inherit; + text-decoration: none; + border: 1px solid #eaeaea; + border-radius: 10px; + transition: color 0.15s ease, border-color 0.15s ease; + max-width: 300px; +} + +.card:hover, +.card:focus, +.card:active { + color: #0070f3; + border-color: #0070f3; +} + +.card h2 { + margin: 0 0 1rem 0; + font-size: 1.5rem; +} + +.card p { + margin: 0; + font-size: 1.25rem; + line-height: 1.5; +} + +.logo { + height: 1em; + margin-left: 0.5rem; +} + +@media (max-width: 600px) { + .grid { + width: 100%; + flex-direction: column; + } +} diff --git a/demos/middleware/styles/globals.css b/demos/middleware/styles/globals.css new file mode 100644 index 0000000000..e5e2dcc23b --- /dev/null +++ b/demos/middleware/styles/globals.css @@ -0,0 +1,16 @@ +html, +body { + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; +} + +a { + color: inherit; + text-decoration: none; +} + +* { + box-sizing: border-box; +} diff --git a/demos/middleware/tsconfig.json b/demos/middleware/tsconfig.json new file mode 100644 index 0000000000..593821eda6 --- /dev/null +++ b/demos/middleware/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "noEmit": true, + "incremental": true, + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "strict": false, + "moduleResolution": "node" + }, + "exclude": [ + "node_modules", + "../../src/templates/edge/*" + ] +} diff --git a/demos/next-export/package.json b/demos/next-export/package.json index 86581e9f8f..33b0b1ea36 100644 --- a/demos/next-export/package.json +++ b/demos/next-export/package.json @@ -4,6 +4,9 @@ "description": "", "devDependencies": { "@netlify/plugin-nextjs": "*", + "@types/fs-extra": "^9.0.13", + "@types/jest": "^27.4.1", + "@types/node": "^17.0.25", "husky": "^7.0.4", "if-env": "^1.0.4", "npm-run-all": "^4.1.5", diff --git a/demos/plugin-wrapper/package-lock.json b/demos/plugin-wrapper/package-lock.json index 11a2e6611e..9bd7b537cc 100644 --- a/demos/plugin-wrapper/package-lock.json +++ b/demos/plugin-wrapper/package-lock.json @@ -1,5 +1,14 @@ { "name": "local-plugin", "version": "1.0.0", - "lockfileVersion": 1 + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "local-plugin", + "version": "1.0.0", + "hasInstallScript": true, + "license": "ISC" + } + } } diff --git a/demos/plugin-wrapper/package.json b/demos/plugin-wrapper/package.json index 34e2ec7063..8ed900862c 100644 --- a/demos/plugin-wrapper/package.json +++ b/demos/plugin-wrapper/package.json @@ -9,6 +9,9 @@ }, "devDependencies": { "@netlify/plugin-nextjs": "*", + "@types/fs-extra": "^9.0.13", + "@types/jest": "^27.4.1", + "@types/node": "^17.0.25", "husky": "^7.0.4", "if-env": "^1.0.4", "npm-run-all": "^4.1.5", diff --git a/demos/server-components/README.md b/demos/server-components/README.md new file mode 100644 index 0000000000..eb3c5af11c --- /dev/null +++ b/demos/server-components/README.md @@ -0,0 +1,34 @@ +# Next.js 12 React Server Components Demo (Alpha) + +Try the demo live here: [**https://next-plugin-rsc-demo.netlify.app**](https://next-plugin-rsc-demo.netlify.app). + +## Introduction + +This is a demo app of the Hacker News website clone, which shows Next.js 12's experimental React Server Components +support. We recommend you taking a look at these links, before trying out the experimental feature: + +- [**Introducing Zero-Bundle-Size React Server Components**](https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html) +- [**Everything About React Server Components**](https://vercel.com/blog/everything-about-react-server-components) +- [**Docs of React Server Components in Next.js**](https://nextjs.org/docs/advanced-features/react-18#react-server-components) + +This demo is built for showing what features that Server Components provide and what the application structure might +look like. **It's not ready for production adoption, or performance benchmarking** as the underlying APIs are not stable +yet, and might change or be improved in the future. + +## Technical Details + +This Next.js application uses React 18 (RC build) and the new +[Edge Runtime](https://nextjs.org/docs/api-reference/edge-runtime). It has `runtime` set to `'edge'` and feature flag +`serverComponents` enabled. You can check out +[next.config.js](https://github.com/vercel/next-react-server-components/blob/main/next.config.js) for more details. + +### Running Locally + +1. `yarn install` +2. `yarn dev` + +Go to `localhost:3000`. + +## License + +This demo is MIT licensed. diff --git a/demos/server-components/components/comment-form.js b/demos/server-components/components/comment-form.js new file mode 100644 index 0000000000..6c1b33b390 --- /dev/null +++ b/demos/server-components/components/comment-form.js @@ -0,0 +1,22 @@ +export default () => ( +
+ +