Skip to content
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

Vite + Content Collections #129

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 0 additions & 27 deletions .eslintrc.js

This file was deleted.

23 changes: 23 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"extends": [
"@remix-run/eslint-config",
"@remix-run/eslint-config/node",
"@remix-run/eslint-config/jest-testing-library"
],
// We're using Vitest which has a very similar API to Jest
// (so the linting plugins work nicely), but it means we have to explicitly
// set the Jest version.
"settings": {
"jest": {
"version": 28
}
},
"overrides": [
{
"files": ["*.spec.{ts,tsx}"],
"rules": {
"testing-library/prefer-screen-queries": "off"
}
}
]
}
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ node_modules
/playwright-report

# New line breaks matter on MDX files, and prettier has broken some MDX files after formatting
/blog/**/*.mdx
/app/content/**/*.mdx
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes.
1 change: 1 addition & 0 deletions app/components/Footer/Footer.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { describe, it, expect } from "vitest";
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";

Expand Down
2 changes: 1 addition & 1 deletion app/components/MeetupLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function MeetupLink({
return (
<a
href={link}
className="btn-primary btn mb-8"
className="btn btn-primary mb-8"
target="_blank"
rel="noopener noreferrer"
>
Expand Down
5 changes: 3 additions & 2 deletions app/components/Navbar/Navbar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { describe, it, expect } from "vitest";
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
import { MemoryRouter as Router } from "react-router-dom";
Expand All @@ -9,13 +10,13 @@ describe("Navbar", () => {
render(
<Router>
<Navbar />
</Router>
</Router>,
);

expect(screen.getByRole("navigation")).toBeInTheDocument();

expect(
screen.getByRole("link", { name: homepageLinkTitle })
screen.getByRole("link", { name: homepageLinkTitle }),
).toBeInTheDocument();

// Check for 2 links for each, for both mobile and desktop nav menus
Expand Down
4 changes: 2 additions & 2 deletions app/components/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function Navbar() {
<nav className="navbar bg-base-300">
<div className="navbar-start">
<div className="dropdown">
<label tabIndex={0} className="btn-ghost btn md:hidden">
<label tabIndex={0} className="btn btn-ghost md:hidden">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
Expand All @@ -31,7 +31,7 @@ export default function Navbar() {
<SocialLinks />
</ul>
</div>
<Link to="/" className="btn-ghost btn text-xl normal-case">
<Link to="/" className="btn btn-ghost text-xl normal-case">
{homepageLinkTitle}
</Link>
</div>
Expand Down
3 changes: 2 additions & 1 deletion app/components/Navbar/SocialLinks.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { describe, it, expect } from "vitest";
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
import { MemoryRouter as Router } from "react-router-dom";
Expand All @@ -14,7 +15,7 @@ describe("SocialLinks", () => {
render(
<Router>
<SocialLinks />
</Router>
</Router>,
);

const meetupLink = screen.getByRole("link", { name: "Meetup" });
Expand Down
9 changes: 5 additions & 4 deletions app/components/NextEventInfo/NextEventInfo.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";

import type { SerializedMeetupEvent } from "./NextEventInfo";
import NextEventInfo from "./NextEventInfo";
Expand Down Expand Up @@ -31,14 +32,14 @@ describe("NextEventInfo", () => {
render(<NextEventInfo event={eventWithVenue} />);
expect(screen.getByText("Venue name")).toBeInTheDocument();
expect(
screen.getByText("Venue address, Venue city, Venue state")
screen.getByText("Venue address, Venue city, Venue state"),
).toBeInTheDocument();
});

it("renders default venue for online events", () => {
render(<NextEventInfo event={DEFAULT_ONLINE_EVENT} />);
expect(
screen.getByText("H-E-B Digital & Favor Eastside Tech Hub (and online)")
screen.getByText("H-E-B Digital & Favor Eastside Tech Hub (and online)"),
).toBeInTheDocument();
expect(screen.getByText("2416 E 6th St, Austin, TX")).toBeInTheDocument();
});
Expand All @@ -52,15 +53,15 @@ describe("NextEventInfo", () => {
const eventWithManyAttending = { ...DEFAULT_ONLINE_EVENT, going: 10 };
render(<NextEventInfo event={eventWithManyAttending} />);
expect(
screen.getByRole("link", { name: "Join 10 others at our next meetup!" })
screen.getByRole("link", { name: "Join 10 others at our next meetup!" }),
).toBeInTheDocument();
});

it("renders button label when no one is attending", () => {
const eventWithNoOneAttending = { ...DEFAULT_ONLINE_EVENT, going: 0 };
render(<NextEventInfo event={eventWithNoOneAttending} />);
expect(
screen.getByRole("link", { name: "Join us at our next meetup!" })
screen.getByRole("link", { name: "Join us at our next meetup!" }),
).toBeInTheDocument();
});
});
3 changes: 2 additions & 1 deletion app/components/NextEventInfo/NextEventInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { MeetupEvent } from "~/models/meetup.parsing";
import type { MeetupEvent } from "~/lib/meetup.models";
import { formatDateTime, isEmptyString } from "~/utils";
import MeetupLink from "~/components/MeetupLink";
import type { SerializeFrom } from "@remix-run/node";
Expand All @@ -15,6 +15,7 @@ const DEFAULT_VENUE: Venue = {
function isValidVenue(venue: MeetupEvent["venue"]): venue is Venue {
return (
venue !== null &&
venue !== undefined &&
!isEmptyString(venue.address) &&
!isEmptyString(venue.city) &&
!isEmptyString(venue.state)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: Making the Remix Austin Blog
date: 01 May 2023
date: 2023-05-01
author: Matt Hernandez
description: How did we make a blog integration in Remix? Read about the crazy winding journey here.
imageUrl: /posts/img/blog-banner-resized-690.jpg
imageUrl: /img/blog/blog-banner-resized-690.jpg
imageAlt: The word "Blog" over a blurred image of a code editor
tags:
- Remix
Expand All @@ -14,12 +14,12 @@ tags:
- Austin
---

import { BlogImage } from "./components/BlogImage";
import { AuthorFooter } from "./components/AuthorFooter";
import colbyWhite from "./img/colby-white.jpg";
import mattConfused from "./img/matt-confused.jpg";
import refreshFunctionality from "./img/refresh.jpg";
import mattAuthor from "./img/matt-author.png";
import { BlogImage } from "~/components/Blog/BlogImage";
import { AuthorFooter } from "~/components/Blog/AuthorFooter";
import colbyWhite from "~/assets/colby-white.jpg?url";
import mattConfused from "~/assets/matt-confused.jpg?url";
import refreshFunctionality from "~/assets/refresh.jpg?url";
import mattAuthor from "~/assets/matt-author.png?url";

## So how did we get here?

Expand Down Expand Up @@ -130,7 +130,7 @@ each post's front matter. That would then allow us to list all of our blog posts
without having to worry about all that boilerplate.

Lastly, if each compiled blog post has a lot of boilerplate, it seemed like a better idea to have each
blog post built on demand by the server when the post is requested. That would save us from having one
blog post built on demand by the server when the post is requested. That would save us from having one
mega-file with everything in it.

These are all good things to fix, but they take some serious thought into how to do it right.
Expand Down
15 changes: 15 additions & 0 deletions app/content/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as v from "valibot";

const blogCollection = v.object({
title: v.string(),
date: v.date(),
author: v.string(),
description: v.string(),
imageUrl: v.string([]),
imageAlt: v.string(),
tags: v.array(v.string()),
});

export const collections = {
blog: blogCollection,
};
22 changes: 0 additions & 22 deletions app/entry.client.tsx

This file was deleted.

53 changes: 0 additions & 53 deletions app/entry.server.tsx

This file was deleted.

57 changes: 57 additions & 0 deletions app/lib/content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import frontmatter from "front-matter";
import { parse } from "path-esm";
import type { ReactNode } from "react";
import type { Output } from "valibot";
import { parse as validate } from "valibot";
import { collections } from "~/content/config";

Check failure on line 6 in app/lib/content.ts

View workflow job for this annotation

GitHub Actions / 👷 Development / ʦ TypeScript

Cannot find module '~/content/config' or its corresponding type declarations.
import type { MDXProps } from "mdx/types";

type MDXComponent = (props: MDXProps) => ReactNode;

export type Content<Data, Collection> = {
collection: Collection;
slug: string;
body: string;
data: Data;
};

export type Collecitons = typeof collections;
export type CollectionKey = keyof Collecitons;

const collectionFiles = import.meta.glob<{ default: MDXComponent }>(

Check failure on line 21 in app/lib/content.ts

View workflow job for this annotation

GitHub Actions / 👷 Development / ʦ TypeScript

The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'es2022', 'esnext', 'system', 'node16', or 'nodenext'.

Check failure on line 21 in app/lib/content.ts

View workflow job for this annotation

GitHub Actions / 👷 Development / ʦ TypeScript

Property 'glob' does not exist on type 'ImportMeta'.
"/app/content/**/*.{md,mdx}",
{
eager: true,
},
);

const collectionFilesRaw = import.meta.glob("/app/content/**/*.{md,mdx}", {

Check failure on line 28 in app/lib/content.ts

View workflow job for this annotation

GitHub Actions / 👷 Development / ʦ TypeScript

The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'es2022', 'esnext', 'system', 'node16', or 'nodenext'.

Check failure on line 28 in app/lib/content.ts

View workflow job for this annotation

GitHub Actions / 👷 Development / ʦ TypeScript

Property 'glob' does not exist on type 'ImportMeta'.
eager: true,
as: "raw",
});

export function getCollection<Collection extends CollectionKey>(
collection: Collection,
): {
collection: Collection;
slug: string;
content: MDXComponent;
data: Output<Collecitons[Collection]>;
}[] {
return Object.entries(collectionFiles)
.filter(([path]) => {
const contentDir = parse(parse(path).dir).name;
return contentDir === collection;
})
.map(([path, module]) => {
let file = collectionFilesRaw[path];
let { attributes } = frontmatter(file);
let data = validate(collections[collection], attributes);
return {
slug: parse(path).name,
content: module.default,

Check failure on line 52 in app/lib/content.ts

View workflow job for this annotation

GitHub Actions / 👷 Development / ʦ TypeScript

Property 'default' does not exist on type 'unknown'.
data,
collection,
};
});
}
Loading
Loading