From 899e95ce5aa3e8f42f8fcef9d3e386f356670bd3 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Mon, 24 Jan 2022 15:35:01 +0100 Subject: [PATCH 1/4] add preact example --- examples/basic-preact/.gitignore | 7 + examples/basic-preact/README.md | 36 +++ examples/basic-preact/app/entry.client.tsx | 12 + examples/basic-preact/app/entry.server.tsx | 21 ++ examples/basic-preact/app/root.tsx | 248 ++++++++++++++++++ .../basic-preact/app/routes/demos/about.tsx | 44 ++++ .../app/routes/demos/about/index.tsx | 17 ++ .../app/routes/demos/about/whoa.tsx | 20 ++ .../basic-preact/app/routes/demos/actions.tsx | 101 +++++++ .../basic-preact/app/routes/demos/correct.tsx | 3 + .../basic-preact/app/routes/demos/params.tsx | 43 +++ .../app/routes/demos/params/$id.tsx | 112 ++++++++ .../app/routes/demos/params/index.tsx | 36 +++ examples/basic-preact/app/routes/index.tsx | 100 +++++++ examples/basic-preact/app/styles/dark.css | 7 + .../basic-preact/app/styles/demos/about.css | 26 ++ .../basic-preact/app/styles/demos/remix.css | 120 +++++++++ examples/basic-preact/app/styles/global.css | 98 +++++++ examples/basic-preact/package.json | 31 +++ examples/basic-preact/public/favicon.ico | Bin 0 -> 16958 bytes examples/basic-preact/remix.config.js | 11 + examples/basic-preact/remix.env.d.ts | 2 + examples/basic-preact/sandbox.config.json | 6 + examples/basic-preact/tsconfig.json | 20 ++ 24 files changed, 1121 insertions(+) create mode 100644 examples/basic-preact/.gitignore create mode 100644 examples/basic-preact/README.md create mode 100644 examples/basic-preact/app/entry.client.tsx create mode 100644 examples/basic-preact/app/entry.server.tsx create mode 100644 examples/basic-preact/app/root.tsx create mode 100644 examples/basic-preact/app/routes/demos/about.tsx create mode 100644 examples/basic-preact/app/routes/demos/about/index.tsx create mode 100644 examples/basic-preact/app/routes/demos/about/whoa.tsx create mode 100644 examples/basic-preact/app/routes/demos/actions.tsx create mode 100644 examples/basic-preact/app/routes/demos/correct.tsx create mode 100644 examples/basic-preact/app/routes/demos/params.tsx create mode 100644 examples/basic-preact/app/routes/demos/params/$id.tsx create mode 100644 examples/basic-preact/app/routes/demos/params/index.tsx create mode 100644 examples/basic-preact/app/routes/index.tsx create mode 100644 examples/basic-preact/app/styles/dark.css create mode 100644 examples/basic-preact/app/styles/demos/about.css create mode 100644 examples/basic-preact/app/styles/demos/remix.css create mode 100644 examples/basic-preact/app/styles/global.css create mode 100644 examples/basic-preact/package.json create mode 100644 examples/basic-preact/public/favicon.ico create mode 100644 examples/basic-preact/remix.config.js create mode 100644 examples/basic-preact/remix.env.d.ts create mode 100644 examples/basic-preact/sandbox.config.json create mode 100644 examples/basic-preact/tsconfig.json diff --git a/examples/basic-preact/.gitignore b/examples/basic-preact/.gitignore new file mode 100644 index 00000000000..51c3c7eb52b --- /dev/null +++ b/examples/basic-preact/.gitignore @@ -0,0 +1,7 @@ +node_modules + +/.cache +/build +/public/build +/package-lock.json +/yarn.lock \ No newline at end of file diff --git a/examples/basic-preact/README.md b/examples/basic-preact/README.md new file mode 100644 index 00000000000..d6b0cc9d2c9 --- /dev/null +++ b/examples/basic-preact/README.md @@ -0,0 +1,36 @@ +# Welcome to Remix! + +This is a very basic example of a simple Remix app using the Remix App Server. + +- [Remix Docs](https://remix.run/docs) + +## Development + +From your terminal: + +```sh +npm install +npm run dev +``` + +This starts your app in development mode, rebuilding assets on file changes. + +## Deployment + +First, build your app for production: + +```sh +npm run build +``` + +Then run the app in production mode: + +```sh +npm start +``` + +## Preview + +Open this example on [CodeSandbox](https://codesandbox.com): + +[![Open in CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/remix-run/remix/tree/main/examples/basic) diff --git a/examples/basic-preact/app/entry.client.tsx b/examples/basic-preact/app/entry.client.tsx new file mode 100644 index 00000000000..668de76555c --- /dev/null +++ b/examples/basic-preact/app/entry.client.tsx @@ -0,0 +1,12 @@ +import { hydrate } from "react-dom"; +import { RemixBrowser } from "remix"; + +const documentElement = document.documentElement; +const apply = (n: HTMLElement) => document.replaceChild(n, documentElement); +// Temp fix +hydrate(, { + childNodes: [documentElement], + firstChild: documentElement, + insertBefore: apply, + appendChild: apply +}) diff --git a/examples/basic-preact/app/entry.server.tsx b/examples/basic-preact/app/entry.server.tsx new file mode 100644 index 00000000000..9749895d06d --- /dev/null +++ b/examples/basic-preact/app/entry.server.tsx @@ -0,0 +1,21 @@ +import { renderToString } from "react-dom/server"; +import { RemixServer } from "remix"; +import type { EntryContext } from "remix"; + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext +) { + const markup = renderToString( + + ); + + responseHeaders.set("Content-Type", "text/html"); + + return new Response("" + markup, { + status: responseStatusCode, + headers: responseHeaders + }); +} diff --git a/examples/basic-preact/app/root.tsx b/examples/basic-preact/app/root.tsx new file mode 100644 index 00000000000..46200009328 --- /dev/null +++ b/examples/basic-preact/app/root.tsx @@ -0,0 +1,248 @@ +import * as React from "react"; +import { + Link, + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, + useCatch, + useLocation +} from "remix"; +import type { LinksFunction } from "remix"; + +import deleteMeRemixStyles from "~/styles/demos/remix.css"; +import globalStylesUrl from "~/styles/global.css"; +import darkStylesUrl from "~/styles/dark.css"; + +/** + * The `links` export is a function that returns an array of objects that map to + * the attributes for an HTML `` element. These will load `` tags on + * every route in the app, but individual routes can include their own links + * that are automatically unloaded when a user navigates away from the route. + * + * https://remix.run/api/app#links + */ +export const links: LinksFunction = () => { + return [ + { rel: "stylesheet", href: globalStylesUrl }, + { + rel: "stylesheet", + href: darkStylesUrl, + media: "(prefers-color-scheme: dark)" + }, + { rel: "stylesheet", href: deleteMeRemixStyles } + ]; +}; + +/** + * The root module's default export is a component that renders the current + * route via the `` component. Think of this as the global layout + * component for your app. + */ +export default function App() { + return ( + + + + + + ); +} + +function Document({ + children, + title +}: { + children: React.ReactNode; + title?: string; +}) { + return ( + + + + + {title ? {title} : null} + + + + + {children} + + + + {process.env.NODE_ENV === "development" ? : null} + + + ); +} + +function Layout({ children }: React.PropsWithChildren<{}>) { + return ( +
+
+
+ + + + +
+
+
+
{children}
+
+
+
+

