diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..4b1cd8a9 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: https://joebell.co.uk/sponsors diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d31aca4..95702e25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI +name: ci on: push: @@ -9,22 +9,22 @@ on: jobs: pnpm: runs-on: ubuntu-latest - strategy: fail-fast: false matrix: script: [lint] - steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 - uses: pnpm/action-setup@v2 with: - version: 7 + version: 8 + run_install: false - uses: actions/setup-node@v3 with: cache: "pnpm" node-version-file: ".nvmrc" - - run: pnpm install --frozen-lockfile - - run: pnpm build + - run: pnpm i - name: ${{ matrix.script }} run: pnpm ${{ matrix.script }} diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml deleted file mode 100644 index f24b40dc..00000000 --- a/.github/workflows/commitlint.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: commitlint -on: [pull_request] - -jobs: - commitlint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: wagoid/commitlint-github-action@v4 diff --git a/.husky/commit-msg b/.husky/commit-msg deleted file mode 100755 index cda329bf..00000000 --- a/.husky/commit-msg +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -pnpm exec commitlint --edit "$1" \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index d2902b1c..184b48f9 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -pnpm pre-commit +pnpm precommit diff --git a/.nvmrc b/.nvmrc index b8c9fdcb..ecb0f8a9 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16.15.1 \ No newline at end of file +18.13.0 \ No newline at end of file diff --git a/ACQUISITION.md b/ACQUISITION.md deleted file mode 100644 index 6f246906..00000000 --- a/ACQUISITION.md +++ /dev/null @@ -1,47 +0,0 @@ -

- - - Open to Acquisition 🟢 - - -

- ---- - -# Acquisition - -## Why Is This Project Open to Acquisition? - -Plaiceholder is a labour of love by [Joe Bell][twitter] – a quest for **the** perfect "low quality image placeholder" (LQIP) solution. - -[Joe][twitter] loves to maintain and innovate the project, but is held by the constraints of his day-to-day work and limited free time. - -Under a new ownership, Plaiceholder could: - -- Innovate at a more consistent and faster pace -- Attract more consumers and contributors -- Attract more customers to your product/business (see below) - -## Why Should You Acquire This Project? - -> TL;DR If you're a content API or image storage solution, this project is for you. - -Content APIs and image storage solutions with on-the-fly image optimization services are growing in popularity, for a good reason; enabling developers to build high-performance pages quickly, with faster build-times. - -A lot of these providers don't take into account LQIP generation; which could be fetched on the server/build-time to improve the perceived performance of images used. Plaiceholder's open-source functions could be used under-the-hood to provide that unique functionality, and help you to attract more developers/customers to your service. - -## Terms - -- All open-source code **must** remain open-source -- Stock options will be considered dependent on the company, but a cash sale (as a B2B transaction) is preferred. - -## Contact Details - -**Joe Bell** -Twitter: [@joebell\_][twitter] -Email: [joe@bigattic.com][email] - -[twitter]: https://twitter.com/joebell_ -[email]: mailto:joe@bigattic.com -[repo]: https://github.com/joe-bell/plaiceholder -[site]: https://plaiceholder.co diff --git a/README.md b/README.md index 437a8d1b..6df31d84 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -

- - - Open to Acquisition 🟢 - - -

