From 6b5ae7f7d5ae7a466e3c13d15954c61304d7cafb Mon Sep 17 00:00:00 2001 From: Hyeseong Kim Date: Mon, 5 May 2025 00:48:19 +0900 Subject: [PATCH] Demo using Cloudflare Pages Functions --- .../api/packages_url_resources.json.js | 105 ++++++++++++++++++ public/functions/api/resources.json.js | 96 ++++++++++++++++ wrangler.jsonc | 4 + 3 files changed, 205 insertions(+) create mode 100644 public/functions/api/packages_url_resources.json.js create mode 100644 public/functions/api/resources.json.js create mode 100644 wrangler.jsonc diff --git a/public/functions/api/packages_url_resources.json.js b/public/functions/api/packages_url_resources.json.js new file mode 100644 index 000000000..e466a9005 --- /dev/null +++ b/public/functions/api/packages_url_resources.json.js @@ -0,0 +1,105 @@ +let urlResources = [ + { + "name": "ReScript Test Framework", + "description": "The most minimalistic testing library you will find for testing ReScript code", + "keywords": ["testing", "minimal", "experimental"], + "urlHref": "https://github.com/rescript-lang/rescript-project-template/blob/test/tests/Tests.res", + "official": true + }, + { + "name": "genType", + "description": "Better interop with JS & TS in ReScript", + "keywords": ["rescript", "typescript"], + "urlHref": "https://github.com/reason-association/genType", + "official": true + } +] + +export async function onRequestGET(context) { + const packages = await fetchNpmPackages() + return Response.json({ + ...packages, + urlResources, + }) +} + +async function fetchNpmPackages() { + let baseUrl = "https://registry.npmjs.org/-/v1/search?text=keywords:rescript&size=250&maintenance=1.0&popularity=0.5&quality=0.9" + + let [data1, data2, data3] = await Promise.all3([ + fetch(baseUrl) + .then(res => res.json()) + .then(data => parsePkgs(data)), + fetch(baseUrl + "&from=250") + .then(res => res.json()) + .then(data => parsePkgs(data)), + fetch(baseUrl + "&from=500") + .then(res => res.json()) + .then(data => parsePkgs(data)), + ]) + + let unmaintained = [] + + function shouldAllow(pkg) { + // These are packages that we do not want to filter out when loading searching from NPM. + let packageAllowList = [] + + if (packageAllowList.includes(pkg)) { + return true + } + + if (pkg.name.includes("reason")) { + return false + } + + if (pkg.maintenanceScore < 0.3) { + unmaintained.push(pkg) + return false + } + + return true + } + + let packages = [] + for (let pkg of data1) if (shouldAllow(pkg)) packages.push(pkg) + for (let pkg of data2) if (shouldAllow(pkg)) packages.push(pkg) + for (let pkg of data3) if (shouldAllow(pkg)) packages.push(pkg) + + return { + packages, + unmaintained, + } +} + +function parsePkgs(data) { + return data["objects"].map(item => { + let pkg = item["package"] + return { + name: pkg["name"], + version: pkg["version"], + keywords: uniqueKeywords(filterKeywords(pkg["keywords"])), + description: pkg["description"] ?? "", + repositoryHref: pkg["links"]?.["repository"] ?? null, + npmHref: pkg["links"]["npm"], + searchScore: item["searchScore"], + maintenanceScore: item["score"]["detail"]["maintenance"], + } + }) +} + +function filterKeywords(keywords) { + return keywords.filter(kw => { + let k = kw.toLowerCase() + return !( + k === "reasonml" || + k === "reason" || + k === "ocaml" || + k === "bucklescript" || + k === "rescript" + ) + }) +} + +function uniqueKeywords(keywords) { + return [...new Set(keywords)] +} diff --git a/public/functions/api/resources.json.js b/public/functions/api/resources.json.js new file mode 100644 index 000000000..0942199f8 --- /dev/null +++ b/public/functions/api/resources.json.js @@ -0,0 +1,96 @@ +/** This is the list of community content we want to generate. */ +/** If you have content you would like to add, please open up a PR adding the link to this list and then run `npm run generate-resources` */ +let urls = [ + // 2025 + "https://dev.to/dzakh/javascript-schema-library-from-the-future-5420", + "https://www.youtube.com/watch?v=yKl2fSdnw7w", + "https://github.com/rescript-lang/awesome-rescript", // regardless of age this seems like it should always be near the top + // 2024 + "https://www.youtube.com/watch?v=MC-dbM-GEuw", + "https://dev.to/jderochervlk/rescript-has-come-a-long-way-maybe-its-time-to-switch-from-typescript-29he", + "https://www.youtube.com/watch?v=f0gDMjuaCZo", + "https://www.geldata.com/blog/rescript-and-edgedb", + "https://www.youtube.com/watch?v=37FY6a-zY20", + // 2023 + "https://dev.to/cometkim/when-and-where-to-use-rescript-the-rescript-happy-path-47ni", + // 2022 + "https://www.greyblake.com/blog/from-typescript-to-rescript/", + "https://dev.to/zth/getting-rid-of-your-dead-code-in-rescript-3mba", + "https://www.youtube.com/watch?v=KDL-kRgilkQ", + "https://dev.to/srikanthkyatham/rescript-react-error-boundary-usage-3b05", + // "https://www.daggala.com/belt_vs_js_array_in_rescript/" I think we should exclude this one since it's related to API we are deprecating + // 2021 + "https://fullsteak.dev/posts/fullstack-rescript-architecture-overview", + "https://scalac.io/blog/rescript-for-react-development/", + "https://yangdanny97.github.io/blog/2021/07/09/Migrating-to-Rescript", + "https://alexfedoseev.com/blog/post/responsive-images-and-cumulative-layout-shift", + "https://dev.to/ryyppy/rescript-records-nextjs-undefined-and-getstaticprops-4890", +] + +export async function onRequestGET(context) { + const resources = []; + + for (let url of urls) { + let resource = await fetchUrlResource(url) + if (resource) { + resources.push(resource) + } + } + + return Response.json(resources) +} + +function makeCollector() { + let state = { + url: null, + title: null, + description: null, + image: null, + } + return { + get state() { + return { ...state }; + }, + element(element) { + let property = element.getAttribute('property') + let content = element.getAttribute('content') + let [_og, namespace, key] = property.split(':') + switch (namespace) { + case 'url': + state.url = content + return + case 'title': + state.title = content + return + case 'description': + state.description = content + return + case 'image': { + if (!key || key === 'url') { + state.image = content + } + } + } + // FIXME: should flush + } + } +} + +async function fetchUrlResource(url) { + let response = await fetch(url) + if (!response.ok) { + return null + } + + let collector = makeCollector() + let stream = new HTMLRewriter() + .on('meta[property^=og][content]', collector) + .transform(response) + + for await (let _chunk of stream) { + // noop + // FIXME: no need to iterate full content, just the first few hundreds killobytes enough + } + + return collector.state +} diff --git a/wrangler.jsonc b/wrangler.jsonc new file mode 100644 index 000000000..2c294da8c --- /dev/null +++ b/wrangler.jsonc @@ -0,0 +1,4 @@ +{ + "name": "rescript-lang-org", + "pages_build_output_dir": "out" +}