-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
fix: add missing fs method rewrites to handle fetchRemoteFile in dsg/ssr engine #38822
Changes from all commits
8454f94
e9d20a9
c08ed55
9d8b979
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ import React from "react" | |
import { GatsbyImage } from "gatsby-plugin-image" | ||
import Layout from "../../../components/layout" | ||
|
||
const RemoteFile = ({ data }) => { | ||
const RemoteFile = ({ data, serverData }) => { | ||
return ( | ||
<Layout> | ||
{data.allMyRemoteImage.nodes.map(node => { | ||
|
@@ -54,6 +54,7 @@ const RemoteFile = ({ data }) => { | |
</div> | ||
) | ||
})} | ||
<pre>{JSON.stringify(serverData, null, 2)}</pre> | ||
</Layout> | ||
) | ||
} | ||
|
@@ -65,8 +66,7 @@ export const pageQuery = graphql` | |
id | ||
url | ||
filename | ||
# FILE_CDN is not supported in SSR/DSG yet | ||
# publicUrl | ||
publicUrl | ||
resize(width: 100) { | ||
height | ||
width | ||
|
@@ -75,23 +75,17 @@ export const pageQuery = graphql` | |
fixed: gatsbyImage( | ||
layout: FIXED | ||
width: 100 | ||
# only NONE placeholder is supported in SSR/DSG | ||
# placeholder: DOMINANT_COLOR | ||
placeholder: NONE | ||
placeholder: DOMINANT_COLOR | ||
) | ||
constrained: gatsbyImage( | ||
layout: CONSTRAINED | ||
width: 300 | ||
# only NONE placeholder is supported in SSR/DSG | ||
# placeholder: DOMINANT_COLOR | ||
placeholder: NONE | ||
placeholder: BLURRED | ||
) | ||
constrained_traced: gatsbyImage( | ||
layout: CONSTRAINED | ||
width: 300 | ||
# only NONE placeholder is supported in SSR/DSG | ||
# placeholder: DOMINANT_COLOR | ||
placeholder: NONE | ||
placeholder: TRACED_SVG | ||
) | ||
full: gatsbyImage(layout: FULL_WIDTH, width: 500, placeholder: NONE) | ||
} | ||
|
@@ -109,3 +103,11 @@ export const pageQuery = graphql` | |
` | ||
|
||
export default RemoteFile | ||
|
||
export function getServerData() { | ||
return { | ||
props: { | ||
ssr: true, | ||
}, | ||
} | ||
} | ||
Comment on lines
+107
to
+113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This page wasn't actually SSR... Making it SSR uncovered some other problems that this PR is also fixing |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import crypto from "crypto" | ||
import { createHash } from "crypto" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This caused some problems with
in lambdas, so easiest way was just to avoid having babel adding that |
||
import { basename } from "path" | ||
|
||
import type { FileCdnUrlGeneratorFn, FileCdnSourceImage } from "gatsby" | ||
|
@@ -21,8 +21,7 @@ export const generateFileUrl: FileCdnUrlGeneratorFn = function generateFileUrl( | |
baseURL.searchParams.append(`cd`, source.internal.contentDigest) | ||
} else { | ||
baseURL = new URL( | ||
`${placeholderOrigin}${pathPrefix}/_gatsby/file/${crypto | ||
.createHash(`md5`) | ||
`${placeholderOrigin}${pathPrefix}/_gatsby/file/${createHash(`md5`) | ||
.update(source.url) | ||
.digest(`hex`)}/${basename(source.filename)}` | ||
) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,12 +43,34 @@ export class GraphQLEngine { | |
this.getRunner() | ||
} | ||
|
||
private setupPathPrefix(pathPrefix: string): void { | ||
if (pathPrefix) { | ||
store.dispatch({ | ||
type: `SET_PROGRAM`, | ||
payload: { | ||
prefixPaths: true, | ||
}, | ||
}) | ||
|
||
store.dispatch({ | ||
type: `SET_SITE_CONFIG`, | ||
payload: { | ||
...store.getState().config, | ||
pathPrefix, | ||
}, | ||
}) | ||
} | ||
} | ||
|
||
Comment on lines
+46
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needed for pathPrefix to be actually honored when generating FILE_CDN urls |
||
private async _doGetRunner(): Promise<GraphQLRunner> { | ||
await tracerReadyPromise | ||
|
||
const wrapActivity = reporter.phantomActivity(`Initializing GraphQL Engine`) | ||
wrapActivity.start() | ||
try { | ||
// @ts-ignore PATH_PREFIX is being "inlined" by bundler | ||
this.setupPathPrefix(PATH_PREFIX) | ||
|
||
// @ts-ignore SCHEMA_SNAPSHOT is being "inlined" by bundler | ||
store.dispatch(actions.createTypes(SCHEMA_SNAPSHOT)) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ import { promisify } from "util" | |
|
||
import type { IGatsbyPage } from "../../internal" | ||
import type { ISSRData } from "./entry" | ||
import { link } from "linkfs" | ||
import { link, rewritableMethods as linkRewritableMethods } from "linkfs" | ||
|
||
const cdnDatastore = `%CDN_DATASTORE_PATH%` | ||
const PATH_PREFIX = `%PATH_PREFIX%` | ||
|
@@ -30,7 +30,7 @@ function setupFsWrapper(): string { | |
global.__GATSBY.root = TEMP_DIR | ||
|
||
// TODO: don't hardcode this | ||
const cacheDir = `/var/task/.cache` | ||
const cacheDir = `${process.cwd()}/.cache` | ||
|
||
// we need to rewrite fs | ||
const rewrites = [ | ||
|
@@ -47,25 +47,132 @@ function setupFsWrapper(): string { | |
to: TEMP_CACHE_DIR, | ||
rewrites, | ||
}) | ||
|
||
// copied from https://github.com/streamich/linkfs/blob/master/src/index.ts#L126-L142 | ||
function mapPathUsingRewrites(fsPath: fs.PathLike): string { | ||
let filename = path.resolve(String(fsPath)) | ||
for (const [from, to] of rewrites) { | ||
if (filename.indexOf(from) === 0) { | ||
const rootRegex = /(?:^[a-zA-Z]:\\$)|(?:^\/$)/ // C:\ vs / | ||
const isRoot = from.match(rootRegex) | ||
const baseRegex = `^(` + from.replace(/\\/g, `\\\\`) + `)` | ||
|
||
if (isRoot) { | ||
const regex = new RegExp(baseRegex) | ||
filename = filename.replace(regex, () => to + path.sep) | ||
} else { | ||
const regex = new RegExp(baseRegex + `(\\\\|/|$)`) | ||
filename = filename.replace(regex, (_match, _p1, p2) => to + p2) | ||
} | ||
} | ||
} | ||
return filename | ||
} | ||
|
||
function applyRename< | ||
T = typeof import("fs") | typeof import("fs").promises | ||
>(fsToRewrite: T, lfs: T, method: "rename" | "renameSync"): void { | ||
const original = fsToRewrite[method] | ||
if (original) { | ||
// @ts-ignore - complains about __promisify__ which doesn't actually exist in runtime | ||
lfs[method] = ( | ||
...args: Parameters<typeof import("fs")["rename" | "renameSync"]> | ||
): ReturnType<typeof import("fs")["rename" | "renameSync"]> => { | ||
args[0] = mapPathUsingRewrites(args[0]) | ||
args[1] = mapPathUsingRewrites(args[1]) | ||
// @ts-ignore - can't decide which signature this is, but we just preserve the original | ||
// signature here and mostly care about first 2 arguments being PathLike | ||
return original.apply(fsToRewrite, args) | ||
} | ||
} | ||
} | ||
|
||
// linkfs doesn't handle following methods, so we need to add them manually | ||
linkRewritableMethods.push(`createWriteStream`, `createReadStream`, `rm`) | ||
|
||
function createLinkedFS< | ||
T = typeof import("fs") | typeof import("fs").promises | ||
>(fsToRewrite: T): T { | ||
const linkedFS = link(fsToRewrite, rewrites) as T | ||
|
||
// linkfs doesn't handle `to` argument in `rename` and `renameSync` methods | ||
applyRename(fsToRewrite, linkedFS, `rename`) | ||
applyRename(fsToRewrite, linkedFS, `renameSync`) | ||
|
||
return linkedFS | ||
} | ||
|
||
// Alias the cache dir paths to the temp dir | ||
const lfs = link(fs, rewrites) as typeof import("fs") | ||
const lfs = createLinkedFS(fs) | ||
|
||
// linkfs doesn't pass across the `native` prop, which graceful-fs needs | ||
for (const key in lfs) { | ||
if (Object.hasOwnProperty.call(fs[key], `native`)) { | ||
lfs[key].native = fs[key].native | ||
} | ||
} | ||
|
||
const dbPath = path.join(TEMP_CACHE_DIR, `data`, `datastore`) | ||
|
||
// 'promises' is not initially linked within the 'linkfs' | ||
// package, and is needed by underlying Gatsby code (the | ||
// @graphql-tools/code-file-loader) | ||
lfs.promises = link(fs.promises, rewrites) | ||
lfs.promises = createLinkedFS(fs.promises) | ||
|
||
const originalWritesStream = fs.WriteStream | ||
function LinkedWriteStream( | ||
this: fs.WriteStream, | ||
...args: Parameters<(typeof fs)["createWriteStream"]> | ||
): fs.WriteStream { | ||
args[0] = mapPathUsingRewrites(args[0]) | ||
args[1] = | ||
typeof args[1] === `string` | ||
? { | ||
flags: args[1], | ||
// @ts-ignore there is `fs` property in options doh (https://nodejs.org/api/fs.html#fscreatewritestreampath-options) | ||
fs: lfs, | ||
} | ||
: { | ||
...(args[1] || {}), | ||
// @ts-ignore there is `fs` property in options doh (https://nodejs.org/api/fs.html#fscreatewritestreampath-options) | ||
fs: lfs, | ||
} | ||
|
||
// @ts-ignore TS doesn't like extending prototype "classes" | ||
return originalWritesStream.apply(this, args) | ||
} | ||
LinkedWriteStream.prototype = Object.create(originalWritesStream.prototype) | ||
// @ts-ignore TS doesn't like extending prototype "classes" | ||
lfs.WriteStream = LinkedWriteStream | ||
|
||
const originalReadStream = fs.ReadStream | ||
function LinkedReadStream( | ||
this: fs.ReadStream, | ||
...args: Parameters<(typeof fs)["createReadStream"]> | ||
): fs.ReadStream { | ||
args[0] = mapPathUsingRewrites(args[0]) | ||
args[1] = | ||
typeof args[1] === `string` | ||
? { | ||
flags: args[1], | ||
// @ts-ignore there is `fs` property in options doh (https://nodejs.org/api/fs.html#fscreatewritestreampath-options) | ||
fs: lfs, | ||
} | ||
: { | ||
...(args[1] || {}), | ||
// @ts-ignore there is `fs` property in options doh (https://nodejs.org/api/fs.html#fscreatewritestreampath-options) | ||
fs: lfs, | ||
} | ||
|
||
// @ts-ignore TS doesn't like extending prototype "classes" | ||
return originalReadStream.apply(this, args) | ||
} | ||
LinkedReadStream.prototype = Object.create(originalReadStream.prototype) | ||
// @ts-ignore TS doesn't like extending prototype "classes" | ||
lfs.ReadStream = LinkedReadStream | ||
|
||
const dbPath = path.join(TEMP_CACHE_DIR, `data`, `datastore`) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like there's some repetition here with extending fs.WriteStream and fs.ReadStream, you could consider extracting/reusing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it's basically the same thing twice (just once for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it, I'm ok leaving as is. |
||
// Gatsby uses this instead of fs if present | ||
// eslint-disable-next-line no-underscore-dangle | ||
// @ts-ignore __promisify__ stuff | ||
global._fsWrapper = lfs | ||
|
||
if (!cdnDatastore) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test was disabled because of problems that this PR is fixing. In context of this test those problems weren't fatal, however it meant that image placeholders were just not working.
The fix in this PR also solves other problems than just placeholders, but this was existing test that already we had