+> **Note** +> +> The `plaiceholder` project is feature complete and will now be kept in maintenance mode. +> +> [**Read the migration guide**](https://plaiceholder.co/docs/upgrading-to-3) for further information. +> +> If this project has been useful to you, please consider [sponsoring my work](https://joebell.co.uk/sponsors). --- @@ -47,19 +47,7 @@
-## Documentation 📖 - -Visit **[plaiceholder.co][plaiceholder]** to get started with the open-source free-to-use packages. - -### Migrating from `1.0`? - -See the [migration guide](https://github.com/joe-bell/plaiceholder/releases/tag/v2.0.0) for further details. - -## Sponsors - -We're still working on a process for supporting Plaiceholder – check back soon for more details. - -### Long-term Supporters +## Supporters Thanks go to these wondeful people for their long-standing support. @@ -105,14 +93,3 @@ Copyright © 2013-2022, Lovell Fuller. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"). [plaiceholder]: https://plaiceholder.co -[blurhash]: https://blurha.sh/ -[react-blurhash]: https://github.com/woltapp/react-blurhash -[next/image]: https://nextjs.org/docs/basic-features/image-optimization - ---- - -

- - Powered by Vercel - -

diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 8186153c..00000000 --- a/docs/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Website - -This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. - -## Installation - -```console -pnpm install -``` - -## Local Development - -```console -pnpm start -``` - -This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. - -## Build - -```console -pnpm build -``` - -This command generates static content into the `build` directory and can be served using any static contents hosting service. - -## Deployment - -```console -GIT_USER= USE_SSH=true pnpm deploy -``` - -If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. diff --git a/docs/babel.config.js b/docs/babel.config.js deleted file mode 100644 index bfd75dbd..00000000 --- a/docs/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: [require.resolve("@docusaurus/core/lib/babel/preset")], -}; diff --git a/docs/components/IntroHeader/index.tsx b/docs/components/IntroHeader/index.tsx new file mode 100644 index 00000000..d7b26ea8 --- /dev/null +++ b/docs/components/IntroHeader/index.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { cx } from "class-variance-authority"; +import styles from "./styles.module.css"; + +interface IntroHeaderProps { + headline: string; + tagline: string; +} + +function IntroHeader({ headline, tagline }: IntroHeaderProps) { + return ( +
+

+ {headline} +

+

{tagline}

+
+ ); +} + +export default IntroHeader; diff --git a/docs/components/IntroHeader/styles.module.css b/docs/components/IntroHeader/styles.module.css new file mode 100644 index 00000000..470624a0 --- /dev/null +++ b/docs/components/IntroHeader/styles.module.css @@ -0,0 +1,21 @@ +.container { + margin-bottom: 2.5rem; +} + +.container > * { + display: block; + margin: 0; +} + +.container > * + * { + margin-top: 1rem; +} + +.headline { + line-height: 2.5rem; + max-width: 24ch; +} + +.tagline { + font-size: 1.2rem; +} diff --git a/docs/docs/examples/11ty.md b/docs/docs/examples/11ty.md deleted file mode 100644 index 06350873..00000000 --- a/docs/docs/examples/11ty.md +++ /dev/null @@ -1,6 +0,0 @@ -# 11ty - -Includes Base64, CSS, SVG and Tailwind implementations. - -- [**Demo**](https://with-11ty.plaiceholder.co) -- [**View Source**](https://github.com/joe-bell/plaiceholder/tree/main/examples/11ty) diff --git a/docs/docs/examples/remix.md b/docs/docs/examples/remix.md deleted file mode 100644 index 326adab8..00000000 --- a/docs/docs/examples/remix.md +++ /dev/null @@ -1,18 +0,0 @@ -# Remix - -:::info - -1. Some Remix deployment targets don't support plaiceholder (specifically, the `sharp` dependency) - - For most cases, we recommend using Vercel (which this example uses) - -2. Remix doesn't encourage developers to access `public` files – _feel free to correct me on this_ – so only **remote images** are supported in this example - - _(which means no `@plaiceholder/tailwindcss`)_ - -::: - -Includes Base64, CSS, SVG and Tailwind implementations. - -- [**Demo**](https://with-remix.plaiceholder.co) -- [**View Source**](https://github.com/joe-bell/plaiceholder/tree/main/examples/remix) diff --git a/docs/docs/plugins/next.md b/docs/docs/plugins/next.md deleted file mode 100644 index 6f8b2bb5..00000000 --- a/docs/docs/plugins/next.md +++ /dev/null @@ -1,25 +0,0 @@ -# Next.js - -An essential plugin for Next.js, ensuring that all Plaiceholder functions start in the main thread. - -## Installation - -1. Add the package alongside your [existing `plaiceholder` installation](/getting-started): - - ```sh npm2yarn - npm install @plaiceholder/next - ``` - -2. Wrap your Next.js config with `withPlaiceholder`: - - ```js title="next.config.js" - const { withPlaiceholder } = require("@plaiceholder/next"); - - module.exports = withPlaiceholder({ - // your Next.js config - }); - ``` - -## Usage - -…and that's it! Use Next.js and [plaiceholder](/usage) as you would expect. diff --git a/docs/docs/plugins/tailwind.mdx b/docs/docs/plugins/tailwind.mdx deleted file mode 100644 index e3225b6a..00000000 --- a/docs/docs/plugins/tailwind.mdx +++ /dev/null @@ -1,192 +0,0 @@ -# Tailwind - -:::note - -Due to limitations with Tailwind's JIT engine: - -1. `@plaiceholder/tailwindcss` only supports **Local** images. -2. Images must not have an `_` in their name (Tailwind treats this as a space) - -::: - -## Installation - -1. Install the package alongside your existing [`tailwindcss`](https://tailwindcss.com/docs/installation) and [`plaiceholder`](/getting-started) installation: - - ```sh npm2yarn - npm install @plaiceholder/tailwindcss - ``` - -2. Add the plugin to your `tailwind.config.js`: - - ```js title="tailwind.config.js" {2,8} - module.exports = { - content: [], - theme: { - extend: {}, - }, - variants: {}, - plugins: [require("@plaiceholder/tailwindcss")], - }; - ``` - -## Usage - -Once installed, pure CSS image LQIPs can be created with the following JIT class format: - - - - -```html - -
-``` - - - - -```jsx -const Example = () => ( - // returns a pure CSS LQIP for - // `./public/path-to-your-image.jpg` -
-); -``` - - - - -The class **only** returns the "pixels" (`linear-gradient` values), allowing you to configure your preferred "blur" effect: - - - - -```html - -
-``` - - - - -```jsx -const Example = () => ( - // returns a pure CSS LQIP for - // `./public/path-to-your-image.jpg` -
-); -``` - - - - -## Utils - -[Dynamic values aren't supported](https://tailwindcss.com/docs/just-in-time-mode#known-limitations) by JIT mode, meaning arbitrary LQIPs can't be computed. - -The values **must** exist at build-time. - -```jsx -// ❌ NOT POSSIBLE -const Example = ({ src }) => ( -
; -); -``` - -To circumvent this, `@plaiceholder/tailwindcss` offers an additional `utils` entry point to extract image paths from the JIT classes on the server-side. - -For example, in a Next.js setup. - - - - -```js -import { getPlaiceholder } from "plaiceholder"; -import { extractImgSrc } from "@plaiceholder/tailwindcss/utils"; - -try { - const plaiceholder = "plaiceholder-[/path-to-your-image.jpg]"; - - getPlaiceholder(extractImgSrc(plaiceholder)).then(({ img }) => - console.log(img) - ); -} catch (err) { - err; -} - -// Logs -// { -// src: '…', -// width: …, -// height: …, -// type: '…' -// } -``` - - - - -```tsx title="pages/example.tsx" -import * as React from "react"; -import type { InferGetStaticPropsType } from "next"; -import Image from "next/image"; -import { getPlaiceholder } from "plaiceholder"; -import { extractImgSrc } from "@plaiceholder/tailwindcss/utils"; - -export const getStaticProps = async () => { - const plaiceholder = "plaiceholder-[/path-to-your-image.jpg]"; - const { img } = await getPlaiceholder(extractImgSrc(plaiceholder)); - - return { - props: { - img, - plaiceholder, - }, - }; -}; - -const Page: React.FC> = ({ - img, - plaiceholder, -}) => ( -
-
- -
-); - -export default Page; -``` - - - diff --git a/docs/docs/usage.mdx b/docs/docs/usage.mdx deleted file mode 100644 index 9f128152..00000000 --- a/docs/docs/usage.mdx +++ /dev/null @@ -1,434 +0,0 @@ ---- -sidebar_position: 3 ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Usage - -```js -getPlaiceholder(src, options); -``` - -## Parameters - -- `src`: string reference to an image. Can be either; - 1. a file path to an image inside the root `public` directory, referenced from the base URL (`/`). - 2. a remote image URL. -- `options`: _(optional)_ - - - `dir`: a file path to your preferred static assets directory; where local images are resolved from (default: `./public`) - - `size`: an integer (between `4` and `64`) - to adjust the returned placeholder size (default: `4`) - - Sharp Configuration - - Under-the-hood, plaiceholder uses [Sharp](https://sharp.pixelplumbing.com) to transform images; a small selection of options have been exposed for further customization: - - :::note - - Plaiceholder has no plans to expand these options. If you need more control on the ouput, we recommend rolling your own Sharp-based LQIP solution. - - ::: - - - `brightness`: brightness multiplier (default: `1`) - - `format`: force output to a [specified output](https://sharp.pixelplumbing.com/api-output#toformat) (default: `["png"]`) - - `hue`: degrees for hue rotation (no default) - - `lightness`: lightness addend (no default) - - `removeAlpha`: remove alpha channel for transparent images (default: `true`) - - _Note: this option is a [no-op for Blurhash](/#tradeoffs)._ - - - `saturation`: saturation multiplier (default: `1.2`) - -## Return Values - -### `css` - -Converts a specified image into a low-res placeholder, outputted as a set of `linear-gradient`s (in the form of a JavaScript style object). - -For a "blurred" effect, extend the returned styles with `filter: blur()` and `transform: scale()`. - -#### Example - - - - -```js -import { getPlaiceholder } from "plaiceholder"; - -try { - getPlaiceholder("/path-to-your-image.jpg").then(({ css }) => - console.log(css) - ); -} catch (err) { - err; -} - -// Logs -// { -// backgroundImage: "…" -// backgroundPosition: "…" -// backgroundSize: "…" -// backgroundRepeat: "…" -// } -``` - - - - -```tsx title="pages/example.tsx" -import * as React from "react"; -import type { InferGetStaticPropsType } from "next"; -import Image from "next/image"; -import { getPlaiceholder } from "plaiceholder"; - -export const getStaticProps = async () => { - const { css, img } = await getPlaiceholder("/path-to-your-image.jpg"); - - return { - props: { - img, - css, - }, - }; -}; - -const Page: React.FC> = ({ - img, - css, -}) => ( -
-
- - -
-); - -export default Page; -``` - - - - -See the [11ty example](/examples/11ty) - - - - -### `svg` - -Converts a specified image into a low-res placeholder, outputted as an SVG. - -For a "blurred" effect, extend the returned SVG's styles with `filter: blur()` and `transform: scale()`. - -:::note - -Although it returns the SVG in the format of [`React.createElement()`](https://reactjs.org/docs/react-api.html#createelement) arguments, you are not constrained to using React.js. - -e.g. See the 11ty example. - -::: - -#### Example - - - - -```js -import { getPlaiceholder } from "plaiceholder"; - -try { - getPlaiceholder("/path-to-your-image.jpg").then(({ svg }) => - console.log(svg) - ); -} catch (err) { - err; -} - -// Logs -// [ -// "svg", -// { ...svgProps } -// [ -// [ -// "rect", -// { ...rectProps } -// ], -// ...etc -// ] -// ] -``` - - - - -```tsx title="pages/example.tsx" -import * as React from "react"; -import type { InferGetStaticPropsType } from "next"; -import Image from "next/image"; -import { getPlaiceholder } from "plaiceholder"; - -export const getStaticProps = async () => { - const { svg, img } = await getPlaiceholder("/path-to-your-image.jpg"); - - return { - props: { - img, - svg, - }, - }; -}; - -const Page: React.FC> = ({ - img, - svg, -}) => ( -
- {React.createElement( - svg[0], - { - ...svg[1], - style: { - ...svg[1].style, - transform: ["scale(1.5)", svg[1].style.transform].join(" "), - filter: "blur(40px)", - }, - }, - svg[2].map((child) => - React.createElement(child[0], { - key: [child[1].x, child[1].y].join(), - ...child[1], - }) - ) - )} - - -
-); - -export default Page; -``` - -
- - -See the [11ty example](/examples/11ty) - - -
- -### `base64` - -Converts a specified image into a low-res image, encoded as Base64 string. - -For a "blurred" effect, add `filter: blur()` and `transform: scale()` styles to the image. - -#### Example - - - - -```js -import { getPlaiceholder } from "plaiceholder"; - -try { - getPlaiceholder("/path-to-your-image.jpg").then(({ base64 }) => - console.log(base64) - ); -} catch (err) { - err; -} - -// Logs -// data:image/jpeg;base64,/9j/2wBDAAYEBQY… -``` - - - - -```tsx title="pages/example.tsx" -import * as React from "react"; -import type { InferGetStaticPropsType } from "next"; -import Image from "next/image"; -import { getPlaiceholder } from "plaiceholder"; - -export const getStaticProps = async () => { - const { base64, img } = await getPlaiceholder("/path-to-your-image.jpg"); - - return { - props: { - imageProps: { - ...img, - blurDataURL: base64, - }, - }, - }; -}; - -const Page: React.FC> = ({ - imageProps, -}) => ( -
- -
-); - -export default Page; -``` - -
- - -See the [11ty example](/examples/11ty) - - -
- -### `blurhash` - -Converts a specified image into a low-res image, encoded as Blurhash string accompanied by its width and height - -This can be passed into a library such as [react-blurhash](https://github.com/woltapp/react-blurhash). - -#### Example - - - - -```js -import { getPlaiceholder } from "plaiceholder"; - -try { - getPlaiceholder("/path-to-your-image.jpg").then(({ blurhash }) => - console.log(blurhash) - ); -} catch (err) { - err; -} - -// Logs -// { -// hash: "U.QSL{%1bdxtR...", -// height: 32, -// width: 32 -// } -``` - - - - -```tsx title="pages/example.tsx" -import * as React from "react"; -import type { InferGetStaticPropsType } from "next"; -import Image from "next/image"; -import { getPlaiceholder } from "plaiceholder"; -import { BlurhashCanvas } from "react-blurhash"; - -export const getStaticProps = async () => { - const { blurhash, img } = await getPlaiceholder("/path-to-your-image.jpg"); - - return { - props: { - img, - blurhash, - }, - }; -}; - -const Page: React.FC> = ({ - img, - blurhash, -}) => ( -
- - - -
-); - -export default Page; -``` - -
-
- -### `img` - -Returns all essential `` attributes via the `img` object. - -```js -import { getPlaiceholder } from "plaiceholder"; - -try { - getPlaiceholder("/path-to-your-image.jpg").then(({ img }) => - console.log(img) - ); -} catch (err) { - err; -} - -// Logs -// { -// src: '…', -// width: …, -// height: …, -// type: '…' -// } -``` diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js deleted file mode 100644 index 146336b4..00000000 --- a/docs/docusaurus.config.js +++ /dev/null @@ -1,123 +0,0 @@ -// @ts-check - -const path = require("path"); -const { withUrl } = require("./src/utils"); - -const title = "Plaiceholder: Docs"; -const github = "https://github.com/joe-bell/plaiceholder"; - -/** @type {import('@docusaurus/types').DocusaurusConfig} */ -module.exports = { - title, - tagline: "Beautiful image placeholders, without the hassle.", - url: withUrl(), - baseUrl: "/", - onBrokenLinks: "throw", - onBrokenMarkdownLinks: "warn", - favicon: "assets/img/favicon@192px.png", - organizationName: "joe-bell", - projectName: "plaiceholder", - themeConfig: { - algolia: { - appId: "3TA0CXE0R3", - apiKey: "bf663ef0d2d7e3bc4c32fe8c83f29f92", - indexName: "plaiceholder", - contextualSearch: true, - }, - metaTags: [{ name: "data-title", content: title }], - navbar: { - title: "Plaiceholder", - logo: { - href: "/", - alt: "Plaice Fish", - src: "assets/img/logo-light.png", - srcDark: "assets/img/logo-dark.png", - }, - items: [ - { to: "/", label: "Docs", position: "left" }, - { - type: "dropdown", - label: "🤎 Sponsors", - position: "right", - items: [ - { - to: "/sponsors/tips", - label: "Tips & Donations", - }, - ], - }, - { - href: github, - position: "right", - label: "GitHub", - className: "navbar__github-link", - }, - ], - }, - footer: { - style: "dark", - links: [ - { - title: "Learn", - items: [ - { - label: "Introduction", - to: "/", - }, - { - label: "Usage", - to: "/usage", - }, - ], - }, - { - title: "More", - items: [ - { - label: "GitHub", - href: github, - }, - { - label: "Twitter", - href: "https://twitter.com/joebell_", - }, - ], - }, - { - items: [ - { - html: ` -
- Powered by Vercel - `, - }, - ], - }, - ], - copyright: `Copyright © ${new Date().getFullYear()} Joe Bell`, - }, - }, - presets: [ - [ - "@docusaurus/preset-classic", - { - docs: { - sidebarPath: require.resolve("./sidebars.js"), - editUrl: path.join(github, "/edit/main/docs/"), - path: "docs", - routeBasePath: "/", - remarkPlugins: [ - [require("@docusaurus/remark-plugin-npm2yarn"), { sync: true }], - ], - }, - pages: { - remarkPlugins: [require("@docusaurus/remark-plugin-npm2yarn")], - }, - blog: false, - theme: { - customCss: require.resolve("./src/css/custom.css"), - }, - }, - ], - ], -}; diff --git a/docs/next-env.d.ts b/docs/next-env.d.ts new file mode 100644 index 00000000..4f11a03d --- /dev/null +++ b/docs/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/docs/next.config.mjs b/docs/next.config.mjs new file mode 100644 index 00000000..e996f8dd --- /dev/null +++ b/docs/next.config.mjs @@ -0,0 +1,24 @@ +import nextra from "nextra"; + +const withNextra = nextra({ + theme: "nextra-theme-docs", + themeConfig: "./theme.config.tsx", + staticImage: true, +}); + +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { + async redirects() { + return [ + { + source: "/", + destination: "/docs", + permanent: false, + }, + ]; + }, +}; + +export default withNextra(nextConfig); diff --git a/docs/package.json b/docs/package.json index 52039f15..73d1adf8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,54 +5,22 @@ "author": "Joe Bell (https://joebell.co.uk)", "license": "Apache-2.0", "scripts": { - "build": "docusaurus build", - "clear": "docusaurus clear", - "deploy": "docusaurus deploy", - "dev": "docusaurus start", - "docusaurus": "docusaurus", + "build": "next build", + "dev": "next dev", "lint:ts": "tsc --noEmit", - "serve": "docusaurus serve", - "swizzle": "docusaurus swizzle", - "write-heading-ids": "docusaurus write-heading-ids", - "write-translations": "docusaurus write-translations" - }, - "browserslist": { - "production": [ - ">0.5%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "next": "pnpm next" }, "dependencies": { - "@docusaurus/core": "2.0.1", - "@docusaurus/plugin-sitemap": "2.0.1", - "@docusaurus/preset-classic": "2.0.1", - "@docusaurus/remark-plugin-npm2yarn": "2.0.1", - "@docusaurus/theme-classic": "2.0.1", - "@docusaurus/theme-common": "2.0.1", - "@mdx-js/react": "1.6.22", - "@svgr/webpack": "6.2.1", - "clsx": "1.1.1", - "file-loader": "6.2.0", - "prism-react-renderer": "1.3.3", - "react": "17.0.2", - "react-dom": "17.0.2", - "url-loader": "4.1.1" + "class-variance-authority": "0.6.0", + "next": "13.4.3", + "nextra": "^2.5.2", + "nextra-theme-docs": "^2.5.2", + "react": "18.2.0", + "react-dom": "18.2.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.0.1", - "@docusaurus/types": "2.0.1", - "@tsconfig/docusaurus": "1.0.6", - "@types/node": "18.7.13", - "@types/react": "17.0.45", - "@types/react-helmet": "6.1.5", - "@types/react-router-dom": "5.3.3", - "typescript": "4.7.4", - "webpack": "^5.74.0" + "@types/node": "20.1.7", + "@types/react": "18.2.6", + "typescript": "5.0.4" } } diff --git a/docs/pages/_app.tsx b/docs/pages/_app.tsx new file mode 100644 index 00000000..2b73f4c9 --- /dev/null +++ b/docs/pages/_app.tsx @@ -0,0 +1,10 @@ +import type { AppProps } from "next/app"; +import "../styles/global.css"; + +export default function MyApp({ Component, pageProps }: AppProps) { + return ( + <> + + + ); +} diff --git a/docs/pages/_document.tsx b/docs/pages/_document.tsx new file mode 100644 index 00000000..ae954886 --- /dev/null +++ b/docs/pages/_document.tsx @@ -0,0 +1,21 @@ +import { Html, Head, Main, NextScript } from "next/document"; +import Script from "next/script"; + +export default function Document() { + return ( + + + + + +
+ + + + ); +} diff --git a/docs/pages/_meta.json b/docs/pages/_meta.json new file mode 100644 index 00000000..346dd0bd --- /dev/null +++ b/docs/pages/_meta.json @@ -0,0 +1,17 @@ +{ + "index": { + "type": "page", + "title": "Introduction", + "display": "hidden" + }, + "docs": { + "type": "page", + "title": "Documentation" + }, + "sponsors": { + "type": "page", + "title": "Sponsors", + "href": "https://joebell.co.uk/sponsors", + "newWindow": true + } +} diff --git a/docs/pages/docs/_meta.json b/docs/pages/docs/_meta.json new file mode 100644 index 00000000..d6392546 --- /dev/null +++ b/docs/pages/docs/_meta.json @@ -0,0 +1,10 @@ +{ + "index": "Introduction", + "getting-started": "", + "upgrading-to-3": "Upgrading to 3.0", + "usage": "", + "plugins": "Plugins", + "examples": "Examples", + "faqs": "FAQs", + "community": "" +} diff --git a/docs/docs/community.mdx b/docs/pages/docs/community.mdx similarity index 56% rename from docs/docs/community.mdx rename to docs/pages/docs/community.mdx index 06447ccd..3ca5c886 100644 --- a/docs/docs/community.mdx +++ b/docs/pages/docs/community.mdx @@ -1,17 +1,15 @@ ---- -sidebar_position: 7 ---- +import { Callout } from "nextra-theme-docs"; # Community ## Alternative Implementations -Want to use Plaiceholder in a non-Node.js environment? The community have got you covered, with a fantastic selection of externally-maintained alternative implementations: +Want to use Plaiceholder in a non-JS environment? The community have got you covered, with a fantastic selection of externally-maintained alternative implementations: - [**php-plaiceholder**](https://github.com/Accudio/php-plaiceholder) by [@accudio](https://twitter.com/Accudio) -:::note + Want to create your own implementation? See the [contributing guidelines](https://github.com/joe-bell/plaiceholder/blob/main/CONTRIBUTING.md#implementations). -::: + diff --git a/docs/docs/examples/_category_.json b/docs/pages/docs/examples/_category_.json similarity index 100% rename from docs/docs/examples/_category_.json rename to docs/pages/docs/examples/_category_.json diff --git a/docs/docs/examples/astro.md b/docs/pages/docs/examples/astro.md similarity index 71% rename from docs/docs/examples/astro.md rename to docs/pages/docs/examples/astro.md index 2dbf15ac..59d9d856 100644 --- a/docs/docs/examples/astro.md +++ b/docs/pages/docs/examples/astro.md @@ -1,6 +1,4 @@ # Astro -Includes Base64, CSS, SVG and Tailwind implementations. - - [**Demo**](https://with-astro.plaiceholder.co) - [**View Source**](https://github.com/joe-bell/plaiceholder/tree/main/examples/astro) diff --git a/docs/docs/examples/next.md b/docs/pages/docs/examples/next.md similarity index 68% rename from docs/docs/examples/next.md rename to docs/pages/docs/examples/next.md index 796d45ee..f6223c9f 100644 --- a/docs/docs/examples/next.md +++ b/docs/pages/docs/examples/next.md @@ -1,6 +1,4 @@ # Next.js -Includes Base64, Blurhash, CSS, SVG and Tailwind implementations. - - [**Demo**](https://with-next.plaiceholder.co) - [**View Source**](https://github.com/joe-bell/plaiceholder/tree/main/examples/next) diff --git a/docs/docs/faqs.md b/docs/pages/docs/faqs.mdx similarity index 53% rename from docs/docs/faqs.md rename to docs/pages/docs/faqs.mdx index 23bb58b8..ef72d731 100644 --- a/docs/docs/faqs.md +++ b/docs/pages/docs/faqs.mdx @@ -1,13 +1,13 @@ ---- -sidebar_position: 6 ---- +import { Callout } from "nextra-theme-docs"; # FAQs -## Why have you misspelled "placeholder"? + -A [Plaice](https://en.wikipedia.org/wiki/European_plaice) is a flat fish that lays stationary on the sea-bed, much like an image placehol… actually this is bullshit, all the other good names were taken. +**`plaiceholder` packages are [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).** -## What about remote images in Tailwind? + -In it's current state, `@plaiceholder/tailwindcss` only supports local images (added to `public`). PRs to add support for remote images are welcomed ❤️. +## Why have you misspelled "placeholder"? + +A [Plaice](https://en.wikipedia.org/wiki/European_plaice) is a flat fish that lays stationary on the sea-bed, much like an image placehol… actually this is bullshit, all the other good names were taken. diff --git a/docs/docs/getting-started.mdx b/docs/pages/docs/getting-started.mdx similarity index 51% rename from docs/docs/getting-started.mdx rename to docs/pages/docs/getting-started.mdx index 82e4ce0a..29644347 100644 --- a/docs/docs/getting-started.mdx +++ b/docs/pages/docs/getting-started.mdx @@ -1,15 +1,18 @@ ---- -sidebar_position: 2 ---- +import { Callout } from "nextra-theme-docs"; # Getting Started -:::note + -Plaiceholder is a [Node.js](https://nodejs.org/en/) library. -It's designed **only** to work in a Node.js environment, **not** the browser. +**`plaiceholder` packages are [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).** -::: + + + + +Plaiceholder is a server-side library. It will **not** work in the browser. + + ## Prerequisites @@ -17,12 +20,12 @@ Under-the-hood, Plaiceholder uses the wonderful and powerful [`sharp`](https://s Some frameworks or libraries include `sharp` by default, so double-check before you install. -```sh npm2yarn +```sh npm install sharp ``` ## Installation -```sh npm2yarn +```sh npm install plaiceholder ``` diff --git a/docs/docs/intro.mdx b/docs/pages/docs/index.mdx similarity index 64% rename from docs/docs/intro.mdx rename to docs/pages/docs/index.mdx index 12e3098b..fea25ed4 100644 --- a/docs/docs/intro.mdx +++ b/docs/pages/docs/index.mdx @@ -1,23 +1,26 @@ ---- -sidebar_position: 1 -slug: / -hide_title: true ---- - -import IntroHeader from "../src/components/IntroHeader"; - -# Introduction +import { Callout } from "nextra-theme-docs"; +import IntroHeader from "../../components/IntroHeader"; ---- + + +**`plaiceholder@3.0`: The Final Release** + +The `plaiceholder` project is feature complete and will now be kept in maintenance mode. + +[**Read the migration guide**](/docs/upgrading-to-3) for further information. + +If this project has been useful to you, please consider [sponsoring my work](https://joebell.co.uk/sponsors). + + ## Overview -"Plaiceholder" is a suite of **Node.js** functions for creating low quality image placeholders (LQIP). +"Plaiceholder" is a suite of **server-side** functions for creating low quality image placeholders (LQIP). There is no "one-size-fits-all" LQIP strategy, so we offer them all… @@ -27,8 +30,6 @@ There is no "one-size-fits-all" LQIP strategy, so we offer them all… ~1.2kB when rendered in HTML - [**Base64**](/usage#base64) ~300B asset size -- [**Blurhash**](/usage#blurhash) - Size TBC (but ~3.6kB if using [`react-blurhash`](https://bundlephobia.com/package/react-blurhash)) Give each strategy a try to see what works best for your individual use-case. @@ -42,6 +43,3 @@ Whilst most strategies offer fast `DOMContentLoaded` and `LCP`, it's important t Doesn't (yet) leverage [SVG filter primitives](https://developer.mozilla.org/en-US/Web/SVG/Element/feGaussianBlur) - [**Base64**](/usage#base64) Not the nicest looking. Even though plaiceholder makes an opinionated choice to slightly increase saturation for base64 images under-the-hood, colors can often appear drab -- [**Blurhash**](/usage#blurhash) - Uses `canvas`, so it's not ideal to use with above-the-fold content. - [Doesn't account for transparency](https://github.com/woltapp/blurhash/issues/100). diff --git a/docs/docs/plugins/_category_.json b/docs/pages/docs/plugins/_category_.json similarity index 100% rename from docs/docs/plugins/_category_.json rename to docs/pages/docs/plugins/_category_.json diff --git a/docs/pages/docs/plugins/next.mdx b/docs/pages/docs/plugins/next.mdx new file mode 100644 index 00000000..1d19dfad --- /dev/null +++ b/docs/pages/docs/plugins/next.mdx @@ -0,0 +1,46 @@ +import { Callout } from "nextra-theme-docs"; + +# Next.js + + + +**`plaiceholder` packages are [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).** + + + +An **essential** plugin for Next.js, ensuring that all Plaiceholder functions start in the main thread. + +## Installation + +1. Add the package alongside your [existing `plaiceholder` installation](/getting-started): + + ```sh + npm install @plaiceholder/next + ``` + +2. Wrap your Next.js config with `withPlaiceholder`: + + + + Your Next.js config **must** use the `.mjs` extension. + _(or `.ts` when supported)_ + + + + ```js filename="next.config.mjs" + // @ts-check + import withPlaiceholder from "@plaiceholder/next"; + + /** + * @type {import('next').NextConfig} + */ + const config = { + // your Next.js config + }; + + export default withPlaiceholder(config); + ``` + +## Usage + +…and that's it! Use Next.js and [plaiceholder](/usage) as you would expect. diff --git a/docs/pages/docs/plugins/tailwind.mdx b/docs/pages/docs/plugins/tailwind.mdx new file mode 100644 index 00000000..4e264d98 --- /dev/null +++ b/docs/pages/docs/plugins/tailwind.mdx @@ -0,0 +1,127 @@ +import { Callout, Tab, Tabs } from "nextra-theme-docs"; + +# Tailwind + + + +**`plaiceholder` packages are [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).** + + + +## Installation + +1. Install the package alongside your existing [`tailwindcss`](https://tailwindcss.com/docs/installation) and [`plaiceholder`](/getting-started) installation: + + ```sh + npm install @plaiceholder/tailwindcss + ``` + +2. Add the plugin to your Tailwind config: + + + + Your Tailwind config **must** use the `.mjs` or `.ts` extension. + + + + ```js filename="tailwind.config.mjs" {2,11} + // @ts-check + import plaiceholder from "@plaiceholder/tailwindcss"; + + /** @type {import('tailwindcss').Config} */ + export default { + content: [], + theme: { + extend: {}, + }, + variants: {}, + plugins: [plaiceholder()], + }; + ``` + +3. Implement a `resolver` to control how the plugin converts a specified image path (the content within the class square brackets `[]`) into a `Buffer`. + + For example: + + ```js filename="tailwind.config.mjs" {15-16} + // @ts-check + import fs from "node:fs"; + import path from "node:path"; + import plaiceholder from "@plaiceholder/tailwindcss"; + + /** @type {import('tailwindcss').Config} */ + export default { + content: [], + theme: { + extend: {}, + }, + variants: {}, + plugins: [ + plaiceholder({ + resolver: (src) => + fs.readFileSync(path.join("./public", `${src}.jpg`)), + }), + ], + }; + ``` + +## Usage + +Once installed, pure CSS image LQIPs can be created with the following class format: + +```html + +
+``` + +The class **only** returns the "pixels" (`linear-gradient` values), allowing you to configure your preferred "blur" effect: + +```html + +
+``` + +## Utils + +[Dynamic values aren't supported](https://tailwindcss.com/docs/just-in-time-mode#known-limitations) by JIT mode, meaning arbitrary LQIPs can't be computed. + +The values **must** exist at build-time. + +```jsx +// ❌ NOT POSSIBLE +const Example = ({ src }) => ( +
; +); +``` + +To circumvent this, `@plaiceholder/tailwindcss` offers an additional `utils` entry point to extract image paths from the classes on the server-side. + +```js +import { extractImgSrc } from "@plaiceholder/tailwindcss/utils"; + +const plaiceholder = "plaiceholder-[image-slug]"; +const src = extractImgSrc(plaiceholder); + +console.log(src); + +// Logs +// "image-slug" +``` + +## Limitations + +1. Supports **only** ESM/TypeScript Tailwind config files. + _i.e. `tailwind.config.mjs` or `tailwind.config.ts`_ +2. Images must **not** have an `_` in their name + _Tailwind treats this as a space._ + +3. Only Node.js environments are supported + _Tailwind doesn't support asynchrnous configuration, so this plugin depends on `make-synchronous` to work; a library that uses Node.js internals._ diff --git a/docs/pages/docs/upgrading-to-3.mdx b/docs/pages/docs/upgrading-to-3.mdx new file mode 100644 index 00000000..cba4ce04 --- /dev/null +++ b/docs/pages/docs/upgrading-to-3.mdx @@ -0,0 +1,122 @@ +import { Callout } from "nextra-theme-docs"; + +# Upgrading to 3.0 + +`3.0` marks the end of the `plaiceholder` project, with this major release focussing on making the project feature complete and as future-proof as possible. + +Once the `peerDependency` `sharp` [enables support for Non-Node.js projects](https://github.com/lovell/sharp/pull/3522), `plaiceholder` _should_ work seamlessly on all supported runtimes. + +Preparing for wider support meant dropping some of the "magic" features that `plaiceholder` previously offered. +There is **no obligation** to upgrade to `3.0` if you would prefer to keep using the previous API! + +If this project has been useful to you, please consider [sponsoring my work](https://joebell.co.uk/sponsors). + +## Migrate to ESM + + + +**`plaiceholder` packages are [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).** + + + +If you're using any of the `plaiceholder` packages in a CommonJS project, you'll need to migrate to ESM before continuing. + +## Update Your Dependencies + +Ensure all `plaiceholder` and `@plaiceholder/*` packages are set to at least `3.0.0`. + +## Update Your Code + +### `plaiceholder` + +1. **Implement your own image resolver** + + Previously, `plaiceholder` would automatically resolve your image based on a specified file path or URL, e.g. `getPlaiceholder('./path-to-image')`. + + To enable future adoption within other non-Node.js runtimes, this feature has been removed. + + Instead, **you will need to pass a `Buffer`** to `getPlaiceholder()`, based on your own use-case. + + In the case of Node.js, for local images in `/public`: + + ```js + import path from "node:path"; + import fs from "node:fs/promises"; + + const getImage = async (src: string) => { + const file = await fs.readFile(path.join("./public", src)); + const buffer = await fs.readFile(file); + + const { + metadata: { height, width }, + ...plaiceholder + } = await getPlaiceholder(buffer, { size: 10 }); + + return { + ...plaiceholder, + img: { src, height, width }, + }; + }; + + // Usage + const { base64, img } = await getImage("/assets/image/example.jpg"); + ``` + + In the case of Node.js, for remote images: + + ```js + const getImage = async (src: string) => { + const buffer = await fetch(src).then(async (res) => + Buffer.from(await res.arrayBuffer()) + ); + + const { + metadata: { height, width }, + ...plaiceholder + } = await getPlaiceholder(buffer, { size: 10 }); + + return { + ...plaiceholder, + img: { src, height, width }, + }; + }; + + // Usage + const { base64, img } = await getImage( + "https://images.unsplash.com/photo-1621961458348-f013d219b50c?auto=format&fit=crop&w=2850&q=80" + ); + ``` + +1. **`blurhash` is no longer returned** + + Switch any `blurhash` consumers to an alternative LQIP strategy, or use the `blurhash` package directly. + +1. **`img` → `metadata`** + + Update any `img` consumers to make use of the new `metadata` object. + + Instead, use `metadata` to [create your **own** `` attributes](/docs/usage#generating-img--attributes). + + _Note: `img.type` has moved to `metadata.format`._ + +1. **No longer `removeAlpha` by default** + + To preserve previous behaviour, you can specify `removeAlpha: true` within `getPlaiceholder()`'s options. + +1. **Update Your Types** + + - `TGetPlaiceholderSrc` → `GetPlaiceholderSrc` + - `IGetPlaiceholderOptions` → `GetPlaiceholderOptions` + - `IGetPlaiceholderReturn` → `GetPlaiceholderReturn` + - `IGetPlaiceholder` → `typeof getPlaiceholder` + +### `@plaiceholder/next` + +1. Migrate your `next.config.js` to `next.config.mjs` + _(or `.ts` when supported)_ +1. Ensure your config matches the [configuration steps](/docs/plugins/next). + +### `@plaiceholder/tailwindcss` + +1. Migrate your `tailwind.config.js` to `tailwind.config.mjs` or `tailwind.config.ts` +1. Follow the [configuration steps to add a `resolver`](/docs/plugins/tailwind). diff --git a/docs/pages/docs/usage.mdx b/docs/pages/docs/usage.mdx new file mode 100644 index 00000000..8d888427 --- /dev/null +++ b/docs/pages/docs/usage.mdx @@ -0,0 +1,278 @@ +import { Callout, Tab, Tabs } from "nextra-theme-docs"; + +# Usage + + + +**`plaiceholder` packages are [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).** + + + +```js +getPlaiceholder(input, options); +``` + +## Parameters + +- `input`: raw `Buffer` image source. + +- `options`: _(optional)_ + + - `size`: an integer (between `4` and `64`) + to adjust the returned placeholder size (default: `4`) + - `autoOrient`: automatically orient image based on its EXIF data (default: `false`) + - Sharp Configuration + + Under-the-hood, plaiceholder uses [Sharp](https://sharp.pixelplumbing.com) to transform images; a small selection of options have been exposed for further customization: + + + + Plaiceholder has no plans to expand these options. If you need more control on the ouput, we recommend rolling your own Sharp-based LQIP solution. + + + + - `brightness`: brightness multiplier (default: `1`) + - `format`: force output to a [specified output](https://sharp.pixelplumbing.com/api-output#toformat) (default: `["png"]`) + - `hue`: degrees for hue rotation (no default) + - `lightness`: lightness addend (no default) + - `removeAlpha`: remove alpha channel for transparent images (default: `false`) + - `saturation`: saturation multiplier (default: `1.2`) + +## Return Values + +### `base64` + +Converts a specified image into a low-res image, encoded as Base64 string. + +For a "blurred" effect, add `filter: blur()` and `transform: scale()` styles to the image. + +```js +import fs from "node:fs/promises"; +import { getPlaiceholder } from "plaiceholder"; + +try { + const file = await fs.readFile("/path-to-your-image.jpg"); + + const { base64 } = await getPlaiceholder(file); + + console.log(base64); +} catch (err) { + err; +} + +// Logs +// data:image/jpeg;base64,/9j/2wBDAAYEBQY… +``` + +### `color` + +Gets the dominant color from a specified image, as individual `r`, `g`,`b` values and as a `hex` code. + +```js +import fs from "node:fs/promises"; +import { getPlaiceholder } from "plaiceholder"; + +try { + const file = await fs.readFile("/path-to-your-image.jpg"); + + const { color } = await getPlaiceholder(file); + + console.log(color); +} catch (err) { + err; +} + +// Logs +// { +// r: …, +// g: …, +// b: …, +// hex: '…' }, +// } +``` + +### `css` + +Converts a specified image into a low-res placeholder, outputted as a set of `linear-gradient`s (in the form of a JavaScript style object). + +For a "blurred" effect, extend the returned styles with `filter: blur()` and `transform: scale()`. + +```js +import fs from "node:fs/promises"; +import { getPlaiceholder } from "plaiceholder"; + +try { + const file = await fs.readFile("/path-to-your-image.jpg"); + + const { css } = await getPlaiceholder(file); + + console.log(css); +} catch (err) { + err; +} + +// Logs +// { +// backgroundImage: "…" +// backgroundPosition: "…" +// backgroundSize: "…" +// backgroundRepeat: "…" +// } +``` + +### `metadata` + +Returns all input metadata ([via `sharp`](https://sharp.pixelplumbing.com/api-input#metadata)). + +```js +import fs from "node:fs/promises"; +import { getPlaiceholder } from "plaiceholder"; + +try { + const file = await fs.readFile("/path-to-your-image.jpg"); + + const { metadata } = await getPlaiceholder(file); + + console.log(metadata); +} catch (err) { + err; +} + +// Logs +// { +// width: …, +// height: …, +// format: '…', +// size: …, +// space: '…', +// channels: …, +// depth: '…', +// density: …, +// chromaSubsampling: '…', +// isProgressive: …, +// resolutionUnit: '…', +// hasProfile: …, +// hasAlpha: …, +// orientation: …, +// exif: …, +// icc: …, +// iptc: …, +// xmp: … +// } +``` + +#### Generating `` Attributes + +A common use-case for `metadata` would be to generate all essential attributes for your source ``: + +```js +import fs from "node:fs/promises"; +import { getPlaiceholder } from "plaiceholder"; + +try { + const src = "/path-to-your-image.jpg"; + const file = await fs.readFile(src); + + const { + metadata: { height, width }, + } = await getPlaiceholder(file); + + const img = { src, height, width }; + + console.log(img); +} catch (err) { + err; +} + +// Logs +// { +// src: '…', +// width: …, +// height: …, +// } +``` + +### `pixels` + +Returns the raw output pixel values as an array of rows, where each row item corresponds to a column. + +Each pixel is represented by individual `r`, `g` and`b` values. + +For images with transparency, an `a` value represents the pixel's alpha value. + +```js +import fs from "node:fs/promises"; +import { getPlaiceholder } from "plaiceholder"; + +try { + const file = await fs.readFile("/path-to-your-image.jpg"); + + const { color } = await getPlaiceholder(file); + + console.log(color); +} catch (err) { + err; +} + +// Logs +// [ +// // Row 1 +// [ +// // Row 1, Column 1 +// { r: 179, g: 155, b: 178 }, +// // Row 1, Column 2 +// { r: 246, g: 152, b: 170 }, +// // Row 1, Column 3 +// { r: 145, g: 106, b: 123 } +// ], +// // Row 2 +// [ +// // Row 2, Column 1 +// { r: 179, g: 155, b: 178 }, +// // Row 2, Column 2 +// { r: 246, g: 152, b: 170 }, +// // Row 2, Column 3 +// { r: 145, g: 106, b: 123 } +// ], +// ] +``` + +### `svg` + +Converts a specified image into a low-res placeholder, outputted as an SVG. + +For a "blurred" effect, extend the returned SVG's styles with `filter: blur()` and `transform: scale()`. + + + +Although it returns the SVG in the format of [`React.createElement()`](https://reactjs.org/docs/react-api.html#createelement) arguments, you are not constrained to using React.js. + + + +```js +import fs from "node:fs/promises"; +import { getPlaiceholder } from "plaiceholder"; + +try { + const file = await fs.readFile("/path-to-your-image.jpg"); + + const { svg } = await getPlaiceholder(file); + + console.log(svg); +} catch (err) { + err; +} + +// Logs +// [ +// "svg", +// { ...svgProps } +// [ +// [ +// "rect", +// { ...rectProps } +// ], +// ...etc +// ] +// ] +``` diff --git a/docs/static/assets/img/favicon@192px.png b/docs/public/assets/img/favicon@192px.png similarity index 100% rename from docs/static/assets/img/favicon@192px.png rename to docs/public/assets/img/favicon@192px.png diff --git a/docs/static/assets/img/logo@192px.png b/docs/public/assets/img/logo@192px.png similarity index 100% rename from docs/static/assets/img/logo@192px.png rename to docs/public/assets/img/logo@192px.png diff --git a/docs/static/assets/img/logo-light.png b/docs/public/assets/img/logo@96px.png similarity index 100% rename from docs/static/assets/img/logo-light.png rename to docs/public/assets/img/logo@96px.png diff --git a/docs/public/assets/img/og.png b/docs/public/assets/img/og.png new file mode 100644 index 00000000..f5b1bdc0 Binary files /dev/null and b/docs/public/assets/img/og.png differ diff --git a/docs/sidebars.js b/docs/sidebars.js deleted file mode 100644 index c0889202..00000000 --- a/docs/sidebars.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - tutorialSidebar: [{ type: "autogenerated", dirName: "." }], -}; diff --git a/docs/src/components/IntroHeader/index.jsx b/docs/src/components/IntroHeader/index.jsx deleted file mode 100644 index 7132bde6..00000000 --- a/docs/src/components/IntroHeader/index.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from "react"; -import styles from "./styles.module.css"; - -/** - * @param {Record<'headline' | 'tagline', string>} props - */ -function IntroHeader({ headline, tagline, link }) { - return ( -
-

