Skip to content

feat!: upgrade to ipx 2 #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion example/netlify/functions/ipx.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createIPXHandler } from '@netlify/ipx'

export const handler = createIPXHandler({
const handle = createIPXHandler({
remotePatterns: [
{
protocol: 'https',
Expand All @@ -17,3 +17,16 @@ export const handler = createIPXHandler({
'X-Test': 'foobar'
}
})

export const handler = async (event, context) => {
try {
return await handle(event, context)
} catch (e) {
// eslint-disable-next-line no-console
console.error(e)
return {
statusCode: 500,
body: 'Internal Server Error'
}
}
}
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@
"test": "ava",
"dev": "netlify dev"
},
"engines": {
"node": ">=18.0.0"
},
Comment on lines +19 to +21
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ipx 2 uses H3, which uses global fetch objects. Rather than polyfilling it seems to make sense to just set the ngines requirement, as both Next and Gatsby now require it too

"dependencies": {
"@netlify/functions": "^2.3.0",
"etag": "^1.8.1",
"fs-extra": "^11.0.0",
"ipx": "^1.3.1",
"ipx": "^2.0.0",
"micromatch": "^4.0.5",
"mkdirp": "^3.0.0",
"murmurhash": "^2.0.0",
Expand Down Expand Up @@ -52,4 +55,4 @@
"test/**/*.test.ts"
]
}
}
}
7 changes: 6 additions & 1 deletion src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,13 @@ const USAGE_TRACKING_KEY = 'usage-tracking'
const trackingLock = new Lock()

async function getTracking (metadataStore: Storage): Promise<UsageTracking> {
return ((await metadataStore.getItem(USAGE_TRACKING_KEY)) ??
const usage = ((await metadataStore.getItem(USAGE_TRACKING_KEY)) ||
{}) as UsageTracking

if (typeof usage !== 'object') {
return {}
}
return usage
}

async function markUsageStart (
Expand Down
48 changes: 33 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { join } from 'path'
import { tmpdir } from 'os'
import { createIPX, handleRequest, IPXOptions } from 'ipx'
import { createIPX, createIPXPlainServer, ipxFSStorage, ipxHttpStorage, IPXOptions } from 'ipx'
import { builder, Handler } from '@netlify/functions'
import { parseURL } from 'ufo'
import etag from 'etag'
Expand Down Expand Up @@ -30,6 +30,10 @@ export interface IPXHandlerOptions extends Partial<IPXOptions> {
* Restrict local image access to a specific prefix
*/
localPrefix?: string
/**
* List of domains to allow for remote images
*/
domains?: string[]
/**
* Patterns used to verify remote image URLs
*/
Expand All @@ -54,9 +58,21 @@ export function createIPXHandler ({
remotePatterns,
responseHeaders,
localPrefix,
domains = [],
...opts
}: IPXHandlerOptions = {}, loadSourceImage = defaultLoadSourceImage) {
const ipx = createIPX({ ...opts, dir: join(cacheDir, 'cache') })
const ipx = createIPX(
{
storage: ipxFSStorage({
dir: join(cacheDir, 'cache')
}),
httpStorage: ipxHttpStorage({
...opts
})
})

const handleRequest = createIPXPlainServer(ipx)

if (!basePath.endsWith('/')) {
basePath = `${basePath}/`
}
Expand All @@ -73,7 +89,7 @@ export function createIPXHandler ({
headers: plainText
}
}
let domains = (opts as IPXOptions).domains || []

const remoteURLPatterns = remotePatterns || []
const requestEtag = event.headers['if-none-match']
const eventPath = event.path.replace(basePath, '')
Expand Down Expand Up @@ -195,19 +211,21 @@ export function createIPXHandler ({

const res = await handleRequest(
{
url: `/${modifiers}/${cacheKey}`,
headers: event.headers
},
ipx
path: `/${modifiers}/${cacheKey}`,
headers: event.headers,
method: 'GET'
}
)

const headers = Object.fromEntries(res.headers)

const body =
typeof res.body === 'string' ? res.body : res.body.toString('base64')
typeof res.body === 'string' ? res.body : (res.body as Buffer).toString('base64')

res.headers.etag = responseEtag || JSON.parse(etag(body))
delete res.headers['Last-Modified']
headers.etag = responseEtag || JSON.parse(etag(body))
delete headers['Last-Modified']

if (requestEtag && requestEtag === res.headers.etag) {
if (requestEtag && requestEtag === headers.etag) {
return {
statusCode: 304,
message: 'Not Modified'
Expand All @@ -216,14 +234,14 @@ export function createIPXHandler ({

if (responseHeaders) {
for (const [header, value] of Object.entries(responseHeaders)) {
res.headers[header] = value
headers[header] = value
}
}

return {
statusCode: res.statusCode,
message: res.statusMessage,
headers: res.headers,
statusCode: res.status,
statusText: res.statusText,
headers,
isBase64Encoded: typeof res.body !== 'string',
body
}
Expand Down
Loading