Point0by 1gr14
  • Меню
    • GitHub
    • Блог
  • Introduction
    • Overview
    • Getting Started
    • Full Overview
    • Benchmarks
    • Points
  • Points
    • Page
    • Layout
    • Component
    • Provider
    • Mountable
    • Query
    • Infinite Query
    • Mutation
    • Action
    • Root
    • Base
    • Plugin
  • Methods
    • Validation
    • Loader
    • Context
    • Middleware
    • Loading & error
    • .with
    • Mapper
    • Transformer
    • Stage Methods
  • Core
    • Navigation
    • SSR
    • RSC
    • Request
    • Response
    • Error handling
    • Env
    • Head
    • MDX
    • Assets
    • File upload
    • OpenAPI
    • Query client
    • Events
    • Infer
  • Engine
    • Engine Config
    • Engine Runtime
    • CLI
    • Dev
    • Build
    • Compiler
    • Generator
    • Project MCP
    • Docs MCP
    • Importer
    • Public dir
    • Testing
    • Deploy
    • Bun or Vite
  • Extra
    • SsrStore
    • CookieStore
    • Basic Auth
    • CORS
    • Cache-Control
    • Compress
  • Examples
    • Basic
    • Vite
    • Better Auth
    • Capacitor
    • Expo
  • 1gr14
    • Главная
    • Start0
    • Поддержать
    • Обучение
    • Группа
    • Блог
    • Автор
  • Сообщество
    • Discord
    • Telegram
  • Опенсорс
    • Point0
    • Route0
    • Error0
    • Flat
  • Аккаунт
    • Войти
    • Регистрация
Point0by 1gr14
Создаю опенсорс во славу Господа Иисуса Христа ☦️
С любовью ко всем разработчикам на свете ❤️
Условия использованияПолитика конфиденциальностиСергей Дмитриев 2026 😎

Importer

  • Раздел: Engine

On this page

  1. Two ways to declare a rule
  2. deny — forbid a wrong-side import
  3. mock — let the server see but not run client code
  4. cold — dev-hot-reload only
  5. onDeny — log in dev vs throw in build
  6. How the guard works
  7. Pattern syntax
  8. Reference
  9. ImporterOptionsInput
  10. Marker modules
  11. Rule precedence

Point0 compiles the same source twice: once for the server, once for the client. That makes it easy to accidentally pull a server-only module (Prisma, a secret, fs) into a file the client also reaches. The importer is the compiler's guard: per-side rules that, at compile time, forbid a wrong-side import (deny), replace it with a harmless proxy (mock), or mark a file as a dev-restart trigger (cold).

// examples/basic/src/lib/prisma.ts
import '@point0/core/cold' // editing this file restarts the dev server
import { PrismaClient } from '@/generated/prisma/client'
import { serverEnv } from '@/lib/env/server'
import '@point0/core/server-only' // this file can never reach the client bundle

If any client-reachable file imports this module, the client build fails — the server code cannot land in the browser bundle.

Two ways to declare a rule

You can protect a module from inside the module (a marker import) or from the engine config (a rule that matches the import path). Same effect.

In-file markers — add one of three bare imports to a file:

import '@point0/core/server-only' // deny this file on the client side
import '@point0/core/client-only' // deny this file on the server side
import '@point0/core/cold' // (dev --hot only) editing restarts the server

These are marker modules with no runtime value — server-only.ts and client-only.ts are literally export {}. The compiler recognizes the specifier string and rewrites the marker import on the wrong side; on the correct side the import stays put and loads a harmless empty module. Use a marker when you own the file.

Config rules — match the import target on server.importer / client.importer, for files you can't or don't want to edit (third-party packages, generated code):

// engine.ts
export const engine = Engine.create({
  client: {
    importer: {
      deny: [
        './lib/prisma.ts', // file paths start with a dot
        'dotenv', // bare libraries by package name
      ],
    },
  },
})