© You!

+
+
+
+ ); +} + +export function CatchBoundary() { + const caught = useCatch(); + + let message; + switch (caught.status) { + case 401: + message = ( +

+ Oops! Looks like you tried to visit a page that you do not have access + to. +

+ ); + break; + case 404: + message = ( +

Oops! Looks like you tried to visit a page that does not exist.

+ ); + break; + + default: + throw new Error(caught.data || caught.statusText); + } + + return ( + + +

+ {caught.status}: {caught.statusText} +

+ {message} +
+
+ ); +} + +export function ErrorBoundary({ error }: { error: Error }) { + console.error(error); + return ( + + +
+

There was an error

+

{error.message}

+
+

+ Hey, developer, you should replace this with what you want your + users to see. +

+
+
+
+ ); +} + +function RemixLogo(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + Remix Logo + + + + + + + ); +} + +/** + * Provides an alert for screen reader users when the route changes. + */ +const RouteChangeAnnouncement = React.memo(() => { + const [hydrated, setHydrated] = React.useState(false); + const [innerHtml, setInnerHtml] = React.useState(""); + const location = useLocation(); + + React.useEffect(() => { + setHydrated(true); + }, []); + + const firstRenderRef = React.useRef(true); + React.useEffect(() => { + // Skip the first render because we don't want an announcement on the + // initial page load. + if (firstRenderRef.current) { + firstRenderRef.current = false; + return; + } + + const pageTitle = + location.pathname === "/" ? "Remix demo home page" : document.title; + setInnerHtml(`Navigated to ${pageTitle}`); + }, [location.pathname]); + + // Render nothing on the server. The live region provides no value unless + // scripts are loaded and the browser takes over normal routing. + if (!hydrated) { + return null; + } + + return ( +
+ {innerHtml} +
+ ); +}); diff --git a/examples/basic-preact/app/routes/demos/about.tsx b/examples/basic-preact/app/routes/demos/about.tsx new file mode 100644 index 00000000000..9c02c726a94 --- /dev/null +++ b/examples/basic-preact/app/routes/demos/about.tsx @@ -0,0 +1,44 @@ +import { Outlet } from "remix"; +import type { MetaFunction, LinksFunction } from "remix"; + +import stylesUrl from "~/styles/demos/about.css"; + +export const meta: MetaFunction = () => { + return { + title: "About Remix" + }; +}; + +export const links: LinksFunction = () => { + return [{ rel: "stylesheet", href: stylesUrl }]; +}; + +export default function Index() { + return ( +
+
+

About Us

+

+ Ok, so this page isn't really about us, but we did want to + show you a few more things Remix can do. +

+

+ Did you notice that things look a little different on this page? The + CSS that we import in the route file and include in its{" "} + links export is only included on this route and its + children. +

+

+ Wait a sec...its children? To understand what we mean by + this,{" "} + + read all about nested routes in the docs + + . +

+
+ +
+
+ ); +} diff --git a/examples/basic-preact/app/routes/demos/about/index.tsx b/examples/basic-preact/app/routes/demos/about/index.tsx new file mode 100644 index 00000000000..4b6deb1bae3 --- /dev/null +++ b/examples/basic-preact/app/routes/demos/about/index.tsx @@ -0,0 +1,17 @@ +import { Link } from "remix"; + +export default function AboutIndex() { + return ( +
+

+ You are looking at the index route for the /about URL + segment, but there are nested routes as well! +

+

+ + Check out one of them here. + +

+
+ ); +} diff --git a/examples/basic-preact/app/routes/demos/about/whoa.tsx b/examples/basic-preact/app/routes/demos/about/whoa.tsx new file mode 100644 index 00000000000..644a1bedc92 --- /dev/null +++ b/examples/basic-preact/app/routes/demos/about/whoa.tsx @@ -0,0 +1,20 @@ +import { Link } from "remix"; + +export default function AboutIndex() { + return ( +
+

+ Whoa, this is a nested route! We render the /about layout + route component, and its Outlet renders our route + component. 🤯 +

+

+ + + Go back to the /about index. + + +

+
+ ); +} diff --git a/examples/basic-preact/app/routes/demos/actions.tsx b/examples/basic-preact/app/routes/demos/actions.tsx new file mode 100644 index 00000000000..8b639cbd95f --- /dev/null +++ b/examples/basic-preact/app/routes/demos/actions.tsx @@ -0,0 +1,101 @@ +import { useEffect, useRef } from "react"; +import type { ActionFunction } from "remix"; +import { Form, json, useActionData, redirect } from "remix"; + +export function meta() { + return { title: "Actions Demo" }; +} + +// When your form sends a POST, the action is called on the server. +// - https://remix.run/api/conventions#action +// - https://remix.run/guides/data-updates +export const action: ActionFunction = async ({ request }) => { + const formData = await request.formData(); + const answer = formData.get("answer"); + + // Typical action workflows start with validating the form data that just came + // over the network. Clientside validation is fine, but you definitely need it + // server side. If there's a problem, return the data and the component can + // render it. + if (!answer || typeof answer !== "string") { + return json("Come on, at least try!", { status: 400 }); + } + + if (answer !== "egg") { + return json(`Sorry, ${answer} is not right.`, { status: 400 }); + } + + // Finally, if the data is valid, you'll typically write to a database or send or + // email or log the user in, etc. It's recommended to redirect after a + // successful action, even if it's to the same place so that non-JavaScript workflows + // from the browser doesn't repost the data if the user clicks back. + return redirect("/demos/correct"); +}; + +export default function ActionsDemo() { + // https://remix.run/api/remix#useactiondata + const actionMessage = useActionData(); + const answerRef = useRef(null); + + // This form works without JavaScript, but when we have JavaScript we can make + // the experience better by selecting the input on wrong answers! Go ahead, disable + // JavaScript in your browser and see what happens. + useEffect(() => { + if (actionMessage && answerRef.current) { + answerRef.current.select(); + } + }, [actionMessage]); + + return ( +
+
+

Actions!

+

+ This form submission will send a post request that we handle in our + `action` export. Any route can export an action to handle data + mutations. +

+
+

Post an Action

+

+ What is more useful when it is broken? +

+ +
+ +
+ {actionMessage ? ( +

+ {actionMessage} +

+ ) : null} +
+
+ + +
+ ); +} diff --git a/examples/basic-preact/app/routes/demos/correct.tsx b/examples/basic-preact/app/routes/demos/correct.tsx new file mode 100644 index 00000000000..162ebeb7873 --- /dev/null +++ b/examples/basic-preact/app/routes/demos/correct.tsx @@ -0,0 +1,3 @@ +export default function NiceWork() { + return

You got it right!

; +} diff --git a/examples/basic-preact/app/routes/demos/params.tsx b/examples/basic-preact/app/routes/demos/params.tsx new file mode 100644 index 00000000000..e60e9342b96 --- /dev/null +++ b/examples/basic-preact/app/routes/demos/params.tsx @@ -0,0 +1,43 @@ +import { Link, Outlet } from "remix"; + +export function meta() { + return { title: "Boundaries Demo" }; +} + +export default function Boundaries() { + return ( +
+
+ +
+ + +
+ ); +} diff --git a/examples/basic-preact/app/routes/demos/params/$id.tsx b/examples/basic-preact/app/routes/demos/params/$id.tsx new file mode 100644 index 00000000000..7c191bad813 --- /dev/null +++ b/examples/basic-preact/app/routes/demos/params/$id.tsx @@ -0,0 +1,112 @@ +import { json, useCatch, useLoaderData } from "remix"; +import type { LoaderFunction, MetaFunction } from "remix"; + +// The `$` in route filenames becomes a pattern that's parsed from the URL and +// passed to your loaders so you can look up data. +// - https://remix.run/api/conventions#loader-params +export const loader: LoaderFunction = async ({ params }) => { + // pretend like we're using params.id to look something up in the db + + if (params.id === "this-record-does-not-exist") { + // If the record doesn't exist we can't render the route normally, so + // instead we throw a 404 response to stop running code here and show the + // user the catch boundary. + throw new Response("Not Found", { status: 404 }); + } + + // now pretend like the record exists but the user just isn't authorized to + // see it. + if (params.id === "shh-its-a-secret") { + // Again, we can't render the component if the user isn't authorized. You + // can even put data in the response that might help the user rectify the + // issue! Like emailing the webmaster for access to the page. (Oh, right, + // `json` is just a Response helper that makes it easier to send JSON + // responses). + throw json({ webmasterEmail: "hello@remix.run" }, { status: 401 }); + } + + // Sometimes your code just blows up and you never anticipated it. Remix will + // automatically catch it and send the UI to the error boundary. + if (params.id === "kaboom") { + lol(); + } + + // but otherwise the record was found, user has access, so we can do whatever + // else we needed to in the loader and return the data. (This is boring, we're + // just gonna return the params.id). + return { param: params.id }; +}; + +export default function ParamDemo() { + const data = useLoaderData(); + return ( +

