twilightEslintConfig
A shared ESLint config that warns on engine barrel imports, deep imports into component folders, window.location and document queries.
import { twilightEslintConfig } from '@salla.sa/twilight-theme-engine/eslint';In plain words
ESLint is a tool that reads your code without running it and points out patterns that are likely mistakes, in your editor or from a terminal command. Its settings live in eslint.config.js, as a list of config objects.
twilightEslintConfig is such a list, written for Salla themes. Spread it into yours and you get warnings for three habits that hurt a theme: importing a whole bundle of engine pages at once, reaching past a component folder's index file, and reading the page address or searching the page with document instead of going through React and the router.
It only warns. Nothing it finds stops a build.
Signature
const twilightEslintConfig: Array<{
name: string; // 'twilight/best-practices'
files: string[]; // ['**/*.{ts,tsx}']
rules: Record<string, unknown>;
}>
// also the default export of @salla.sa/twilight-theme-engine/eslint
// Its one config object, every rule at 'warn':
// no-restricted-imports '@salla.sa/twilight-theme-engine/routes', '.../components',
// ../<folder>/*, ../../<folder>/*, ../../../<folder>/* for 11 folder names
// no-restricted-properties window.location, document.querySelector, document.querySelectorAll,
// document.getElementById, document.getElementsByClassName
// no-restricted-globals locationTry it live
twilightEslintConfig object, imported into this page: every restriction a rule carries and the message ESLint prints for it.Try this: set the severity to error: the code tab adds a severity-only override, and ESLint keeps the options listed here.twilight/best-practices applies to **/*.{ts,tsx}. no-restricted-imports ships at warn.
| Flags | Message |
|---|---|
@salla.sa/twilight-theme-engine/routes | Import from a granular route path (e.g. @salla.sa/twilight-theme-engine/routes/cart). Barrel imports pull ALL route modules and break code-splitting. |
@salla.sa/twilight-theme-engine/components | Import from a granular component path (e.g. @salla.sa/twilight-theme-engine/cart). Barrel imports increase bundle size. |
../common/* ../../common/* ../../../common/* | Import from the barrel export '../common' instead. Deep imports bypass lazy loading and break code-splitting. |
../cart/* ../../cart/* ../../../cart/* | Import from the barrel export '../cart' instead. Deep imports bypass lazy loading and break code-splitting. |
../product/* ../../product/* ../../../product/* | Import from the barrel export '../product' instead. Deep imports bypass lazy loading and break code-splitting. |
../layout/* ../../layout/* ../../../layout/* | Import from the barrel export '../layout' instead. Deep imports bypass lazy loading and break code-splitting. |
../home/* ../../home/* ../../../home/* | Import from the barrel export '../home' instead. Deep imports bypass lazy loading and break code-splitting. |
../modal/* ../../modal/* ../../../modal/* | Import from the barrel export '../modal' instead. Deep imports bypass lazy loading and break code-splitting. |
../drawer/* ../../drawer/* ../../../drawer/* | Import from the barrel export '../drawer' instead. Deep imports bypass lazy loading and break code-splitting. |
../dropdown/* ../../dropdown/* ../../../dropdown/* | Import from the barrel export '../dropdown' instead. Deep imports bypass lazy loading and break code-splitting. |
../collapse/* ../../collapse/* ../../../collapse/* | Import from the barrel export '../collapse' instead. Deep imports bypass lazy loading and break code-splitting. |
../toast/* ../../toast/* ../../../toast/* | Import from the barrel export '../toast' instead. Deep imports bypass lazy loading and break code-splitting. |
../navigation/* ../../navigation/* ../../../navigation/* | Import from the barrel export '../navigation' instead. Deep imports bypass lazy loading and break code-splitting. |
// eslint.config.js
import tseslint from 'typescript-eslint';
import { twilightEslintConfig } from '@salla.sa/twilight-theme-engine/eslint';
export default [
// A TypeScript parser first: the engine's config brings rules, not a parser.
...tseslint.configs.recommended,
...twilightEslintConfig,
];
Example
// Needs eslint and typescript-eslint in devDependencies.
import tseslint from 'typescript-eslint';
import { twilightEslintConfig } from '@salla.sa/twilight-theme-engine/eslint';
export default [
// A TypeScript parser first: the engine's config brings rules, not a parser.
...tseslint.configs.recommended,
...twilightEslintConfig,
{
files: ['**/*.{ts,tsx}'],
rules: {
// Severity only: ESLint keeps the engine's options for this rule.
'no-restricted-imports': 'error',
},
},
];
How it behaves
A flat config (ESLint 9): an array you spread into
export default [...]. It uses core ESLint rules only, so there is no plugin to install, and every rule is atwarn.no-restricted-importsflags the exact specifiers@salla.sa/twilight-theme-engine/routesand@salla.sa/twilight-theme-engine/components, and relative imports one to three levels up into a folder namedcommon,cart,product,layout,home,modal,drawer,dropdown,collapse,toastornavigationthat go deeper (../common/Link,../../layout/Header).../commonitself is allowed.no-restricted-propertiesflags every use ofwindow.location, reads included (window.location.search), anddocument.querySelector,querySelectorAll,getElementByIdandgetElementsByClassName.no-restricted-globalsflags the bare globallocation; a variable or parameter you namedlocationis not flagged.Why
window.locationis on the list: on localhost and the preview host the address bar also carries the store's username, so a path built from it gets that segment twice. Read the address with useLocation and move with useNavigate orLink.The build-time best-practices plugin checks overlapping patterns and prints them in the terminal; this config puts them in your editor and CI. Only the plugin checks
classListchanges,<img>sizes and hook slots.The module is plain data with no Node imports, so it is safe to import anywhere, which is how the live demo renders the real object.
Gotchas
export default [...twilightEslintConfig]alone, asdocs/getting-started/11-conventions.mdshows, fails on every file with a type annotation or JSX: the config matches**/*.{ts,tsx}but sets no parser, and ESLint's default parser stops at the first type annotation or JSX tag with a fatalParsing error. Fix: put a TypeScript parser config first, such as...tseslint.configs.recommended.To raise a rule to
error, write only the severity ('no-restricted-imports': 'error'): ESLint keeps the options of the earlier config object.docs/22-best-practices-enforcement.mdre-spreads them withtwilightEslintConfig[0].rules['no-restricted-imports'].slice(1), which is unnecessary and does not type-check in aneslint.config.ts, becauserulesis typedRecord<string, unknown>.Writing new options for one of these rules replaces the engine's list instead of adding to it:
'no-restricted-imports': ['error', { paths: [{ name: 'lodash' }] }]stops flagging deep component imports. Fix: repeat the engine's entries along with yours.The folder patterns match names, not the engine: your own
app/components/home/Hero.tsximported as../home/Herois flagged too. Fix: give that folder anindex.tsand import../home, or rename the folder.Blind spots: four or more levels up (
../../../../common/Link), same-folder paths (./common/Link), path aliases (~/components/common/Link), dynamicimport('../common/Link'),globalThis.locationandself.locationare not flagged.Every rule is a warning, so
eslint .exits successfully with findings. Fix: runeslint . --max-warnings 0in CI, or raise the severities.
Related
The build-time code check twilightReact() already runs: six rules print colored findings in the terminal during vite build, never failing it.
useLocationReads the current address as the router sees it (path, parsed query, raw query, hash), in the server render and in the browser.
useNavigateReturns a navigate(path) function that moves to another store page from code, without reloading the whole page.
LinkAn anchor that moves between store pages without reloading, adding the language (and, on localhost or the preview host, the store) to the path.