A deny config rule and a server-only/client-only marker are two distinct code paths that produce the same denial — the markers are always on, independent of any config.

deny — forbid a wrong-side import

A denied import is rewritten to a virtual module that throws at module-eval time. Importing it from the wrong side surfaces this message:

Import denied on side "server" for scope "root"
  Rule: ./lib/prisma.ts
  Importer: src/pages/home.tsx:12:8
  Import: @/lib/prisma
  Resolved: src/lib/prisma.ts

To know trace of imports to target "@/lib/prisma" from source <source-file-path> run in terminal:
point0 trace --side server --scope root "./lib/prisma.ts" "<source-file-path>"

Suggestions:
  - To see how <source-file-path> looks after compiling without client code, run in terminal: point0 compile --side server --scope root "<source-file-path>"

Import denied on side "server" (or "client") is the line to grep for. (When no scope is set, the message prints --scope <scope> as a literal placeholder.) The message hands you two commands to debug it:

  • point0 trace — print the chain of imports that pulled the denied module in, so you can find which file to fix.
  • point0 compile — show how a source file looks after the compiler strips the opposite side's code, which is why the import is denied.

mock — let the server see but not run client code

Sometimes the server must be allowed to see client code (it shares a file with a server loader) but must not execute it. You can't deny it — denying would fail the build. You mock it: the import is replaced with a no-op proxy.

// examples/expo/src/engine.ts — an Expo page declares
// `const styles = StyleSheet.create({})` and imports react-native, but the
// page also carries a server loader. The server compiles the file, so it must
// see react-native — but never run it.
export const engine = Engine.create({
  server: {
    scope: 'root',
    importer: {
      mock: ['react-native', 'expo-router'],
    },
  },
})

The proxy comes from createMock() — a recursive Proxy. Any property access, call, new, or await on a mocked module returns another mock, so the code "runs" and nothing happens:

import { StyleSheet } from 'react-native' // mocked on the server
const styles = StyleSheet.create({}) // => a mock; no error, no real work

Mocking is per-side: the import resolves to the proxy only on the side whose importer config lists the module; on the other side it keeps its real value. A mock never denies — even with onDeny: 'throw', a mocked import emits no error.

cold — dev-hot-reload only

cold is unlike deny/mock: it has no effect on builds, prod, or non-hot dev — it is read only when building the server hot-reload store under point0 dev --hot. It also matches the file's own path, not an import target.

import '@point0/core/cold' // this file + its static-import subtree run cold

A cold file is externalized from the hot graph: editing it restarts the server child instead of hot-swapping. Use it for server boot singletons (a DB client, a queue connection) whose state shouldn't survive a hot swap. Cold flows downward through static imports and stops at lazy import(). The config-side equivalent is server.importer.cold (a list of globs); a cold rule on a client importer is a silent no-op. Full behavior is on dev.

onDeny — log in dev vs throw in build

When a deny or marker fires, what happens depends on onDeny:

  • 'log' — console.error the message and keep going. The throwing virtual module is still emitted, so the code errors only if it actually runs.
  • 'throw' — a fatal CriticalCompilerError. The bundler plugin re-throws it, so the build fails (exit code 1).

You rarely set onDeny yourself, because Point0 sets it for you by phase:

dev    → onDeny defaults to 'log'   → denial is logged, dev keeps running
build  → onDeny forced to 'throw'   → denial is fatal, the build fails

So a wrong-side import slips through in point0 dev (logged, the module errors at runtime if evaluated) but fails point0 build. The build is where import protection is enforced; there is no flag to make dev fail fast on a denial.

How the guard works

The importer runs as part of the compiler, which is a bundler plugin (Bun or Vite). For each file with a known side:

  1. The compiler walks every import in the file.
  2. A matching rule (marker, deny, or mock) rewrites the import specifier to a @point0/virtual module path. First match wins, in order: marker → deny → mock.
  3. The bundler resolves that virtual path back through the same plugin, which builds the module: a deny becomes a module that throws; a mock becomes the proxy.
  4. In a build, the deny throw is a CriticalCompilerError → the bundle never completes → the server module cannot be emitted into the client bundle.

