@salla.sa/twilight-theme-engine

中文文档
1.0.142发布于 2026/11/02MITTypeScriptES Module

Core engine for Salla React themes - component registry, hooks, contexts, routes, and base components

原始使用文档
文档目录13 节
  1. Installation
  2. What's in it
  3. Documentation
  4. Usage
  5. Wiring the engine into a theme
  6. Component registry
  7. Hook slots
  8. Store, theme, and user
  9. Translations
  10. Money and assets
  11. Import paths
  12. Development
  13. License

@salla.sa/twilight-theme-engine

The SDK behind Salla React themes. It ships the storefront routes, components, hooks, API clients, and the Vite plugin that a theme composes — so a theme is a small app, not a fork of a storefront.

Built for TanStack Start, server-rendered at the edge.

Installation

You normally don't install this yourself — salla theme create --type react scaffolds a theme that already depends on it.

pnpm add @salla.sa/twilight-theme-engine

Peer dependencies are pinned to tilde ranges (~1.166.2 for @tanstack/*, vite ^8.1.5, react ^19.2.4). The engine's dist references @tanstack/react-start internals, so drifting outside the tested minor breaks SSR at runtime. Let the install warn you rather than forcing a resolution.

What's in it

AreaWhat it gives you
RoutesReady-made storefront pages — product, cart, category, blog, /account/*, …
ComponentsHeader/Footer, product, cart, home blocks, modal/drawer/dropdown primitives
HooksuseStore, useTheme, useUser, useMoney, useAsset, useWishlist, …
RegistrySwap any registered component by name
Hook slotsInject content at defined extension points
APITyped clients for the Salla store API, for use in SSR loaders
i18nreact-i18next integration, Arabic/RTL first
ToolingVite plugin (route generation, virtual modules), ESLint config, SSR runtime
CLItwilight-check-manifest: lists mistakes in a theme's twilight.json

Documentation

Theme-facing guides live on the Twilight docs site — start with the Getting Started track or the Architecture Overview.

Engine-internal deep dives are in docs/ here:

DocumentTopic
HOME_COMPONENTS.mdHome component system and the data prop contract
usePageConfig.mdUnified page configuration
useLocation.mdLocation and query-param handling
store-identity.mdWhich store a request is for, and why not config
router-context.mdRequest-scoped context across SSR and CSR
route-ids.mdRoute identifiers
route-pagination.mdPagination in loaders
ProductCard.mdThe ProductCard contract
gtm-integration.mdGoogle Tag Manager
i18n-instance-lifecycle.mdHow the i18n instance is created and shared
type-improvement-guide.mdTyping conventions

Subsystem notes: src/api/README.md, src/vite/README.md, tests/README.md.

Usage

Wiring the engine into a theme

Two files do it. app/start.ts installs the request middleware:

import { createStart } from '@tanstack/react-start';
import { twilightMiddleware, earlyHintsMiddleware } from '@salla.sa/twilight-theme-engine/tanstack';

export const startInstance = createStart(() => {
  return {
    requestMiddleware: [twilightMiddleware(), earlyHintsMiddleware()],
  };
});

app/routes/__root.tsx builds the root route and renders the provider:

import { TwilightProvider } from '@salla.sa/twilight-theme-engine';
import { createTwilightRootRoute } from '@salla.sa/twilight-theme-engine/tanstack';
import themeTranslations from 'virtual:twilight/theme-translations';

export const Route = createTwilightRootRoute()({ shellComponent: RootComponent });

<TwilightProvider> takes the bundled translations (plus optional debug, skeleton, onReady, onError). Store, locale, and user are not props — the middleware resolves them per request.

Component registry

import { registry, Component } from '@salla.sa/twilight-theme-engine';

registry.register('product:card', ProductCard);
registry.override('product:card', MyProductCard);

<Component name="product:card" product={product} />;

Keys are colon-separated, and the engine only resolves three of them: product:card, the home:* blocks (via registerHomeComponents()), and account:layout-pending. Overriding any other name is a silent no-op — everything else is imported directly.

Hook slots

import { defineHooks } from '@salla.sa/twilight-theme-engine/hooks/HookRegistry';
import { HookSlot } from '@salla.sa/twilight-theme-engine/hooks/HookSlot';

defineHooks({
  'body:start': () => <AnnouncementBar />,
  'product:form.end': ({ product }) => <TrustBadges product={product} />,
});

<HookSlot name="product:form.end" context={{ product }} />;

Store, theme, and user

import { useStore } from '@salla.sa/twilight-theme-engine/hooks/useStore';
import { useTheme } from '@salla.sa/twilight-theme-engine/hooks/useTheme';
import { useTwilight } from '@salla.sa/twilight-theme-engine';

function MyComponent() {
  const store = useStore();
  const { color, font, settings, isRTL } = useTheme();
  const { user } = useTwilight();

  const isDark = settings?.footer_is_dark ?? false;
}

Settings are a plain object — read them with ?. and your own default. There is no settings.get().

Translations

import { useTranslation } from '@salla.sa/twilight-theme-engine/i18n';

function MyComponent() {
  const { t, locale, isRTL } = useTranslation();

  return (
    <>
      <h1>{t('pages.cart.title')}</h1>
      <p>{t('pages.products.sold_times', { count: 5 })}</p>
    </>
  );
}

useTranslation() returns react-i18next's t, i18n, and ready, plus locale info (locale, direction, isRTL, isLTR, languageName). The /i18n subpath also exports I18nProvider, FALLBACK_LOCALE, SUPPORTED_LOCALES, RTL_LOCALES, isLocale, and getLanguageInfo.

Money and assets

import { useMoney } from '@salla.sa/twilight-theme-engine/hooks/useMoney';
import { useAsset } from '@salla.sa/twilight-theme-engine/hooks/useAsset';

function ProductPrice({ product }) {
  const { format } = useMoney(); // also: parse, isValid
  const { cdn } = useAsset(); // also: asset, isPlaceholder

  return (
    <div>
      <img src={cdn(product.image.url, 400)} alt="" loading="lazy" />
      <p>{format(product.price)}</p>
    </div>
  );
}

format() returns a ReactNode (not a string) — it renders the currency symbol as markup.

Import paths

The engine has ~90 subpath exports. Import from the narrowest one so bundles stay small:

// Core — provider, registry, context
import {
  TwilightProvider,
  useTwilight,
  registry,
  Component,
} from '@salla.sa/twilight-theme-engine';

// Hooks — one subpath each
import { useStore } from '@salla.sa/twilight-theme-engine/hooks/useStore';
import { useMoney } from '@salla.sa/twilight-theme-engine/hooks/useMoney';

// i18n
import { useTranslation, I18nProvider } from '@salla.sa/twilight-theme-engine/i18n';

// Components — grouped by domain
import { Link } from '@salla.sa/twilight-theme-engine/common';
import { Header, Footer } from '@salla.sa/twilight-theme-engine/layout';
import { CartItem, CartSummary } from '@salla.sa/twilight-theme-engine/cart';
import { ProductCard } from '@salla.sa/twilight-theme-engine/product';

// Routes
import { Home, DefaultHomeComponents } from '@salla.sa/twilight-theme-engine/routes/home';

// API clients — for loaders
import { store } from '@salla.sa/twilight-theme-engine/api/store';

// TanStack integration
import { createRouter, createTwilightRootRoute } from '@salla.sa/twilight-theme-engine/tanstack';

// Types
import type { Store, Theme, Product } from '@salla.sa/twilight-theme-engine/types';

The authoritative list is the exports field of package.json. Never deep-import into dist/ — those paths aren't part of the public API.

Development

From the repo root:

pnpm --filter @salla.sa/twilight-theme-engine build          # tsup + tsc + dts stubs
pnpm --filter @salla.sa/twilight-theme-engine test
pnpm --filter @salla.sa/twilight-theme-engine test:coverage
pnpm --filter @salla.sa/twilight-theme-engine typecheck
CommandDescription
pnpm buildBundle, add JS extensions, emit declarations and ambient .d.ts
pnpm devtsup --watch
pnpm testVitest (test:watch, test:coverage, test:ci)
pnpm typechecktsc --noEmit
pnpm cleanRemove dist/

The themes consume dist/, so build the engine before running or type-checking a theme.

Adding a new public export means adding a subpath to exports — otherwise consumers can't reach it. Verify a change survives publishing by installing Tania standalone (why).

Testing conventions, including the SSR-vs-browser split, are in tests/README.md.

License

MIT

版本与发布记录

@salla.sa/twilight-theme-engine 共发布 149 个版本,首次发布于 2026/07/14;过去 12 个月发布了 149 个版本。

版本发布日期距今
1.0.142latest2026/11/021 天前
1.0.1412026/10/313 天前
1.0.1402026/10/313 天前
1.0.1392026/10/295 天前
1.0.1382026/10/286 天前
1.0.1372026/10/277 天前

其他发布通道:beta → 1.0.0-beta.9。

关于 @salla.sa/twilight-theme-engine 的常见问题

@salla.sa/twilight-theme-engine 是什么?

Salla React主题的核心引擎——组件注册表、钩子、上下文、路由和基本组件

如何安装 @salla.sa/twilight-theme-engine?

在项目目录运行 npm install @salla.sa/twilight-theme-engine,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^19.2.8)、esbuild(0.28.2)、react-dom(^19.2.8)、@uidotdev/usehooks(^2.4.1)、@vitejs/plugin-react(^6.1.0)、@tanstack/react-query(~5.102.8)、@tanstack/react-start(~1.168.57)、@tanstack/react-router(~1.170.38)、@tanstack/router-plugin(~1.168.40)、@tanstack/react-router-ssr-query(~1.167.2)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。它还提供命令行工具 twilight-check-manifest,可以用 npx twilight-check-manifest 直接运行。

@salla.sa/twilight-theme-engine 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@salla.sa/twilight-theme-engine 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@salla.sa/twilight-theme-engine 可以商用吗?

@salla.sa/twilight-theme-engine 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@salla.sa/twilight-theme-engine 还在维护吗?

最新版本 1.0.142 发布于 2026/11/02(1 天前);至今共发布 149 个版本,其中过去 12 个月发布了 149 个;首次发布于 2026/07/14。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。