|
|
|
@@ -1,75 +1,85 @@
|
|
|
|
|
---
|
|
|
|
|
description: Shared code comes through # aliases in package.json imports, each src/ subfolder's entry is index.ts, barrels keep imports few; never ../ out of a directory
|
|
|
|
|
description: An alias owns its target (folder index.ts = folder and below, any other file = that file); no overlap; outside an area only the exact alias reaches it; ../ is banned in every file; no folder names in the rule
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## `#` aliases
|
|
|
|
|
## `#` aliases own their targets
|
|
|
|
|
|
|
|
|
|
Shared code is imported through Node subpath imports declared in
|
|
|
|
|
`package.json` `imports`, never by a relative path that climbs out of a
|
|
|
|
|
directory.
|
|
|
|
|
`package.json` `imports`. Node, TypeScript, Vite, Vite+'s config bundler, and
|
|
|
|
|
Oxlint's plugin loader all resolve them natively, so `.ts` source runs
|
|
|
|
|
unbuilt. `vp pack` inlines them, so they never reach package consumers.
|
|
|
|
|
|
|
|
|
|
**Targets (user rule, 2026-10-16):** an alias may point ONLY at a direct
|
|
|
|
|
subfolder of `src/`: its `index.ts` (`"#helpers": "./src/helpers/index.ts"`)
|
|
|
|
|
or a `#x/*` pattern inside that one folder. Never alias anything outside
|
|
|
|
|
`src/` (`scripts/`, root files), `src/` itself (no `#plugin` for
|
|
|
|
|
`./src/index.ts`), or a deeper folder (`./src/a/b/`). Code that would need
|
|
|
|
|
such an alias uses a relative import instead; `scripts/` and
|
|
|
|
|
`vite.config.ts` import each other relatively. Layer rules
|
|
|
|
|
(`enforce-layer-order`) must still pass, so reorder entries if needed.
|
|
|
|
|
One rule for every file, never tied to folder or file names (user rule
|
|
|
|
|
2026-10-17; it replaces the earlier "aliases only point at direct `src/`
|
|
|
|
|
subfolders" rule and the "import-check checks only aliased folders" scope):
|
|
|
|
|
|
|
|
|
|
- **An alias may point anywhere** and owns its target. An alias to a
|
|
|
|
|
folder's `index.ts` owns that folder and everything below it; an alias to
|
|
|
|
|
any other file owns only that file.
|
|
|
|
|
- **No overlap.** No file may be owned by 2 aliases: no alias inside another
|
|
|
|
|
alias's area, and no 2 aliases with the same target. import-check's
|
|
|
|
|
`enforce-layer-order` reports it once at the top of every file 2 aliases
|
|
|
|
|
claim. Fix by merging the aliases or moving files apart.
|
|
|
|
|
- **Outside an area, only the exact alias reaches it.** A deeper alias
|
|
|
|
|
specifier (`#a/one.ts`, `no-barrel-bypass`) and a relative path into the
|
|
|
|
|
area (`./a/one.ts`, `no-relative-bypass`; `./b/f.ts` to a file alias,
|
|
|
|
|
`prefer-alias-import`, fixed to the alias) are bypasses.
|
|
|
|
|
- **Inside an area, relative imports of siblings and subfolders are free.**
|
|
|
|
|
- **`../` is banned in every file**, owned or not (`no-parent-import`).
|
|
|
|
|
Anything above a file needs an alias.
|
|
|
|
|
- **Files no alias owns** need no `index.ts` and no alias, and import `./`
|
|
|
|
|
siblings and subfolders freely.
|
|
|
|
|
- Unchanged: no `/*` subpath patterns, the key order of `imports` is the
|
|
|
|
|
layer order `enforce-layer-order` checks (add a new alias at the right
|
|
|
|
|
layer), and entry files are `index.ts` only.
|
|
|
|
|
|
|
|
|
|
```jsonc
|
|
|
|
|
"imports": {
|
|
|
|
|
"#helpers": "./src/helpers/index.ts",
|
|
|
|
|
"#components/*": "./src/components/*"
|
|
|
|
|
"#a": "./a/index.ts", // owns a/**
|
|
|
|
|
"#f": "./b/f.ts", // owns only b/f.ts
|
|
|
|
|
"#a-sub": "./a/sub/index.ts", // no: inside #a
|
|
|
|
|
"#a2": "./a/index.ts" // no: same target as #a
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { typeNameTail } from "#helpers"; // yes
|
|
|
|
|
import { typeNameTail } from "../helpers/ast.ts"; // no: import/no-relative-parent-imports
|
|
|
|
|
// in main.ts, which no alias owns
|
|
|
|
|
import { a } from "#a"; // yes
|
|
|
|
|
import { one } from "#a/one.ts"; // no: deeper alias specifier
|
|
|
|
|
import { one } from "./a/one.ts"; // no: relative path into #a
|
|
|
|
|
import { f } from "#f"; // yes
|
|
|
|
|
import { f } from "./b/f.ts"; // no: relative path to a file alias
|
|
|
|
|
import { g } from "./b/g.ts"; // yes: unowned
|
|
|
|
|
// in a/one.ts, owned by #a
|
|
|
|
|
import { two } from "./two.ts"; // yes: sibling
|
|
|
|
|
import { deep } from "./sub/deep.ts"; // yes: subfolder of the same area
|
|
|
|
|
import { x } from "../x.ts"; // no: no ../ anywhere
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Node, TypeScript, Vite, Vite+'s config bundler, and Oxlint's plugin loader
|
|
|
|
|
all resolve them natively, so `.ts` source runs unbuilt. `vp pack` inlines them,
|
|
|
|
|
so they never reach package consumers.
|
|
|
|
|
|
|
|
|
|
- Deno's trailing-slash mappings (`"#components/"`) don't exist in Node; they
|
|
|
|
|
become patterns, `"#components/*": "./src/components/*"`.
|
|
|
|
|
- Aliases point at `.ts` files only. There is no `#package` alias
|
|
|
|
|
([[ts-only-files-and-imports]]).
|
|
|
|
|
- In repos linted by import-check, the key order of `imports` is the layer
|
|
|
|
|
order `enforce-layer-order` checks; add a new alias at the right layer.
|
|
|
|
|
- Lint `jsPlugins` entries in `vite.config.ts` stay path strings, even when
|
|
|
|
|
the file has an alias.
|
|
|
|
|
- Keep test-only helpers in their own alias (e.g. `#testing`) so they never
|
|
|
|
|
land in a bundle.
|
|
|
|
|
|
|
|
|
|
## `index.ts` entries and barrels
|
|
|
|
|
## Barrels
|
|
|
|
|
|
|
|
|
|
- Every `src/` subfolder's entry file is `index.ts`, never `mod.ts` (decided
|
|
|
|
|
2026-10-12; import-check's `enforce-mod-file` requires it only in folders a
|
|
|
|
|
`#` alias owns). Folders outside `src/`, such as `scripts/`, have none.
|
|
|
|
|
- A directory used from outside exposes its API through its `index.ts`
|
|
|
|
|
barrel, and callers import the barrel (`#ui`), not files inside it
|
|
|
|
|
(`no-barrel-bypass`).
|
|
|
|
|
- An area used from outside exposes its API through its alias target, and
|
|
|
|
|
callers import the alias (`#ui`), not files inside it.
|
|
|
|
|
- Barrels are also the standard fix for `import/max-dependencies`: one barrel
|
|
|
|
|
import replaces many direct ones ([[lint-overrides-policy]]).
|
|
|
|
|
- `import/no-cycle` is on; a barrel must not create a cycle.
|
|
|
|
|
|
|
|
|
|
Relative imports inside one directory (`./sibling.ts`) are fine, with the
|
|
|
|
|
`.ts` extension written out.
|
|
|
|
|
Relative imports are written with the `.ts` extension.
|
|
|
|
|
|
|
|
|
|
## `scripts/` is flat (user rule, 2026-10-16)
|
|
|
|
|
|
|
|
|
|
- `scripts/` has no subfolders and no `index.ts`. Barrels and `index.ts`
|
|
|
|
|
entries exist only inside `src/`; delete any `index.ts` outside it.
|
|
|
|
|
- `scripts/` has no subfolders and no `index.ts`.
|
|
|
|
|
- Each tool is one file, `scripts/<name>.ts`, imported directly by file:
|
|
|
|
|
`import { decorators } from "./scripts/decorators.ts";` in
|
|
|
|
|
`vite.config.ts`.
|
|
|
|
|
`vite.config.ts`. No alias owns `scripts/`, so these `./` imports are free.
|
|
|
|
|
- Code in a former `scripts/<tool>/` folder is merged or renamed into flat
|
|
|
|
|
`scripts/<name>.ts` files. Non-TS assets (HTML, images, fixtures) move to
|
|
|
|
|
where they belong (next to the tool's output, `.agents/`, or a gitignored
|
|
|
|
|
scratch dir). A subfolder that is really an app moves under `src/` as an
|
|
|
|
|
aliased folder.
|
|
|
|
|
- import-check 0.10.0+ checks folder layout only where the importer or the
|
|
|
|
|
target sits in a `#`-aliased folder, so `vite.config.ts` →
|
|
|
|
|
`./scripts/x.ts` passes without any override. Remove overrides that only
|
|
|
|
|
dodged import-check.
|
|
|
|
|
scratch dir). A subfolder that is really an app moves under an alias.
|
|
|
|
|