All three import forms are rewritten — static import, dynamic import(), and require(). Import protection runs only when the compiler is enabled for that side: setting compiler: false for a side turns its import protection off entirely — no plugin, no rewrites, no denial. A built engine also has the compiler off (it never compiles sources at runtime), so import protection lives in the build itself, not in the running app.

Pattern syntax

deny / mock / cold each take an array of strings or RegExp. Strings are normalized:

deny: [
  'react', // bare package → matches the package and everything under it
  './lib/prisma.ts', // relative path → resolved against cwd
  '/abs/path/secret.ts', // absolute path → used verbatim
  '*.server.ts', // glob starting with * → used verbatim
  /\.secret\./, // RegExp → matched with .test()
  '!react-dom', // leading ! → an exclude (un-matches a broader rule)
  './deps/package.json', // package.json → expands to a deny per dependency
]

A few details:

  • Bare package names expand to **/node_modules/<name>{,/**}, so a rule like 'react' matches the package and its whole subtree.

  • Relative patterns need cwd to resolve. The engine always fills cwd (from the engine file's directory), so this only bites raw-compiler users. String matching uses minimatch; regexes use .test().

  • package.json reference — a pattern ending in package.json is read at compiler-init time, and every dependency name in it (from dependencies, devDependencies, peerDependencies, optionalDependencies, and the bundle variants) becomes its own bare-package rule. A malformed file throws and fails compiler init.

  • Order matters. Rules evaluate in declared order, last match wins. With ! excludes you can include a directory, exclude a subtree, then re-include a leaf:

    deny: ['./dir/**', '!./dir/special/**', './dir/special/keep/**']
    //      include dir   exclude special     re-include keep   → keep is denied

Reference

ImporterOptionsInput

Lives on server.importer and client.importer (also reachable via compiler.importer).

OptionTypeDefaultWhat it does
denyArray<string | RegExp>noneForbid a matched import target. Rewrites it to a throwing module. Every mode (dev/build/prod).
mockArray<string | RegExp>noneReplace a matched import target with a createMock() proxy. Every mode.
coldArray<string | RegExp>noneDev --hot only. Matches the file's own path; the file + its static subtree restart instead of hot-swap. No-op outside server.importer. See dev.
cwdstringthe engine file's dirRelative patterns and reported paths resolve against this. Auto-filled by the engine.
onDeny'throw' | 'log''log'What a denial does: 'throw' → fatal CriticalCompilerError (fails the build); 'log' → console.error and continue. Build forces 'throw'; dev keeps 'log'.

Marker modules

ImportEffect
import '@point0/core/server-only'The importing file is denied on the client side.
import '@point0/core/client-only'The importing file is denied on the server side.
import '@point0/core/cold'The file (+ its static subtree) runs cold under dev --hot.

A marker only guards the file that carries it: to guard a third-party package this way, the package's own source would have to import @point0/core/server-only (or client-only). For a dependency you can't edit, use a deny config rule on the import target instead.

Rule precedence

For each import, the first matching rule wins, in order: marker → deny → mock. The marker stage only applies to the bare marker import itself (an import of @point0/core/server-only or @point0/core/client-only); deny and mock match the import target. An import that matches both a deny and a mock rule is denied, not mocked.

НазадDocs MCPДалееPublic dir

Enjoying Point0?

Give it a star on GitHub, ask questions, and follow what's new

Star Point0

A star on GitHub helps more developers find Point0
Star on GitHub

Start0

Point0 integrated into the Start0 SaaS boilerplate — the best way to use it
Explore Start0

YouTube

Tutorials, walkthroughs, and updates
YouTube

Discord

Questions and chat in English
Join Discord

Telegram

News channel and chat in Russian
ChannelChat

Twitter

Posts and updates
Twitter