mirror of
https://github.com/dillonkearns/elm-pages.git
synced 2024-09-11 09:05:28 +03:00
Use in-memory fs or real fs for http cache depending on whether context is serverless.
This commit is contained in:
parent
9a60119484
commit
d13a397507
@ -139,7 +139,8 @@ async function render(event, context) {
|
||||
mode,
|
||||
event.path,
|
||||
await reqToJson(event, requestTime),
|
||||
addWatcher
|
||||
addWatcher,
|
||||
false
|
||||
);
|
||||
console.log("@@@renderResult", JSON.stringify(renderResult, null, 2));
|
||||
|
||||
|
@ -8,3 +8,9 @@
|
||||
targetPort = 1234
|
||||
autoLaunch = true
|
||||
framework = "#custom"
|
||||
|
||||
[[redirects]]
|
||||
from = "/secret"
|
||||
to = ".netlify/functions/secret"
|
||||
status = 200
|
||||
force = true
|
||||
|
3
examples/pokedex/secret-note.txt
Normal file
3
examples/pokedex/secret-note.txt
Normal file
@ -0,0 +1,3 @@
|
||||
This is a top secret note. You can only access this if the elm-pages server lets you in. It's not a very good secret because this repo is open source, but if it were a private repo then you wouldn't be able to access this data without logging in first!
|
||||
|
||||
The secret is that there isn't actually a secret.
|
132
examples/pokedex/src/Page/Secret.elm
Normal file
132
examples/pokedex/src/Page/Secret.elm
Normal file
@ -0,0 +1,132 @@
|
||||
module Page.Secret exposing (Data, Model, Msg, page)
|
||||
|
||||
import DataSource exposing (DataSource)
|
||||
import DataSource.File
|
||||
import Head
|
||||
import Head.Seo as Seo
|
||||
import Html
|
||||
import Html.Attributes as Attr
|
||||
import Page exposing (Page, PageWithState, StaticPayload)
|
||||
import PageServerResponse exposing (PageServerResponse)
|
||||
import Pages.PageUrl exposing (PageUrl)
|
||||
import Pages.Url
|
||||
import Route
|
||||
import Server.Request as Request
|
||||
import ServerResponse
|
||||
import Shared
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
type alias Msg =
|
||||
Never
|
||||
|
||||
|
||||
type alias RouteParams =
|
||||
{}
|
||||
|
||||
|
||||
page : Page RouteParams Data
|
||||
page =
|
||||
Page.serverRender
|
||||
{ head = head
|
||||
, data = data
|
||||
}
|
||||
|> Page.buildNoState { view = view }
|
||||
|
||||
|
||||
type Data
|
||||
= LoggedIn LoggedInInfo
|
||||
| NotLoggedIn
|
||||
|
||||
|
||||
type alias LoggedInInfo =
|
||||
{ username : String
|
||||
, secretNote : String
|
||||
}
|
||||
|
||||
|
||||
data : RouteParams -> Request.Handler (PageServerResponse Data)
|
||||
data routeParams =
|
||||
Request.oneOfHandler
|
||||
[ Request.expectCookie "username"
|
||||
|> Request.thenRespond
|
||||
(\username ->
|
||||
username
|
||||
|> LoggedInInfo
|
||||
|> DataSource.succeed
|
||||
|> DataSource.andMap (DataSource.File.rawFile "./secret-note.txt")
|
||||
|> DataSource.map LoggedIn
|
||||
|> DataSource.map PageServerResponse.RenderPage
|
||||
)
|
||||
, Request.succeed ()
|
||||
|> Request.thenRespond
|
||||
(\() ->
|
||||
NotLoggedIn
|
||||
|> DataSource.succeed
|
||||
|> DataSource.map PageServerResponse.RenderPage
|
||||
--"/login"
|
||||
-- |> ServerResponse.temporaryRedirect
|
||||
-- --|> ServerResponse.withStatusCode 404
|
||||
-- |> PageServerResponse.ServerResponse
|
||||
-- |> DataSource.succeed
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
head :
|
||||
StaticPayload Data RouteParams
|
||||
-> List Head.Tag
|
||||
head static =
|
||||
Seo.summary
|
||||
{ canonicalUrlOverride = Nothing
|
||||
, siteName = "elm-pages"
|
||||
, image =
|
||||
{ url = Pages.Url.external "TODO"
|
||||
, alt = "elm-pages logo"
|
||||
, dimensions = Nothing
|
||||
, mimeType = Nothing
|
||||
}
|
||||
, description = "TODO"
|
||||
, locale = Nothing
|
||||
, title = "TODO title" -- metadata.title -- TODO
|
||||
}
|
||||
|> Seo.website
|
||||
|
||||
|
||||
view :
|
||||
Maybe PageUrl
|
||||
-> Shared.Model
|
||||
-> StaticPayload Data RouteParams
|
||||
-> View Msg
|
||||
view maybeUrl sharedModel static =
|
||||
case static.data of
|
||||
LoggedIn loggedInInfo ->
|
||||
{ title = "Secret"
|
||||
, body =
|
||||
[ Html.main_ [ Attr.style "max-width" "800px" ]
|
||||
[ Html.h1 [] [ Html.text "This is a secret page" ]
|
||||
, Html.p []
|
||||
[ Html.text <| "Welcome, " ++ loggedInInfo.username ++ "!"
|
||||
]
|
||||
, Html.p []
|
||||
[ Html.text loggedInInfo.secretNote
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
NotLoggedIn ->
|
||||
{ title = "Secret"
|
||||
, body =
|
||||
[ Html.main_ [ Attr.style "max-width" "800px" ]
|
||||
[ Html.h1 [] [ Html.text "You're not logged in" ]
|
||||
, Route.link Route.Login
|
||||
[]
|
||||
[ Html.text <| "Login" ]
|
||||
]
|
||||
]
|
||||
}
|
@ -20,7 +20,8 @@ async function run({ mode, pathname, serverRequest }) {
|
||||
if (mode === "dev-server" && patterns.size > 0) {
|
||||
parentPort.postMessage({ tag: "watch", data: [...patterns] });
|
||||
}
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
if (mode === "dev-server") {
|
||||
|
@ -3,12 +3,10 @@
|
||||
const path = require("path");
|
||||
const matter = require("gray-matter");
|
||||
const globby = require("globby");
|
||||
// const fsPromises = require("fs").promises;
|
||||
const fsPromises = require("memfs").promises;
|
||||
const fsPromises = require("fs").promises;
|
||||
const preRenderHtml = require("./pre-render-html.js");
|
||||
const { lookupOrPerform } = require("./request-cache.js");
|
||||
const kleur = require("kleur");
|
||||
const { fs, Volume, vol } = require("memfs");
|
||||
kleur.enabled = true;
|
||||
|
||||
process.on("unhandledRejection", (error) => {
|
||||
@ -26,6 +24,7 @@ module.exports =
|
||||
* @param {string} path
|
||||
* @param {{ method: string; hostname: string; query: Record<string, string | undefined>; headers: Record<string, string>; host: string; pathname: string; port: number | null; protocol: string; rawUrl: string; }} request
|
||||
* @param {(pattern: string) => void} addDataSourceWatcher
|
||||
* @param {boolean} hasFsAccess
|
||||
* @returns
|
||||
*/
|
||||
async function run(
|
||||
@ -34,9 +33,14 @@ module.exports =
|
||||
mode,
|
||||
path,
|
||||
request,
|
||||
addDataSourceWatcher
|
||||
addDataSourceWatcher,
|
||||
hasFsAccess
|
||||
) {
|
||||
vol.reset();
|
||||
console.log({ hasFsAccess });
|
||||
const { fs, resetInMemoryFs } = require("./request-cache-fs.js")(
|
||||
hasFsAccess
|
||||
);
|
||||
resetInMemoryFs();
|
||||
foundErrors = false;
|
||||
pendingDataSourceResponses = [];
|
||||
pendingDataSourceCount = 0;
|
||||
@ -49,7 +53,9 @@ module.exports =
|
||||
mode,
|
||||
path,
|
||||
request,
|
||||
addDataSourceWatcher
|
||||
addDataSourceWatcher,
|
||||
fs,
|
||||
hasFsAccess
|
||||
);
|
||||
return result;
|
||||
};
|
||||
@ -69,7 +75,9 @@ function runElmApp(
|
||||
mode,
|
||||
pagePath,
|
||||
request,
|
||||
addDataSourceWatcher
|
||||
addDataSourceWatcher,
|
||||
fs,
|
||||
hasFsAccess
|
||||
) {
|
||||
const isDevServer = mode !== "build";
|
||||
let patternsToWatch = new Set();
|
||||
@ -150,7 +158,7 @@ function runElmApp(
|
||||
}
|
||||
} else if (fromElm.tag === "DoHttp") {
|
||||
const requestToPerform = fromElm.args[0];
|
||||
runHttpJob(app, mode, requestToPerform);
|
||||
runHttpJob(app, mode, requestToPerform, fs, hasFsAccess);
|
||||
} else if (fromElm.tag === "Glob") {
|
||||
const globPattern = fromElm.args[0];
|
||||
patternsToWatch.add(globPattern);
|
||||
@ -218,9 +226,7 @@ function jsonOrNull(string) {
|
||||
async function runJob(app, filePath) {
|
||||
pendingDataSourceCount += 1;
|
||||
try {
|
||||
const fileContents =
|
||||
// await fsPromises.readFile(path.join(process.cwd(), filePath))
|
||||
(await fsPromises.readFile(filePath)).toString();
|
||||
const fileContents = (await fsPromises.readFile(filePath)).toString();
|
||||
const parsedFile = matter(fileContents);
|
||||
|
||||
pendingDataSourceResponses.push({
|
||||
@ -246,6 +252,8 @@ async function runJob(app, filePath) {
|
||||
}),
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(fsPromises);
|
||||
console.error("222@@@", e);
|
||||
sendError(app, {
|
||||
title: "Error reading file",
|
||||
message: `A DataSource.File read failed because I couldn't find this file: ${kleur.yellow(
|
||||
@ -258,18 +266,19 @@ async function runJob(app, filePath) {
|
||||
}
|
||||
}
|
||||
|
||||
async function runHttpJob(app, mode, requestToPerform) {
|
||||
async function runHttpJob(app, mode, requestToPerform, fs, hasFsAccess) {
|
||||
pendingDataSourceCount += 1;
|
||||
try {
|
||||
const responseFilePath = await lookupOrPerform(
|
||||
mode,
|
||||
requestToPerform.unmasked
|
||||
requestToPerform.unmasked,
|
||||
hasFsAccess
|
||||
);
|
||||
|
||||
pendingDataSourceResponses.push({
|
||||
request: requestToPerform,
|
||||
response: (
|
||||
await fsPromises.readFile(responseFilePath, "utf8")
|
||||
await fs.promises.readFile(responseFilePath, "utf8")
|
||||
).toString(),
|
||||
});
|
||||
} catch (error) {
|
||||
@ -319,51 +328,6 @@ function flushQueue(app) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filePath
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function readFileTask(app, filePath) {
|
||||
// console.log(`Read file ${filePath}`);
|
||||
try {
|
||||
const fileContents = (
|
||||
await fsPromises.readFile(path.join(process.cwd(), filePath))
|
||||
).toString();
|
||||
// console.log(`DONE reading file ${filePath}`);
|
||||
const parsedFile = matter(fileContents);
|
||||
|
||||
return {
|
||||
request: {
|
||||
masked: {
|
||||
url: `file://${filePath}`,
|
||||
method: "GET",
|
||||
headers: [],
|
||||
body: { tag: "EmptyBody", args: [] },
|
||||
},
|
||||
unmasked: {
|
||||
url: `file://${filePath}`,
|
||||
method: "GET",
|
||||
headers: [],
|
||||
body: { tag: "EmptyBody", args: [] },
|
||||
},
|
||||
},
|
||||
response: JSON.stringify({
|
||||
parsedFrontmatter: parsedFile.data,
|
||||
withoutFrontmatter: parsedFile.content,
|
||||
rawFile: fileContents,
|
||||
jsonFile: jsonOrNull(fileContents),
|
||||
}),
|
||||
};
|
||||
} catch (e) {
|
||||
sendError(app, {
|
||||
title: "Error reading file",
|
||||
message: `A DataSource.File read failed because I couldn't find this file: ${kleur.yellow(
|
||||
filePath
|
||||
)}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} globPattern
|
||||
* @returns {Promise<Object>}
|
||||
|
13
generator/src/request-cache-fs.js
Normal file
13
generator/src/request-cache-fs.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = function (/** @type {boolean} */ hasFsAccess) {
|
||||
if (hasFsAccess) {
|
||||
return {
|
||||
fs: require("fs"),
|
||||
resetInMemoryFs: () => {},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
fs: require("memfs").fs,
|
||||
resetInMemoryFs: require("memfs").vol.reset,
|
||||
};
|
||||
}
|
||||
};
|
@ -1,9 +1,7 @@
|
||||
const path = require("path");
|
||||
const undici = require("undici");
|
||||
// const fs = require("fs");
|
||||
const objectHash = require("object-hash");
|
||||
const kleur = require("kleur");
|
||||
const fs = require("memfs");
|
||||
|
||||
/**
|
||||
* To cache HTTP requests on disk with quick lookup and insertion, we store the hashed request.
|
||||
@ -18,14 +16,17 @@ function requestToString(request) {
|
||||
/**
|
||||
* @param {Object} request
|
||||
*/
|
||||
function fullPath(request) {
|
||||
// return path.join(
|
||||
// process.cwd(),
|
||||
// ".elm-pages",
|
||||
// "http-response-cache",
|
||||
// requestToString(request)
|
||||
// );
|
||||
return path.join("/", requestToString(request));
|
||||
function fullPath(request, hasFsAccess) {
|
||||
if (hasFsAccess) {
|
||||
return path.join(
|
||||
process.cwd(),
|
||||
".elm-pages",
|
||||
"http-response-cache",
|
||||
requestToString(request)
|
||||
);
|
||||
} else {
|
||||
return path.join("/", requestToString(request));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,10 +34,12 @@ function fullPath(request) {
|
||||
* @param {{url: string; headers: {[x: string]: string}; method: string; body: Body } } rawRequest
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
function lookupOrPerform(mode, rawRequest) {
|
||||
function lookupOrPerform(mode, rawRequest, hasFsAccess) {
|
||||
console.log({ hasFsAccess });
|
||||
const { fs } = require("./request-cache-fs.js")(hasFsAccess);
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const request = toRequest(rawRequest);
|
||||
const responsePath = fullPath(request);
|
||||
const responsePath = fullPath(request, hasFsAccess);
|
||||
|
||||
if (fs.existsSync(responsePath)) {
|
||||
// console.log("Skipping request, found file.");
|
||||
|
Loading…
Reference in New Issue
Block a user