+ The param is {data.param} +

+ ); +} + +// https://remix.run/api/conventions#catchboundary +// https://remix.run/api/remix#usecatch +// https://remix.run/api/guides/not-found +export function CatchBoundary() { + const caught = useCatch(); + + let message: React.ReactNode; + switch (caught.status) { + case 401: + message = ( +

+ Looks like you tried to visit a page that you do not have access to. + Maybe ask the webmaster ({caught.data.webmasterEmail}) for access. +

+ ); + break; + case 404: + message = ( +

Looks like you tried to visit a page that does not exist.

+ ); + break; + default: + message = ( +

+ There was a problem with your request! +
+ {caught.status} {caught.statusText} +

+ ); + } + + return ( + <> +

Oops!

+

{message}

+

+ (Isn't it cool that the user gets to stay in context and try a different + link in the parts of the UI that didn't blow up?) +

+ + ); +} + +// https://remix.run/api/conventions#errorboundary +// https://remix.run/api/guides/not-found +export function ErrorBoundary({ error }: { error: Error }) { + console.error(error); + return ( + <> +

Error!

+

{error.message}

+

+ (Isn't it cool that the user gets to stay in context and try a different + link in the parts of the UI that didn't blow up?) +

+ + ); +} + +export const meta: MetaFunction = ({ data }) => { + return { + title: data ? `Param: ${data.param}` : "Oops..." + }; +}; diff --git a/examples/basic-preact/app/routes/demos/params/index.tsx b/examples/basic-preact/app/routes/demos/params/index.tsx new file mode 100644 index 00000000000..264dd5f6ddc --- /dev/null +++ b/examples/basic-preact/app/routes/demos/params/index.tsx @@ -0,0 +1,36 @@ +export default function Boundaries() { + return ( + <> +

Params

+

+ When you name a route segment with $ like{" "} + routes/users/$userId.js, the $ segment will be parsed from + the URL and sent to your loaders and actions by the same name. +

+

Errors

+

+ When a route throws an error in it's action, loader, or component, Remix + automatically catches it, won't even try to render the component, but it + will render the route's ErrorBoundary instead. If the route doesn't have + one, it will bubble up to the routes above it until it hits the root. +

+

So be as granular as you want with your error handling.

+

Not Found

+

+ (and other{" "} + + client errors + + ) +

+

+ Loaders and Actions can throw a Response instead of an + error and Remix will render the CatchBoundary instead of the component. + This is great when loading data from a database isn't found. As soon as + you know you can't render the component normally, throw a 404 response + and send your app into the catch boundary. Just like error boundaries, + catch boundaries bubble, too. +

+ + ); +} diff --git a/examples/basic-preact/app/routes/index.tsx b/examples/basic-preact/app/routes/index.tsx new file mode 100644 index 00000000000..621d37c37c5 --- /dev/null +++ b/examples/basic-preact/app/routes/index.tsx @@ -0,0 +1,100 @@ +import type { MetaFunction, LoaderFunction } from "remix"; +import { useLoaderData, json, Link } from "remix"; + +type IndexData = { + resources: Array<{ name: string; url: string }>; + demos: Array<{ name: string; to: string }>; +}; + +// Loaders provide data to components and are only ever called on the server, so +// you can connect to a database or run any server side code you want right next +// to the component that renders it. +// https://remix.run/api/conventions#loader +export const loader: LoaderFunction = () => { + const data: IndexData = { + resources: [ + { + name: "Remix Docs", + url: "https://remix.run/docs" + }, + { + name: "React Router Docs", + url: "https://reactrouter.com/docs" + }, + { + name: "Remix Discord", + url: "https://discord.gg/VBePs6d" + } + ], + demos: [ + { + to: "demos/actions", + name: "Actions" + }, + { + to: "demos/about", + name: "Nested Routes, CSS loading/unloading" + }, + { + to: "demos/params", + name: "URL Params and Error Boundaries" + } + ] + }; + + // https://remix.run/api/remix#json + return json(data); +}; + +// https://remix.run/api/conventions#meta +export const meta: MetaFunction = () => { + return { + title: "Remix Starter", + description: "Welcome to remix!" + }; +}; + +// https://remix.run/guides/routing#index-routes +export default function Index() { + const data = useLoaderData(); + + return ( +
+
+

