Skip to content

Commit

Permalink
feat: add mono repository support (#123)
Browse files Browse the repository at this point in the history
* feat: add mono repository support

chore: fix fixture preparation

* Update src/run/handlers/cache.cts

Co-authored-by: Eduardo Bouças <[email protected]>

* Update .eslintrc.cjs

Co-authored-by: Eduardo Bouças <[email protected]>

* Update .eslintrc.cjs

Co-authored-by: Eduardo Bouças <[email protected]>

* chore: update comment

---------

Co-authored-by: Eduardo Bouças <[email protected]>
  • Loading branch information
lukasholzer and eduardoboucas authored Jan 4, 2024
1 parent 18f18e3 commit 53f0f36
Show file tree
Hide file tree
Showing 72 changed files with 4,113 additions and 464 deletions.
38 changes: 36 additions & 2 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ module.exports = {
'import/no-namespace': 'off',
'import/extensions': 'off',
'max-depth': 'off',
'func-style': 'off',
'class-methods-use-this': 'off',
'promise/prefer-await-to-then': 'off',
'promise/prefer-await-to-callbacks': 'off',
'promise/catch-or-return': 'off',
'promise/always-return': 'off',
'max-nested-callbacks': 'off',
'no-inline-comments': 'off',
'line-comment-position': 'off',
'max-lines': 'off',
},
overrides: [
...overrides,
Expand All @@ -29,8 +39,32 @@ module.exports = {
{
files: ['src/**/*.test.*'],
rules: {
'max-statements': off,
'max-lines-per-function': off,
'max-statements': 'off',
'max-lines-per-function': 'off',
},
},
{
files: ['src/build/**/*.ts'],
rules: {
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'path',
importNames: ['resolve'],
message:
'Please use `PluginContext.resolve` instead to comply with our monorepo support',
},
{
name: 'node:path',
importNames: ['resolve'],
message:
'Please use `PluginContext.resolve` instead to comply with our monorepo support',
},
],
},
],
},
},
],
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ jobs:
run: npm ci
- name: 'Build'
run: npm run build
- name: 'Lint'
run: npm run lint
- name: 'Prepare Fixtures'
run: npm run pretest
- name: 'Test'
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ edge-runtime/vendor
/playwright/.cache/

deno.lock
.eslintcache
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@netlify/next-runtime",
"version": "5.0.0-alpha.27",
"version": "5.0.0-alpha.30",
"description": "Run Next.js seamlessly on Netlify",
"main": "./dist/index.js",
"type": "module",
Expand All @@ -18,6 +18,7 @@
"pretest": "node tests/prepare.mjs",
"build": "node ./tools/build.js",
"build:watch": "node ./tools/build.js --watch",
"lint": "eslint --cache --format=codeframe --max-warnings=0 src/**/*.ts",
"test": "vitest",
"test:ci": "vitest run --reporter=default --retry=3",
"e2e": "playwright test",
Expand Down
19 changes: 7 additions & 12 deletions src/build/cache.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import type { NetlifyPluginOptions } from '@netlify/build'
import { resolve } from 'node:path'
import { join } from 'node:path'

