Skip to content

Latest commit

 

History

History
103 lines (74 loc) · 5.82 KB

BOILERPLATES.md

File metadata and controls

103 lines (74 loc) · 5.82 KB

Any folder contained in boilerplates folder is considered a boilerplate.

Each boilerplate may be related to one or multiple features.

Note

react, eslint, auth0, etc. are each considered as features. They are visible on the website and available through flags in the CLI. They are defined in features.ts. On the CLI, each flag (i.e. --solid) enables the given feature.

Some features are well contained in a dedicated boilerplate (Cloudflare), some others are split into several ones.

Anatomy of a boilerplate

Tip

Create a new boilerplate with pnpm run new-boilerplate <name>

Each boilerplate contains at least:

  • files folder, which contains files that will be used by Bati CLI to scaffold a new app. This is where all code related to features must be placed.
  • package.json, with a special bati property, linking one or many features (or the lack of) to the boilerplate. More on that below.

Note

Empty files will not be deployed and in the case of precedence over any other file at the same destination the original file will be delete.

How to keep code maintainable

The most important thing is to avoid duplicating code as much as possible. And we have several tools to help us:

One other important goal is to keep code safely typed. Thanks to Bati's special syntax, mixing code and templating is usually straightforward.

Tip

You can require boilerplates files from any other boilerplate. Use the special @batijs/* imports to achieve this. For instance, importing @batijs/trpc/trpc/server imports the boilerplates/trpc/files/trpc/server.ts file. Typing information is conserved when doing so. Upon scaffolding, those imports are replaced by relative ones.

package.json#bati condition

All boilerplates package.json files contain a bati property, which dictates if the boilerplate is part of a feature. Most notably, the bati.if property leverages sift syntax to include or exclude said boilerplate depending on CLI flags.

Tip

Take a look at existing boilerplates package.json for inspiration

$*.ts files

files/ folder can contain special $*.ts files which, contrary to other files which are mostly copied as-is, are interpreted.

Note

When generating the proper filename of the destination file, the $ and .ts parts are removed. For instance, $vite.config.ts.ts is renamed vite.config.ts, or $package.json.ts is renamed package.json.

Those files MUST export a default function which returns either a string or undefined (or some serializable object for .json files). If the function returns undefined, nothing happens (existing file is not altered, and no empty file is created).

// files/$vite.config.ts.ts

// bati's utils
import { addVitePlugin, loadAsMagicast, type TransformerProps } from "@batijs/core";

// the exported default function, which always takes a `TransformerProps` as its first parameter.
export default async function getViteConfig(props: TransformerProps) {
  // Multiple `$*.ts` files can target the same file, here its target is `vite.config.ts`
  // Some utils allows you access the already generated file, and modify it.
  
  // Here, `loadAsMagicast` loads previously created `vite.config.ts`, which always exists because defined in `boilerplates/shared/files`.
  const mod = await loadAsMagicast(props);
  // Other utils like this one exist:
  // - `loadAsJson`: loads a JSON file and parses it
  // - `loadYaml`: loads a YAML file and parses it
  // - `loadMarkdown`: loads README or any markdown file and provides utils to manipulate it
  // - `props.readfile`: loads previous file as string if it exists

  // Then we edit the AST to add a vite plugin
  addVitePlugin(mod, {
    from: "vite-plugin-compiled-react",
    constructor: "compiled",
    imported: "compiled",
    options: { extract: true },
  });

  // Finally we return the updated code as a string
  return mod.generate().code;
}

Tip

Take a look at existing boilerplates for examples

Advanced rules

Some features could be incompatible with one another. For instance, compiled can only be used with react. To materialize those conflicts (or contextual information), Bati defines them in packages/features/src/rules.

  • enum.ts uniquely identify a message shown by the CLI or the website
  • rules.ts where the logic behind each message is defined

Each message should then be defined on the website and on the CLI.