mirror of
https://github.com/LukeHagar/unicorn-utterances.git
synced 2025-12-07 12:57:45 +00:00
chore: migrate unicorn profile pic mapping to Rollup plugin
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
||||||
|
|
||||||
import exportedIndex from "../searchIndex";
|
import exportedIndex from "../searchIndex";
|
||||||
|
import unicornProfilePicMap from "../public/unicorn-profile-pic-map";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
|
import type { PostInfo } from "../src/types/PostInfo";
|
||||||
|
|
||||||
const myIndex = Fuse.parseIndex(exportedIndex.index);
|
const myIndex = Fuse.parseIndex(exportedIndex.index);
|
||||||
|
|
||||||
@@ -18,11 +20,19 @@ const fuse = new Fuse(
|
|||||||
myIndex
|
myIndex
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const unicornProfilePicObj = {};
|
||||||
|
for (const picMapItem of unicornProfilePicMap) {
|
||||||
|
unicornProfilePicObj[picMapItem.id] = picMapItem;
|
||||||
|
}
|
||||||
|
|
||||||
export default async (req: VercelRequest, res: VercelResponse) => {
|
export default async (req: VercelRequest, res: VercelResponse) => {
|
||||||
// TODO: `pickdeep` only required fields
|
// TODO: `pickdeep` only required fields
|
||||||
const searchStr = req?.query?.query as string;
|
const searchStr = req?.query?.query as string;
|
||||||
if (!searchStr) return [];
|
if (!searchStr) return [];
|
||||||
if (Array.isArray(searchStr)) return [];
|
if (Array.isArray(searchStr)) return [];
|
||||||
const items = fuse.search(searchStr).map((item) => item.item);
|
const posts = fuse.search(searchStr).map((item) => item.item as PostInfo);
|
||||||
res.send(items);
|
const unicornProfilePicMap = posts.flatMap((post) =>
|
||||||
|
post.authorsMeta.map((authorMeta) => unicornProfilePicObj[authorMeta.id])
|
||||||
|
);
|
||||||
|
res.send({ posts, totalPosts: posts.length, unicornProfilePicMap });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { rehypeExcerpt } from "./src/utils/markdown/rehype-excerpt";
|
|||||||
import { rehypeUnicornPopulatePost } from "./src/utils/markdown/rehype-unicorn-populate-post";
|
import { rehypeUnicornPopulatePost } from "./src/utils/markdown/rehype-unicorn-populate-post";
|
||||||
import { rehypeWordCount } from "./src/utils/markdown/rehype-word-count";
|
import { rehypeWordCount } from "./src/utils/markdown/rehype-word-count";
|
||||||
import { rehypeUnicornGetSuggestedPosts } from "./src/utils/markdown/rehype-unicorn-get-suggested-posts";
|
import { rehypeUnicornGetSuggestedPosts } from "./src/utils/markdown/rehype-unicorn-get-suggested-posts";
|
||||||
|
import generateUnicornProfilePicMap from "./src/utils/rollup/generate-unicorn-profile-pic-map";
|
||||||
import copy from "rollup-plugin-copy";
|
import copy from "rollup-plugin-copy";
|
||||||
import preact from "@astrojs/preact";
|
import preact from "@astrojs/preact";
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ import behead from "remark-behead";
|
|||||||
import rehypeRaw from "rehype-raw";
|
import rehypeRaw from "rehype-raw";
|
||||||
|
|
||||||
import image from "@astrojs/image";
|
import image from "@astrojs/image";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
integrations: [image(), preact()],
|
integrations: [image(), preact()],
|
||||||
@@ -46,6 +48,12 @@ export default defineConfig({
|
|||||||
}),
|
}),
|
||||||
enforce: "pre",
|
enforce: "pre",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
...generateUnicornProfilePicMap({
|
||||||
|
output: path.resolve("./public/unicorn-profile-pic-map.ts"),
|
||||||
|
}),
|
||||||
|
enforce: "pre",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
markdown: {
|
markdown: {
|
||||||
|
|||||||
1
public/.gitignore
vendored
1
public/.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
content/**
|
content/**
|
||||||
|
unicorn-profile-pic-map.ts
|
||||||
@@ -3,12 +3,6 @@ import { ProfilePictureMap } from "utils/get-unicorn-profile-pic-map";
|
|||||||
import styles from "./filter-search-bar.module.scss";
|
import styles from "./filter-search-bar.module.scss";
|
||||||
import SearchField from "./search-field/search-field.astro";
|
import SearchField from "./search-field/search-field.astro";
|
||||||
// import FilterListbox from "./filter-listbox/filter-listbox.astro";
|
// import FilterListbox from "./filter-listbox/filter-listbox.astro";
|
||||||
|
|
||||||
export interface FilterSearchBarProps {
|
|
||||||
unicornProfilePicMap: ProfilePictureMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { unicornProfilePicMap } = Astro.props as FilterSearchBarProps;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class={styles.iconContainer}>
|
<div class={styles.iconContainer}>
|
||||||
@@ -18,14 +12,11 @@ const { unicornProfilePicMap } = Astro.props as FilterSearchBarProps;
|
|||||||
</div>
|
</div>
|
||||||
<!-- <FilterListbox class={styles.filterField} /> -->
|
<!-- <FilterListbox class={styles.filterField} /> -->
|
||||||
</div>
|
</div>
|
||||||
<script define:vars={{ unicornProfilePicMap }}>
|
|
||||||
window.unicornProfilePicMap =
|
|
||||||
window.unicornProfilePicMap || unicornProfilePicMap;
|
|
||||||
</script>
|
|
||||||
<script>
|
<script>
|
||||||
import { render, createElement, Fragment } from "preact";
|
import { render, createElement, Fragment } from "preact";
|
||||||
import { PostInfo } from "types/PostInfo";
|
import { PostInfo } from "types/PostInfo";
|
||||||
import { debounce } from "utils/debounce";
|
import { debounce } from "utils/debounce";
|
||||||
|
import { ProfilePictureMap } from "utils/get-unicorn-profile-pic-map";
|
||||||
import { PostCard } from "../../components/post-card/post-card";
|
import { PostCard } from "../../components/post-card/post-card";
|
||||||
|
|
||||||
const searchInput: HTMLElement = document.querySelector("#search-input");
|
const searchInput: HTMLElement = document.querySelector("#search-input");
|
||||||
@@ -46,7 +37,6 @@ const { unicornProfilePicMap } = Astro.props as FilterSearchBarProps;
|
|||||||
let abortController: AbortController | undefined;
|
let abortController: AbortController | undefined;
|
||||||
|
|
||||||
function doSearch(val) {
|
function doSearch(val) {
|
||||||
const unicornProfilePicMap = (window as any).unicornProfilePicMap || [];
|
|
||||||
if (abortController) {
|
if (abortController) {
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
abortController = undefined;
|
abortController = undefined;
|
||||||
@@ -54,23 +44,28 @@ const { unicornProfilePicMap } = Astro.props as FilterSearchBarProps;
|
|||||||
abortController = new AbortController();
|
abortController = new AbortController();
|
||||||
fetch(`/api/search?query=${val}`, { signal: abortController.signal })
|
fetch(`/api/search?query=${val}`, { signal: abortController.signal })
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((serverVal: PostInfo[]) => {
|
.then(
|
||||||
// TODO: Debounce to avoid server kill
|
(serverVal: {
|
||||||
const FilledPostCard = createElement(
|
posts: PostInfo[];
|
||||||
Fragment,
|
totalPosts: number;
|
||||||
{},
|
unicornProfilePicMap: ProfilePictureMap;
|
||||||
serverVal.map((post) =>
|
}) => {
|
||||||
createElement(PostCard, {
|
const FilledPostCard = createElement(
|
||||||
unicornProfilePicMap,
|
Fragment,
|
||||||
post,
|
{},
|
||||||
})
|
serverVal.posts.map((post) =>
|
||||||
)
|
createElement(PostCard, {
|
||||||
);
|
unicornProfilePicMap: serverVal.unicornProfilePicMap,
|
||||||
render(FilledPostCard, searchPostList);
|
post,
|
||||||
loadingEl && loadingEl.remove();
|
})
|
||||||
loadingEl = undefined;
|
)
|
||||||
abortController = undefined;
|
);
|
||||||
});
|
render(FilledPostCard, searchPostList);
|
||||||
|
loadingEl && loadingEl.remove();
|
||||||
|
loadingEl = undefined;
|
||||||
|
abortController = undefined;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const debounceDoSearch = debounce(doSearch, 1000, false);
|
const debounceDoSearch = debounce(doSearch, 1000, false);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const unicornProfilePicMap = await getUnicornProfilePicMap();
|
|||||||
> -->
|
> -->
|
||||||
<PostListHeader siteDescription={siteMetadata.description} />
|
<PostListHeader siteDescription={siteMetadata.description} />
|
||||||
<main>
|
<main>
|
||||||
<FilterSearchBar unicornProfilePicMap={unicornProfilePicMap} />
|
<FilterSearchBar />
|
||||||
<PostList
|
<PostList
|
||||||
listAriaLabel="List of posts"
|
listAriaLabel="List of posts"
|
||||||
postsToDisplay={posts}
|
postsToDisplay={posts}
|
||||||
|
|||||||
@@ -1,37 +1,6 @@
|
|||||||
import { getPicture } from "@astrojs/image";
|
import unicornProfilePicMap from "../../public/unicorn-profile-pic-map";
|
||||||
import { unicorns } from "utils/data";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: THIS IS A MAJOR OPTIMIZATION (15KB on every page load), READ THIS
|
|
||||||
*
|
|
||||||
* Right now, we're storing an inlined script of `getPicture` into the HTML of the page because Astro cannot
|
|
||||||
* use both `defer` external scripts, and `declare:vars`. This means that 15KB of this profile pic mapping is externalized currently.
|
|
||||||
*
|
|
||||||
* To fix this, instead of generating this at Astro build time, we should do this during an earlier compile step that simply runs
|
|
||||||
* this `getPicture` code and then outputs to a JS file, then the Astro build will read from this JS file
|
|
||||||
*/
|
|
||||||
export const getUnicornProfilePicMap = async () => {
|
export const getUnicornProfilePicMap = async () => {
|
||||||
/**
|
|
||||||
* We do it this was so that we only generate the list of images once
|
|
||||||
*
|
|
||||||
* This allows us to share the cached image format between multiple different pages
|
|
||||||
*/
|
|
||||||
globalThis.unicornProfilePicMap =
|
|
||||||
globalThis.unicornProfilePicMap ||
|
|
||||||
(await Promise.all(
|
|
||||||
unicorns.map(async (unicorn) => ({
|
|
||||||
...(await getPicture({
|
|
||||||
src: unicorn.profileImgMeta.relativeServerPath,
|
|
||||||
formats: ["webp", "png"],
|
|
||||||
widths: [72, 48],
|
|
||||||
aspectRatio: 1,
|
|
||||||
})),
|
|
||||||
id: unicorn.id,
|
|
||||||
}))
|
|
||||||
));
|
|
||||||
const unicornProfilePicMap: Array<
|
|
||||||
Awaited<ReturnType<typeof getPicture>> & { id: string }
|
|
||||||
> = globalThis.unicornProfilePicMap;
|
|
||||||
return unicornProfilePicMap;
|
return unicornProfilePicMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
61
src/utils/rollup/generate-unicorn-profile-pic-map.ts
Normal file
61
src/utils/rollup/generate-unicorn-profile-pic-map.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import { unicorns } from "../data";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* They need to be the same `getImage` with the same `globalThis` instance, thanks to the "hack" workaround.
|
||||||
|
*/
|
||||||
|
import { getPicture } from "../../../node_modules/@astrojs/image";
|
||||||
|
import sharp_service from "../../../node_modules/@astrojs/image/dist/loaders/sharp.js";
|
||||||
|
|
||||||
|
interface GenerateUnicornProfilePicMapProps {
|
||||||
|
output: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateUnicornProfilePicMap(
|
||||||
|
options: GenerateUnicornProfilePicMapProps
|
||||||
|
) {
|
||||||
|
const { output } = options;
|
||||||
|
let copied = false;
|
||||||
|
return {
|
||||||
|
name: "generateUnicornProfilePicMap",
|
||||||
|
options: async () => {
|
||||||
|
// Only run once per dev HMR instance
|
||||||
|
if (copied) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HACK: This is a hack that heavily relies on `getImage`'s internals :(
|
||||||
|
globalThis.astroImage = {
|
||||||
|
...(globalThis.astroImage || {}),
|
||||||
|
loader: sharp_service ?? globalThis.astroImage?.loader,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We do it this was so that we only generate the list of images once
|
||||||
|
*
|
||||||
|
* This allows us to share the cached image format between multiple different pages
|
||||||
|
*/
|
||||||
|
const unicornProfilePicMap = await Promise.all(
|
||||||
|
unicorns.map(async (unicorn) => ({
|
||||||
|
...(await getPicture({
|
||||||
|
src: unicorn.profileImgMeta.relativeServerPath,
|
||||||
|
formats: ["webp", "png"],
|
||||||
|
widths: [72, 48],
|
||||||
|
aspectRatio: 1,
|
||||||
|
})),
|
||||||
|
id: unicorn.id,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const js = `const data = ${JSON.stringify(unicornProfilePicMap)};
|
||||||
|
|
||||||
|
export default data;`;
|
||||||
|
|
||||||
|
await fs.promises.writeFile(output, js);
|
||||||
|
|
||||||
|
copied = true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default generateUnicornProfilePicMap;
|
||||||
Reference in New Issue
Block a user