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 exportedIndex from "../searchIndex";
|
||||
import unicornProfilePicMap from "../public/unicorn-profile-pic-map";
|
||||
import Fuse from "fuse.js";
|
||||
import type { PostInfo } from "../src/types/PostInfo";
|
||||
|
||||
const myIndex = Fuse.parseIndex(exportedIndex.index);
|
||||
|
||||
@@ -18,11 +20,19 @@ const fuse = new Fuse(
|
||||
myIndex
|
||||
);
|
||||
|
||||
const unicornProfilePicObj = {};
|
||||
for (const picMapItem of unicornProfilePicMap) {
|
||||
unicornProfilePicObj[picMapItem.id] = picMapItem;
|
||||
}
|
||||
|
||||
export default async (req: VercelRequest, res: VercelResponse) => {
|
||||
// TODO: `pickdeep` only required fields
|
||||
const searchStr = req?.query?.query as string;
|
||||
if (!searchStr) return [];
|
||||
if (Array.isArray(searchStr)) return [];
|
||||
const items = fuse.search(searchStr).map((item) => item.item);
|
||||
res.send(items);
|
||||
const posts = fuse.search(searchStr).map((item) => item.item as PostInfo);
|
||||
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 { rehypeWordCount } from "./src/utils/markdown/rehype-word-count";
|
||||
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 preact from "@astrojs/preact";
|
||||
|
||||
@@ -25,6 +26,7 @@ import behead from "remark-behead";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
|
||||
import image from "@astrojs/image";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
integrations: [image(), preact()],
|
||||
@@ -46,6 +48,12 @@ export default defineConfig({
|
||||
}),
|
||||
enforce: "pre",
|
||||
},
|
||||
{
|
||||
...generateUnicornProfilePicMap({
|
||||
output: path.resolve("./public/unicorn-profile-pic-map.ts"),
|
||||
}),
|
||||
enforce: "pre",
|
||||
},
|
||||
],
|
||||
},
|
||||
markdown: {
|
||||
|
||||
1
public/.gitignore
vendored
1
public/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
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 SearchField from "./search-field/search-field.astro";
|
||||
// import FilterListbox from "./filter-listbox/filter-listbox.astro";
|
||||
|
||||
export interface FilterSearchBarProps {
|
||||
unicornProfilePicMap: ProfilePictureMap;
|
||||
}
|
||||
|
||||
const { unicornProfilePicMap } = Astro.props as FilterSearchBarProps;
|
||||
---
|
||||
|
||||
<div class={styles.iconContainer}>
|
||||
@@ -18,14 +12,11 @@ const { unicornProfilePicMap } = Astro.props as FilterSearchBarProps;
|
||||
</div>
|
||||
<!-- <FilterListbox class={styles.filterField} /> -->
|
||||
</div>
|
||||
<script define:vars={{ unicornProfilePicMap }}>
|
||||
window.unicornProfilePicMap =
|
||||
window.unicornProfilePicMap || unicornProfilePicMap;
|
||||
</script>
|
||||
<script>
|
||||
import { render, createElement, Fragment } from "preact";
|
||||
import { PostInfo } from "types/PostInfo";
|
||||
import { debounce } from "utils/debounce";
|
||||
import { ProfilePictureMap } from "utils/get-unicorn-profile-pic-map";
|
||||
import { PostCard } from "../../components/post-card/post-card";
|
||||
|
||||
const searchInput: HTMLElement = document.querySelector("#search-input");
|
||||
@@ -46,7 +37,6 @@ const { unicornProfilePicMap } = Astro.props as FilterSearchBarProps;
|
||||
let abortController: AbortController | undefined;
|
||||
|
||||
function doSearch(val) {
|
||||
const unicornProfilePicMap = (window as any).unicornProfilePicMap || [];
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
abortController = undefined;
|
||||
@@ -54,14 +44,18 @@ const { unicornProfilePicMap } = Astro.props as FilterSearchBarProps;
|
||||
abortController = new AbortController();
|
||||
fetch(`/api/search?query=${val}`, { signal: abortController.signal })
|
||||
.then((res) => res.json())
|
||||
.then((serverVal: PostInfo[]) => {
|
||||
// TODO: Debounce to avoid server kill
|
||||
.then(
|
||||
(serverVal: {
|
||||
posts: PostInfo[];
|
||||
totalPosts: number;
|
||||
unicornProfilePicMap: ProfilePictureMap;
|
||||
}) => {
|
||||
const FilledPostCard = createElement(
|
||||
Fragment,
|
||||
{},
|
||||
serverVal.map((post) =>
|
||||
serverVal.posts.map((post) =>
|
||||
createElement(PostCard, {
|
||||
unicornProfilePicMap,
|
||||
unicornProfilePicMap: serverVal.unicornProfilePicMap,
|
||||
post,
|
||||
})
|
||||
)
|
||||
@@ -70,7 +64,8 @@ const { unicornProfilePicMap } = Astro.props as FilterSearchBarProps;
|
||||
loadingEl && loadingEl.remove();
|
||||
loadingEl = undefined;
|
||||
abortController = undefined;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const debounceDoSearch = debounce(doSearch, 1000, false);
|
||||
|
||||
@@ -35,7 +35,7 @@ const unicornProfilePicMap = await getUnicornProfilePicMap();
|
||||
> -->
|
||||
<PostListHeader siteDescription={siteMetadata.description} />
|
||||
<main>
|
||||
<FilterSearchBar unicornProfilePicMap={unicornProfilePicMap} />
|
||||
<FilterSearchBar />
|
||||
<PostList
|
||||
listAriaLabel="List of posts"
|
||||
postsToDisplay={posts}
|
||||
|
||||
@@ -1,37 +1,6 @@
|
||||
import { getPicture } from "@astrojs/image";
|
||||
import { unicorns } from "utils/data";
|
||||
import unicornProfilePicMap from "../../public/unicorn-profile-pic-map";
|
||||
|
||||
/**
|
||||
* 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 () => {
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
|
||||
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