Skip to content

Commit 7186068

Browse files
committed
feat(oxfmt): replace Prettier with shared Oxfmt preset
Add oxfmt-preset.mjs and local oxfmt.config.mjs, document consumer setup, and record the decision. Add epic-web/no-prettier-ignore for Oxfmt ignore comments. Remove prettier.js, Prettier dependencies, and the prettier discussion template in favor of oxfmt-suggestion. BREAKING CHANGE: Prettier is no longer shipped from this package. Drop the `prettier` field and `@epic-web/config/prettier` import, install `oxfmt` (peer dependency), and follow the README to extend `@epic-web/config/oxfmt` from an `oxfmt.config.ts` (or equivalent) file. Made-with: Cursor
1 parent 1180f59 commit 7186068

21 files changed

Lines changed: 4899 additions & 4283 deletions

.github/DISCUSSION_TEMPLATE/prettier-suggestion.yml renamed to .github/DISCUSSION_TEMPLATE/oxfmt-suggestion.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ body:
2121
description: >-
2222
Link to the documentation for the config option you're suggesting
2323
changing
24-
placeholder: https://prettier.io/docs/en/options#jsx-quotes
24+
placeholder: https://oxc.rs/docs/guide/usage/formatter/config-file-reference.html
2525
validations:
2626
required: true
2727
- type: textarea

.github/DISCUSSION_TEMPLATE/oxlint-suggestion.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,17 @@ body:
1919
attributes:
2020
label: Rule or Config Docs
2121
description: >-
22-
Link to the Oxlint rule or configuration docs you're suggesting
23-
changing
22+
Link to the Oxlint rule or configuration docs you're suggesting changing
2423
placeholder: https://oxc.rs/docs/guide/usage/linter/rules/
2524
validations:
2625
required: true
2726
- type: textarea
2827
attributes:
2928
label: Suggested Change
3029
description: >-
31-
Write out the suggested change in configuration. If the rule or config is
32-
already present, explain why it should be removed or changed. If it's not
33-
present, explain why it should be added. And provide the final
30+
Write out the suggested change in configuration. If the rule or config
31+
is already present, explain why it should be removed or changed. If it's
32+
not present, explain why it should be added. And provide the final
3433
configuration for the option.
3534
validations:
3635
required: true

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
node_modules
22
.idea
3+
epic-web-config-*.tgz
4+
tmp-oxfmt-smoke

README.md

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<div>
22
<h1 align="center"><a href="https://npm.im/@epic-web/config">👮 @epic-web/config</a></h1>
33
<strong>
4-
Reasonable Oxlint, Prettier, and TypeScript configs for epic web devs
4+
Reasonable Oxlint, Oxfmt, and TypeScript configs for epic web devs
55
</strong>
66
<p>
77
This makes assumptions about the way you prefer to develop software and gives you configurations that will actually help you in your development.
@@ -43,7 +43,7 @@ configuring code quality tools or babysitting them.
4343
This package provides shared defaults for the tools this repo currently ships:
4444

4545
- Oxlint
46-
- Prettier
46+
- Oxfmt
4747
- TypeScript
4848

4949
## Decisions
@@ -57,33 +57,64 @@ Technically you configure everything yourself, but you can use the configs in
5757
this project as a starter for your projects (and in some cases you don't need to
5858
configure anything more than the defaults).
5959

60-
### Prettier
60+
### Oxfmt (formatter)
6161

62-
The easiest way to use this config is in your `package.json`:
62+
Install [Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html) alongside this
63+
package (it is listed in `peerDependencies`).
6364

64-
```json
65-
"prettier": "@epic-web/config/prettier"
66-
```
65+
The `@epic-web/config/oxfmt` entry resolves to a plain `.mjs` preset so Node
66+
does not need to strip TypeScript from files under `node_modules` when you
67+
extend it.
6768

68-
<details>
69-
<summary>Customizing Prettier</summary>
69+
Create an `oxfmt.config.ts` file in your project root:
70+
71+
```ts
72+
import epicOxfmt from '@epic-web/config/oxfmt'
73+
import { defineConfig } from 'oxfmt'
7074

71-
If you want to customize things, you should probably just copy/paste the
72-
built-in config. But if you really want, you can override it using regular
73-
JavaScript stuff.
75+
export default defineConfig({
76+
...epicOxfmt,
77+
printWidth: 100,
78+
})
79+
```
7480

75-
Create a `.prettierrc.js` file in your project root with the following content:
81+
Oxfmt does not have an `extends` field; spreading the preset and setting any
82+
top-level option afterward is how you override it (same idea for
83+
`ignorePatterns`: spread `epicOxfmt.ignorePatterns` and append paths).
7684

