@ilyasemenov/oxc-config 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ilya Semenov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # @ilyasemenov/oxc-config
2
+
3
+ Reusable oxlint, oxfmt, and ESLint configurations for projects I develop or oversee.
4
+
5
+ Code is linted with oxlint and formatted with oxfmt.
6
+ For Vue, ESLint additionally lints and formats templates, which oxlint cannot do.
7
+ Both HTML and Pug templates are supported.
8
+
9
+ ## Setup
10
+
11
+ Install:
12
+
13
+ ```sh
14
+ npm install -D @ilyasemenov/oxc-config oxlint oxfmt
15
+ ```
16
+
17
+ Create `oxlint.config.ts`:
18
+
19
+ ```ts
20
+ import base from "@ilyasemenov/oxc-config/oxlint"
21
+ import { defineConfig } from "oxlint"
22
+
23
+ export default defineConfig({
24
+ extends: [base],
25
+ })
26
+ ```
27
+
28
+ Create `oxfmt.config.ts`:
29
+
30
+ ```ts
31
+ export { default } from "@ilyasemenov/oxc-config/oxfmt"
32
+ ```
33
+
34
+ To change formatting options, spread the config into your own: `defineConfig({ ...base, printWidth: 100 })`.
35
+
36
+ ### Vue
37
+
38
+ For Vue projects, also install:
39
+
40
+ ```sh
41
+ npm install -D eslint @antfu/eslint-config typescript-eslint typescript@~6.0.3
42
+ ```
43
+
44
+ typescript-eslint doesn't support TypeScript 6.1+ yet.
45
+
46
+ Create `eslint.config.js`:
47
+
48
+ ```js
49
+ // @ts-check
50
+
51
+ import config from "@ilyasemenov/oxc-config/eslint"
52
+
53
+ export default await config()
54
+ ```
55
+
56
+ It uses the Vue rules from [@antfu/eslint-config](https://github.com/antfu/eslint-config) with my preferences on top, and only checks `.vue` files.
57
+ The oxlint config leaves unused variables in Vue files to ESLint, so use both.
58
+
59
+ To add your own Vue rules:
60
+
61
+ ```js
62
+ // @ts-check
63
+
64
+ import config from "@ilyasemenov/oxc-config/eslint"
65
+ import { defineConfig } from "eslint/config"
66
+
67
+ export default defineConfig({
68
+ files: ["**/*.vue"],
69
+ extends: [await config()],
70
+ rules: {
71
+ "vue/require-v-for-key": "error",
72
+ },
73
+ })
74
+ ```
75
+
76
+ ### Pug
77
+
78
+ For Pug templates, install:
79
+
80
+ ```sh
81
+ npm install -D eslint-plugin-vue-pug@flat
82
+ ```
83
+
84
+ As of 29.09.2026, the `latest` tag still points to the older 0.x version.
85
+ Then enable Pug in `eslint.config.js`:
86
+
87
+ ```js
88
+ // @ts-check
89
+
90
+ import config from "@ilyasemenov/oxc-config/eslint"
91
+
92
+ export default await config({ vuePug: true })
93
+ ```
94
+
95
+ The Pug preset disables some HTML checks for all Vue files, so enable it only in Pug projects.
96
+
97
+ ## A few preferences
98
+
99
+ - No semicolons.
100
+ - Imports grouped as side effects, builtins, external, internal, parent, sibling/index, and unknown, with a blank line between groups.
101
+ - `bun:*` imports grouped with `node:*`; `~`, `#`, and `@/` treated as internal prefixes.
102
+ - Package subpaths kept together: `#app/…` before `#app-server/…`.
103
+ - Named imports sorted by their original name, ignoring aliases; type imports before value imports from the same module.
104
+ - Vue component names in kebab-case, for both HTML and Pug.
105
+ - No empty component blocks; `v-for` without a key and string concatenation are allowed.
106
+
107
+ Imports are sorted by Perfectionist in oxlint: oxfmt's `sortImports` doesn't support this order, so keep it disabled.
108
+
109
+ ## Running it
110
+
111
+ ```sh
112
+ npx oxlint --fix
113
+ npx eslint --fix '**/*.vue'
114
+ npx oxfmt
115
+ ```
116
+
117
+ Leave out the ESLint command in projects without Vue.
118
+ If you also use Stylelint, run it before oxfmt.
119
+
120
+ If an import needs both moving and sorting inside `{ … }`, oxlint needs a second `--fix` run ([oxc#16118](https://github.com/oxc-project/oxc/issues/16118)).
121
+
122
+ ### Monorepos
123
+
124
+ Running oxlint from the root also loads configs of nested packages.
125
+ If they resolve different copies of the same JS plugin, oxlint fails with `Plugin name 'perfectionist' is already registered`.
126
+ If the root config covers all packages, pass it explicitly to skip nested configs:
127
+
128
+ ```sh
129
+ npx oxlint --config oxlint.config.ts --fix
130
+ ```
131
+
132
+ Otherwise, run oxlint from each package directory.
133
+
134
+ ## Development
135
+
136
+ ```sh
137
+ mise trust
138
+ mise install
139
+ bun install
140
+ bun test
141
+ bun run types
142
+ bun run build
143
+ bun run lint
144
+ ```
145
+
146
+ Tests run directly against the source configs; there's no need to build first.
@@ -0,0 +1,14 @@
1
+ import { Linter } from "eslint";
2
+ //#region src/eslint.d.ts
3
+ /** Options for the Vue SFC configuration. */
4
+ export interface EslintOptions {
5
+ /**
6
+ * Enable Pug templates with the optional eslint-plugin-vue-pug peer.
7
+ * This disables incompatible HTML rules for all Vue files in the config.
8
+ * @default false
9
+ */
10
+ vuePug?: boolean;
11
+ }
12
+ /** Create Vue SFC rules complementing oxlint, with optional Pug support. */
13
+ export default function createConfig({ vuePug }?: EslintOptions): Promise<Linter.Config[]>;
14
+ //#endregion
@@ -0,0 +1,52 @@
1
+ import { vue } from "@antfu/eslint-config";
2
+ import { defineConfig } from "eslint/config";
3
+ import tseslint from "typescript-eslint";
4
+ //#region src/eslint.ts
5
+ /** Create Vue SFC rules complementing oxlint, with optional Pug support. */
6
+ async function createConfig({ vuePug = false } = {}) {
7
+ const configs = defineConfig(await vue({
8
+ typescript: true,
9
+ sfcBlocks: false,
10
+ stylistic: { braceStyle: "1tbs" }
11
+ }), {
12
+ name: "ilyasemenov/vue/oxlint",
13
+ plugins: { "@typescript-eslint": tseslint.plugin },
14
+ rules: { "@typescript-eslint/no-unused-vars": ["error", { ignoreRestSiblings: true }] }
15
+ }, {
16
+ name: "ilyasemenov/vue/rules",
17
+ rules: {
18
+ "vue/component-name-in-template-casing": [
19
+ "error",
20
+ "kebab-case",
21
+ { registeredComponentsOnly: false }
22
+ ],
23
+ "vue/no-empty-component-block": "error",
24
+ "vue/prefer-template": "off",
25
+ "vue/require-v-for-key": "off"
26
+ }
27
+ });
28
+ if (vuePug) {
29
+ const { default: pluginVuePug } = await import("eslint-plugin-vue-pug");
30
+ configs.push(...defineConfig(pluginVuePug.configs["flat/recommended"], {
31
+ name: "ilyasemenov/vue-pug/compat",
32
+ rules: { "vue/html-quotes": [
33
+ "error",
34
+ "double",
35
+ { avoidEscape: true }
36
+ ] }
37
+ }, {
38
+ name: "ilyasemenov/vue-pug/rules",
39
+ rules: { "vue-pug/component-name-in-template-casing": [
40
+ "error",
41
+ "kebab-case",
42
+ { registeredComponentsOnly: false }
43
+ ] }
44
+ }));
45
+ }
46
+ return defineConfig({
47
+ files: ["**/*.vue"],
48
+ extends: configs
49
+ });
50
+ }
51
+ //#endregion
52
+ export { createConfig as default };
@@ -0,0 +1,7 @@
1
+ //#region src/oxfmt.d.ts
2
+ /** Shared formatting; import sorting belongs to oxlint. */
3
+ declare const _default: {
4
+ semi: false;
5
+ };
6
+ //#endregion
7
+ export { _default as default };
package/dist/oxfmt.mjs ADDED
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "oxfmt";
2
+ //#region src/oxfmt.ts
3
+ /** Shared formatting; import sorting belongs to oxlint. */
4
+ var oxfmt_default = defineConfig({ semi: false });
5
+ //#endregion
6
+ export { oxfmt_default as default };
@@ -0,0 +1,31 @@
1
+ //#region src/oxlint.d.ts
2
+ /** Import sorting and Vue script rules, paired with the ESLint entry point. */
3
+ declare const _default: {
4
+ jsPlugins: string[];
5
+ rules: {
6
+ "perfectionist/sort-imports": ["error", {
7
+ type: "custom";
8
+ alphabet: string;
9
+ environment: "bun";
10
+ groups: (string | string[])[];
11
+ internalPattern: string[];
12
+ newlinesBetween: number;
13
+ fallbackSort: {
14
+ type: string;
15
+ };
16
+ }];
17
+ "perfectionist/sort-named-imports": ["error", {
18
+ type: "custom";
19
+ alphabet: string;
20
+ ignoreAlias: boolean;
21
+ }];
22
+ };
23
+ overrides: {
24
+ files: string[];
25
+ rules: {
26
+ "no-unused-vars": "off";
27
+ };
28
+ }[];
29
+ };
30
+ //#endregion
31
+ export { _default as default };
@@ -0,0 +1,48 @@
1
+ import { Alphabet } from "eslint-plugin-perfectionist/alphabet";
2
+ import { defineConfig } from "oxlint";
3
+ //#region src/oxlint.ts
4
+ const alphabet = Alphabet.generateRecommendedAlphabet().sortByNaturalSort().placeCharacterBefore({
5
+ characterBefore: "/",
6
+ characterAfter: "-"
7
+ }).placeCharacterBefore({
8
+ characterBefore: ".",
9
+ characterAfter: "/"
10
+ }).getCharacters().replaceAll(/[\uD800-\uDFFF]/g, "");
11
+ /** Import sorting and Vue script rules, paired with the ESLint entry point. */
12
+ var oxlint_default = defineConfig({
13
+ jsPlugins: [import.meta.resolve("eslint-plugin-perfectionist")],
14
+ rules: {
15
+ "perfectionist/sort-imports": ["error", {
16
+ type: "custom",
17
+ alphabet,
18
+ environment: "bun",
19
+ groups: [
20
+ "side-effect",
21
+ "builtin",
22
+ "external",
23
+ "internal",
24
+ "parent",
25
+ ["sibling", "index"],
26
+ "unknown"
27
+ ],
28
+ internalPattern: [
29
+ "^~",
30
+ "^#",
31
+ "^@/"
32
+ ],
33
+ newlinesBetween: 1,
34
+ fallbackSort: { type: "type-import-first" }
35
+ }],
36
+ "perfectionist/sort-named-imports": ["error", {
37
+ type: "custom",
38
+ alphabet,
39
+ ignoreAlias: true
40
+ }]
41
+ },
42
+ overrides: [{
43
+ files: ["**/*.vue"],
44
+ rules: { "no-unused-vars": "off" }
45
+ }]
46
+ });
47
+ //#endregion
48
+ export { oxlint_default as default };
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@ilyasemenov/oxc-config",
3
+ "version": "0.1.0",
4
+ "description": "Shared oxlint, oxfmt, and ESLint configurations for JavaScript, TypeScript, and Vue",
5
+ "homepage": "https://github.com/IlyaSemenov/oxc-config#readme",
6
+ "bugs": "https://github.com/IlyaSemenov/oxc-config/issues",
7
+ "license": "MIT",
8
+ "author": "Ilya Semenov",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/IlyaSemenov/oxc-config.git"
12
+ },
13
+ "files": [
14
+ "README.md",
15
+ "dist"
16
+ ],
17
+ "type": "module",
18
+ "exports": {
19
+ "./oxlint": {
20
+ "types": "./dist/oxlint.d.mts",
21
+ "import": "./dist/oxlint.mjs"
22
+ },
23
+ "./oxfmt": {
24
+ "types": "./dist/oxfmt.d.mts",
25
+ "import": "./dist/oxfmt.mjs"
26
+ },
27
+ "./eslint": {
28
+ "types": "./dist/eslint.d.mts",
29
+ "import": "./dist/eslint.mjs"
30
+ }
31
+ },
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "provenance": true
35
+ },
36
+ "scripts": {
37
+ "build": "tsdown",
38
+ "prepare": "lefthook install",
39
+ "types": "tsc && tsc -p tests/tsconfig.json",
40
+ "test": "bun test",
41
+ "prepublishOnly": "bun run build",
42
+ "lint": "oxlint --fix && oxfmt",
43
+ "lint:check": "oxlint && oxfmt --check"
44
+ },
45
+ "dependencies": {
46
+ "eslint-plugin-perfectionist": "^5.12.1"
47
+ },
48
+ "devDependencies": {
49
+ "@antfu/eslint-config": "^9.5.1",
50
+ "@arethetypeswrong/core": "^0.18.5",
51
+ "@changesets/cli": "^3.0.3",
52
+ "@tsconfig/bun": "^1.0.11",
53
+ "@types/bun": "^1.4.2",
54
+ "eslint": "^10.11.0",
55
+ "eslint-plugin-vue-pug": "^1.0.0-alpha.6",
56
+ "oxfmt": "^0.71.0",
57
+ "oxlint": "^1.86.0",
58
+ "publint": "^0.3.24",
59
+ "tsdown": "^0.23.0",
60
+ "typescript": "~6.0.3",
61
+ "typescript-eslint": "^8.71.0"
62
+ },
63
+ "peerDependencies": {
64
+ "@antfu/eslint-config": "^9.5.1",
65
+ "eslint": "^10.11.0",
66
+ "eslint-plugin-vue-pug": "^1.0.0-alpha.6",
67
+ "oxfmt": "^0.71.0",
68
+ "oxlint": "^1.86.0",
69
+ "typescript-eslint": "^8.71.0"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "eslint": {
73
+ "optional": true
74
+ },
75
+ "@antfu/eslint-config": {
76
+ "optional": true
77
+ },
78
+ "eslint-plugin-vue-pug": {
79
+ "optional": true
80
+ },
81
+ "typescript-eslint": {
82
+ "optional": true
83
+ }
84
+ },
85
+ "engines": {
86
+ "node": "^20.19.0 || ^22.18.0 || >=24.0.0"
87
+ },
88
+ "packageManager": "bun@1.4.2"
89
+ }