Skip to content
Twilight React Playground
ثيم رائدaren

twilightEslintConfig

objectBeginnerlive demo

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     location

Try it live

The real 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.
Storefront canvas · ar · RTL

twilight/best-practices applies to **/*.{ts,tsx}. no-restricted-imports ships at warn.

FlagsMessage
@salla.sa/twilight-theme-engine/routesImport 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/componentsImport 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.
Controls
warn is what the engine ships.
What a theme writes
// 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

eslint.config.js
// 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 at warn.

  • no-restricted-imports flags the exact specifiers @salla.sa/twilight-theme-engine/routes and @salla.sa/twilight-theme-engine/components, and relative imports one to three levels up into a folder named common, cart, product, layout, home, modal, drawer, dropdown, collapse, toast or navigation that go deeper (../common/Link, ../../layout/Header). ../common itself is allowed.

  • no-restricted-properties flags every use of window.location, reads included (window.location.search), and document.querySelector, querySelectorAll, getElementById and getElementsByClassName. no-restricted-globals flags the bare global location; a variable or parameter you named location is not flagged.

  • Why window.location is 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 or Link.

  • 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 classList changes, <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, as docs/getting-started/11-conventions.md shows, 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 fatal Parsing 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.md re-spreads them with twilightEslintConfig[0].rules['no-restricted-imports'].slice(1), which is unnecessary and does not type-check in an eslint.config.ts, because rules is typed Record<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.tsx imported as ../home/Hero is flagged too. Fix: give that folder an index.ts and 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), dynamic import('../common/Link'), globalThis.location and self.location are not flagged.

  • Every rule is a warning, so eslint . exits successfully with findings. Fix: run eslint . --max-warnings 0 in CI, or raise the severities.

Related

Source and docs