@plumeria/next-plugin

View as Markdown

@plumeria/next-plugin is a plugin for using Plumeria with Next.js. It supports both Turbopack and Webpack for Next.js and provides style compilation and HMR (Hot Module Replacement) at build time.

Installation

Terminal
npm i -D @plumeria/next-plugin
Terminal
yarn add -D @plumeria/next-plugin
Terminal
pnpm i -D @plumeria/next-plugin

Usage

Wrap your configuration using withPlumeria in next.config.ts or next.config.js.

next.config.ts
import type { NextConfig } from "next";
import { withPlumeria } from "@plumeria/next-plugin";

const nextConfig: NextConfig = {
  // Existing configuration
};

export default withPlumeria(nextConfig);

API

withPlumeria(nextConfig, options)

Takes a Next.js configuration object and returns a new configuration object with the necessary Turbopack or Webpack configurations added for Plumeria.

  • nextConfig: Next.js configuration object.
  • options: Compiler options, passed through to the loader.
function withPlumeria(nextConfig: NextConfig, options?: LoaderOptions): NextConfig;

Options

next.config.ts
export default withPlumeria(nextConfig, {
  include: ['./src/**/*.{ts,tsx}'],
  exclude: ['**/node_modules/**', '**/.next/**'],
  styleProp: 'sx',
});
  • include: Globs the stylesheet is compiled from. The default is every js/jsx/ts/tsx file.
  • exclude: Globs excluded from that compilation. The default is node_modules, dist and .next.
  • styleProp: The JSX prop that carries styles. The default is 'classStyle'.
  • withoutLogicalProperties: Rejects the logical name of a property that also has a physical name. The default is false.
  • withoutPhysicalProperties: Rejects the physical name of a property that also has a logical name. The default is false.
interface LoaderOptions {
  include?: string[];
  exclude?: string[];
  styleProp?: string;
  withoutLogicalProperties?: boolean | { sizes?: boolean };
  withoutPhysicalProperties?: boolean | { sizes?: boolean };
}

styleProp

Renaming the prop takes two steps, and they have to agree. Tell the plugin:

next.config.ts
export default withPlumeria(nextConfig, { styleProp: 'sx' });

and declare the same name for TypeScript instead of referencing the default:

plumeria.d.ts
import type { Style } from '@plumeria/core';

declare global {
  namespace React {
    interface HTMLAttributes<T> {
      sx?: Style;
    }
    interface SVGAttributes<T> {
      sx?: Style;
    }
  }
}

For the default name, reference the declaration that ships with the package instead:

plumeria.d.ts
/// <reference types="@plumeria/core/class-style" />

If the two disagree, the prop type-checks but is never compiled away. @plumeria/eslint-plugin reads the same name from settings.plumeria.styleProp.

withoutLogicalProperties and withoutPhysicalProperties

Two names for the same box edge cannot be ranked by specificity, so a file that uses both leaves the result to source order. These options settle it by rejecting one spelling:

next.config.ts
export default withPlumeria(nextConfig, { withoutPhysicalProperties: true });

The build fails on the first rejected property, and the message names the file and the property to write instead. Nothing is rewritten. { sizes: true } extends the check from the edges to the twelve axis pairs — width, height, their min and max forms, and overflow, overscroll-behavior and contain-intrinsic-size on both axes:

next.config.ts
export default withPlumeria(nextConfig, {
  withoutPhysicalProperties: { sizes: true },
});

Enabling both is a configuration error, and the direction has to match the lint rule you run: withoutPhysicalProperties with no-physical-properties and withoutLogicalProperties with no-logical-properties, { sizes } included. Set opposite directions and the rule renames a property the build then rejects.

Specificity explains why the pair cannot be ranked, and why neither spelling is rewritten for you.

Driving the loader

@plumeria/turbopack-loader is the loader withPlumeria installs, and it can be called directly with a webpack loader context of your own:

const loader = require('@plumeria/turbopack-loader');
const fn = loader.default ?? loader;

fn.call(
  {
    resourcePath: `${__dirname}/fixture.tsx`,
    async: () => (err, content) => (err ? reject(err) : resolve(content)),
    addDependency: () => {},
    clearDependencies: () => {},
  },
  source,
);

withPlumeria takes no part in this. The test requires the loader itself, so add @plumeria/turbopack-loader to your dev dependencies — it arrives as a dependency of this package either way, but a strict package manager such as pnpm does not put a transitive dependency where your own code can reach it.

The rewritten code comes back through async. The stylesheet is written only when NODE_ENV is development or production — a test run is neither, so the file is left alone — and those writes are taken under a lock, so parallel callers do not lose rules. Those four context properties are part of the published surface. See Testing.

On this page