react-native-template-feature-boundary
v0.1.6
Published
React Native CLI template enforcing strict feature-based architecture through ESLint rules.
Downloads
1,044
Maintainers
Readme
RnFeatureBoundary
The recommended way to use this template is via the React Native CLI:
npx @react-native-community/cli init MyApp --template react-native-template-feature-boundary
A React Native CLI template that enforces a strict feature-based architecture through ESLint rules. Every feature is self-contained under src/features/<name>/ and exposes a public API via index.ts. Other features, shared code, and navigation can only interact through these public boundaries.
Table of Contents
- Installation
- Architecture Overview
- Folder Structure
- Boundary Rules
- Adding a New Feature
- How to Import Correctly
- Navigation
- Testing
- Troubleshooting
Installation
Use the React Native CLI to scaffold a new project from this template:
npx @react-native-community/cli init MyApp --template react-native-template-feature-boundaryTip: You can pin the CLI version if needed:
npx @react-native-community/cli@latest init MyApp --template react-native-template-feature-boundary
Architecture Overview
src/
├── features/ # Business domains (home, user, profile, settings, ...)
│ ├── home/
│ │ ├── __tests__/ # All tests for this feature
│ │ ├── screens/ # Screens owned by this feature
│ │ └── index.ts # Public API: what other features may consume
│ ├── user/
│ │ ├── hooks/
│ │ ├── types/
│ │ └── index.ts
│ ├── profile/
│ │ ├── screens/
│ │ └── index.ts
│ └── settings/
│ ├── screens/
│ └── index.ts
├── navigation/ # Router setup (empty by default; bring your own)
└── shared/ # Reusable UI / utilities (no barrel file)
├── components/
└── types/src/features/<name>/index.tsis the only contract other code may use.src/shared/can be imported from anywhere, includingApp.tsx.src/navigation/is left empty so you can install any navigation solution.
Boundary Rules
ESLint (eslint-plugin-boundaries) enforces the following import constraints:
| From | Can import |
| -------------------------------------- | ---------------------------------------------------------------- |
| Any file in src/ | shared, navigation |
| navigation | feature-public (screens via index.ts) |
| feature-internal (same feature) | feature-internal (same feature) |
| feature-internal (different feature) | feature-public only |
| feature-public | feature-internal (own feature), feature-public (any feature) |
Root files (App.tsx, index.js, etc.) are outside the boundary model and can import anything freely.
Adding a New Feature
Create the folder:
mkdir src/features/myFeatureAdd internal files (screens, hooks, types, etc.):
mkdir src/features/myFeature/screens touch src/features/myFeature/screens/MyFeatureScreen.tsxCreate the public API:
// src/features/myFeature/index.ts export { MyFeatureScreen } from "./screens/MyFeatureScreen";(Optional) Add tests:
mkdir src/features/myFeature/__tests__ touch src/features/myFeature/__tests__/MyFeatureScreen.test.tsx
Cleaning Up Demo Features
This template ships with sample features (home, user, profile, settings) to demonstrate the boundary architecture. Once you are ready to build your own features you can continue by modifying the demo feature, but if you want to clear the demo feature and completely write your own, run:
npm run cleanupThis will remove the demo folders and reset App.tsx to a minimal placeholder.
Why this matters:
App.tsxis outside thesrc/boundary rules and will not be architecture-checked. Do not write your app logic directly inApp.tsx. Create features undersrc/features/<name>/and import them in App.tsx.
⚠️ Warning: If you have modified files inside
src/features/home/,user/,profile/, orsettings/, before running this script, those changes will be lost. The script asks for confirmation before deleting anything.
How to Import Correctly
Valid
// From a screen inside 'profile' -> user public API
import { User } from "../../user";
// From navigation -> feature public API
import { HomeScreen } from "../features/home";
// From anywhere -> shared
import { Container } from "../shared/components/Container";Invalid (will fail lint)
// Deep import into another feature
import { useUser } from "../../user/hooks/useUser";
// Deep import into another feature's types
import { User } from "../../user/types/userTypes";ESLint error you will see:
Architecture violation: features may depend on other features only through their public API. Feature "profile" cannot import internal files from feature "user". Replace the deep import with an import from
src/features/user/index.ts.
Navigation
No navigation library is pre-installed. Install whichever you prefer and wire screens by importing them from the feature public APIs.
See src/navigation/README.md for the golden rule.
Testing
Tests live inside each feature at src/features/<name>/__tests__/. There is no root __tests__ folder.
Example:
// src/features/home/__tests__/HomeScreen.test.tsx
import React from "react";
import ReactTestRenderer from "react-test-renderer";
import { HomeScreen } from "..";
describe("HomeScreen", () => {
it("renders correctly", () => {
ReactTestRenderer.create(
<HomeScreen safeAreaInsets={{ top: 0, bottom: 0, left: 0, right: 0 }} />,
);
});
});Run all tests:
npm testNote
- On every commit, Prettier and ESLint checks are run automatically. If they fail, the commit is blocked.
- On every push, the test suite is run automatically. If any test fails, the push is blocked.
Only commits and pushes that pass all configured checks are allowed to proceed.
Troubleshooting
Q: ESLint shows boundaries/no-unknown for a new file I created.
A: Make sure the file sits inside one of the recognized folders:
src/features/<name>/for featuressrc/shared/for shared codesrc/navigation/for navigation setup
Files outside src/ or in unlisted folders inside src/ are treated as unknown by the boundary plugin. if its a new folder you can add it in the eslint.config.mjs under setting in boundaries/elements array.
Q: Can I add path aliases (e.g. @/features/home)?
A: Yes, but the template intentionally avoids them to keep compatibility high. If you add aliases, you must also configure babel-plugin-module-resolver and ensure the ESLint import/resolver understands them so that eslint-plugin-boundaries still classifies files correctly.