export const saveBuildCache = async ({
constants: { PUBLISH_DIR },
utils: { cache },
}: Pick<NetlifyPluginOptions, 'constants' | 'utils'>) => {
if (await cache.save(resolve(PUBLISH_DIR, 'cache'))) {
import type { PluginContext } from './plugin-context.js'

export const saveBuildCache = async (ctx: PluginContext) => {
if (await ctx.utils.cache.save(join(ctx.publishDir, 'cache'))) {
console.log('Next.js cache saved.')
} else {
console.log('No Next.js cache to save.')
}
}

export const restoreBuildCache = async ({
constants: { PUBLISH_DIR },
utils: { cache },
}: Pick<NetlifyPluginOptions, 'constants' | 'utils'>) => {
if (await cache.restore(resolve(PUBLISH_DIR, 'cache'))) {
export const restoreBuildCache = async (ctx: PluginContext) => {
if (await ctx.utils.cache.restore(join(ctx.publishDir, 'cache'))) {
console.log('Next.js cache restored.')
} else {
console.log('No Next.js cache to restore.')
Expand Down
42 changes: 0 additions & 42 deletions src/build/config.ts

This file was deleted.

27 changes: 0 additions & 27 deletions src/build/constants.ts

This file was deleted.

111 changes: 26 additions & 85 deletions src/build/content/prerendered.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,16 @@
import type { NetlifyPluginOptions } from '@netlify/build'
import glob from 'fast-glob'
import { Buffer } from 'node:buffer'
import { existsSync } from 'node:fs'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { getPrerenderManifest } from '../config.js'
import { BLOB_DIR } from '../constants.js'

export type CacheEntry = {
lastModified: number
value: CacheValue
}

type CacheValue = PageCacheValue | RouteCacheValue | FetchCacheValue

export type PageCacheValue = {
kind: 'PAGE'
html: string
pageData: string
headers?: { [k: string]: string }
status?: number
}

type RouteCacheValue = {
kind: 'ROUTE'
body: string
headers: { [k: string]: string }
status: number
}
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'

type FetchCacheValue = {
kind: 'FETCH'
data: {
headers: { [k: string]: string }
body: string
url: string
status?: number
tags?: string[]
}
}

/**
* Write a cache entry to the blob upload directory using
* base64 keys to avoid collisions with directories
*/
const writeCacheEntry = async (key: string, value: CacheValue) => {
const path = resolve(BLOB_DIR, Buffer.from(key).toString('base64'))
const entry = JSON.stringify({
lastModified: Date.now(),
value,
} satisfies CacheEntry)
import glob from 'fast-glob'

await mkdir(dirname(path), { recursive: true })
await writeFile(path, entry, 'utf-8')
}
import type {
CacheValue,
FetchCacheValue,
PageCacheValue,
PluginContext,
RouteCacheValue,
} from '../plugin-context.js'

/**
* Normalize routes by stripping leading slashes and ensuring root path is index
Expand Down Expand Up @@ -87,15 +44,10 @@ const buildFetchCacheValue = async (path: string): Promise<FetchCacheValue> => (
/**
* Upload prerendered content to the blob store
*/
export const copyPrerenderedContent = async ({
constants: { PUBLISH_DIR },
utils: {
build: { failBuild },
},
}: Pick<NetlifyPluginOptions, 'constants' | 'utils'>) => {
export const copyPrerenderedContent = async (ctx: PluginContext): Promise<void> => {
try {
// read prerendered content and build JSON key/values for the blob store
const manifest = await getPrerenderManifest({ PUBLISH_DIR })
const manifest = await ctx.getPrerenderManifest()

await Promise.all(
Object.entries(manifest.routes).map(async ([route, meta]) => {
Expand All @@ -104,62 +56,51 @@ export const copyPrerenderedContent = async ({

switch (true) {
case meta.dataRoute?.endsWith('.json'):
value = await buildPagesCacheValue(resolve(PUBLISH_DIR, 'server/pages', key))
value = await buildPagesCacheValue(join(ctx.publishDir, 'server/pages', key))
break

case meta.dataRoute?.endsWith('.rsc'):
value = await buildAppCacheValue(resolve(PUBLISH_DIR, 'server/app', key))
value = await buildAppCacheValue(join(ctx.publishDir, 'server/app', key))
break

case meta.dataRoute === null:
value = await buildRouteCacheValue(resolve(PUBLISH_DIR, 'server/app', key))
value = await buildRouteCacheValue(join(ctx.publishDir, 'server/app', key))
break

default:
throw new Error(`Unrecognized content: ${route}`)
}

await writeCacheEntry(key, value)
await ctx.writeCacheEntry(key, value)
}),
)

// app router 404 pages are not in the prerender manifest
// so we need to check for them manually
if (existsSync(resolve(PUBLISH_DIR, `server/app/_not-found.html`))) {
if (existsSync(join(ctx.publishDir, `server/app/_not-found.html`))) {
const key = '404'
const value = await buildAppCacheValue(resolve(PUBLISH_DIR, 'server/app/_not-found'))
await writeCacheEntry(key, value)
const value = await buildAppCacheValue(join(ctx.publishDir, 'server/app/_not-found'))
await ctx.writeCacheEntry(key, value)
}
} catch (error) {
failBuild(
'Failed assembling prerendered content for upload',
error instanceof Error ? { error } : {},
)
ctx.failBuild('Failed assembling prerendered content for upload', error)
}
}

/**
* Upload fetch content to the blob store
*/
export const copyFetchContent = async ({
constants: { PUBLISH_DIR },
utils: {
build: { failBuild },
},
}: Pick<NetlifyPluginOptions, 'constants' | 'utils'>) => {
export const copyFetchContent = async (ctx: PluginContext): Promise<void> => {
try {
const paths = await glob(['!(*.*)'], {
cwd: resolve(PUBLISH_DIR, 'cache/fetch-cache'),
cwd: join(ctx.publishDir, 'cache/fetch-cache'),
extglob: true,
})

await Promise.all(
paths.map(async (key) => {
const value = await buildFetchCacheValue(resolve(PUBLISH_DIR, 'cache/fetch-cache', key))
await writeCacheEntry(key, value)
paths.map(async (key): Promise<void> => {
const value = await buildFetchCacheValue(join(ctx.publishDir, 'cache/fetch-cache', key))
await ctx.writeCacheEntry(key, value)
}),
)
} catch (error) {
failBuild('Failed assembling fetch content for upload', error instanceof Error ? { error } : {})
ctx.failBuild('Failed assembling fetch content for upload', error)
}
}
Loading

0 comments on commit 53f0f36

Please sign in to comment.