77-
```js
78-
import defaultConfig from '@epic-web/config/prettier'
85+
Add scripts (see the
86+
[migration guide](https://oxc.rs/docs/guide/usage/formatter/migrate-from-prettier.html)):
7987

80-
/** @type {import("prettier").Options} */
81-
export default {
82-
...defaultConfig,
83-
// .. your overrides here...
88+
```json
89+
{
90+
"scripts": {
91+
"format": "oxfmt",
92+
"format:check": "oxfmt --check"
93+
}
8494
}
8595
```
8696

97+
The shared config sets 80 `printWidth`, tabs (spaces only in `package.json`
98+
overrides), no semicolons, single quotes, `trailingComma: "all"`, Tailwind class
99+
sorting via native `sortTailwindcss`, and MDX overrides for `proseWrap` /
100+
`htmlWhitespaceSensitivity`. Options that Oxfmt does not support
101+
(`insertPragma`, `requirePragma`) are omitted.
102+
103+
[`ignorePatterns`](https://oxc.rs/docs/guide/usage/formatter/ignore-files.html)
104+
covers common build, cache, Playwright, Prisma, and lockfile paths used across
105+
Epic projects. Adjust in your `defineConfig` if your layout differs.
106+
107+
<details>
108+
<summary>Customizing Oxfmt</summary>
109+
110+
If you want to customize things heavily, you can copy the options from
111+
[`oxfmt-preset.mjs`](./oxfmt-preset.mjs) into your own config. For small tweaks,
112+
keep spreading `epicOxfmt` and override or extend fields as in the example
113+
above.
114+
115+
Use `"type": "module"` in your `package.json` when your `oxfmt.config.ts` uses
116+
`import` / `export` syntax (same as for other ESM tooling).
117+
87118
</details>
88119

89120
### TypeScript
@@ -138,6 +169,13 @@ Some Oxlint rule IDs still use the `eslint/` namespace because that is how
138169
Oxlint exposes those compatibility rules. You do not need to install ESLint to
139170
use them.
140171

172+
The `epic-web/no-prettier-ignore` rule warns on `prettier-ignore` in JavaScript
173+
and TypeScript comments and can auto-fix them to `oxfmt-ignore` for Oxfmt. Keep
174+
`prettier-ignore` where Oxfmt still recommends it (for example non-JS regions in
175+
Vue); see the
176+
[inline ignore comments](https://oxc.rs/docs/guide/usage/formatter/ignore-comments.html)
177+
docs.
178+
141179
#### Not yet covered
142180

143181
The following rule families are intentionally omitted because they are not yet
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Oxfmt over Prettier
2+
3+
Date: 2026-04-14
4+
5+
Status: accepted
6+
7+
## Context
8+
9+
This package previously shipped a shared [Prettier](https://prettier.io)
10+
configuration (`prettier.js`) and documented the `"prettier"` field in
11+
`package.json` as the primary way to adopt consistent formatting across Epic Web
12+
projects.
13+
14+
We already recommend [Oxlint](https://oxc.rs/docs/guide/usage/linter.html) from
15+
the same [Oxc](https://oxc.rs) project for fast linting.
16+
[Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html) is Oxc’s formatter: it
17+
targets Prettier-compatible output for many options, runs as a single native
18+
CLI, and implements common needs (for example Tailwind class sorting) without
19+
Prettier’s plugin ecosystem.
20+
21+
Teams were maintaining Prettier plus `prettier-plugin-tailwindcss` and separate
22+
ignore files; lockfiles and generated paths were duplicated across
23+
`.prettierignore` and formatter-adjacent tooling.
24+
25+
## Decision
26+
27+
Adopt **Oxfmt** as the shared formatter for `@epic-web/config`:
28+
29+
- Replace the Prettier entry point with a published `oxfmt-preset.mjs` (via
30+
`defineConfig`) and document `oxfmt` as a peer dependency.
31+
- Map the old Prettier options into Oxfmt where supported; document gaps (for
32+
example `insertPragma` / `requirePragma`, and regex-based Tailwind attribute
33+
names).
34+
- Centralize formatter-only ignores in `ignorePatterns` alongside `.gitignore`
35+
behavior described in the Oxfmt docs.
36+
- Encourage `oxfmt-ignore` over `prettier-ignore` in JS/TS via the
37+
`epic-web/no-prettier-ignore` lint rule, while keeping `prettier-ignore` where
38+
Oxfmt still recommends it for non-JS regions.
39+
40+
## Consequences
41+
42+
**Positive**
43+
44+
- One vendor (Oxc) for lint + format, fewer moving parts and clearer upgrade
45+
paths.
46+
- Faster formatting at scale; no Prettier plugin install for Tailwind sorting.
47+
- Simpler consumer setup: `oxfmt` scripts and a single TypeScript config export.
48+
49+
**Negative / tradeoffs**
50+
51+
- Prettier plugins are not supported; anything that relied on an unsupported
52+
plugin needs another tool or must be dropped.
53+
- Output is “Prettier-like” but not identical everywhere; teams should expect a
54+
one-time format churn when switching.
55+
- Some Prettier options and comment forms have no equivalent; those are called
56+
out in `oxfmt-preset.mjs` and the readme.
57+
58+
Historical decision docs that mention Prettier remain valid as context for
59+
_formatting style_; this decision supersedes the _tool choice_ for applying that
60+
style in new work.

docs/style-guide.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ Much of this is subjective, but most opinions are thought through and based on
1818
years of experience working with large codebases and teams.
1919

2020
Note: Not every possible formatting opinion is mentioned because they are
21-
handled automatically by [Prettier](https://prettier.io) anyway.
21+
handled automatically by [Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html)
22+
anyway.
2223

2324
## JavaScript
2425

lint-rules/epic-web-plugin.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { definePlugin } from '@oxlint/plugins'
22
import noManualDispose from './no-manual-dispose.js'
3+
import noPrettierIgnore from './no-prettier-ignore.js'
34
import preferDisposeInTests from './prefer-dispose-in-tests.js'
45

56
const plugin = definePlugin({
@@ -8,9 +9,10 @@ const plugin = definePlugin({
89
},
910
rules: {
1011
'no-manual-dispose': noManualDispose,
12+
'no-prettier-ignore': noPrettierIgnore,
1113
'prefer-dispose-in-tests': preferDisposeInTests,
1214
},
1315
})
1416

1517
export default plugin
16-
export { noManualDispose, preferDisposeInTests }
18+
export { noManualDispose, noPrettierIgnore, preferDisposeInTests }

lint-rules/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ Oxlint JS plugin rules.
1414
## Rules
1515

1616
- [`epic-web/no-manual-dispose`](./no-manual-dispose.md)
17+
- [`epic-web/no-prettier-ignore`](./no-prettier-ignore.md)
1718
- [`epic-web/prefer-dispose-in-tests`](./prefer-dispose-in-tests.md)

lint-rules/no-manual-dispose.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use `using` or `await using`.
88
Manual cleanup with `try/finally` and disposal calls is easier to get wrong and
99
less readable than language-level disposables.
1010

11-
This rule is implemented as an Oxlint JS plugin rule using Oxlint's
12-
`createOnce` API.
11+
This rule is implemented as an Oxlint JS plugin rule using Oxlint's `createOnce`
12+
API.
1313

1414
## What it warns on
1515

lint-rules/no-prettier-ignore.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* @param {import('eslint').AST.Token} comment
3+
*/
4+
function isPrettierIgnoreDirective(comment) {
5+
const v = comment.value
6+
if (comment.type === 'Line') {
7+
return /^\s*prettier-ignore(?:-next|-start|-end)?(?:\s|$)/.test(v)
8+
}
9+
return /^\s*prettier-ignore(?:-next|-start|-end)?(?:\s|$)/m.test(v)
10+
}
11+
12+
/**
13+
* @param {string} text
14+
*/
15+
function replacePrettierIgnoreWithOxfmt(text) {
16+
return text
17+
.replace(/prettier-ignore-next/g, 'oxfmt-ignore')
18+
.replace(/prettier-ignore-start/g, 'oxfmt-ignore-start')
19+
.replace(/prettier-ignore-end/g, 'oxfmt-ignore-end')
20+
.replace(/prettier-ignore/g, 'oxfmt-ignore')
21+
}
22+
23+
const rule = {
24+
meta: {
25+
type: 'suggestion',
26+
docs: {
27+
description:
28+
'Prefer `oxfmt-ignore` over `prettier-ignore` in JS/TS comments formatted by Oxfmt',
29+
},
30+
schema: [],
31+
fixable: 'code',
32+
messages: {
33+
useOxfmtIgnore:
34+
'Use `oxfmt-ignore` instead of `prettier-ignore` for Oxfmt. Keep `prettier-ignore` only for non-JS regions (for example Vue template/style or HTML) per Oxfmt docs.',
35+
},
36+
},
37+
createOnce(context) {
38+
return {
39+
Program() {
40+
const sourceCode = context.sourceCode
41+
for (const comment of sourceCode.getAllComments()) {
42+
if (!isPrettierIgnoreDirective(comment)) continue
43+
44+
const range = /** @type {[number, number]} */ (comment.range)
45+
const raw = sourceCode.text.slice(range[0], range[1])
46+
const next = replacePrettierIgnoreWithOxfmt(raw)
47+
if (next === raw) continue
48+
49+
context.report({
50+
loc: comment.loc,
51+
messageId: 'useOxfmtIgnore',
52+
fix(fixer) {
53+
return fixer.replaceTextRange(range, next)
54+
},
55+
})
56+
}
57+
},
58+
}
59+
},
60+
}
61+
62+
export default rule

0 commit comments

Comments
 (0)