Ctrl K
ComunidadGuías…

tsdown.config.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * dsh-web-tools — tsdown build for the browser client bundle.
 *
 * DSH's web shell loads third-party client plugins as SINGLE script bundles
 * served from /plugins/<package-name>/client.js and registered through the
 * shared module loader (`window.__ModuleLoader__.load({ id, factory })`).
 * This config replicates the official DSH client-bundle preset
 * (packages/client/tsdown.client.ts in deepseek-harness, same shape as
 * dsh-better-sidebar):
 *
 * - the host half stays on plain `tsc` (see tsconfig.build.json) — Node ESM;
 * - the client half is bundled here as one CJS-closure script whose `require`
 *   resolves only the platform module table (react, cordis, the injected
 *   @deepseek-ai client services); everything else is inlined;
 * - the purity gate rejects any @deepseek-ai value import that is not a
 *   platform module or an inline-safe wire layer: cross-plugin collaboration
 *   goes through cordis services, never value imports;
 * - the bundle registers itself via window.__ModuleLoader__.load with the
 *   package-name id (`dsh-web-tools`; client-modules compose keys on the
 *   package name — keep it in sync with package.json `name`).
 *
 * Types ship from `tsc -p tsconfig.build.json` (lib/*.d.ts), not from tsdown.
 */
import { builtinModules } from 'node:module'
import type { UserConfig } from 'tsdown'

/** Node builtins must never survive into the browser module-loader factory. */
const NODE_BUILTINS = new Set([
  ...builtinModules,
  ...builtinModules.map(id => `node:${id}`),
])

/**
 * Module specifiers the web shell seeds into its module table. This is the
 * ACTUAL seed key set, taken from the served shell bundle's
 * `staticModules` (`dsh-web-frontend/dist/assets/index-*.js`), which the loader
 * installs verbatim as `new Map(Object.entries(options.staticModules))`.
 *
 * The previous list was inaccurate in both directions: it allowed four
 * specifiers that are not table keys (`cordis` — the key is
 * `@deepseek-ai/cordis` — plus dsh-client-web-react, dsh-client-schema-form and
 * dsh-client-runtime/client, three packages that are not even published), and
 * it omitted two real keys (dsh-client-store, dsh-client-ui-dockkit). An
 * allowed-but-absent specifier compiles into a bare `require(...)` that throws
 * the loader's "missed the module table" error at load, while an omitted-but-real
 * key is rejected here at build time — so the list is only useful when it matches.
 */
const CLIENT_EXTERNALS = [
  'react',
  'react/jsx-runtime',
  'react-dom',
  'react-dom/client',
  '@deepseek-ai/cordis',
  '@deepseek-ai/dsh-client-store',
  '@deepseek-ai/dsh-client-ui-slots',
  '@deepseek-ai/dsh-client-ui-primitives',
  '@deepseek-ai/dsh-client-ui-dockkit',
]

/** Wire/type layers a client bundle may inline (mirror of the official INLINE_SAFE list). */
const INLINE_SAFE = /^@deepseek-ai\/dsh-(host-apiproxy|session|llm|tools|brand)(\/|$)/

/** A rolldown plugin as tsdown's config accepts it (contextual `this` for load/resolveId). */
type BuildPlugin = NonNullable<UserConfig['plugins']>

/** The official client-bundle purity gate: only platform modules + inline-safe wire layers may be imported from @deepseek-ai/*. */
function purityGatePlugin(): BuildPlugin {
  return {
    name: 'dsh-client-bundle-purity',
    resolveId(source: string) {
      if (NODE_BUILTINS.has(source)) {
        throw new Error(
          `client bundle purity: Node builtin "${source}" cannot run in the browser module table — `
          + 'select the dependency browser export or add an explicit browser implementation',
        )
      }
      if (!source.startsWith('@deepseek-ai/')) return null
      if (CLIENT_EXTERNALS.includes(source)) return null // platform module: external wins
      if (INLINE_SAFE.test(source)) return null // wire/type layer: inline is the point
      throw new Error(
        `client bundle purity: "${source}" is not a platform module (CLIENT_EXTERNALS) and not an inline-safe wire layer — `
        + 'cross-plugin value imports are forbidden; collaborate through cordis services (type-only imports are erased and never reach this gate)',
      )
    },
  }
}

export default [
  // Official profile channel: bundle id = package name (package.json `name`).
  {
    entry: { client: 'src/client/index.ts' },
    outDir: 'lib',
    format: 'cjs',
    platform: 'browser',
    dts: false,
    sourcemap: false,
    // clean stays off: the build script removes lib/ wholesale before tsc, so
    // a tsdown clean here would wipe the lib types tsc just emitted.
    clean: false,
    // Platform module-table entries stay external (resolved by the loader's
    // `require` at runtime); every other dependency is inlined into the bundle.
    deps: {
      neverBundle: [...CLIENT_EXTERNALS],
      alwaysBundle: (id: string) => (CLIENT_EXTERNALS.includes(id) ? false : true),
    },
    define: {
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV ?? 'production'),
      'import.meta.env.MODE': JSON.stringify(process.env.NODE_ENV ?? 'production'),
      'import.meta.env': JSON.stringify({ MODE: process.env.NODE_ENV ?? 'production' }),
      'import.meta.resolve': 'undefined',
    },
    // CJS output otherwise makes some transitive packages resolve their Node
    // entry even though this bundle runs in the browser. Keep browser
    // conditional exports authoritative for both source import() and
    // generated require() edges.
    inputOptions: {
      resolve: {
        conditionNames: ['browser', 'import', 'require', 'default'],
      },
    },
    plugins: [purityGatePlugin()],
    outputOptions: {
      entryFileNames: 'client.js',
      banner: `window.__ModuleLoader__.load({ id: "dsh-web-tools", factory: (require) => {`,
      footer: `return module.exports; } });`,
      intro: `var module = { exports: {} }; var exports = module.exports;`,
      // The CJS wrapper factory's `require` only resolves module-table entries
      // (react, cordis, ...); it cannot load relative chunk URLs in the
      // browser. Disable code splitting so the artifact is one script.
      codeSplitting: false,
    },
  },
] satisfies UserConfig[]