Skip to content

Instantly share code, notes, and snippets.

@zachleat
Forked from pspeter3/.eleventyignore
Last active October 16, 2023 23:59
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save zachleat/b274ee939759b032bc320be1a03704a2 to your computer and use it in GitHub Desktop.
Save zachleat/b274ee939759b032bc320be1a03704a2 to your computer and use it in GitHub Desktop.
Eleventy 11ty.js Extensions
module.exports = function(eleventyConfig) {
// v2.0.0-canary.19 or newer
eleventyConfig.addExtension([ "11ty.jsx", "11ty.ts", "11ty.tsx" ], {
key: "11ty.js",
});
};
import h from "vhtml";
export const data = {
title: "Eleventy 11ty.js Extensions",
};
interface Context {
log(message: string): void;
}
interface Data {
readonly title: string;
}
export function render(this: Context, { title }: Data) {
this.log(title);
return (
<html lang="en">
<head>
<title>{title}</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@1.5.0/css/pico.min.css"
integrity="sha256-TMMCiSUqLaqob0cQkqOwl6oJLd2X5WKYJ4ML+BQRQOA="
crossorigin="anonymous"
></link>
</head>
<body>
<main className="container">
<h1>{title}</h1>
<p>
Example repo to show the value of{" "}
<a href="https://github.com/11ty/eleventy/issues/2248">
11ty/eleventy#2248
</a>
.
</p>
</main>
</body>
</html>
);
}
{
"name": "11ty-extensions",
"description": "Example repo to show the value of https://github.com/11ty/eleventy/issues/2248",
"scripts": {
"build": "node --require esbuild-register node_modules/.bin/eleventy",
},
"repository": {
"type": "git",
"url": "git+ssh://git@gist.github.com/56931a837cb854c55bb06024287ead95.git"
},
"devDependencies": {
"@11ty/eleventy": "^2.0.0-canary.19",
"@types/vhtml": "^2.2.4",
"esbuild-register": "^3.3.2",
"patch-package": "^6.4.7",
"typescript": "^4.6.3",
"vhtml": "^2.2.0"
}
}
{
"compilerOptions": {
"target": "es2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"jsx": "react", /* Specify what JSX code is generated. */
"jsxFactory": "h", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
"module": "commonjs", /* Specify what module code is generated. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
@pauleveritt
Copy link

The signature for render had two arguments, Context and Data. But it appears TemplateContent._render only passes a single data argument: let rendered = await fn(data).

@zachleat
Copy link
Author

zachleat commented Jan 3, 2023

@pauleveritt I’m not a super typescript expert but I believe it’s declaring this a la https://www.typescriptlang.org/docs/handbook/2/functions.html#declaring-this-in-a-function

@pauleveritt
Copy link

Either way, the example render function above says it takes two arguments: this and some Data object.

But I think that's incorrect. I think only one argument is passed. (Meaning, my question is kind of independent of TS).

@zachleat
Copy link
Author

zachleat commented Jan 3, 2023

@pauleveritt ah, I must be missing something here. In a standard 11ty.js template with a render method (or export) you’re absolutely right, render only passes in one argument: https://www.11ty.dev/docs/languages/javascript/#classes

But above, in a tsx Typescript template, you can declare this separately, no? Perhaps @pspeter3 might be more helpful here, since they wrote the original code 😅

@pspeter3
Copy link

pspeter3 commented Jan 3, 2023

@pauleveritt it's a TypeScript feature which lets you declare the type of this in a function. See https://www.typescriptlang.org/docs/handbook/2/functions.html#declaring-this-in-a-function. The annotation is nice for frameworks like Eleventy which pass data through the thisArg. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call for more details.

@pauleveritt
Copy link

@pspeter3 My apologies, you are exactly right. I just switched back and it works. I guess my previous issue was a typing error not related to the this part.

FWIW, I spent around 20h in front of this last week, migrating a very big Gatsby site. It's been a joy. I wonder if you're interested in two things:

  • Me doing a blessed-by-you tutorial or something
  • Discussing how to avoid prop-drilling, where sub-sub-subcomponent can get to page/collection/config data?

@zachleat
Copy link
Author

zachleat commented Jan 3, 2023

I’m assuming those questions are directed at me and the answer to both is yes.

@ethancalvo
Copy link

ethancalvo commented Feb 3, 2023

Hey, I suggest adding the following to the script section in the package.json example just to demonstrate how to use the eleventy flags

 "serve":"node --require esbuild-register node_modules/.bin/eleventy --serve"

@pauleveritt
Copy link

As an update: @khalidabuhakmeh and I have gotten very, very far into this. One place we stumbled: vhtml doesn't support async. When we got to a component that needed to render an image, we were in trouble.

We investigated Nano JSX, dropped it in. Was quite interesting. The vhtml got its last activity 3 years ago, whereas Nano JSX is quite active. Unfortunately the Suspense component didn't really pan out for the renderSSR approach.

We found a clever workaround on the issue involving, of all things, collections.

@danielrob
Copy link

Why not put the config file in ts too?

"dev": "node --require esbuild-register node_modules/.bin/eleventy --config=.eleventy.ts --serve"

@pauleveritt
Copy link

Well @khalidabuhakmeh put our Gatsby->11ty 2.0 plus TS/TSX/Vite rewrite into production last week. It's been a blast. We did some neat content and component things.

Thanks all for this gist which I stumbled across in December and completely changed directions.

@pspeter3
Copy link

I'm curious how you're using Vite and 11ty together

@pauleveritt
Copy link

@pspeter3 We plan soon to write a bunch of stuff about what we're doing. I'll confess, I approached this as writing a larger system, Gatsby-ish.

I have a wild idea for a next step. To avoid prop drilling, I'd like child components to have access to the this: Context above. It's hard: vhtml is the caller. I have an idea for that. But I worry the work @zachleat is doing on dependency tracking for incremental will get broken.

@pauleveritt
Copy link

@pspeter3 You up for doing a Meet sometime, I can show you what we did? You after all were the trigger. 😀

@pspeter3
Copy link

Sure! Definitely curious to learn more. I also think you may be able to use Preact instead of vhtml so you can pass the data via context. I also think that may be better for memory since vhtml does some caching internally. I'll try playing with that idea today and let you know how it goes.

@pauleveritt
Copy link

@pspeter3 Here is a fairly-recent setup: https://github.com/pauleveritt/g3-tsx ... just before we got out of spike solution mode. Interesting bits:

  • Use Typebox to read frontmatter into validated schemas
  • Custom collections which provide instances of our validated, cleaned-up resource classes
  • A (still primitive) reference system which dereferences relations after the content model is built
  • A very aggressive testing strategy

@pauleveritt
Copy link

@pspeter3 Did you ever get a chance to play with preact to pass data via context?

@pspeter3
Copy link

I'd like to clean up the types better but I got a working prototype with the following:

import { createContext } from "preact";
import { useContext } from "preact/hooks";
import type { FunctionComponent, VNode } from "preact";

export interface EleventyContext {}

const Context = createContext<EleventyContext | null>(null);

export type EleventyComponent<T> = (
  this: EleventyContext,
  data: T
) => VNode<{}>;

export function createEleventContextProvider<T>(
  Component: FunctionComponent<{ data: T }>
): EleventyComponent<T> {
  return function (this: EleventyContext, data: T): VNode<{}> {
    return (
      <Context.Provider value={this}>
        <Component data={data} />
      </Context.Provider>
    );
  };
}

export function useEleventyContext(): EleventyContext {
  const value = useContext(Context);
  if (value === null) {
    throw new Error("Missing EleventyContext");
  }
  return value;
}

@pspeter3
Copy link

This is the way I set up the component

import { Name } from "./_components/name";
import { createEleventContextProvider } from "./_utilities/eleventy-context";

export const data = {
  layout: "html",
  title: "Preact Test",
};

export const render = createEleventContextProvider(
  ({ data: title }: { data: typeof data }) => (
    <>
      <h1>{title}</h1>
      <Name />
    </>
  )
);

The Name component is using the useEleventyContext hook as a test but it's not actually important here.

@pauleveritt
Copy link

Nicer than what I came up with, thanks @pspeter3

@pauleveritt
Copy link

@pspeter3 Can I see your component? Curious if you've eliminated import of h and what are your return types.

@pspeter3
Copy link

pspeter3 commented May 5, 2023

I did manage to eliminate h but I think there is an issue with esbuild-register not respecting the jsx-import-source flag.

@pauleveritt
Copy link

@pspeter3 I think I have h replaced, but have some typing things happening. Any chance you're up for a demo repo or a Google Meet?

@pauleveritt
Copy link

@pspeter3 Here's a recreation of what I'm struggling with. I can make a full repo if it would help https://gist.github.com/pauleveritt/bef52a4f1f7dd9d7d77ed5d53d8d08a3

My guess is that .bind isn't doing what I think it's doing. But inside MyView, the this isn't what it's supposed to be. I tried adding it to the MyView args list but couldn't get the typing right.

@pauleveritt
Copy link

@pspeter3 I made a repo with the minimal example but the render function is returning an object (from JSX processing) when 11ty expects a string. Obviously I don't have the h/rendering installed correctly in tsconfig.

@pauleveritt
Copy link

As a note to others: esbuild-register appears to have landed jsxImportSource last year. Need to investigate how to make it work.

@pspeter3
Copy link

That's for esbuild unfortunately. You need egoist/esbuild-register#84

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment