{"version":3,"file":"prepareSwcOptimization.cjs","names":["join","getProjectRequire","getAppLogger","colorize","ANSIColors","mkdir","dirname","writeFile","readFileSync","runOnce"],"sources":["../../../src/server/prepareSwcOptimization.ts"],"sourcesContent":["import { readFileSync, rmSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type {\n  getPurgePluginOptions as GetPurgePluginOptions,\n  runIntlayerPurgePipeline as RunIntlayerPurgePipeline,\n  SerializedFieldRenameMap,\n  serializeFieldRenameMap as SerializeFieldRenameMap,\n} from '@intlayer/babel';\nimport type { CallerDescriptor } from '@intlayer/config/callers';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport type { GetConfigurationOptions } from '@intlayer/config/node';\nimport { getProjectRequire } from '@intlayer/config/utils';\nimport { runOnce } from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\n\n/**\n * Field-rename tables keyed by dictionary key, forwarded to the\n * `@intlayer/swc` plugin's `fieldRenameMap` option.\n */\nexport type FieldRenameMapByDictionaryKey = Record<\n  string,\n  SerializedFieldRenameMap\n>;\n\n/** Verbosity forwarded to the `@intlayer/swc` plugin's `logLevel` option. */\nexport type SwcLogLevel = 'off' | 'info' | 'debug';\n\n/**\n * Subset of `@intlayer/babel` this module needs. The package is only an\n * optional runtime dependency, so it is required lazily and never imported at\n * module scope.\n */\ntype IntlayerBabelModule = {\n  getPurgePluginOptions: typeof GetPurgePluginOptions;\n  runIntlayerPurgePipeline: typeof RunIntlayerPurgePipeline;\n  serializeFieldRenameMap: typeof SerializeFieldRenameMap;\n};\n\n/**\n * Opt-in to the SWC plugin's per-file tracing.\n *\n * The plugin transforms one file at a time with no cross-file state, so all it\n * can report is a line per file — hundreds of them on a real project, and with\n * no counterpart in the Vite build output. The purge and minify summaries that\n * `log.mode` does control are emitted by the pipeline itself, in Node; this\n * variable is purely a debugging aid on top.\n */\nconst SWC_LOG_LEVEL_ENV_VAR = 'INTLAYER_SWC_LOG_LEVEL';\n\n/** Sentinel coordinating the pipeline across the processes of one build. */\nconst PURGE_SENTINEL_FILE_NAME = 'intlayer-swc-purge.lock';\n\n/** Where the serialised field-rename tables are cached between processes. */\nconst FIELD_RENAME_MAP_FILE_NAME = 'swc-field-rename-map.json';\n\n/**\n * Next.js evaluates `next.config.*` in the main build process and again in each\n * Turbopack/webpack worker. The dictionary rewrite must happen exactly once —\n * a second run would derive the short aliases from the already-renamed JSON —\n * so the sentinel window has to cover a whole build startup.\n */\nconst PURGE_CACHE_TIMEOUT_MS = 30 * 1000;\n\n/** Absolute path of a file in the intlayer cache directory. */\nconst getCacheFilePath = (\n  intlayerConfig: IntlayerConfig,\n  fileName: string\n): string =>\n  join(intlayerConfig.system.baseDir, '.intlayer', 'cache', fileName);\n\n/**\n * Resolves the verbosity the `@intlayer/swc` plugin should report at.\n *\n * Off unless {@link SWC_LOG_LEVEL_ENV_VAR} asks for tracing: `log.mode` governs\n * the purge and minify reporting, which the pipeline emits in Node so the\n * Next.js build reads the same as the Vite one.\n */\nexport const resolveSwcLogLevel = (\n  intlayerConfig: IntlayerConfig\n): SwcLogLevel => {\n  if (intlayerConfig.log.mode === 'disabled') return 'off';\n\n  const requestedLogLevel = process.env[SWC_LOG_LEVEL_ENV_VAR];\n\n  return requestedLogLevel === 'info' || requestedLogLevel === 'debug'\n    ? requestedLogLevel\n    : 'off';\n};\n\n/**\n * Whether the purge / minify pipeline should run for this build.\n *\n * Compat-adapter callers do not disable it: their descriptors reach the usage\n * analyser intact (`compatCallers`), which records the dictionaries they read\n * and marks those as unrenamable — so purging stays field-accurate and the\n * minify step leaves them alone.\n */\nconst getIsPurgePipelineEnabled = (\n  intlayerConfig: IntlayerConfig,\n  isSwcPluginUsable: boolean\n): boolean => {\n  const { purge, minify, optimize } = intlayerConfig.build;\n\n  if (optimize === false) return false;\n  if (!purge && !minify) return false;\n\n  // The pipeline rewrites the compiled dictionaries in place — dropping unused\n  // fields and renaming the rest to short aliases. Only the SWC plugin rewrites\n  // the matching source accesses, so running this without it would ship\n  // dictionaries whose keys no longer match the code reading them.\n  if (!isSwcPluginUsable) return false;\n\n  // `editor.enabled` is deliberately not checked here: it only stands the\n  // field-rename step down — inside the pipeline, and with its own explanation\n  // — the same way the Vite build does. Purging still runs, and the rename map\n  // the SWC plugin receives comes back empty, so source and JSON stay in sync.\n\n  return true;\n};\n\n/**\n * Exports this module needs from `@intlayer/babel`. Checked one by one so a\n * version predating the purge pipeline is rejected like a missing package,\n * instead of failing later with `runIntlayerPurgePipeline is not a function`.\n */\nconst REQUIRED_BABEL_EXPORTS = [\n  'getPurgePluginOptions',\n  'runIntlayerPurgePipeline',\n  'serializeFieldRenameMap',\n] as const satisfies readonly (keyof IntlayerBabelModule)[];\n\n/**\n * Lazily loads `@intlayer/babel`, or `null` when it is not installed or too old\n * to expose the purge pipeline.\n */\nconst loadIntlayerBabel = (\n  intlayerConfig: IntlayerConfig\n): IntlayerBabelModule | null => {\n  try {\n    const requireFunction =\n      intlayerConfig.build?.require ?? getProjectRequire();\n\n    const intlayerBabel = requireFunction(\n      '@intlayer/babel'\n    ) as Partial<IntlayerBabelModule>;\n\n    const hasEveryRequiredExport = REQUIRED_BABEL_EXPORTS.every(\n      (exportName) => typeof intlayerBabel?.[exportName] === 'function'\n    );\n\n    if (!hasEveryRequiredExport) return null;\n\n    return intlayerBabel as IntlayerBabelModule;\n  } catch {\n    return null;\n  }\n};\n\n/**\n * Whether the purge / minify pipeline can run at all, i.e. whether a usable\n * `@intlayer/babel` is resolvable from the project.\n *\n * Used by `withIntlayer` to keep the build report honest: announcing\n * `Dictionary minification enabled` while the pipeline cannot load would describe a\n * pass that never happens.\n */\nexport const getIsPurgePipelineAvailable = (\n  intlayerConfig: IntlayerConfig\n): boolean => loadIntlayerBabel(intlayerConfig) !== null;\n\n/**\n * Runs the purge / minify pipeline and writes the resulting field-rename\n * tables to the cache file so every process of the build reads the same ones.\n */\nconst runPurgePipelineOnce = async (\n  intlayerConfig: IntlayerConfig,\n  configOptions: GetConfigurationOptions | undefined,\n  dictionaries: Dictionary[] | undefined,\n  compatCallers: CallerDescriptor[] | undefined,\n  fieldRenameMapFilePath: string\n): Promise<void> => {\n  const appLogger = getAppLogger(intlayerConfig);\n\n  const intlayerBabel = loadIntlayerBabel(intlayerConfig);\n\n  if (!intlayerBabel) {\n    // Not verbose: the build asked for purge / minify and is silently getting\n    // neither, which is exactly the kind of thing a default build must report.\n    appLogger(\n      [\n        'Dictionary purge and minification are',\n        colorize('disabled', ANSIColors.GREY_DARK),\n        'because',\n        colorize('@intlayer/babel', ANSIColors.GREY_LIGHT),\n        'is missing or too old to expose the purge pipeline — install it in',\n        'this project to enable them.',\n      ],\n      { level: 'warn' }\n    );\n    return;\n  }\n\n  const {\n    getPurgePluginOptions,\n    runIntlayerPurgePipeline,\n    serializeFieldRenameMap,\n  } = intlayerBabel;\n\n  // `runOnce` discards whatever this callback throws, so a pipeline failure\n  // would otherwise leave the build with no dictionaries rewritten and no\n  // explanation of why.\n  let fieldRenameMap: FieldRenameMapByDictionaryKey;\n\n  try {\n    // The pipeline reports what it purged and minified through the intlayer\n    // logger, matching the Vite build output.\n    const pruneContext = runIntlayerPurgePipeline(\n      getPurgePluginOptions({\n        configOptions,\n        dictionaries,\n        // Without them the analyser would not recognise `useTranslation(...)`\n        // & co. and would purge the fields those call sites read.\n        overrides: compatCallers?.length ? { compatCallers } : undefined,\n      })\n    );\n\n    fieldRenameMap = serializeFieldRenameMap(pruneContext);\n  } catch (pipelineError) {\n    appLogger(\n      [\n        'Dictionary purge and minification',\n        colorize('failed', ANSIColors.RED),\n        '— the compiled dictionaries are left untouched.',\n        pipelineError instanceof Error\n          ? `(${pipelineError.message})`\n          : String(pipelineError),\n      ],\n      { level: 'error' }\n    );\n    return;\n  }\n\n  await mkdir(dirname(fieldRenameMapFilePath), { recursive: true });\n  await writeFile(\n    fieldRenameMapFilePath,\n    JSON.stringify(fieldRenameMap),\n    'utf-8'\n  );\n};\n\n/** Reads the cached field-rename tables, or `{}` when none were written. */\nconst readFieldRenameMapFile = (\n  fieldRenameMapFilePath: string\n): FieldRenameMapByDictionaryKey => {\n  try {\n    return JSON.parse(\n      readFileSync(fieldRenameMapFilePath, 'utf-8')\n    ) as FieldRenameMapByDictionaryKey;\n  } catch {\n    return {};\n  }\n};\n\n/** Removes the cached field-rename tables, ignoring a missing file. */\nconst removeFieldRenameMapFile = (fieldRenameMapFilePath: string): void => {\n  try {\n    rmSync(fieldRenameMapFilePath, { force: true });\n  } catch {\n    // Nothing to clean up.\n  }\n};\n\ntype PrepareSwcOptimizationParams = {\n  /** Options forwarded to the intlayer configuration loader. */\n  configOptions?: GetConfigurationOptions;\n  /** Pre-loaded dictionaries, to avoid a second `getDictionaries` call. */\n  dictionaries?: Dictionary[];\n  /** Compat-adapter callers declared by the consuming plugin, if any. */\n  compatCallers?: CallerDescriptor[];\n  /**\n   * Whether the `@intlayer/swc` plugin is installed *and* the resolved Next.js\n   * version can load it. False leaves the compiled dictionaries untouched.\n   */\n  isSwcPluginUsable: boolean;\n};\n\n/**\n * Prepares the parts of the build optimisation the `@intlayer/swc` plugin\n * cannot do itself, and returns the field-rename tables it needs.\n *\n * Removing unused content fields (`build.purge`) and assigning short field\n * aliases (`build.minify`) require reading every component source file and\n * rewriting the compiled dictionary JSON — file I/O and cross-file state that a\n * per-file Wasm transform has no access to. That analysis therefore runs here,\n * in Node, through `@intlayer/babel`; the plugin only receives the resulting\n * tables and rewrites the matching source accesses\n * (`content.title` → `content.a`).\n *\n * The work is coordinated by a sentinel file: Next.js loads `next.config.*` in\n * several processes per build, and running the minify pass twice would derive\n * the aliases from the already-renamed dictionaries. Processes that lose the\n * race read the tables the winner cached.\n *\n * @returns The field-rename tables, keyed by dictionary key. Empty when the\n *   pipeline is disabled, unavailable, or renamed nothing.\n */\nexport const prepareSwcOptimization = async (\n  intlayerConfig: IntlayerConfig,\n  params: PrepareSwcOptimizationParams\n): Promise<FieldRenameMapByDictionaryKey> => {\n  const { configOptions, dictionaries, compatCallers, isSwcPluginUsable } =\n    params;\n\n  const fieldRenameMapFilePath = getCacheFilePath(\n    intlayerConfig,\n    FIELD_RENAME_MAP_FILE_NAME\n  );\n\n  // A stale file from an earlier build would rename source accesses that the\n  // dictionaries no longer match.\n  if (!getIsPurgePipelineEnabled(intlayerConfig, isSwcPluginUsable)) {\n    removeFieldRenameMapFile(fieldRenameMapFilePath);\n    return {};\n  }\n\n  await runOnce(\n    getCacheFilePath(intlayerConfig, PURGE_SENTINEL_FILE_NAME),\n    () =>\n      runPurgePipelineOnce(\n        intlayerConfig,\n        configOptions,\n        dictionaries,\n        compatCallers,\n        fieldRenameMapFilePath\n      ),\n    { cacheTimeoutMs: PURGE_CACHE_TIMEOUT_MS }\n  );\n\n  return readFieldRenameMapFile(fieldRenameMapFilePath);\n};\n"],"mappings":"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"}