{headline}

-

{tagline}

-
- ); -} - -export default IntroHeader; diff --git a/docs/src/components/IntroHeader/styles.module.css b/docs/src/components/IntroHeader/styles.module.css deleted file mode 100644 index 298aad02..00000000 --- a/docs/src/components/IntroHeader/styles.module.css +++ /dev/null @@ -1,77 +0,0 @@ -.container { - margin-bottom: 2.5rem; -} - -.container > * { - display: block; - margin: 0; -} - -.container > * + * { - margin-top: 1rem; -} - -.headline { - font-weight: 700; - font-size: 2.25rem; - line-height: 2.5rem; - margin-top: 0.5rem; - letter-spacing: -0.015em; -} - -.tagline { - font-size: 1.2rem; - color: var(--ifm-color-gray-700); -} - -[data-theme="dark"] .tagline { - color: var(--ifm-color-gray-300); -} - -.btn { - --btn-color: var(--ifm-color-white); - --btn-bg-color: var(--ifm-color-gray-900); - --btn-bg-color-hover: var(--ifm-color-gray-800); - display: inline-flex; - appearance: none; - align-items: center; - justify-content: center; - transition: 250ms ease; - transition-property: color, background-color; - user-select: none; - position: relative; - white-space: nowrap; - vertical-align: middle; - text-decoration: none; - outline: none; - width: auto; - line-height: 1.2; - border-radius: 0.375rem; - font-weight: 600; - height: 2.5rem; - min-width: 2.5rem; - font-size: 1rem; - padding-left: 1rem; - padding-right: 1rem; - background-color: var(--btn-bg-color); - color: var(--btn-color); -} - -.btn.btn { - text-decoration: none; -} - -.btn:hover { - color: var(--btn-color); - background-color: var(--btn-bg-color-hover); -} - -.btn:focus { - z-index: 1; - box-shadow: 0 0 0 3px rgb(66 153 225 / 60%); -} - -[data-theme="dark"] .btn { - --btn-bg-color: var(--ifm-color-gray-800); - --btn-bg-color-hover: var(--ifm-color-gray-700); -} diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css deleted file mode 100644 index 5988c2dd..00000000 --- a/docs/src/css/custom.css +++ /dev/null @@ -1,109 +0,0 @@ -/* stylelint-disable docusaurus/copyright-header */ -/** - * Any CSS included here will be global. The classic template - * bundles Infima by default. Infima is a CSS framework designed to - * work well for content-centric websites. - */ - -/* You can override the default Infima variables here. */ -:root { - --ifm-color-primary: #1f2937; - --ifm-color-primary-dark: var(--ifm-color-primary); - --ifm-color-primary-darker: var(--ifm-color-primary); - --ifm-color-primary-darkest: var(--ifm-color-primary); - --ifm-color-primary-light: var(--ifm-color-primary); - --ifm-color-primary-lighter: var(--ifm-color-primary); - --ifm-color-primary-lightest: var(--ifm-color-primary); - --ifm-footer-link-hover-color: var(--ifm-color-gray-300); - --ifm-code-font-size: 95%; -} - -[data-theme="dark"] { - --ifm-color-primary: #ffffff; -} - -.docusaurus-highlight-code-line { - background-color: rgb(72, 77, 91); - display: block; - margin: 0 calc(-1 * var(--ifm-pre-padding)); - padding: 0 var(--ifm-pre-padding); -} - -.markdown a { - text-decoration: underline; -} - -.navbar__brand { - --brand-padding: 0.5rem; - --brand-margin: calc(var(--brand-padding) * -1); - display: inline-flex; - padding: var(--brand-padding); - margin: var(--brand-margin); - margin-right: calc(var(--brand-margin) + 1rem); - border-radius: 0.25rem; - flex-direction: row-reverse; - text-transform: lowercase; - color: var(--ifm-color-primary); - align-items: center; -} - -.navbar__brand:hover { - background-color: var(--ifm-menu-color-background-hover); -} - -.navbar__title { - font-weight: 700; - font-size: 1.25rem; - color: var(--ifm-color-primary); -} - -.navbar__logo { - max-height: 1.5rem; - flex-shrink: 0; - margin-right: 0; - margin-left: 0.5rem; -} - -.navbar__github-link { - align-items: center; - justify-content: flex-start; -} - -@media (min-width: 996px) { - .navbar__github-link { - display: inline-flex; - } -} - -.navbar__github-link::before { - content: ""; - position: relative; - flex-grow: 0; - background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - height: 1.5rem; - width: 1.5rem; - margin-right: 0.5rem; - vertical-align: middle; -} - -[data-theme="dark"] .navbar__github-link::before { - filter: invert(1); -} - -.navbar__github-link:hover::before { - opacity: 0.6; -} - -/* Hide external icon */ -.navbar__github-link svg { - display: none; -} - -.navbar__toggle { - margin-right: 1rem; -} - -.footer { - --ifm-footer-background-color: #242526; -} diff --git a/docs/src/pages/sponsors/tips.md b/docs/src/pages/sponsors/tips.md deleted file mode 100644 index 764b4cbc..00000000 --- a/docs/src/pages/sponsors/tips.md +++ /dev/null @@ -1,26 +0,0 @@ -# Tips & Donations - -Previously, [plaiceholder.co](/) offered personal sponsorship in the form of a -monthly membership. Unfortunately, maintaining such a system is extremely time-consuming, costly and brings the many tax headaches associated with business-to-consumer transactions. - -For the time being, one-off tips and donations are accepted via cash or crypto. - -## Cash - -### Cash App - -Accepting GBP or USD - -``` -ÂŁjoebell5649 -``` - -## Crypto - -### Ethereum (ETH) - -``` -0xC756F748ff6A499f3C826529A0Da30FF1A3ac28c -``` - -![Ethereum QR Code](/assets/img/wallet/eth.png) diff --git a/docs/src/theme/Root.js b/docs/src/theme/Root.js deleted file mode 100644 index c1c17678..00000000 --- a/docs/src/theme/Root.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; -import Head from "@docusaurus/Head"; -import { useThemeConfig } from "@docusaurus/theme-common"; -import { withUrl } from "../utils"; - -function CustomRoot({ children }) { - const { metaTags } = useThemeConfig(); - - const title = metaTags.find((item) => item.name === "data-title").content; - - return ( - - - - - - - - - - - {children} - - ); -} -export default CustomRoot; diff --git a/docs/src/utils.js b/docs/src/utils.js deleted file mode 100644 index d3ecbe41..00000000 --- a/docs/src/utils.js +++ /dev/null @@ -1,15 +0,0 @@ -const isProduction = process.env.NODE_ENV !== "development"; - -const domain = "plaiceholder.co"; -const url = ["https://", domain].join(""); - -/** @param {string} [externalPathName] */ -const withUrl = (externalPathName) => - externalPathName ? [url, externalPathName].join("") : url; - -module.exports = { - isProduction, - domain, - url, - withUrl, -}; diff --git a/docs/static/assets/img/logo-dark.png b/docs/static/assets/img/logo-dark.png deleted file mode 100644 index 3349bac5..00000000 Binary files a/docs/static/assets/img/logo-dark.png and /dev/null differ diff --git a/docs/static/assets/img/og.png b/docs/static/assets/img/og.png deleted file mode 100644 index 8707762c..00000000 Binary files a/docs/static/assets/img/og.png and /dev/null differ diff --git a/docs/static/assets/img/powered-by-vercel.svg b/docs/static/assets/img/powered-by-vercel.svg deleted file mode 100644 index 87782868..00000000 --- a/docs/static/assets/img/powered-by-vercel.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/docs/static/assets/img/wallet/eth.png b/docs/static/assets/img/wallet/eth.png deleted file mode 100644 index 362101f8..00000000 Binary files a/docs/static/assets/img/wallet/eth.png and /dev/null differ diff --git a/docs/styles/global.css b/docs/styles/global.css new file mode 100644 index 00000000..9d93278a --- /dev/null +++ b/docs/styles/global.css @@ -0,0 +1,48 @@ +.navbar__brand { + --brand-padding: 0.5rem; + --brand-margin: calc(var(--brand-padding) * -1); + display: inline-flex; + padding: var(--brand-padding); + margin: var(--brand-margin); + margin-right: calc(var(--brand-margin) + 1rem); + border-radius: 0.25rem; + flex-direction: row-reverse; + text-transform: lowercase; + align-items: center; +} + +.navbar__title { + font-weight: 700; + font-size: 0; +} + +.navbar__logo { + max-width: 2.875rem; + flex-shrink: 0; + margin-right: 0; +} + +@media (min-width: 420px) { + .navbar__title { + font-size: 1.25rem; + } + + .navbar__logo { + margin-left: 0.5rem; + } +} + +.footer-text { + display: flex; + align-items: center; + flex-direction: column; + width: 100%; + gap: 2rem; +} + +@media (min-width: 768px) { + .footer-text { + justify-content: space-between; + flex-direction: row; + } +} diff --git a/docs/theme.config.tsx b/docs/theme.config.tsx new file mode 100644 index 00000000..8768aeed --- /dev/null +++ b/docs/theme.config.tsx @@ -0,0 +1,148 @@ +import { useConfig, type DocsThemeConfig } from "nextra-theme-docs"; +import { useRouter } from "next/router"; + +export const config = { + title: "Plaiceholder", + branch: "main", + repo: "joe-bell/plaiceholder", + domain: "plaiceholder.co", + description: "Beautiful image placeholders, without the hassle.", + author: { + name: "Joe Bell", + url: "https://joebell.co.uk", + twitter: "joebell_", + }, + og: "/assets/img/og.png", + favicon: "/assets/img/favicon.png", +} as const; + +export const PROJECT = `https://github.com/${config.repo}`; +export const SITE = `https://${config.domain}`; +export const TWITTER = `https://twitter.com/${config.author.twitter}`; + +const nextraConfig: DocsThemeConfig = { + primaryHue: 30, + chat: { + link: TWITTER, + icon: ( + + + + ), + }, + docsRepositoryBase: `${PROJECT}/tree/${config.branch}/docs`, + logo: ( + + + Plaice Fish + + {config.title} + + ), + project: { + link: PROJECT, + }, + footer: { + text: ( +
+ ), + }, + head: () => { + const { asPath } = useRouter(); + const { frontMatter } = useConfig(); + + const canonical = new URL(asPath, SITE).toString(); + + return ( + <> + + + + + + + + + + + + + + ); + }, + sidebar: { + toggleButton: true, + }, + useNextSeoProps() { + const { asPath } = useRouter(); + + const shared = { + openGraph: { + images: [ + { + url: new URL(config.og, SITE).toString(), + width: 1200, + height: 628, + alt: "Plaice fish, acompanied by text. Plaiceholder. Beautiful image placeholders, without the hassle. Choose-your-own adventure, from pure CSS to SVG.", + type: "image/png", + }, + ], + }, + }; + + if (["/", "/docs"].includes(asPath)) { + return { ...shared, titleTemplate: config.title }; + } + + return { ...shared, titleTemplate: `%s | ${config.title}` }; + }, +}; + +export default nextraConfig; diff --git a/docs/tsconfig.json b/docs/tsconfig.json index 4ffb6f0f..093985aa 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -1,3 +1,19 @@ { - "extends": "@tsconfig/docusaurus/tsconfig.json" + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve" + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] } diff --git a/docs/vercel.json b/docs/vercel.json new file mode 100644 index 00000000..328572e3 --- /dev/null +++ b/docs/vercel.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "framework": "nextjs", + "buildCommand": "pnpm build", + "outputDirectory": ".next" +} diff --git a/examples/11ty/.eleventy.js b/examples/11ty/.eleventy.js deleted file mode 100644 index f6c40c09..00000000 --- a/examples/11ty/.eleventy.js +++ /dev/null @@ -1,51 +0,0 @@ -const path = require("path"); -const prettier = require("prettier"); - -const { imageGrid } = require("./src/_components/image-grid"); -const { - imageShortcode, - imageWithPlaiceholderBase64, - imageWithPlaiceholderCSS, - imageWithPlaiceholderSVG, -} = require("./src/_lib/image"); - -module.exports = function (config) { - config.addPassthroughCopy({ "public/assets": "assets" }); - - config.addCollection("pages", (collection) => - collection.getFilteredByGlob("./src/_pages/**/*") - ); - - // Components - config.addJavaScriptFunction("imageGrid", imageGrid); - - config.addJavaScriptFunction("image", imageShortcode); - config.addJavaScriptFunction( - "imageWithPlaiceholderBase64", - imageWithPlaiceholderBase64 - ); - config.addJavaScriptFunction( - "imageWithPlaiceholderCSS", - imageWithPlaiceholderCSS - ); - config.addJavaScriptFunction( - "imageWithPlaiceholderSVG", - imageWithPlaiceholderSVG - ); - - // Prettify output - config.addTransform("prettier", function (content, outputPath) { - const extname = path.extname(outputPath); - - return [".html", ".json"].includes(extname) - ? prettier.format(content, { parser: extname.replace(/^./, "") }) - : content; - }); - - return { - dir: { - includes: "_includes", - layouts: "_layouts", - }, - }; -}; diff --git a/examples/11ty/.gitignore b/examples/11ty/.gitignore deleted file mode 100644 index d5692dc2..00000000 --- a/examples/11ty/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -_site -cache -src/_modules \ No newline at end of file diff --git a/examples/11ty/modules/@plaiceholder/ui.ts b/examples/11ty/modules/@plaiceholder/ui.ts deleted file mode 100644 index 22ff19c2..00000000 --- a/examples/11ty/modules/@plaiceholder/ui.ts +++ /dev/null @@ -1,2 +0,0 @@ -// @ts-ignore -export * from "@plaiceholder/ui"; diff --git a/examples/11ty/package.json b/examples/11ty/package.json deleted file mode 100644 index 54525a0a..00000000 --- a/examples/11ty/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "private": true, - "name": "example-with-11ty", - "author": "Joe Bell (https://joebell.co.uk)", - "license": "Apache-2.0", - "scripts": { - "build": "run-s build:ui build:deps build:css build:11ty", - "build:11ty": "eleventy --input=src", - "build:css": "NODE_ENV=production postcss ./src/styles/index.css -o ./_site/styles/index.css", - "build:deps": "pnpm --filter 'example-with-11ty^...' build", - "build:ui": "esbuild @plaiceholder/ui --bundle --platform=node --outfile=src/_modules/@plaiceholder/ui.js --banner:js=\"// Transpiled via 'npm run build:ui'\"", - "clean": "rimraf _site", - "dev": "run-p dev:*", - "dev:11ty": "eleventy --input=src --serve --port=5000", - "dev:css": "NODE_ENV=development postcss ./src/styles/index.css -o ./_site/styles/index.css -w", - "dev:deps": "pnpm --filter 'example-with-11ty^...' dev", - "dev:ui": "pnpm build:ui --watch", - "start": "serve _site" - }, - "devDependencies": { - "@11ty/eleventy": "1.0.2", - "@11ty/eleventy-img": "2.0.1", - "@plaiceholder/tailwindcss": "workspace:*", - "@plaiceholder/ui": "workspace:*", - "autoprefixer": "10.4.8", - "clean-css": "5.3.1", - "esbuild": "0.15.6", - "glob": "8.0.3", - "npm-run-all": "4.1.5", - "plaiceholder": "workspace:*", - "postcss": "8.4.16", - "postcss-cli": "10.0.0", - "prettier": "2.7.1", - "rimraf": "3.0.2", - "serve": "14.0.1", - "sharp": "0.30.7", - "tailwindcss": "3.1.8" - } -} diff --git a/examples/11ty/postcss.config.js b/examples/11ty/postcss.config.js deleted file mode 100644 index 12a703d9..00000000 --- a/examples/11ty/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/examples/11ty/public/assets/favicon@192px.png b/examples/11ty/public/assets/favicon@192px.png deleted file mode 100644 index 93e0b443..00000000 Binary files a/examples/11ty/public/assets/favicon@192px.png and /dev/null differ diff --git a/examples/11ty/public/assets/logo@192px.png b/examples/11ty/public/assets/logo@192px.png deleted file mode 100644 index cfd70d3a..00000000 Binary files a/examples/11ty/public/assets/logo@192px.png and /dev/null differ diff --git a/examples/11ty/src/_components/image-grid.js b/examples/11ty/src/_components/image-grid.js deleted file mode 100644 index 9cf11b3e..00000000 --- a/examples/11ty/src/_components/image-grid.js +++ /dev/null @@ -1,17 +0,0 @@ -const { imageList, imageListItem } = require("../_modules/@plaiceholder/ui"); - -/** @param {string|string[]} items */ -function imageGrid(items) { - const children = typeof items === "string" ? [items] : items; - - return `
    2 ? 3 : 2, - })}"> - ${children - .map((item) => `
  • ${item}
  • `) - .join("")} -
`; -} - -module.exports = { imageGrid }; diff --git a/examples/11ty/src/_data/assets/unsplash.js b/examples/11ty/src/_data/assets/unsplash.js deleted file mode 100644 index b79fbba6..00000000 --- a/examples/11ty/src/_data/assets/unsplash.js +++ /dev/null @@ -1,16 +0,0 @@ -const glob = require("glob"); - -module.exports = function getAllUnsplashImagePaths() { - return glob - .sync("./public/assets/images/unsplash/*.{jpg,png}") - .map((file) => { - const sep = "/"; - const fileArr = file.split(sep); - - const filePath = fileArr - .slice(fileArr.indexOf("public") + 1, fileArr.length) - .join(sep); - - return [sep, filePath].join(""); - }); -}; diff --git a/examples/11ty/src/_data/config.json b/examples/11ty/src/_data/config.json deleted file mode 100644 index 03e907f1..00000000 --- a/examples/11ty/src/_data/config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "project": "plaiceholder", - "title": "11ty", - "domain": "https://plaiceholder.co", - "meta": { - "title": "Plaiceholder Ă— 11ty", - "description": "Using Plaiceholder in 11ty" - }, - "social": { - "github": "https://github.com/joe-bell/plaiceholder/tree/main/examples/11ty", - "twitter": "https://twitter.com/joebell_" - } -} diff --git a/examples/11ty/src/_layouts/example.11ty.js b/examples/11ty/src/_layouts/example.11ty.js deleted file mode 100644 index 1e81dbd6..00000000 --- a/examples/11ty/src/_layouts/example.11ty.js +++ /dev/null @@ -1,48 +0,0 @@ -const { - article, - articleContent, - articleHeader, - articleHeaderSubtitle, - articleHeaderTitle, - backBar, - backBarLink, - icon, -} = require("../_modules/@plaiceholder/ui"); - -module.exports.data = { - layout: "root", -}; - -exports.render = function (data) { - return ` - `; -}; diff --git a/examples/11ty/src/_layouts/home.11ty.js b/examples/11ty/src/_layouts/home.11ty.js deleted file mode 100644 index 7282cb53..00000000 --- a/examples/11ty/src/_layouts/home.11ty.js +++ /dev/null @@ -1,26 +0,0 @@ -const { - article, - articleContent, - articleHeader, - articleHeaderSubtitle, - articleHeaderTitle, -} = require("../_modules/@plaiceholder/ui"); - -module.exports.data = { - layout: "root", -}; - -exports.render = function (data) { - return ` -
-
-

${data.title}

-

- ${data.subTitle} -

-
-
- ${data.content} -
-
`; -}; diff --git a/examples/11ty/src/_layouts/root.11ty.js b/examples/11ty/src/_layouts/root.11ty.js deleted file mode 100644 index 2c441e41..00000000 --- a/examples/11ty/src/_layouts/root.11ty.js +++ /dev/null @@ -1,127 +0,0 @@ -const { - icon, - iconLink, - layoutHeader, - layoutHeaderContainer, - layoutMain, - logo, - logoBrand, - logoIcon, - logoTitle, - socialList, - socialListItem, - iconLinkLabel, -} = require("../_modules/@plaiceholder/ui"); - -exports.render = function (data) { - return ` - - - - - - - - - - - - - - - - - ${ - data.title - ? [data.config.meta.title, data.title].join(" | ") - : data.config.meta.title - } - - - - -
- -
-
- ${data.content} -
- - - -`; -}; diff --git a/examples/11ty/src/_lib/image.js b/examples/11ty/src/_lib/image.js deleted file mode 100644 index 49d67e33..00000000 --- a/examples/11ty/src/_lib/image.js +++ /dev/null @@ -1,119 +0,0 @@ -const path = require("path"); -const Image = require("@11ty/eleventy-img"); -const { getPlaiceholder } = require("plaiceholder"); -const { propsToString, stylesToString } = require("./to-string"); - -/** - * @param {string} src - * @param {string} alt - * @param {string} sizes - * @returns {Promise} - */ -async function imageShortcode(src, alt, sizes) { - // Prefix `public` to any local images - const transformedSrc = src.startsWith("/") ? path.join("public", src) : src; - - // https://www.11ty.dev/docs/plugins/image/ - let metadata = await Image(transformedSrc, { - widths: [300, 600], - formats: ["avif", "jpeg"], - outputDir: "./_site/img/", - }); - - let imageAttributes = { - class: "w-full", - alt, - sizes, - loading: "lazy", - decoding: "async", - }; - - return Image.generateHTML(metadata, imageAttributes); -} - -const plaiceholderClass = - "z-[-1] absolute inset-0 transform scale-150 filter blur-2xl w-full h-full"; - -/** - * @type {typeof imageShortcode} - */ -async function imageWithPlaiceholderBase64(src, alt, sizes) { - return Promise.all([getPlaiceholder(src), imageShortcode(src, alt, sizes)]) - .then((values) => { - const [{ base64 }, image] = values; - - return ` - - ${image} - `; - }) - .catch((err) => { - throw err; - }); -} - -/** - * @type {typeof imageShortcode} - */ -async function imageWithPlaiceholderCSS(src, alt, sizes) { - return Promise.all([getPlaiceholder(src), imageShortcode(src, alt, sizes)]) - .then((values) => { - const [{ css }, image] = values; - - return ` -
- ${image} - `; - }) - .catch((err) => { - throw err; - }); -} - -/** - * @type {typeof imageShortcode} - */ -async function imageWithPlaiceholderSVG(src, alt, sizes) { - return Promise.all([getPlaiceholder(src), imageShortcode(src, alt, sizes)]) - .then((values) => { - const [plaiceholder, image] = values; - - const [element, { style, ...props }, children] = plaiceholder.svg; - - return ` - <${element} - style="${stylesToString({ - ...style, - transform: ["scale(1.5)", style.transform].join(" "), - })}" - class="${plaiceholderClass}" - ${propsToString(props)} - > - ${children - .map( - ([childElem, childProps]) => - `<${childElem} ${propsToString(childProps)} />` - ) - .join("")} - - ${image} - `; - }) - .catch((err) => { - throw err; - }); -} - -module.exports = { - imageShortcode, - imageWithPlaiceholderBase64, - imageWithPlaiceholderCSS, - imageWithPlaiceholderSVG, -}; diff --git a/examples/11ty/src/_lib/to-string.js b/examples/11ty/src/_lib/to-string.js deleted file mode 100644 index a8589e02..00000000 --- a/examples/11ty/src/_lib/to-string.js +++ /dev/null @@ -1,29 +0,0 @@ -/** @param {{[key: string]: string}} style */ -function stylesToString(style) { - return Object.keys(style).reduce((acc, cv) => { - return `${(acc += cv - .split(/(?=[A-Z])/) - .join("-") - .toLowerCase())}:${style[cv]};`; - }, ""); -} - -/** @param {{[key: string]: string}} style */ -function propsToString(props) { - return Object.keys(props).reduce((acc, cv) => { - if (["viewBox", "preserveAspectRatio"].includes(cv)) { - return [acc, `${cv}="${props[cv]}" `].join(""); - } - - if (cv === "style") { - return [acc, `style="${stylesToString(props[cv])}" `].join(""); - } - - return `${(acc += cv - .split(/(?=[A-Z])/) - .join("-") - .toLowerCase())}="${props[cv]}" `; - }, ""); -} - -module.exports = { stylesToString, propsToString }; diff --git a/examples/11ty/src/_pages/base64/multiple.11ty.js b/examples/11ty/src/_pages/base64/multiple.11ty.js deleted file mode 100644 index e2c49e3b..00000000 --- a/examples/11ty/src/_pages/base64/multiple.11ty.js +++ /dev/null @@ -1,30 +0,0 @@ -class Base64MultiplePage { - async data() { - return { - title: "Base64", - subTitle: "Multiple", - layout: "example", - permalink: "/base64/multiple/index.html", - }; - } - - async render(data) { - const images = await Promise.all( - data.assets.unsplash.map((src) => - this.imageWithPlaiceholderBase64( - src, - "", - "(min-width: 30em) 50vw, 100vw" - ) - ) - ) - .then((values) => values) - .catch((err) => { - throw err; - }); - - return this.imageGrid(images); - } -} - -module.exports = Base64MultiplePage; diff --git a/examples/11ty/src/_pages/base64/single.11ty.js b/examples/11ty/src/_pages/base64/single.11ty.js deleted file mode 100644 index 4bb0d66d..00000000 --- a/examples/11ty/src/_pages/base64/single.11ty.js +++ /dev/null @@ -1,22 +0,0 @@ -class Base64SinglePage { - async data() { - return { - title: "Base64", - subTitle: "Single", - layout: "example", - permalink: "/base64/single/index.html", - }; - } - - async render() { - const image = await this.imageWithPlaiceholderBase64( - "https://images.unsplash.com/photo-1621961458348-f013d219b50c?auto=format&fit=crop&w=2850&q=80", - "Mountains", - "(min-width: 30em) 50vw, 100vw" - ); - - return this.imageGrid(image); - } -} - -module.exports = Base64SinglePage; diff --git a/examples/11ty/src/_pages/css/multiple.11ty.js b/examples/11ty/src/_pages/css/multiple.11ty.js deleted file mode 100644 index 08386bc6..00000000 --- a/examples/11ty/src/_pages/css/multiple.11ty.js +++ /dev/null @@ -1,26 +0,0 @@ -class CSSMultiplePage { - async data() { - return { - title: "CSS", - subTitle: "Multiple", - layout: "example", - permalink: "/css/multiple/index.html", - }; - } - - async render(data) { - const images = await Promise.all( - data.assets.unsplash.map((src) => - this.imageWithPlaiceholderCSS(src, "", "(min-width: 30em) 50vw, 100vw") - ) - ) - .then((values) => values) - .catch((err) => { - throw err; - }); - - return this.imageGrid(images); - } -} - -module.exports = CSSMultiplePage; diff --git a/examples/11ty/src/_pages/css/single.11ty.js b/examples/11ty/src/_pages/css/single.11ty.js deleted file mode 100644 index f97dbb68..00000000 --- a/examples/11ty/src/_pages/css/single.11ty.js +++ /dev/null @@ -1,22 +0,0 @@ -class CSSSinglePage { - async data() { - return { - title: "CSS", - subTitle: "Single", - layout: "example", - permalink: "/css/single/index.html", - }; - } - - async render() { - const image = await this.imageWithPlaiceholderCSS( - "https://images.unsplash.com/photo-1621961458348-f013d219b50c?auto=format&fit=crop&w=2850&q=80", - "Mountains", - "(min-width: 30em) 50vw, 100vw" - ); - - return this.imageGrid(image); - } -} - -module.exports = CSSSinglePage; diff --git a/examples/11ty/src/_pages/index.11ty.js b/examples/11ty/src/_pages/index.11ty.js deleted file mode 100644 index 21c48a31..00000000 --- a/examples/11ty/src/_pages/index.11ty.js +++ /dev/null @@ -1,102 +0,0 @@ -const { getPlaiceholder } = require("plaiceholder"); -const { - exampleBody, - exampleLink, - exampleList, - exampleListItem, - exampleLQIP, - exampleNav, - exampleNavItem, - exampleTitle, -} = require("../_modules/@plaiceholder/ui"); -const { stylesToString } = require("../_lib/to-string"); - -class IndexPage { - async data() { - return { - title: "11ty", - subTitle: "Choose-your-own adventure", - layout: "home", - permalink: "/index.html", - }; - } - - async render(data) { - const plaiceholders = await Promise.all( - data.assets.unsplash.map((src) => getPlaiceholder(src)) - ) - .then((values) => values.map(({ css }) => css)) - .catch((err) => { - throw err; - }); - - const pages = data.collections.pages - .map(({ url, data }) => ({ ...data, url })) - .filter((page) => page.url !== "/"); - - const sortAlphabetically = (obj, options = { reverse: false }) => { - const entries = Object.entries(obj).sort(); - - return Object.fromEntries(options.reverse ? entries.reverse() : entries); - }; - - const examples = pages.reduce((acc, cv) => { - const [section, page] = cv.url.split("/").filter(Boolean); - - return sortAlphabetically({ - ...acc, - // Order by "Single", then "Multiple" - [section]: sortAlphabetically( - { - ...acc[section], - [page]: cv, - }, - { reverse: true } - ), - }); - }, {}); - - return ` -
    - ${Object.keys(examples) - .map((exampleKey, i) => { - const example = examples[exampleKey]; - const heading = example[Object.keys(example)[0]].title; - - return ` -
  • - - -

    - - ${heading} - -

    -
      - ${Object.keys(example) - .map( - (variant) => ` -
    • - - ${variant} - -
    • ` - ) - .join("")} -
    -
  • `; - }) - .join("")} -
- `; - } -} - -module.exports = IndexPage; diff --git a/examples/11ty/src/_pages/svg/multiple.11ty.js b/examples/11ty/src/_pages/svg/multiple.11ty.js deleted file mode 100644 index bdecc8a5..00000000 --- a/examples/11ty/src/_pages/svg/multiple.11ty.js +++ /dev/null @@ -1,26 +0,0 @@ -class SVGMultiplePage { - async data() { - return { - title: "SVG", - subTitle: "Multiple", - layout: "example", - permalink: "/svg/multiple/index.html", - }; - } - - async render(data) { - const images = await Promise.all( - data.assets.unsplash.map((src) => - this.imageWithPlaiceholderSVG(src, "", "(min-width: 30em) 50vw, 100vw") - ) - ) - .then((values) => values) - .catch((err) => { - throw err; - }); - - return this.imageGrid(images); - } -} - -module.exports = SVGMultiplePage; diff --git a/examples/11ty/src/_pages/svg/single.11ty.js b/examples/11ty/src/_pages/svg/single.11ty.js deleted file mode 100644 index 5e7ed691..00000000 --- a/examples/11ty/src/_pages/svg/single.11ty.js +++ /dev/null @@ -1,22 +0,0 @@ -class SVGSinglePage { - async data() { - return { - title: "SVG", - subTitle: "Single", - layout: "example", - permalink: "/svg/single/index.html", - }; - } - - async render() { - const image = await this.imageWithPlaiceholderSVG( - "https://images.unsplash.com/photo-1621961458348-f013d219b50c?auto=format&fit=crop&w=2850&q=80", - "Mountains", - "(min-width: 30em) 50vw, 100vw" - ); - - return this.imageGrid(image); - } -} - -module.exports = SVGSinglePage; diff --git a/examples/11ty/src/_pages/tailwind/multiple.11ty.js b/examples/11ty/src/_pages/tailwind/multiple.11ty.js deleted file mode 100644 index 00aa5104..00000000 --- a/examples/11ty/src/_pages/tailwind/multiple.11ty.js +++ /dev/null @@ -1,51 +0,0 @@ -const { extractImgSrc } = require("@plaiceholder/tailwindcss/utils"); - -class TailwindMultiplePage { - async data() { - return { - title: "Tailwind", - subTitle: "Multiple", - layout: "example", - permalink: "/tailwind/multiple/index.html", - }; - } - - async render(data) { - const images = await Promise.all( - [ - "plaiceholder-[/assets/images/unsplash/alexander-ant-oR7HxvOe2YE.jpg]", - "plaiceholder-[/assets/images/unsplash/alexander-ant-r7xdS9hjYYE.jpg]", - "plaiceholder-[/assets/images/unsplash/solen-feyissa-0KXl7T2YU0I.jpg]", - "plaiceholder-[/assets/images/unsplash/solen-feyissa-ju3ZBdiXzmA.jpg]", - "plaiceholder-[/assets/images/unsplash/solen-feyissa-tek55norwaQ.jpg]", - "plaiceholder-[/assets/images/unsplash/solen-feyissa-WX1siNmyR4.jpg]", - ].map(async (plaiceholder) => { - const image = await this.image( - extractImgSrc(plaiceholder), - "", - "(min-width: 30em) 50vw, 100vw" - ); - - return { - plaiceholder, - image, - }; - }) - ) - .then((values) => values) - .catch((err) => { - throw err; - }); - - return this.imageGrid( - images.map(({ plaiceholder, image }) => - [ - `
`, - image, - ].join("") - ) - ); - } -} - -module.exports = TailwindMultiplePage; diff --git a/examples/11ty/src/_pages/tailwind/single.11ty.js b/examples/11ty/src/_pages/tailwind/single.11ty.js deleted file mode 100644 index cc656aa2..00000000 --- a/examples/11ty/src/_pages/tailwind/single.11ty.js +++ /dev/null @@ -1,32 +0,0 @@ -const { extractImgSrc } = require("@plaiceholder/tailwindcss/utils"); - -class TailwindSinglePage { - async data() { - return { - title: "Tailwind", - subTitle: "Single", - layout: "example", - permalink: "/tailwind/single/index.html", - }; - } - - async render() { - const plaiceholder = "plaiceholder-[/assets/images/keila-joa@578.jpg]"; - const imgSrc = extractImgSrc(plaiceholder); - - const image = await this.image( - imgSrc, - "Mountains", - "(min-width: 30em) 50vw, 100vw" - ); - - return this.imageGrid( - [ - `
`, - image, - ].join("") - ); - } -} - -module.exports = TailwindSinglePage; diff --git a/examples/11ty/src/styles/index.css b/examples/11ty/src/styles/index.css deleted file mode 100644 index 3bafdc0a..00000000 --- a/examples/11ty/src/styles/index.css +++ /dev/null @@ -1,10 +0,0 @@ -@tailwind base; - -@tailwind components; - -/* prevent CSS transitions on page load */ -.preload * { - transition: none !important; -} - -@tailwind utilities; diff --git a/examples/11ty/tailwind.config.js b/examples/11ty/tailwind.config.js deleted file mode 100644 index 3410ad15..00000000 --- a/examples/11ty/tailwind.config.js +++ /dev/null @@ -1,9 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: ["./src/**/*.js", "./_site/**/*.html"], - theme: { - extend: {}, - }, - variants: {}, - plugins: [require("@plaiceholder/tailwindcss")], -}; diff --git a/examples/astro/astro.config.mjs b/examples/astro/astro.config.mjs deleted file mode 100644 index 18777387..00000000 --- a/examples/astro/astro.config.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import { defineConfig } from "astro/config"; - -import image from "@astrojs/image"; -import tailwind from "@astrojs/tailwind"; - -export default defineConfig({ - integrations: [image(), tailwind()], - vite: { - ssr: { - external: ["image-size", "tiny-glob"], - }, - }, -}); diff --git a/examples/astro/astro.config.ts b/examples/astro/astro.config.ts new file mode 100644 index 00000000..2bb1cad9 --- /dev/null +++ b/examples/astro/astro.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "astro/config"; + +import tailwind from "@astrojs/tailwind"; + +export default defineConfig({ + experimental: { + assets: true, + }, + integrations: [tailwind()], +}); diff --git a/examples/astro/package.json b/examples/astro/package.json index 04b8e759..93d38fe3 100644 --- a/examples/astro/package.json +++ b/examples/astro/package.json @@ -15,16 +15,14 @@ "start": "astro dev" }, "devDependencies": { - "@astrojs/image": "0.3.7", - "@astrojs/tailwind": "1.0.0", + "@astrojs/tailwind": "3.1.2", "@plaiceholder/tailwindcss": "workspace:*", "@plaiceholder/ui": "workspace:*", - "astro": "1.1.2", - "class-variance-authority": "0.2.3", - "image-size": "1.0.2", + "astro": "2.4.5", + "class-variance-authority": "0.6.0", "npm-run-all": "4.1.5", "plaiceholder": "workspace:*", - "sharp": "0.30.7", - "tiny-glob": "0.2.9" + "sharp": "0.32.1", + "tailwindcss": "3.3.2" } } diff --git a/examples/astro/public/assets/images/keila-joa@578.jpg b/examples/astro/public/assets/images/keila-joa@578.jpg deleted file mode 100644 index 946e7850..00000000 Binary files a/examples/astro/public/assets/images/keila-joa@578.jpg and /dev/null differ diff --git a/examples/astro/public/assets/images/unsplash/alexander-ant-oR7HxvOe2YE.jpg b/examples/astro/public/assets/images/unsplash/alexander-ant-oR7HxvOe2YE.jpg deleted file mode 100644 index 41bd935b..00000000 Binary files a/examples/astro/public/assets/images/unsplash/alexander-ant-oR7HxvOe2YE.jpg and /dev/null differ diff --git a/examples/astro/public/assets/images/unsplash/alexander-ant-r7xdS9hjYYE.jpg b/examples/astro/public/assets/images/unsplash/alexander-ant-r7xdS9hjYYE.jpg deleted file mode 100644 index 8ebb0598..00000000 Binary files a/examples/astro/public/assets/images/unsplash/alexander-ant-r7xdS9hjYYE.jpg and /dev/null differ diff --git a/examples/astro/public/assets/images/unsplash/solen-feyissa-0KXl7T2YU0I.jpg b/examples/astro/public/assets/images/unsplash/solen-feyissa-0KXl7T2YU0I.jpg deleted file mode 100644 index 7d740867..00000000 Binary files a/examples/astro/public/assets/images/unsplash/solen-feyissa-0KXl7T2YU0I.jpg and /dev/null differ diff --git a/examples/astro/public/assets/images/unsplash/solen-feyissa-WX1siNmyR4.jpg b/examples/astro/public/assets/images/unsplash/solen-feyissa-WX1siNmyR4.jpg deleted file mode 100644 index 0078d3aa..00000000 Binary files a/examples/astro/public/assets/images/unsplash/solen-feyissa-WX1siNmyR4.jpg and /dev/null differ diff --git a/examples/astro/public/assets/images/unsplash/solen-feyissa-ju3ZBdiXzmA.jpg b/examples/astro/public/assets/images/unsplash/solen-feyissa-ju3ZBdiXzmA.jpg deleted file mode 100644 index c8bbdf54..00000000 Binary files a/examples/astro/public/assets/images/unsplash/solen-feyissa-ju3ZBdiXzmA.jpg and /dev/null differ diff --git a/examples/astro/public/assets/images/unsplash/solen-feyissa-tek55norwaQ.jpg b/examples/astro/public/assets/images/unsplash/solen-feyissa-tek55norwaQ.jpg deleted file mode 100644 index 06914471..00000000 Binary files a/examples/astro/public/assets/images/unsplash/solen-feyissa-tek55norwaQ.jpg and /dev/null differ diff --git a/examples/11ty/public/assets/images/keila-joa@578.jpg b/examples/astro/src/assets/images/keila-joa@578.jpg similarity index 100% rename from examples/11ty/public/assets/images/keila-joa@578.jpg rename to examples/astro/src/assets/images/keila-joa@578.jpg diff --git a/examples/11ty/public/assets/images/unsplash/alexander-ant-oR7HxvOe2YE.jpg b/examples/astro/src/assets/images/unsplash-alexander-ant-oR7HxvOe2YE.jpg similarity index 100% rename from examples/11ty/public/assets/images/unsplash/alexander-ant-oR7HxvOe2YE.jpg rename to examples/astro/src/assets/images/unsplash-alexander-ant-oR7HxvOe2YE.jpg diff --git a/examples/11ty/public/assets/images/unsplash/alexander-ant-r7xdS9hjYYE.jpg b/examples/astro/src/assets/images/unsplash-alexander-ant-r7xdS9hjYYE.jpg similarity index 100% rename from examples/11ty/public/assets/images/unsplash/alexander-ant-r7xdS9hjYYE.jpg rename to examples/astro/src/assets/images/unsplash-alexander-ant-r7xdS9hjYYE.jpg diff --git a/examples/11ty/public/assets/images/unsplash/solen-feyissa-0KXl7T2YU0I.jpg b/examples/astro/src/assets/images/unsplash-solen-feyissa-0KXl7T2YU0I.jpg similarity index 100% rename from examples/11ty/public/assets/images/unsplash/solen-feyissa-0KXl7T2YU0I.jpg rename to examples/astro/src/assets/images/unsplash-solen-feyissa-0KXl7T2YU0I.jpg diff --git a/examples/11ty/public/assets/images/unsplash/solen-feyissa-WX1siNmyR4.jpg b/examples/astro/src/assets/images/unsplash-solen-feyissa-WX1siNmyR4.jpg similarity index 100% rename from examples/11ty/public/assets/images/unsplash/solen-feyissa-WX1siNmyR4.jpg rename to examples/astro/src/assets/images/unsplash-solen-feyissa-WX1siNmyR4.jpg diff --git a/examples/11ty/public/assets/images/unsplash/solen-feyissa-ju3ZBdiXzmA.jpg b/examples/astro/src/assets/images/unsplash-solen-feyissa-ju3ZBdiXzmA.jpg similarity index 100% rename from examples/11ty/public/assets/images/unsplash/solen-feyissa-ju3ZBdiXzmA.jpg rename to examples/astro/src/assets/images/unsplash-solen-feyissa-ju3ZBdiXzmA.jpg diff --git a/examples/11ty/public/assets/images/unsplash/solen-feyissa-tek55norwaQ.jpg b/examples/astro/src/assets/images/unsplash-solen-feyissa-tek55norwaQ.jpg similarity index 100% rename from examples/11ty/public/assets/images/unsplash/solen-feyissa-tek55norwaQ.jpg rename to examples/astro/src/assets/images/unsplash-solen-feyissa-tek55norwaQ.jpg diff --git a/examples/astro/src/config.ts b/examples/astro/src/config.ts index 143ec85b..dc3d58fb 100644 --- a/examples/astro/src/config.ts +++ b/examples/astro/src/config.ts @@ -23,6 +23,9 @@ export const config = { base64: { title: "Base64", }, + color: { + title: "Color", + }, }, }, } as const; diff --git a/examples/astro/src/env.d.ts b/examples/astro/src/env.d.ts index 70f996ff..f7cbe9c1 100644 --- a/examples/astro/src/env.d.ts +++ b/examples/astro/src/env.d.ts @@ -1,2 +1 @@ -// Replace `astro/client` with `@astrojs/image/client` -/// +/// diff --git a/examples/astro/src/lib/images.ts b/examples/astro/src/lib/images.ts deleted file mode 100644 index 911bbe44..00000000 --- a/examples/astro/src/lib/images.ts +++ /dev/null @@ -1,19 +0,0 @@ -import glob from "tiny-glob"; - -export const getAllPublicUnsplashImagePaths = async () => { - const paths = await glob("./public/assets/images/unsplash/*.{jpg,png}").then( - (files) => - files.map((file) => { - const sep = "/"; - const fileArr = file.split(sep); - - const filePath = fileArr - .slice(fileArr.indexOf("public") + 1, fileArr.length) - .join(sep); - - return [sep, filePath].join(""); - }) - ); - - return paths; -}; diff --git a/examples/astro/src/pages/base64/multiple.astro b/examples/astro/src/pages/base64/multiple.astro index a628254e..4f6fbe16 100644 --- a/examples/astro/src/pages/base64/multiple.astro +++ b/examples/astro/src/pages/base64/multiple.astro @@ -1,30 +1,40 @@ --- +import fs from "node:fs/promises"; +import { getImage, Image } from "astro:assets"; import { cx } from "class-variance-authority"; import { getPlaiceholder } from "plaiceholder"; import { imageList, imageListItem } from "@plaiceholder/ui"; import { config } from "@/config"; import Layout from "@/layouts/Layout.astro"; -import { getAllPublicUnsplashImagePaths } from "@/lib/images"; const title = config.examples.pages.base64.title; const heading = config.examples.variants.multiple.title; -const imagePaths = await getAllPublicUnsplashImagePaths(); - -const images = await Promise.all( - imagePaths.map(async (src) => { - const { base64, img } = await getPlaiceholder(src); - - return { - img: { - ...img, - alt: "Paint Splashes", - title: "Photo from Unsplash", - }, - base64, - }; - }) -); +const imageFiles = await Astro.glob("../../assets/images/unsplash-*.{jpg,png}"); + +const getImagesWithPlaiceholder = async (files: Record[]) => + Promise.all( + files.map(async ({ default: file }) => { + const { src, attributes } = await getImage({ src: file }); + + const original = file.src.split("?")[0]; + const buffer = await fs.readFile( + new URL( + import.meta.env.VERCEL ? `../..${original}` : `../../..${original}`, + import.meta.url + ) + ); + + const { + metadata: { height, width }, + ...plaiceholder + } = await getPlaiceholder(buffer); + + return { ...plaiceholder, img: { ...attributes, src, height, width } }; + }) + ); + +const images = await getImagesWithPlaiceholder(imageFiles); --- @@ -48,11 +58,12 @@ const images = await Promise.all( "z-[-1]" )} /> - {/** - * It's a little fiddly globbing over directories with the `Image` - * component, so we'll use a regular ol' `` instead: - */} - + Paint Splashes )) } diff --git a/examples/astro/src/pages/base64/single.astro b/examples/astro/src/pages/base64/single.astro index 2a4adc0a..26888829 100644 --- a/examples/astro/src/pages/base64/single.astro +++ b/examples/astro/src/pages/base64/single.astro @@ -1,5 +1,5 @@ --- -import { Image } from "@astrojs/image/components"; +import { Image } from "astro:assets"; import { cx } from "class-variance-authority"; import { getPlaiceholder } from "plaiceholder"; import { imageList, imageListItem } from "@plaiceholder/ui"; @@ -9,9 +9,21 @@ import Layout from "@/layouts/Layout.astro"; const title = config.examples.pages.base64.title; const heading = config.examples.variants.single.title; -const { base64, img } = await getPlaiceholder( - "https://images.unsplash.com/photo-1621961458348-f013d219b50c?auto=format&fit=crop&w=2850&q=80", - { size: 10 } +const getRemoteImageWithPlaiceholder = async (src: string) => { + const buffer = await fetch(src).then(async (res) => + Buffer.from(await res.arrayBuffer()) + ); + + const { + metadata: { height, width }, + ...plaiceholder + } = await getPlaiceholder(buffer, { size: 10 }); + + return { ...plaiceholder, img: { src, height, width } }; +}; + +const { base64, img } = await getRemoteImageWithPlaiceholder( + "https://images.unsplash.com/photo-1621961458348-f013d219b50c?auto=format&fit=crop&w=2850&q=80" ); --- @@ -34,7 +46,12 @@ const { base64, img } = await getPlaiceholder( "z-[-1]" )} /> - + Snowy mountain peaks diff --git a/examples/astro/src/pages/color/multiple.astro b/examples/astro/src/pages/color/multiple.astro new file mode 100644 index 00000000..b423b0f0 --- /dev/null +++ b/examples/astro/src/pages/color/multiple.astro @@ -0,0 +1,70 @@ +--- +import fs from "node:fs/promises"; +import { getImage, Image } from "astro:assets"; +import { cx } from "class-variance-authority"; +import { getPlaiceholder } from "plaiceholder"; +import { imageList, imageListItem } from "@plaiceholder/ui"; +import { config } from "@/config"; +import Layout from "@/layouts/Layout.astro"; + +const title = config.examples.pages.color.title; +const heading = config.examples.variants.multiple.title; + +const imageFiles = await Astro.glob("../../assets/images/unsplash-*.{jpg,png}"); + +const getImagesWithPlaiceholder = async (files: Record[]) => + Promise.all( + files.map(async ({ default: file }) => { + const { src, attributes } = await getImage({ src: file }); + + const original = file.src.split("?")[0]; + const buffer = await fs.readFile( + new URL( + import.meta.env.VERCEL ? `../..${original}` : `../../..${original}`, + import.meta.url + ) + ); + + const { + metadata: { height, width }, + ...plaiceholder + } = await getPlaiceholder(buffer); + + return { ...plaiceholder, img: { ...attributes, src, height, width } }; + }) + ); + +const images = await getImagesWithPlaiceholder(imageFiles); +--- + + +
    + { + images.map(({ img, color }) => ( +
  • +
    + Paint Splashes +
  • + )) + } +
+
diff --git a/examples/astro/src/pages/color/single.astro b/examples/astro/src/pages/color/single.astro new file mode 100644 index 00000000..69ad48b0 --- /dev/null +++ b/examples/astro/src/pages/color/single.astro @@ -0,0 +1,57 @@ +--- +import { Image } from "astro:assets"; +import { cx } from "class-variance-authority"; +import { getPlaiceholder } from "plaiceholder"; +import { imageList, imageListItem } from "@plaiceholder/ui"; +import { config } from "@/config"; +import Layout from "@/layouts/Layout.astro"; + +const title = config.examples.pages.color.title; +const heading = config.examples.variants.single.title; + +const getRemoteImageWithPlaiceholder = async (src: string) => { + const buffer = await fetch(src).then(async (res) => + Buffer.from(await res.arrayBuffer()) + ); + + const { + metadata: { height, width }, + ...plaiceholder + } = await getPlaiceholder(buffer, { size: 10 }); + + return { ...plaiceholder, img: { src, height, width } }; +}; + +const { color, img } = await getRemoteImageWithPlaiceholder( + "https://images.unsplash.com/photo-1621961458348-f013d219b50c?auto=format&fit=crop&w=2850&q=80" +); +--- + + +
    +
  • +
    +
    + Snowy mountain peaks +
  • +
+
diff --git a/examples/astro/src/pages/css/multiple.astro b/examples/astro/src/pages/css/multiple.astro index 020f30b0..51de26e5 100644 --- a/examples/astro/src/pages/css/multiple.astro +++ b/examples/astro/src/pages/css/multiple.astro @@ -1,30 +1,40 @@ --- +import fs from "node:fs/promises"; +import { getImage, Image } from "astro:assets"; import { cx } from "class-variance-authority"; import { getPlaiceholder } from "plaiceholder"; import { imageList, imageListItem } from "@plaiceholder/ui"; import { config } from "@/config"; import Layout from "@/layouts/Layout.astro"; -import { getAllPublicUnsplashImagePaths } from "@/lib/images"; const title = config.examples.pages.css.title; const heading = config.examples.variants.multiple.title; -const imagePaths = await getAllPublicUnsplashImagePaths(); - -const images = await Promise.all( - imagePaths.map(async (src) => { - const { css, img } = await getPlaiceholder(src); - - return { - img: { - ...img, - alt: "Paint Splashes", - title: "Photo from Unsplash", - }, - css, - }; - }) -); +const imageFiles = await Astro.glob("../../assets/images/unsplash-*.{jpg,png}"); + +const getImagesWithPlaiceholder = async (files: Record[]) => + Promise.all( + files.map(async ({ default: file }) => { + const { src, attributes } = await getImage({ src: file }); + + const original = file.src.split("?")[0]; + const buffer = await fs.readFile( + new URL( + import.meta.env.VERCEL ? `../..${original}` : `../../..${original}`, + import.meta.url + ) + ); + + const { + metadata: { height, width }, + ...plaiceholder + } = await getPlaiceholder(buffer); + + return { ...plaiceholder, img: { ...attributes, src, height, width } }; + }) + ); + +const images = await getImagesWithPlaiceholder(imageFiles); --- @@ -47,11 +57,12 @@ const images = await Promise.all( "z-[-1]" )} /> - {/** - * It's a little fiddly globbing over directories with the `Image` - * component, so we'll use a regular ol' `` instead: - */} - + Paint Splashes )) } diff --git a/examples/astro/src/pages/css/single.astro b/examples/astro/src/pages/css/single.astro index d53b2db6..ef1315e1 100644 --- a/examples/astro/src/pages/css/single.astro +++ b/examples/astro/src/pages/css/single.astro @@ -1,5 +1,5 @@ --- -import { Image } from "@astrojs/image/components"; +import { Image } from "astro:assets"; import { cx } from "class-variance-authority"; import { getPlaiceholder } from "plaiceholder"; import { imageList, imageListItem } from "@plaiceholder/ui"; @@ -9,9 +9,21 @@ import Layout from "@/layouts/Layout.astro"; const title = config.examples.pages.css.title; const heading = config.examples.variants.single.title; -const { css, img } = await getPlaiceholder( - "https://images.unsplash.com/photo-1621961458348-f013d219b50c?auto=format&fit=crop&w=2850&q=80", - { size: 10 } +const getRemoteImageWithPlaiceholder = async (src: string) => { + const buffer = await fetch(src).then(async (res) => + Buffer.from(await res.arrayBuffer()) + ); + + const { + metadata: { height, width }, + ...plaiceholder + } = await getPlaiceholder(buffer, { size: 10 }); + + return { ...plaiceholder, img: { src, height, width } }; +}; + +const { css, img } = await getRemoteImageWithPlaiceholder( + "https://images.unsplash.com/photo-1621961458348-f013d219b50c?auto=format&fit=crop&w=2850&q=80" ); --- @@ -34,7 +46,12 @@ const { css, img } = await getPlaiceholder( )} >
- + Snowy mountain peaks diff --git a/examples/astro/src/pages/index.astro b/examples/astro/src/pages/index.astro index b2bedfb0..b34fb277 100644 --- a/examples/astro/src/pages/index.astro +++ b/examples/astro/src/pages/index.astro @@ -1,4 +1,6 @@ --- +import fs from "node:fs/promises"; +import { getImage } from "astro:assets"; import { getPlaiceholder } from "plaiceholder"; import { exampleBody, @@ -12,17 +14,29 @@ import { } from "@plaiceholder/ui"; import { config } from "@/config"; import Layout from "@/layouts/Layout.astro"; -import { getAllPublicUnsplashImagePaths } from "@/lib/images"; -const imagePaths = await getAllPublicUnsplashImagePaths(); +const imageFiles = await Astro.glob("../assets/images/unsplash-*.{jpg,png}"); -const plaiceholders = await Promise.all( - imagePaths.map(async (src) => { - const { css } = await getPlaiceholder(src); +const getImagesWithPlaiceholder = async (files: Record[]) => + Promise.all( + files.map(async ({ default: file }) => { + const { src, attributes } = await getImage({ src: file }); - return css; - }) -); + const original = file.src.split("?")[0]; + const buffer = await fs.readFile( + new URL(`../..${original}`, import.meta.url) + ); + + const { + metadata: { height, width }, + ...plaiceholder + } = await getPlaiceholder(buffer); + + return { ...plaiceholder, img: { ...attributes, src, height, width } }; + }) + ); + +const images = await getImagesWithPlaiceholder(imageFiles); --- @@ -33,7 +47,7 @@ const plaiceholders = await Promise.all(