Welcome to Remix!

+

We're stoked that you're here. 🥳

+

+ Feel free to take a look around the code to see how Remix does things, + it might be a bit different than what you’re used to. When you're + ready to dive deeper, we've got plenty of resources to get you + up-and-running quickly. +

+

+ Check out all the demos in this starter, and then just delete the{" "} + app/routes/demos and app/styles/demos{" "} + folders when you're ready to turn this into your next project. +

+
+ +
+ ); +} diff --git a/examples/basic-preact/app/styles/dark.css b/examples/basic-preact/app/styles/dark.css new file mode 100644 index 00000000000..81b5196761c --- /dev/null +++ b/examples/basic-preact/app/styles/dark.css @@ -0,0 +1,7 @@ +:root { + --color-foreground: hsl(0, 0%, 100%); + --color-background: hsl(0, 0%, 7%); + --color-links: hsl(213, 100%, 73%); + --color-links-hover: hsl(213, 100%, 80%); + --color-border: hsl(0, 0%, 25%); +} diff --git a/examples/basic-preact/app/styles/demos/about.css b/examples/basic-preact/app/styles/demos/about.css new file mode 100644 index 00000000000..35fc3b90b6e --- /dev/null +++ b/examples/basic-preact/app/styles/demos/about.css @@ -0,0 +1,26 @@ +/* + * Whoa whoa whoa, wait a sec...why are we overriding global CSS selectors? + * Isn't that kind of scary? How do we know this won't have side effects? + * + * In Remix, CSS that is included in a route file will *only* show up on that + * route (and for nested routes, its children). When the user navigates away + * from that route the CSS files linked from those routes will be automatically + * unloaded, making your styles much easier to predict and control. + * + * Read more about styling routes in the docs: + * https://remix.run/guides/styling + */ + +:root { + --color-foreground: hsl(0, 0%, 7%); + --color-background: hsl(56, 100%, 50%); + --color-links: hsl(345, 56%, 39%); + --color-links-hover: hsl(345, 51%, 49%); + --color-border: rgb(184, 173, 20); + --font-body: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + Liberation Mono, Courier New, monospace; +} + +.about__intro { + max-width: 500px; +} diff --git a/examples/basic-preact/app/styles/demos/remix.css b/examples/basic-preact/app/styles/demos/remix.css new file mode 100644 index 00000000000..3131afdef96 --- /dev/null +++ b/examples/basic-preact/app/styles/demos/remix.css @@ -0,0 +1,120 @@ +/* + * You probably want to just delete this file; it's just for the demo pages. + */ +.remix-app { + display: flex; + flex-direction: column; + min-height: 100vh; + min-height: calc(100vh - env(safe-area-inset-bottom)); +} + +.remix-app > * { + width: 100%; +} + +.remix-app__header { + padding-top: 1rem; + padding-bottom: 1rem; + border-bottom: 1px solid var(--color-border); +} + +.remix-app__header-content { + display: flex; + justify-content: space-between; + align-items: center; +} + +.remix-app__header-home-link { + width: 106px; + height: 30px; + color: var(--color-foreground); +} + +.remix-app__header-nav ul { + list-style: none; + margin: 0; + display: flex; + align-items: center; + gap: 1.5em; +} + +.remix-app__header-nav li { + font-weight: bold; +} + +.remix-app__main { + flex: 1 1 100%; +} + +.remix-app__footer { + padding-top: 1rem; + padding-bottom: 1rem; + border-top: 1px solid var(--color-border); +} + +.remix-app__footer-content { + display: flex; + justify-content: center; + align-items: center; +} + +.remix__page { + --gap: 1rem; + --space: 2rem; + display: grid; + grid-auto-rows: min-content; + gap: var(--gap); + padding-top: var(--space); + padding-bottom: var(--space); +} + +@media print, screen and (min-width: 640px) { + .remix__page { + --gap: 2rem; + grid-auto-rows: unset; + grid-template-columns: repeat(2, 1fr); + } +} + +@media screen and (min-width: 1024px) { + .remix__page { + --gap: 4rem; + } +} + +.remix__page > main > :first-child { + margin-top: 0; +} + +.remix__page > main > :last-child { + margin-bottom: 0; +} + +.remix__page > aside { + margin: 0; + padding: 1.5ch 2ch; + border: solid 1px var(--color-border); + border-radius: 0.5rem; +} + +.remix__page > aside > :first-child { + margin-top: 0; +} + +.remix__page > aside > :last-child { + margin-bottom: 0; +} + +.remix__form { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1rem; + border: 1px solid var(--color-border); + border-radius: 0.5rem; +} + +.remix__form > * { + margin-top: 0; + margin-bottom: 0; +} diff --git a/examples/basic-preact/app/styles/global.css b/examples/basic-preact/app/styles/global.css new file mode 100644 index 00000000000..2709c78a555 --- /dev/null +++ b/examples/basic-preact/app/styles/global.css @@ -0,0 +1,98 @@ +/* + * You can just delete everything here or keep whatever you like, it's just a + * quick baseline! + */ +:root { + --color-foreground: hsl(0, 0%, 7%); + --color-background: hsl(0, 0%, 100%); + --color-links: hsl(213, 100%, 52%); + --color-links-hover: hsl(213, 100%, 43%); + --color-border: hsl(0, 0%, 82%); + --font-body: -apple-system, "Segoe UI", Helvetica Neue, Helvetica, Roboto, + Arial, sans-serif, system-ui, "Apple Color Emoji", "Segoe UI Emoji"; +} + +html { + box-sizing: border-box; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +:-moz-focusring { + outline: auto; +} + +:focus { + outline: var(--color-links) solid 2px; + outline-offset: 2px; +} + +html, +body { + padding: 0; + margin: 0; + background-color: var(--color-background); + color: var(--color-foreground); +} + +body { + font-family: var(--font-body); + line-height: 1.5; +} + +a { + color: var(--color-links); + text-decoration: none; +} + +a:hover { + color: var(--color-links-hover); + text-decoration: underline; +} + +hr { + display: block; + height: 1px; + border: 0; + background-color: var(--color-border); + margin-top: 2rem; + margin-bottom: 2rem; +} + +input:where([type="text"]), +input:where([type="search"]) { + display: block; + border: 1px solid var(--color-border); + width: 100%; + font: inherit; + line-height: 1; + height: calc(1ch + 1.5em); + padding-right: 0.5em; + padding-left: 0.5em; + background-color: hsl(0 0% 100% / 20%); + color: var(--color-foreground); +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.container { + --gutter: 16px; + width: 1024px; + max-width: calc(100% - var(--gutter) * 2); + margin-right: auto; + margin-left: auto; +} diff --git a/examples/basic-preact/package.json b/examples/basic-preact/package.json new file mode 100644 index 00000000000..98ea31b73df --- /dev/null +++ b/examples/basic-preact/package.json @@ -0,0 +1,31 @@ +{ + "private": true, + "name": "remix-app-template", + "description": "", + "license": "", + "scripts": { + "postinstall": "remix setup node", + "dev": "remix dev", + "build": "remix build", + "start": "remix-serve build" + }, + "dependencies": { + "@remix-run/react": "1.1.3", + "@remix-run/serve": "1.1.3", + "preact": "^10.6.4", + "preact-render-to-string": "^5.1.19", + "react": "npm:@preact/compat@^17.0.2", + "react-dom": "npm:@preact/compat@^17.0.2", + "remix": "1.1.3" + }, + "devDependencies": { + "@remix-run/dev": "1.1.3", + "@types/react": "^17.0.24", + "@types/react-dom": "^17.0.9", + "typescript": "^4.1.2" + }, + "engines": { + "node": ">=14" + }, + "sideEffects": false +} diff --git a/examples/basic-preact/public/favicon.ico b/examples/basic-preact/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8830cf6821b354114848e6354889b8ecf6d2bc61 GIT binary patch literal 16958 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 EKbET!0{{R3 literal 0 HcmV?d00001 diff --git a/examples/basic-preact/remix.config.js b/examples/basic-preact/remix.config.js new file mode 100644 index 00000000000..75647004844 --- /dev/null +++ b/examples/basic-preact/remix.config.js @@ -0,0 +1,11 @@ +/** + * @type {import('@remix-run/dev/config').AppConfig} + */ +module.exports = { + appDirectory: "app", + assetsBuildDirectory: "public/build", + publicPath: "/build/", + serverBuildDirectory: "build", + devServerPort: 8002, + ignoredRouteFiles: [".*"], +}; diff --git a/examples/basic-preact/remix.env.d.ts b/examples/basic-preact/remix.env.d.ts new file mode 100644 index 00000000000..72e2affe311 --- /dev/null +++ b/examples/basic-preact/remix.env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/basic-preact/sandbox.config.json b/examples/basic-preact/sandbox.config.json new file mode 100644 index 00000000000..4363d87a30d --- /dev/null +++ b/examples/basic-preact/sandbox.config.json @@ -0,0 +1,6 @@ +{ + "hardReloadOnChange": true, + "container": { + "port": 3000 + } +} diff --git a/examples/basic-preact/tsconfig.json b/examples/basic-preact/tsconfig.json new file mode 100644 index 00000000000..749e4b959e8 --- /dev/null +++ b/examples/basic-preact/tsconfig.json @@ -0,0 +1,20 @@ +{ + "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ES2019"], + "isolatedModules": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "moduleResolution": "node", + "resolveJsonModule": true, + "target": "ES2019", + "strict": true, + "baseUrl": ".", + "paths": { + "~/*": ["./app/*"] + }, + + // Remix takes care of building everything in `remix build`. + "noEmit": true + } +} From 68e2f9f1e84d6fbcf0741f63c255bddb01490882 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Mon, 24 Jan 2022 15:35:31 +0100 Subject: [PATCH 2/4] fix bug where a dependent missing module would trigger a misleaading error --- packages/remix-dev/cli/commands.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/remix-dev/cli/commands.ts b/packages/remix-dev/cli/commands.ts index 77ec24161af..b5c5ac9fb1a 100644 --- a/packages/remix-dev/cli/commands.ts +++ b/packages/remix-dev/cli/commands.ts @@ -148,9 +148,10 @@ export async function dev(remixRoot: string, modeArg?: string) { let serve = require("@remix-run/serve"); createApp = serve.createApp; express = require("express"); - } catch (err) { + } catch (err: any) { + const missingModule = err.message.split('\n')[0]; throw new Error( - "Could not locate @remix-run/serve. Please verify you have it installed to use the dev command." + `${missingModule}. Please verify you have it installed to use the dev command.` ); } From c7f827555afea86c9487c7aefec90a6702661f65 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Mon, 24 Jan 2022 16:31:07 +0100 Subject: [PATCH 3/4] add name to contributors --- contributors.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/contributors.yml b/contributors.yml index 5c3caab2e7d..796bbb07a7f 100644 --- a/contributors.yml +++ b/contributors.yml @@ -77,6 +77,7 @@ - joaosamouco - johannesbraeunig - johnson444 +- JoviDeCroock - juhanakristian - juwiragiye - kalch From 6cc41b88b33afefcd3dfd37ab1aa9a371b62122d Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 28 Jan 2022 00:25:10 +0100 Subject: [PATCH 4/4] Update examples/basic-preact/app/root.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michaël De Boey --- examples/basic-preact/app/root.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic-preact/app/root.tsx b/examples/basic-preact/app/root.tsx index 46200009328..5781ad6f68d 100644 --- a/examples/basic-preact/app/root.tsx +++ b/examples/basic-preact/app/root.tsx @@ -72,7 +72,7 @@ function Document({ - {process.env.NODE_ENV === "development" ? : null} + );