npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kekkai/structure-lint

v1.2.0

Published

Config-driven ESLint structure rule generator enforcing one-way folder dependency flow, with a separate CLI for docs sync/verification.

Readme

codecov

English | 繁體中文

📦 @kekkai/structure-lint

一個 config-driven 的 ESLint 結構規則產生器,用來強制專案遵守 單向資料夾依賴(One-way Dependency Flow),並提供獨立 CLI 來同步與驗證專案文件中的依賴規則說明。

🔍 What Problem Does This Solve?

在中大型前端專案中,資料夾結構往往隨著功能成長而逐漸失控:

  • 模組之間可以隨意互相 import
  • 相對路徑(../)跨層引用,難以追蹤實際依賴關係
  • 架構規則只存在於文件,無法被工具驗證
  • 文件與實際程式碼逐漸脫節

@kekkai/structure-lint 的目的不是「規範寫法」,而是 把資料夾結構與依賴方向,轉換成可被 ESLint 強制執行的規則

⚠️ ESLint v9+ Required

本套件基於 ESLint Flat Config 設計,
僅支援 ESLint v9 以上版本

如果你的專案仍使用 .eslintrc(legacy config),
請先升級至 Flat Config 架構後再使用本套件。

✨ Core Ideas

這個套件建立在三個核心概念之上:

  1. Folder-as-a-Module

    • 每個子資料夾視為一個獨立模組(例如 hooks/useShuffleCardscomponents/Card
    • 建議使用 index.ts 作為模組入口,讓引用點一致且可讀
  2. One-way Dependency Flow

    • 依賴關係以「資料夾層級」定義:每個資料夾只能依賴允許的下游資料夾
    • 不允許逆向依賴,也不允許跨層跳轉
  3. Enforceable Imports (Alias-only Cross-folder Imports)

    • 模組內部允許相對路徑(./
    • 跨資料夾引用一律使用專案 alias(例如 ~app/...),避免 ../ 形成不可控的捷徑依賴

🗂️ Example Folder Structure:

src/
├─ components/
│  └─ Card/
│     ├─ Component.tsx
│     ├─ index.ts
│     └─ types.ts
├─ containers/
│  └─ DeckDrawStage/
│     ├─ Container.tsx
│     ├─ index.ts
│     └─ types.ts
└─ main.tsx

📥 Installation

npm install -D @kekkai/structure-lint

本套件同時提供:

  • ESLint Flat Config 的設定產生器
  • 一個獨立的 CLI,用於文件同步與驗證

🚀 Quick Start

  • eslint.config.(js|ts) @kekkai/structure-lint 支援兩種使用方式,請依專案需求選擇最合適的模式:

    import { defineConfig } from 'eslint/config';
    import { createStructureLint } from '@kekkai/structure-lint';
    
    export default defineConfig([
      // Options 1. 使用 structure.config.json
      ...createStructureLint(),
    
      // Options 2. 直接在 eslint.config.ts 中定義規則
      ...createStructureLint({
        appAlias: '~app',
        lintFiles: 'src/{folder}/**/*.{ts,tsx}',
        dependencyFlow: [
          ['pages', 'layouts'],
          ['layouts', 'containers'],
          ['containers', 'components'],
          ['components', 'hooks'],
        ],
      }),
    ]);
  • CLI @kekkai/structure-lint 提供獨立的 CLI,用來將 Dependency Flow 規則同步到指定的 Markdown 文件 中。

    npx structure-lint

    💡 CLI 僅會讀取 structure.config.json。

🧩 structure.config.json

structure.config.json 用來定義專案的 資料夾依賴規則 與 文件同步設定。此檔案會同時被:

  • ESLint 設定 (createStructureLint())
  • CLI (npx structure-lint)

所讀取。

🔧 Configuration Options

{
  /**
   * appAlias
   *   type (required):
   *   - string
   *
   * 專案使用的模組 alias,用於強制跨資料夾 import 時的統一路徑格式。
   * - 所有跨 folder 的 import 必須 使用此 alias
   * - 用來取代不安全、不可控的 `../` 相對路徑
   */
  "appAlias": "~app",

  /**
   * lintFiles
   *   type (required):
   *   - string | string[]
   *
   * 指定 ESLint 要套用結構規則的檔案路徑。
   * - 必須包含 `{folder}` 佔位符
   * - `{folder}` 會在執行時自動替換為 dependencyFlow 中的每個資料夾名稱
   */
  "lintFiles": "src/{folder}/**/*.{ts,tsx}",

  /**
   * moduleLayout
   *   type (optional)
   *   - 'folder' | 'flat'
   *
   * 用來指定各層(layer)中模組的組織方式,
   * 並影響相對路徑(relative import)的限制規則產生方式。
   *
   * - 'folder'(預設):
   *   每個模組以資料夾為單位存在
   *   (例如 components/Card/*),
   *   跨模組只能透過模組入口(index.ts)引用。
   *
   * - 'flat':
   *   模組直接以檔案形式存在於該層底下
   *   (例如 components/Card.tsx)。
   *
   * 預設值:'folder'
   */
  "moduleLayout": "folder",

  /**
   * dependencyFlow
   *   type (required):
   *   - [
   *       string, // from folder
   *       string, // to folder
   *       {
   *         description?: string; // 僅用於文件輸出(Mermaid flowchart 註解),不影響 ESLint 規則
   *         selfOnly?: boolean;   // true 表示 from folder 只能直接依賴 to folder,不可再往下延伸依賴鏈
   *       }?
   *     ][]
   *
   * 定義資料夾之間的 單向依賴關係。
   */
  "dependencyFlow": [
    ["pages", "containers"],
    ["containers", "contexts", { description: "Only Provider" }],
    ["containers", "components"],
    ["components", "hooks"],
    ["hooks", "contexts", { description: "Only Context", selfOnly: true }],
  ],

  /**
   * docs
   *   type (optional)
   *   - {
   *       file: string;      // 要寫入的 Markdown 檔案路徑
   *       markerTag: string; // 用來標記自動產生區塊的識別字
   *       content?: string;  // 自定義文件說明內容
   *     }
   *
   * 設定 CLI 用來 同步 dependencyFlow 至 Markdown 文件。
   */
  "docs": {
    "file": "README.md",
    "markerTag": "DEPENDENCY_RULE"
  },

  /**
   * overrideRules
   *   type (optional)
   *   - {
   *       [key: string]:       // key 必須是 dependencyFlow 中出現過的資料夾名稱
   *         EslintRulesConfig; // value 為 ESLint rules 設定物件
   *     }
   *
   * 為特定資料夾覆寫或補充 ESLint 規則。
   */
  "overrideRules": {
    "contexts": {
      "react-refresh/only-export-components": "off
    }
  },

  /**
   * packageImportRules
   *   type (optional)
   *   - {
   *       name: string;                    // 要限制的模組路徑。可以是 npm 套件名稱,
   *                                        // 也可以是內部 alias 路徑(如 "@/services/httpClient")。
   *                                        // 設定 `pattern: true` 時,視為 glob 匹配。
   *       importNames?: string[];          // 僅限制指定的 named imports。若省略,則限制整個模組。
   *       allowedInFolders: F[];           // 允許使用該模組的資料夾清單。
   *       pattern?: boolean;               // true 時,name 視為 glob 模式(如 "@/services/auth/**")。
   *                                        // 預設:false(字面匹配)。
   *       exemptFilePatterns?: string[];   // 豁免此規則的檔案 glob 列表。
   *                                        // 常見用途:允許測試檔案使用在 production 中被限制的 import。
   *     }[]
   *
   * 限制 特定套件或特定 import 成員 只能在指定資料夾中使用。
   *
   * `name` 欄位也支援內部 alias 路徑,不限於 npm 套件:
   *   { "name": "@/services/httpClient", "allowedInFolders": ["services"] }
   */
  "packageImportRules": [
    {
      "name": "react",
      "importNames": ["createContext"],
      "allowedInFolders": ["contexts"]
    },
    {
      "name": "react",
      "importNames": ["useContext"],
      "allowedInFolders": ["hooks"]
    },
    {
      "name": "@/services/auth/**",
      "allowedInFolders": ["services"],
      "pattern": true
    },
    {
      "name": "vue",
      "importNames": ["provide"],
      "allowedInFolders": ["contexts"],
      "exemptFilePatterns": ["**/*.test.{js,vue}", "**/*.spec.{js,vue}"]
    }
  ],

  /**
   * lintFilesIgnore
   *   type (optional):
   *   - string | string[]
   *
   * 全域排除特定檔案的 glob 模式。
   * 符合條件的檔案不會被任何 structure-lint 規則檢查。
   * 適合用來整批排除測試檔、story 檔等類別。
   */
  "lintFilesIgnore": ["**/*.test.{js,ts,vue}", "**/*.spec.{js,ts,vue}"],

  /**
   * additionalAliases
   *   type (optional):
   *   - Record<string, string>
   *
   * 宣告專案中除了 `appAlias` 以外使用的其他模組 alias。
   * 這些 alias 同樣視為具有層級意識(layer-aware)——
   * 同層與跨層的 import 限制規則也會套用到這些 alias 上,
   * 與 `appAlias` 的處理方式一致。
   *
   * 常見用途:為專案根目錄建立 alias,讓根目錄的工具檔案
   * (vitest.setup.js、storybook/preview.js 等)
   * 可以在不觸發「禁止向上目錄 import」規則的前提下被引用:
   *
   *   // vite.config.ts
   *   resolve: { alias: { '~root': fileURLToPath(new URL('.', import.meta.url)) } }
   *
   *   // 在任何被 lint 的檔案中
   *   import setup from '~root/vitest.setup'  // ✅ 允許
   */
  "additionalAliases": {
    "~root": "."
  },

  /**
   * severity
   *   type (optional):
   *   - 'error' | 'warn'
   *
   * 設定所有 structure-lint 規則的 ESLint 嚴重程度。
   * 預設:'error'。
   *
   * 在逐步導入期間,可設為 'warn' 讓問題浮現但不阻斷 CI,
   * 待程式碼庫整理完畢後再切換為 'error'。
   */
  "severity": "error"
}

dependencyFlow 設定對應的 mermaid flowchart 如下:

flowchart TD
  pages --> containers
  containers --> | Only Provider | contexts
  containers --> components
  components --> hooks
  hooks --> | Only Context | contexts

📝 使用 description 為邊(Edge)加上標註

dependencyFlow 元組的第三個選用欄位 description,會在 Mermaid 圖表中渲染為邊的標籤:

["containers", "contexts", { "description": "Only Provider" }]
// 渲染結果:containers --> | Only Provider | contexts

此欄位純粹用於文件說明,不影響任何 ESLint 規則。 適合用來說明非直覺的依賴關係,例如所使用的設計模式:

"dependencyFlow": [
  ["containers", "contexts", { "description": "Only Provider" }],
  ["hooks",      "contexts", { "description": "Only Context", "selfOnly": true }]
]

渲染結果:

flowchart TD
  containers --> | Only Provider | contexts
  hooks --> | Only Context | contexts

🔄 CLI 變更摘要輸出

執行 npx structure-lint 時,CLI 現在會回報依賴流圖是否實際發生了變更:

✔ No changes to dependency flow diagram.
✔ Docs updated. Updated 3 edge(s) in the dependency flow diagram.

以範例中 markerTag 的設定,CLI 會尋找以下區塊並覆寫內容:

<!-- DEPENDENCY_RULE:START -->
<!-- DEPENDENCY_RULE:END -->

🔁 Circular Dependencies (Optional)

@kekkai/structure-lint 會嚴格限制 跨層 的單向依賴方向,但仍刻意允許 同一層內的模組彼此引用 作為設計取捨。

因此,在相同 layer 之內,仍然可能發生循環依賴(circular dependencies)。 這在模組規模較小時通常是可接受的,但隨著專案成長,可能會逐漸帶來風險。

如果你的團隊希望進一步偵測這類情況,可以選擇性地啟用以下規則:

⚠️ TypeScript 專案必須正確設定 resolver,否則可能無法偵測到循環依賴。 建議搭配使用 eslint-import-resolver-typescript

import imports from 'eslint-plugin-import';
import { defineConfig } from 'eslint/config';
import { createStructureLint } from '@kekkai/structure-lint';

export default defineConfig([
  {
    plugins: {
      import: imports,
    },
    settings: {
      'import/parsers': {
        // Project file extensions handled by the TypeScript parser
        '@typescript-eslint/parser': ['.ts', '.tsx'],
      },
      'import/resolver': {
        typescript: true,
        node: {
          // Project file extensions used for module resolution
          extensions: ['.ts', '.tsx'],
        },
      },
    },
    rules: {
      'import/no-cycle': 'error',
    },
  },
  ...createStructureLint(),
]);

🧠 Philosophy

@kekkai/structure-lint 將資料夾結構視為架構本身。

由你定義依賴規則,
由 ESLint 強制執行,
並確保文件與實際架構保持同步。

適合重視可讀性、可維護性長期結構一致性的團隊。