next-intlayer 9.4.4 → 9.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/dist/cjs/extractorLoader.cjs +148 -0
- package/dist/cjs/extractorLoader.cjs.map +1 -0
- package/dist/cjs/proxy/intlayerProxy.cjs +1 -1
- package/dist/cjs/server/prepareSwcOptimization.cjs +11 -19
- package/dist/cjs/server/prepareSwcOptimization.cjs.map +1 -1
- package/dist/cjs/server/startContentWatcher.cjs +98 -0
- package/dist/cjs/server/startContentWatcher.cjs.map +1 -0
- package/dist/cjs/server/useLocale.cjs +1 -1
- package/dist/cjs/server/useLocale.cjs.map +1 -1
- package/dist/cjs/server/withIntlayer.cjs +98 -20
- package/dist/cjs/server/withIntlayer.cjs.map +1 -1
- package/dist/esm/extractorLoader.mjs +146 -0
- package/dist/esm/extractorLoader.mjs.map +1 -0
- package/dist/esm/proxy/intlayerProxy.mjs +1 -1
- package/dist/esm/server/prepareSwcOptimization.mjs +11 -19
- package/dist/esm/server/prepareSwcOptimization.mjs.map +1 -1
- package/dist/esm/server/startContentWatcher.mjs +97 -0
- package/dist/esm/server/startContentWatcher.mjs.map +1 -0
- package/dist/esm/server/useLocale.mjs +1 -1
- package/dist/esm/server/useLocale.mjs.map +1 -1
- package/dist/esm/server/withIntlayer.mjs +98 -20
- package/dist/esm/server/withIntlayer.mjs.map +1 -1
- package/dist/types/@intlayer/babel/dist/types/babel-plugin-intlayer-field-rename.d.ts +1 -0
- package/dist/types/@intlayer/babel/dist/types/babel-plugin-intlayer-field-rename.d.ts.map +1 -1
- package/dist/types/@intlayer/babel/dist/types/babel-plugin-intlayer-optimize.d.ts +1 -1
- package/dist/types/@intlayer/babel/dist/types/babel-plugin-intlayer-purge.d.ts +1 -0
- package/dist/types/@intlayer/babel/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts +3 -2
- package/dist/types/@intlayer/babel/dist/types/index.d.ts +2 -0
- package/dist/types/@intlayer/babel/dist/types/pruneContext.d.ts +1 -0
- package/dist/types/@intlayer/babel/dist/types/staticAstReaders.d.ts +2 -0
- package/dist/types/@intlayer/babel/dist/types/transformers.d.ts +1 -0
- package/dist/types/extractorLoader.d.ts +63 -0
- package/dist/types/extractorLoader.d.ts.map +1 -0
- package/dist/types/generateStaticParams.d.ts +3 -1
- package/dist/types/generateStaticParams.d.ts.map +1 -1
- package/dist/types/server/prepareSwcOptimization.d.ts +2 -2
- package/dist/types/server/prepareSwcOptimization.d.ts.map +1 -1
- package/dist/types/server/startContentWatcher.d.ts +5 -0
- package/dist/types/server/startContentWatcher.d.ts.map +1 -0
- package/dist/types/server/useLocale.d.ts.map +1 -1
- package/dist/types/server/withIntlayer.d.ts +8 -2
- package/dist/types/server/withIntlayer.d.ts.map +1 -1
- package/package.json +16 -9
|
@@ -45,25 +45,16 @@ const resolveSwcLogLevel = (intlayerConfig) => {
|
|
|
45
45
|
/**
|
|
46
46
|
* Whether the purge / minify pipeline should run for this build.
|
|
47
47
|
*
|
|
48
|
-
* Compat-adapter callers disable it: their descriptors reach
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
48
|
+
* Compat-adapter callers do not disable it: their descriptors reach the usage
|
|
49
|
+
* analyser intact (`compatCallers`), which records the dictionaries they read
|
|
50
|
+
* and marks those as unrenamable — so purging stays field-accurate and the
|
|
51
|
+
* minify step leaves them alone.
|
|
52
52
|
*/
|
|
53
|
-
const getIsPurgePipelineEnabled = (intlayerConfig,
|
|
53
|
+
const getIsPurgePipelineEnabled = (intlayerConfig, isSwcPluginUsable) => {
|
|
54
54
|
const { purge, minify, optimize } = intlayerConfig.build;
|
|
55
55
|
if (optimize === false) return false;
|
|
56
56
|
if (!purge && !minify) return false;
|
|
57
57
|
if (!isSwcPluginUsable) return false;
|
|
58
|
-
if (swcExtraCallers && swcExtraCallers.length > 0) {
|
|
59
|
-
getAppLogger(intlayerConfig)([
|
|
60
|
-
"Dictionary purge and minification are",
|
|
61
|
-
colorize("disabled", ANSIColors.GREY_DARK),
|
|
62
|
-
"because compat-adapter callers are configured — their call sites are",
|
|
63
|
-
"not visible to the usage analyser."
|
|
64
|
-
], { level: "warn" });
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
58
|
return true;
|
|
68
59
|
};
|
|
69
60
|
/**
|
|
@@ -102,7 +93,7 @@ const getIsPurgePipelineAvailable = (intlayerConfig) => loadIntlayerBabel(intlay
|
|
|
102
93
|
* Runs the purge / minify pipeline and writes the resulting field-rename
|
|
103
94
|
* tables to the cache file so every process of the build reads the same ones.
|
|
104
95
|
*/
|
|
105
|
-
const runPurgePipelineOnce = async (intlayerConfig, configOptions, dictionaries, fieldRenameMapFilePath) => {
|
|
96
|
+
const runPurgePipelineOnce = async (intlayerConfig, configOptions, dictionaries, compatCallers, fieldRenameMapFilePath) => {
|
|
106
97
|
const appLogger = getAppLogger(intlayerConfig);
|
|
107
98
|
const intlayerBabel = loadIntlayerBabel(intlayerConfig);
|
|
108
99
|
if (!intlayerBabel) {
|
|
@@ -121,7 +112,8 @@ const runPurgePipelineOnce = async (intlayerConfig, configOptions, dictionaries,
|
|
|
121
112
|
try {
|
|
122
113
|
fieldRenameMap = serializeFieldRenameMap(runIntlayerPurgePipeline(getPurgePluginOptions({
|
|
123
114
|
configOptions,
|
|
124
|
-
dictionaries
|
|
115
|
+
dictionaries,
|
|
116
|
+
overrides: compatCallers?.length ? { compatCallers } : void 0
|
|
125
117
|
})));
|
|
126
118
|
} catch (pipelineError) {
|
|
127
119
|
appLogger([
|
|
@@ -170,13 +162,13 @@ const removeFieldRenameMapFile = (fieldRenameMapFilePath) => {
|
|
|
170
162
|
* pipeline is disabled, unavailable, or renamed nothing.
|
|
171
163
|
*/
|
|
172
164
|
const prepareSwcOptimization = async (intlayerConfig, params) => {
|
|
173
|
-
const { configOptions, dictionaries,
|
|
165
|
+
const { configOptions, dictionaries, compatCallers, isSwcPluginUsable } = params;
|
|
174
166
|
const fieldRenameMapFilePath = getCacheFilePath(intlayerConfig, FIELD_RENAME_MAP_FILE_NAME);
|
|
175
|
-
if (!getIsPurgePipelineEnabled(intlayerConfig,
|
|
167
|
+
if (!getIsPurgePipelineEnabled(intlayerConfig, isSwcPluginUsable)) {
|
|
176
168
|
removeFieldRenameMapFile(fieldRenameMapFilePath);
|
|
177
169
|
return {};
|
|
178
170
|
}
|
|
179
|
-
await runOnce(getCacheFilePath(intlayerConfig, PURGE_SENTINEL_FILE_NAME), () => runPurgePipelineOnce(intlayerConfig, configOptions, dictionaries, fieldRenameMapFilePath), { cacheTimeoutMs: PURGE_CACHE_TIMEOUT_MS });
|
|
171
|
+
await runOnce(getCacheFilePath(intlayerConfig, PURGE_SENTINEL_FILE_NAME), () => runPurgePipelineOnce(intlayerConfig, configOptions, dictionaries, compatCallers, fieldRenameMapFilePath), { cacheTimeoutMs: PURGE_CACHE_TIMEOUT_MS });
|
|
180
172
|
return readFieldRenameMapFile(fieldRenameMapFilePath);
|
|
181
173
|
};
|
|
182
174
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prepareSwcOptimization.mjs","names":[],"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 { SwcExtraCallerConfig } 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 disable it: their descriptors reach `withIntlayer`\n * only in the SWC plugin's lossy wire format, so the usage analyser would not\n * recognise `useTranslation(...)` & co. and would purge fields those call sites\n * still read.\n */\nconst getIsPurgePipelineEnabled = (\n intlayerConfig: IntlayerConfig,\n swcExtraCallers: SwcExtraCallerConfig[] | undefined,\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 if (swcExtraCallers && swcExtraCallers.length > 0) {\n getAppLogger(intlayerConfig)(\n [\n 'Dictionary purge and minification are',\n colorize('disabled', ANSIColors.GREY_DARK),\n 'because compat-adapter callers are configured — their call sites are',\n 'not visible to the usage analyser.',\n ],\n { level: 'warn' }\n );\n return false;\n }\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 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({ configOptions, dictionaries })\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 swcExtraCallers?: SwcExtraCallerConfig[];\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, swcExtraCallers, 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 (\n !getIsPurgePipelineEnabled(\n intlayerConfig,\n swcExtraCallers,\n isSwcPluginUsable\n )\n ) {\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 fieldRenameMapFilePath\n ),\n { cacheTimeoutMs: PURGE_CACHE_TIMEOUT_MS }\n );\n\n return readFieldRenameMapFile(fieldRenameMapFilePath);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkDA,MAAM,wBAAwB;;AAG9B,MAAM,2BAA2B;;AAGjC,MAAM,6BAA6B;;;;;;;AAQnC,MAAM,yBAAyB;;AAG/B,MAAM,oBACJ,gBACA,aAEA,KAAK,eAAe,OAAO,SAAS,aAAa,SAAS,QAAQ;;;;;;;;AASpE,MAAa,sBACX,mBACgB;CAChB,IAAI,eAAe,IAAI,SAAS,YAAY,OAAO;CAEnD,MAAM,oBAAoB,QAAQ,IAAI;CAEtC,OAAO,sBAAsB,UAAU,sBAAsB,UACzD,oBACA;AACN;;;;;;;;;AAUA,MAAM,6BACJ,gBACA,iBACA,sBACY;CACZ,MAAM,EAAE,OAAO,QAAQ,aAAa,eAAe;CAEnD,IAAI,aAAa,OAAO,OAAO;CAC/B,IAAI,CAAC,SAAS,CAAC,QAAQ,OAAO;CAM9B,IAAI,CAAC,mBAAmB,OAAO;CAO/B,IAAI,mBAAmB,gBAAgB,SAAS,GAAG;EACjD,aAAa,cAAc,CAAC,CAC1B;GACE;GACA,SAAS,YAAY,WAAW,SAAS;GACzC;GACA;EACF,GACA,EAAE,OAAO,OAAO,CAClB;EACA,OAAO;CACT;CAEA,OAAO;AACT;;;;;;AAOA,MAAM,yBAAyB;CAC7B;CACA;CACA;AACF;;;;;AAMA,MAAM,qBACJ,mBAC+B;CAC/B,IAAI;EAIF,MAAM,iBAFJ,eAAe,OAAO,WAAW,kBAAkB,EAEhB,CACnC,iBACF;EAMA,IAAI,CAJ2B,uBAAuB,OACnD,eAAe,OAAO,gBAAgB,gBAAgB,UAG/B,GAAG,OAAO;EAEpC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;AAUA,MAAa,+BACX,mBACY,kBAAkB,cAAc,MAAM;;;;;AAMpD,MAAM,uBAAuB,OAC3B,gBACA,eACA,cACA,2BACkB;CAClB,MAAM,YAAY,aAAa,cAAc;CAE7C,MAAM,gBAAgB,kBAAkB,cAAc;CAEtD,IAAI,CAAC,eAAe;EAGlB,UACE;GACE;GACA,SAAS,YAAY,WAAW,SAAS;GACzC;GACA,SAAS,mBAAmB,WAAW,UAAU;GACjD;GACA;EACF,GACA,EAAE,OAAO,OAAO,CAClB;EACA;CACF;CAEA,MAAM,EACJ,uBACA,0BACA,4BACE;CAKJ,IAAI;CAEJ,IAAI;EAOF,iBAAiB,wBAJI,yBACnB,sBAAsB;GAAE;GAAe;EAAa,CAAC,CAGH,CAAC;CACvD,SAAS,eAAe;EACtB,UACE;GACE;GACA,SAAS,UAAU,WAAW,GAAG;GACjC;GACA,yBAAyB,QACrB,IAAI,cAAc,QAAQ,KAC1B,OAAO,aAAa;EAC1B,GACA,EAAE,OAAO,QAAQ,CACnB;EACA;CACF;CAEA,MAAM,MAAM,QAAQ,sBAAsB,GAAG,EAAE,WAAW,KAAK,CAAC;CAChE,MAAM,UACJ,wBACA,KAAK,UAAU,cAAc,GAC7B,OACF;AACF;;AAGA,MAAM,0BACJ,2BACkC;CAClC,IAAI;EACF,OAAO,KAAK,MACV,aAAa,wBAAwB,OAAO,CAC9C;CACF,QAAQ;EACN,OAAO,CAAC;CACV;AACF;;AAGA,MAAM,4BAA4B,2BAAyC;CACzE,IAAI;EACF,OAAO,wBAAwB,EAAE,OAAO,KAAK,CAAC;CAChD,QAAQ,CAER;AACF;;;;;;;;;;;;;;;;;;;;;AAoCA,MAAa,yBAAyB,OACpC,gBACA,WAC2C;CAC3C,MAAM,EAAE,eAAe,cAAc,iBAAiB,sBACpD;CAEF,MAAM,yBAAyB,iBAC7B,gBACA,0BACF;CAIA,IACE,CAAC,0BACC,gBACA,iBACA,iBACF,GACA;EACA,yBAAyB,sBAAsB;EAC/C,OAAO,CAAC;CACV;CAEA,MAAM,QACJ,iBAAiB,gBAAgB,wBAAwB,SAEvD,qBACE,gBACA,eACA,cACA,sBACF,GACF,EAAE,gBAAgB,uBAAuB,CAC3C;CAEA,OAAO,uBAAuB,sBAAsB;AACtD"}
|
|
1
|
+
{"version":3,"file":"prepareSwcOptimization.mjs","names":[],"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":";;;;;;;;;;;;;;;;;;AAkDA,MAAM,wBAAwB;;AAG9B,MAAM,2BAA2B;;AAGjC,MAAM,6BAA6B;;;;;;;AAQnC,MAAM,yBAAyB;;AAG/B,MAAM,oBACJ,gBACA,aAEA,KAAK,eAAe,OAAO,SAAS,aAAa,SAAS,QAAQ;;;;;;;;AASpE,MAAa,sBACX,mBACgB;CAChB,IAAI,eAAe,IAAI,SAAS,YAAY,OAAO;CAEnD,MAAM,oBAAoB,QAAQ,IAAI;CAEtC,OAAO,sBAAsB,UAAU,sBAAsB,UACzD,oBACA;AACN;;;;;;;;;AAUA,MAAM,6BACJ,gBACA,sBACY;CACZ,MAAM,EAAE,OAAO,QAAQ,aAAa,eAAe;CAEnD,IAAI,aAAa,OAAO,OAAO;CAC/B,IAAI,CAAC,SAAS,CAAC,QAAQ,OAAO;CAM9B,IAAI,CAAC,mBAAmB,OAAO;CAO/B,OAAO;AACT;;;;;;AAOA,MAAM,yBAAyB;CAC7B;CACA;CACA;AACF;;;;;AAMA,MAAM,qBACJ,mBAC+B;CAC/B,IAAI;EAIF,MAAM,iBAFJ,eAAe,OAAO,WAAW,kBAAkB,EAEhB,CACnC,iBACF;EAMA,IAAI,CAJ2B,uBAAuB,OACnD,eAAe,OAAO,gBAAgB,gBAAgB,UAG/B,GAAG,OAAO;EAEpC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;AAUA,MAAa,+BACX,mBACY,kBAAkB,cAAc,MAAM;;;;;AAMpD,MAAM,uBAAuB,OAC3B,gBACA,eACA,cACA,eACA,2BACkB;CAClB,MAAM,YAAY,aAAa,cAAc;CAE7C,MAAM,gBAAgB,kBAAkB,cAAc;CAEtD,IAAI,CAAC,eAAe;EAGlB,UACE;GACE;GACA,SAAS,YAAY,WAAW,SAAS;GACzC;GACA,SAAS,mBAAmB,WAAW,UAAU;GACjD;GACA;EACF,GACA,EAAE,OAAO,OAAO,CAClB;EACA;CACF;CAEA,MAAM,EACJ,uBACA,0BACA,4BACE;CAKJ,IAAI;CAEJ,IAAI;EAaF,iBAAiB,wBAVI,yBACnB,sBAAsB;GACpB;GACA;GAGA,WAAW,eAAe,SAAS,EAAE,cAAc,IAAI;EACzD,CAAC,CAGiD,CAAC;CACvD,SAAS,eAAe;EACtB,UACE;GACE;GACA,SAAS,UAAU,WAAW,GAAG;GACjC;GACA,yBAAyB,QACrB,IAAI,cAAc,QAAQ,KAC1B,OAAO,aAAa;EAC1B,GACA,EAAE,OAAO,QAAQ,CACnB;EACA;CACF;CAEA,MAAM,MAAM,QAAQ,sBAAsB,GAAG,EAAE,WAAW,KAAK,CAAC;CAChE,MAAM,UACJ,wBACA,KAAK,UAAU,cAAc,GAC7B,OACF;AACF;;AAGA,MAAM,0BACJ,2BACkC;CAClC,IAAI;EACF,OAAO,KAAK,MACV,aAAa,wBAAwB,OAAO,CAC9C;CACF,QAAQ;EACN,OAAO,CAAC;CACV;AACF;;AAGA,MAAM,4BAA4B,2BAAyC;CACzE,IAAI;EACF,OAAO,wBAAwB,EAAE,OAAO,KAAK,CAAC;CAChD,QAAQ,CAER;AACF;;;;;;;;;;;;;;;;;;;;;AAoCA,MAAa,yBAAyB,OACpC,gBACA,WAC2C;CAC3C,MAAM,EAAE,eAAe,cAAc,eAAe,sBAClD;CAEF,MAAM,yBAAyB,iBAC7B,gBACA,0BACF;CAIA,IAAI,CAAC,0BAA0B,gBAAgB,iBAAiB,GAAG;EACjE,yBAAyB,sBAAsB;EAC/C,OAAO,CAAC;CACV;CAEA,MAAM,QACJ,iBAAiB,gBAAgB,wBAAwB,SAEvD,qBACE,gBACA,eACA,cACA,eACA,sBACF,GACF,EAAE,gBAAgB,uBAAuB,CAC3C;CAEA,OAAO,uBAAuB,sBAAsB;AACtD"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { getAppLogger } from "@intlayer/config/logger";
|
|
2
|
+
import { acquireContentWatcherLock, getCliContentWatcherLabel, getContentWatcherOwner, reportRedundantContentWatcher } from "@intlayer/engine/utils";
|
|
3
|
+
|
|
4
|
+
//#region src/server/startContentWatcher.ts
|
|
5
|
+
/**
|
|
6
|
+
* How often a process that lost the ownership race checks whether the owner is
|
|
7
|
+
* gone. Next.js's short-lived config processes release the lock when they exit,
|
|
8
|
+
* so the long-lived dev server has to be able to pick it up afterwards.
|
|
9
|
+
*/
|
|
10
|
+
const TAKEOVER_POLL_INTERVAL_MS = 5e3;
|
|
11
|
+
/**
|
|
12
|
+
* Next.js helper processes that evaluate `next.config.*` without being the dev
|
|
13
|
+
* server, matched on their entry script.
|
|
14
|
+
*
|
|
15
|
+
* `detached-flush` is the telemetry flusher Next spawns **detached**: it loads
|
|
16
|
+
* the config, outlives the dev server, and — left unchecked — wins the watcher
|
|
17
|
+
* lock and keeps watching after Ctrl-C, while the next `next dev` finds the
|
|
18
|
+
* lock taken and runs with no watcher at all.
|
|
19
|
+
*/
|
|
20
|
+
const NEXT_HELPER_ENTRY_SCRIPTS = ["detached-flush"];
|
|
21
|
+
/**
|
|
22
|
+
* Whether this process is a Next.js helper rather than the dev server itself.
|
|
23
|
+
*
|
|
24
|
+
* Ownership of the watcher has to follow the process the user actually stops,
|
|
25
|
+
* so a helper must never take it.
|
|
26
|
+
*/
|
|
27
|
+
const getIsNextHelperProcess = () => process.argv.some((argument) => NEXT_HELPER_ENTRY_SCRIPTS.some((script) => argument.includes(script)));
|
|
28
|
+
/** Set once the watcher is running in this process. */
|
|
29
|
+
let isWatching = false;
|
|
30
|
+
/**
|
|
31
|
+
* Set while an ownership attempt is in flight.
|
|
32
|
+
*
|
|
33
|
+
* Taking the lock is asynchronous, and `next.config.*` is evaluated several
|
|
34
|
+
* times per command — without this, those evaluations would pile up concurrent
|
|
35
|
+
* attempts, each awaiting the same lock.
|
|
36
|
+
*/
|
|
37
|
+
let isClaimInFlight = false;
|
|
38
|
+
/** Reports a redundant `intlayer watch` running alongside this dev server. */
|
|
39
|
+
const reportRedundantCliWatcher = (configuration, cliWatcherLabel) => reportRedundantContentWatcher(configuration, {
|
|
40
|
+
cliLabel: cliWatcherLabel,
|
|
41
|
+
bundlerLabel: "next-intlayer"
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Starts the content declaration watcher for a Next.js dev server, on webpack
|
|
45
|
+
* as well as on Turbopack.
|
|
46
|
+
*
|
|
47
|
+
* Stands down — with an explanation — when that CLI watcher is running anyway,
|
|
48
|
+
* since two watchers would rebuild the same dictionaries at the same time.
|
|
49
|
+
* Otherwise starting is idempotent: the first process to take the lock watches,
|
|
50
|
+
* and the others retry in the background so a short-lived config process
|
|
51
|
+
* handing the lock back does not leave the session unwatched.
|
|
52
|
+
*
|
|
53
|
+
* @param configuration - The resolved Intlayer configuration.
|
|
54
|
+
*/
|
|
55
|
+
const claimAndWatch = async (configuration) => {
|
|
56
|
+
const appLogger = getAppLogger(configuration);
|
|
57
|
+
if (!await acquireContentWatcherLock(configuration, {
|
|
58
|
+
source: "bundler",
|
|
59
|
+
label: "next-intlayer"
|
|
60
|
+
})) {
|
|
61
|
+
const owner = await getContentWatcherOwner(configuration);
|
|
62
|
+
if (owner?.source === "cli") {
|
|
63
|
+
reportRedundantCliWatcher(configuration, owner.label);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
startContentWatcher(configuration);
|
|
68
|
+
}, TAKEOVER_POLL_INTERVAL_MS).unref?.();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
isWatching = true;
|
|
72
|
+
try {
|
|
73
|
+
const { watch } = await import("@intlayer/engine/watcher");
|
|
74
|
+
await watch({ configuration });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
isWatching = false;
|
|
77
|
+
appLogger(["Failed to watch Intlayer content declarations:", error], { level: "error" });
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const startContentWatcher = (configuration) => {
|
|
81
|
+
if (isWatching || isClaimInFlight) return;
|
|
82
|
+
if (!configuration.content.watch) return;
|
|
83
|
+
if (getIsNextHelperProcess()) return;
|
|
84
|
+
const cliWatcherLabel = getCliContentWatcherLabel();
|
|
85
|
+
if (cliWatcherLabel) {
|
|
86
|
+
reportRedundantCliWatcher(configuration, cliWatcherLabel);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
isClaimInFlight = true;
|
|
90
|
+
claimAndWatch(configuration).finally(() => {
|
|
91
|
+
isClaimInFlight = false;
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
//#endregion
|
|
96
|
+
export { startContentWatcher };
|
|
97
|
+
//# sourceMappingURL=startContentWatcher.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"startContentWatcher.mjs","names":[],"sources":["../../../src/server/startContentWatcher.ts"],"sourcesContent":["import { getAppLogger } from '@intlayer/config/logger';\nimport {\n acquireContentWatcherLock,\n getCliContentWatcherLabel,\n getContentWatcherOwner,\n reportRedundantContentWatcher,\n} from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\n\n/**\n * How often a process that lost the ownership race checks whether the owner is\n * gone. Next.js's short-lived config processes release the lock when they exit,\n * so the long-lived dev server has to be able to pick it up afterwards.\n */\nconst TAKEOVER_POLL_INTERVAL_MS = 5000;\n\n/**\n * Next.js helper processes that evaluate `next.config.*` without being the dev\n * server, matched on their entry script.\n *\n * `detached-flush` is the telemetry flusher Next spawns **detached**: it loads\n * the config, outlives the dev server, and — left unchecked — wins the watcher\n * lock and keeps watching after Ctrl-C, while the next `next dev` finds the\n * lock taken and runs with no watcher at all.\n */\nconst NEXT_HELPER_ENTRY_SCRIPTS = ['detached-flush'];\n\n/**\n * Whether this process is a Next.js helper rather than the dev server itself.\n *\n * Ownership of the watcher has to follow the process the user actually stops,\n * so a helper must never take it.\n */\nconst getIsNextHelperProcess = (): boolean =>\n process.argv.some((argument) =>\n NEXT_HELPER_ENTRY_SCRIPTS.some((script) => argument.includes(script))\n );\n\n/** Set once the watcher is running in this process. */\nlet isWatching = false;\n\n/**\n * Set while an ownership attempt is in flight.\n *\n * Taking the lock is asynchronous, and `next.config.*` is evaluated several\n * times per command — without this, those evaluations would pile up concurrent\n * attempts, each awaiting the same lock.\n */\nlet isClaimInFlight = false;\n\n/** Reports a redundant `intlayer watch` running alongside this dev server. */\nconst reportRedundantCliWatcher = (\n configuration: IntlayerConfig,\n cliWatcherLabel: string\n): void =>\n reportRedundantContentWatcher(configuration, {\n cliLabel: cliWatcherLabel,\n bundlerLabel: 'next-intlayer',\n });\n\n/**\n * Starts the content declaration watcher for a Next.js dev server, on webpack\n * as well as on Turbopack.\n *\n * Stands down — with an explanation — when that CLI watcher is running anyway,\n * since two watchers would rebuild the same dictionaries at the same time.\n * Otherwise starting is idempotent: the first process to take the lock watches,\n * and the others retry in the background so a short-lived config process\n * handing the lock back does not leave the session unwatched.\n *\n * @param configuration - The resolved Intlayer configuration.\n */\nconst claimAndWatch = async (configuration: IntlayerConfig): Promise<void> => {\n const appLogger = getAppLogger(configuration);\n\n const hasAcquiredLock = await acquireContentWatcherLock(configuration, {\n source: 'bundler',\n label: 'next-intlayer',\n });\n\n if (!hasAcquiredLock) {\n // A CLI watcher started from another terminal is just as redundant as one\n // wrapping this command, and worth the same explanation.\n const owner = await getContentWatcherOwner(configuration);\n\n if (owner?.source === 'cli') {\n reportRedundantCliWatcher(configuration, owner.label);\n return;\n }\n\n const retryTimer = setTimeout(() => {\n startContentWatcher(configuration);\n }, TAKEOVER_POLL_INTERVAL_MS);\n\n // Unreferenced so a process whose only remaining work is this retry can\n // still exit.\n retryTimer.unref?.();\n\n return;\n }\n\n isWatching = true;\n\n try {\n // Imported lazily: `@intlayer/engine/watcher` pulls in `@parcel/watcher`, a\n // native module that has no business being loaded during a production\n // build. `withIntlayer` already awaited `prepareIntlayer`, so the watcher\n // only has to pick up the changes that follow.\n const { watch } = await import('@intlayer/engine/watcher');\n\n await watch({ configuration });\n } catch (error) {\n isWatching = false;\n\n appLogger(['Failed to watch Intlayer content declarations:', error], {\n level: 'error',\n });\n }\n};\n\nexport const startContentWatcher = (configuration: IntlayerConfig): void => {\n if (isWatching || isClaimInFlight) return;\n if (!configuration.content.watch) return;\n if (getIsNextHelperProcess()) return;\n\n // Spawned by `intlayer watch --with`: the CLI watcher covers the project, and\n // this is known from the environment alone — no need to touch the lock.\n const cliWatcherLabel = getCliContentWatcherLabel();\n\n if (cliWatcherLabel) {\n reportRedundantCliWatcher(configuration, cliWatcherLabel);\n return;\n }\n\n isClaimInFlight = true;\n\n // Deliberately not awaited: `withIntlayerSync` is synchronous, and the dev\n // server must not wait on the watcher to start serving.\n claimAndWatch(configuration).finally(() => {\n isClaimInFlight = false;\n });\n};\n"],"mappings":";;;;;;;;;AAcA,MAAM,4BAA4B;;;;;;;;;;AAWlC,MAAM,4BAA4B,CAAC,gBAAgB;;;;;;;AAQnD,MAAM,+BACJ,QAAQ,KAAK,MAAM,aACjB,0BAA0B,MAAM,WAAW,SAAS,SAAS,MAAM,CAAC,CACtE;;AAGF,IAAI,aAAa;;;;;;;;AASjB,IAAI,kBAAkB;;AAGtB,MAAM,6BACJ,eACA,oBAEA,8BAA8B,eAAe;CAC3C,UAAU;CACV,cAAc;AAChB,CAAC;;;;;;;;;;;;;AAcH,MAAM,gBAAgB,OAAO,kBAAiD;CAC5E,MAAM,YAAY,aAAa,aAAa;CAO5C,IAAI,CAAC,MALyB,0BAA0B,eAAe;EACrE,QAAQ;EACR,OAAO;CACT,CAAC,GAEqB;EAGpB,MAAM,QAAQ,MAAM,uBAAuB,aAAa;EAExD,IAAI,OAAO,WAAW,OAAO;GAC3B,0BAA0B,eAAe,MAAM,KAAK;GACpD;EACF;EAQA,AANmB,iBAAiB;GAClC,oBAAoB,aAAa;EACnC,GAAG,yBAIM,CAAC,CAAC,QAAQ;EAEnB;CACF;CAEA,aAAa;CAEb,IAAI;EAKF,MAAM,EAAE,UAAU,MAAM,OAAO;EAE/B,MAAM,MAAM,EAAE,cAAc,CAAC;CAC/B,SAAS,OAAO;EACd,aAAa;EAEb,UAAU,CAAC,kDAAkD,KAAK,GAAG,EACnE,OAAO,QACT,CAAC;CACH;AACF;AAEA,MAAa,uBAAuB,kBAAwC;CAC1E,IAAI,cAAc,iBAAiB;CACnC,IAAI,CAAC,cAAc,QAAQ,OAAO;CAClC,IAAI,uBAAuB,GAAG;CAI9B,MAAM,kBAAkB,0BAA0B;CAElD,IAAI,iBAAiB;EACnB,0BAA0B,eAAe,eAAe;EACxD;CACF;CAEA,kBAAkB;CAIlB,cAAc,aAAa,CAAC,CAAC,cAAc;EACzC,kBAAkB;CACpB,CAAC;AACH"}
|
|
@@ -2,6 +2,7 @@ import { resolveAmbientLocale } from "./ambientLocale.mjs";
|
|
|
2
2
|
import { internationalization } from "@intlayer/config/built";
|
|
3
3
|
|
|
4
4
|
//#region src/server/useLocale.ts
|
|
5
|
+
const { defaultLocale, locales: availableLocales } = internationalization ?? {};
|
|
5
6
|
/**
|
|
6
7
|
* On the server side, hook returning the current locale along with the default
|
|
7
8
|
* and available ones.
|
|
@@ -12,7 +13,6 @@ import { internationalization } from "@intlayer/config/built";
|
|
|
12
13
|
* render the provider did not reach.
|
|
13
14
|
*/
|
|
14
15
|
const useLocale = () => {
|
|
15
|
-
const { defaultLocale, locales: availableLocales } = internationalization ?? {};
|
|
16
16
|
return {
|
|
17
17
|
locale: resolveAmbientLocale() ?? defaultLocale,
|
|
18
18
|
defaultLocale,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLocale.mjs","names":[],"sources":["../../../src/server/useLocale.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { DeclaredLocales } from '@intlayer/types/module_augmentation';\nimport { resolveAmbientLocale } from './ambientLocale';\n\nexport type UseLocaleResult = {\n locale: DeclaredLocales;\n defaultLocale: DeclaredLocales;\n availableLocales: DeclaredLocales[];\n};\n\n/**\n * On the server side, hook returning the current locale along with the default\n * and available ones.\n *\n * Reimplemented rather than re-exported from `react-intlayer/server`: that one\n * takes no locale argument, so there is no way to hand it the ambient locale —\n * it would read the server context alone and report the default locale on any\n * render the provider did not reach.\n */\nexport const useLocale = (): UseLocaleResult => {\n const
|
|
1
|
+
{"version":3,"file":"useLocale.mjs","names":[],"sources":["../../../src/server/useLocale.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { DeclaredLocales } from '@intlayer/types/module_augmentation';\nimport { resolveAmbientLocale } from './ambientLocale';\n\nexport type UseLocaleResult = {\n locale: DeclaredLocales;\n defaultLocale: DeclaredLocales;\n availableLocales: DeclaredLocales[];\n};\n\nconst { defaultLocale, locales: availableLocales } = internationalization ?? {};\n\n/**\n * On the server side, hook returning the current locale along with the default\n * and available ones.\n *\n * Reimplemented rather than re-exported from `react-intlayer/server`: that one\n * takes no locale argument, so there is no way to hand it the ambient locale —\n * it would read the server context alone and report the default locale on any\n * render the provider did not reach.\n */\nexport const useLocale = (): UseLocaleResult => {\n const locale = (resolveAmbientLocale() ?? defaultLocale) as DeclaredLocales;\n\n return { locale, defaultLocale, availableLocales };\n};\n"],"mappings":";;;;AAUA,MAAM,EAAE,eAAe,SAAS,qBAAqB,wBAAwB,CAAC;;;;;;;;;;AAW9E,MAAa,kBAAmC;CAG9C,OAAO;EAAE,QAFO,qBAAqB,KAAK;EAEzB;EAAe;CAAiB;AACnD"}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { getIsPurgePipelineAvailable, prepareSwcOptimization, resolveSwcLogLevel } from "./prepareSwcOptimization.mjs";
|
|
2
|
+
import { startContentWatcher } from "./startContentWatcher.mjs";
|
|
2
3
|
import { MINIMUM_SWC_PLUGIN_NEXT_VERSION, getIsSwcPluginSupported } from "./swcPluginCompatibility.mjs";
|
|
3
|
-
import { IMPORT_MODE } from "@intlayer/config/defaultValues";
|
|
4
4
|
import { join, relative, resolve } from "node:path";
|
|
5
5
|
import * as ANSIColors from "@intlayer/config/colors";
|
|
6
|
-
import { formatDictionarySelectorEnvVar, formatNodeTypeToEnvVar, formatOptimizedNestingEnvVar, getConfigEnvVars } from "@intlayer/config/envVars";
|
|
7
6
|
import { colorize, getAppLogger } from "@intlayer/config/logger";
|
|
8
7
|
import { getConfiguration } from "@intlayer/config/node";
|
|
9
8
|
import { compareVersions, getAlias, getHasDictionarySelector, getProjectRequire, getUnusedNodeTypes, normalizePath } from "@intlayer/config/utils";
|
|
9
|
+
import { buildComponentFilesList, runOnce } from "@intlayer/engine/utils";
|
|
10
|
+
import { COMPILER_ENABLED, IMPORT_MODE } from "@intlayer/config/defaultValues";
|
|
11
|
+
import { toSwcExtraCallers } from "@intlayer/config/callers";
|
|
12
|
+
import { formatDictionarySelectorEnvVar, formatNodeTypeToEnvVar, formatOptimizedNestingEnvVar, getConfigEnvVars } from "@intlayer/config/envVars";
|
|
10
13
|
import { getNestedDictionaryGraph } from "@intlayer/core/dictionaryManipulator";
|
|
11
14
|
import { getDictionaries } from "@intlayer/dictionaries-entry";
|
|
12
15
|
import { prepareIntlayer } from "@intlayer/engine/build";
|
|
13
16
|
import { logConfigDetails } from "@intlayer/engine/cli";
|
|
14
|
-
import { buildComponentFilesList, runOnce } from "@intlayer/engine/utils";
|
|
15
|
-
import { IntlayerPlugin } from "@intlayer/webpack";
|
|
16
17
|
import { defu } from "defu";
|
|
17
18
|
import nextPackageJSON from "next/package.json" with { type: "json" };
|
|
18
19
|
|
|
@@ -61,7 +62,70 @@ const resolvePluginPath = (pluginPath, intlayerConfig, isTurbopackEnabled) => {
|
|
|
61
62
|
if (isTurbopackEnabled) return normalizePath(`./${relative(process.cwd(), pluginPathResolved)}`);
|
|
62
63
|
return pluginPathResolved;
|
|
63
64
|
};
|
|
64
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Extensions the extraction loader is registered for.
|
|
67
|
+
*
|
|
68
|
+
* The compiler reads its content out of JSX — element text and the
|
|
69
|
+
* translatable attributes — so a file with no JSX in it has nothing to give,
|
|
70
|
+
* and `*.ts` is left out. `*.js` is in because a plain-JavaScript Next.js app
|
|
71
|
+
* writes its components there.
|
|
72
|
+
*/
|
|
73
|
+
const EXTRACTOR_LOADER_EXTENSIONS = [
|
|
74
|
+
"tsx",
|
|
75
|
+
"jsx",
|
|
76
|
+
"js"
|
|
77
|
+
];
|
|
78
|
+
/**
|
|
79
|
+
* The subset safe to register as a Turbopack rule on any Next.js version.
|
|
80
|
+
*
|
|
81
|
+
* Turbopack rules match every module it processes, dependencies included, and
|
|
82
|
+
* only Next.js 16 can express "skip `node_modules`" (see
|
|
83
|
+
* {@link TURBOPACK_PROJECT_FILES_CONDITION}). Below that, `*.js` would hand
|
|
84
|
+
* every dependency file to a Node loader worker, so it is left out rather than
|
|
85
|
+
* made to cost a cold dev start.
|
|
86
|
+
*/
|
|
87
|
+
const TURBOPACK_ALWAYS_SAFE_EXTENSIONS = ["tsx", "jsx"];
|
|
88
|
+
/**
|
|
89
|
+
* Restricts a Turbopack rule to the project's own files. `foreign` is
|
|
90
|
+
* Turbopack's builtin condition for a module inside `node_modules`.
|
|
91
|
+
*
|
|
92
|
+
* Only understood from Next.js 16 on — earlier releases carry no `condition`
|
|
93
|
+
* field on a rule at all.
|
|
94
|
+
*/
|
|
95
|
+
const TURBOPACK_PROJECT_FILES_CONDITION = { not: "foreign" };
|
|
96
|
+
/**
|
|
97
|
+
* Whether the Intlayer compiler should extract content for this command.
|
|
98
|
+
*
|
|
99
|
+
* Mirrors `getExtractPluginOptions`, which the loader re-evaluates on its side:
|
|
100
|
+
* this is only about not registering a loader that would do nothing.
|
|
101
|
+
*/
|
|
102
|
+
const getIsCompilerEnabled = (intlayerConfig, isDevCommand) => {
|
|
103
|
+
const enabled = intlayerConfig.compiler?.enabled ?? COMPILER_ENABLED;
|
|
104
|
+
if (enabled === "build-only") return !isDevCommand;
|
|
105
|
+
return enabled !== false;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Describes the extraction loader for both bundlers, or `null` when the
|
|
109
|
+
* compiler is off or `@intlayer/babel` is not installed.
|
|
110
|
+
*
|
|
111
|
+
* The loader replaces the `babel.config.js` the compiler used to need on
|
|
112
|
+
* Next.js: Turbopack never reads a Babel config, and on webpack the mere
|
|
113
|
+
* presence of one opts the whole project out of SWC — including the
|
|
114
|
+
* `@intlayer/swc` optimize pass.
|
|
115
|
+
*/
|
|
116
|
+
const getExtractorLoader = (intlayerConfig, isTurbopackEnabled, isDevCommand) => {
|
|
117
|
+
if (!getIsCompilerEnabled(intlayerConfig, isDevCommand)) return null;
|
|
118
|
+
if (!getIsBabelExtractPluginAvailable(intlayerConfig)) return null;
|
|
119
|
+
try {
|
|
120
|
+
return {
|
|
121
|
+
loaderPath: resolvePluginPath("next-intlayer/extractor-loader", intlayerConfig, isTurbopackEnabled),
|
|
122
|
+
mode: isDevCommand ? "dev" : "build"
|
|
123
|
+
};
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const getPruneConfig = ({ intlayerConfig, isBuildCommand, isTurbopackEnabled, isDevCommand, isGteNext13, isSwcPluginSupported, nextVersion, compatCallers, fieldRenameMap }) => {
|
|
65
129
|
const { optimize, minify, purge } = intlayerConfig.build;
|
|
66
130
|
const importMode = intlayerConfig.build.importMode ?? intlayerConfig.dictionary?.importMode;
|
|
67
131
|
const { dictionariesDir, unmergedDictionariesDir, dynamicDictionariesDir, fetchDictionariesDir, mainDir } = intlayerConfig.system;
|
|
@@ -87,7 +151,7 @@ const getPruneConfig = ({ intlayerConfig, isBuildCommand, isTurbopackEnabled, is
|
|
|
87
151
|
colorize(importMode ?? IMPORT_MODE, ANSIColors.BLUE),
|
|
88
152
|
colorize(`)`, ANSIColors.GREY_DARK)
|
|
89
153
|
]);
|
|
90
|
-
const isPurgePipelineEnabled =
|
|
154
|
+
const isPurgePipelineEnabled = getIsPurgePipelineAvailable(intlayerConfig);
|
|
91
155
|
if (isPurgePipelineEnabled && minify) logger(`Dictionary minification ${colorize("enabled", ANSIColors.GREEN)}`);
|
|
92
156
|
if (isPurgePipelineEnabled && purge) logger(`Dictionary purge unused keys ${colorize("enabled", ANSIColors.GREEN)}`);
|
|
93
157
|
} else logger([
|
|
@@ -109,11 +173,7 @@ const getPruneConfig = ({ intlayerConfig, isBuildCommand, isTurbopackEnabled, is
|
|
|
109
173
|
const dynamicDictionariesEntryPath = join(mainDir, "dynamic_dictionaries.mjs");
|
|
110
174
|
const unmergedDictionariesEntryPath = join(mainDir, "unmerged_dictionaries.mjs");
|
|
111
175
|
const fetchDictionariesEntryPath = join(mainDir, "fetch_dictionaries.mjs");
|
|
112
|
-
const filesList = [
|
|
113
|
-
...buildComponentFilesList(intlayerConfig),
|
|
114
|
-
dictionariesEntryPath,
|
|
115
|
-
unmergedDictionariesEntryPath
|
|
116
|
-
];
|
|
176
|
+
const filesList = [...buildComponentFilesList(intlayerConfig), dictionariesEntryPath];
|
|
117
177
|
const dictionaries = getDictionaries(intlayerConfig);
|
|
118
178
|
const dictionaryModeMap = {};
|
|
119
179
|
Object.values(dictionaries).forEach((dictionary) => {
|
|
@@ -134,7 +194,7 @@ const getPruneConfig = ({ intlayerConfig, isBuildCommand, isTurbopackEnabled, is
|
|
|
134
194
|
replaceDictionaryEntry: true,
|
|
135
195
|
nestingDictionaryKeys,
|
|
136
196
|
dictionaryModeMap,
|
|
137
|
-
extraCallers:
|
|
197
|
+
extraCallers: toSwcExtraCallers(compatCallers ?? []),
|
|
138
198
|
fieldRenameMap: fieldRenameMap ?? {},
|
|
139
199
|
logLevel: resolveSwcLogLevel(intlayerConfig)
|
|
140
200
|
}]] } };
|
|
@@ -199,15 +259,26 @@ const withIntlayerSync = (nextConfig = {}, configOptions) => {
|
|
|
199
259
|
const isTurbopackEnabled = configOptions?.enableTurbopack ?? isTurbopackEnabledFromCommand;
|
|
200
260
|
if (isTurbopackEnabled && typeof nextConfig.webpack !== "undefined") appLogger("Turbopack is enabled but a custom webpack config is present. It will be ignored.");
|
|
201
261
|
const { isBuildCommand, isDevCommand } = getCommandsEvent();
|
|
262
|
+
const extractorLoader = getExtractorLoader(intlayerConfig, isTurbopackEnabled ?? false, isDevCommand);
|
|
263
|
+
if (isDevCommand) startContentWatcher(intlayerConfig);
|
|
202
264
|
const turboConfig = {
|
|
203
265
|
resolveAlias: getAlias({
|
|
204
266
|
configuration: intlayerConfig,
|
|
205
267
|
formatter: (value) => `./${value}`
|
|
206
268
|
}),
|
|
207
|
-
rules: {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
269
|
+
rules: {
|
|
270
|
+
"*.node": {
|
|
271
|
+
as: "*.node",
|
|
272
|
+
loaders: ["node-loader"]
|
|
273
|
+
},
|
|
274
|
+
...extractorLoader ? Object.fromEntries((isGteNext16 ? EXTRACTOR_LOADER_EXTENSIONS : TURBOPACK_ALWAYS_SAFE_EXTENSIONS).map((extension) => [`*.${extension}`, {
|
|
275
|
+
loaders: [{
|
|
276
|
+
loader: extractorLoader.loaderPath,
|
|
277
|
+
options: { mode: extractorLoader.mode }
|
|
278
|
+
}],
|
|
279
|
+
...isGteNext16 ? { condition: TURBOPACK_PROJECT_FILES_CONDITION } : {}
|
|
280
|
+
}])) : {}
|
|
281
|
+
}
|
|
211
282
|
};
|
|
212
283
|
const serverExternalPackages = [
|
|
213
284
|
"esbuild",
|
|
@@ -265,7 +336,6 @@ const withIntlayerSync = (nextConfig = {}, configOptions) => {
|
|
|
265
336
|
} else config = {
|
|
266
337
|
...config,
|
|
267
338
|
webpack: (config, options) => {
|
|
268
|
-
const { isServer, nextRuntime } = options;
|
|
269
339
|
if (typeof nextConfig.webpack === "function") config = nextConfig.webpack(config, options);
|
|
270
340
|
if (config.externals === false) config.externals = [];
|
|
271
341
|
const externalExact = /* @__PURE__ */ new Set([
|
|
@@ -285,6 +355,15 @@ const withIntlayerSync = (nextConfig = {}, configOptions) => {
|
|
|
285
355
|
test: /\.node$/,
|
|
286
356
|
loader: "node-loader"
|
|
287
357
|
});
|
|
358
|
+
if (extractorLoader) config.module.rules.push({
|
|
359
|
+
test: new RegExp(`\\.(${EXTRACTOR_LOADER_EXTENSIONS.join("|")})$`),
|
|
360
|
+
exclude: /node_modules/,
|
|
361
|
+
enforce: "pre",
|
|
362
|
+
use: [{
|
|
363
|
+
loader: extractorLoader.loaderPath,
|
|
364
|
+
options: { mode: extractorLoader.mode }
|
|
365
|
+
}]
|
|
366
|
+
});
|
|
288
367
|
config.resolve.alias = {
|
|
289
368
|
...config.resolve.alias,
|
|
290
369
|
...getAlias({
|
|
@@ -292,7 +371,6 @@ const withIntlayerSync = (nextConfig = {}, configOptions) => {
|
|
|
292
371
|
formatter: (value) => resolve(value)
|
|
293
372
|
})
|
|
294
373
|
};
|
|
295
|
-
if (isDevCommand && isServer && nextRuntime === "nodejs") config.plugins.push(new IntlayerPlugin(intlayerConfig));
|
|
296
374
|
return config;
|
|
297
375
|
}
|
|
298
376
|
};
|
|
@@ -306,7 +384,7 @@ const withIntlayerSync = (nextConfig = {}, configOptions) => {
|
|
|
306
384
|
isGteNext13,
|
|
307
385
|
isSwcPluginSupported,
|
|
308
386
|
nextVersion,
|
|
309
|
-
|
|
387
|
+
compatCallers: configOptions?.compatCallers,
|
|
310
388
|
fieldRenameMap: configOptions?.fieldRenameMap
|
|
311
389
|
});
|
|
312
390
|
const intlayerNextConfig = defu(getNewConfig(), pruneConfig);
|
|
@@ -341,7 +419,7 @@ const withIntlayer = async (nextConfig = {}, configOptions) => {
|
|
|
341
419
|
const { isSwcPluginSupported } = getNextVersionFlags(intlayerConfig);
|
|
342
420
|
const fieldRenameMap = isBuildCommand ? await prepareSwcOptimization(intlayerConfig, {
|
|
343
421
|
configOptions: resolvedConfigOptions,
|
|
344
|
-
|
|
422
|
+
compatCallers: configOptions?.compatCallers,
|
|
345
423
|
isSwcPluginUsable: isSwcPluginSupported && getIsSwcPluginAvailable(intlayerConfig)
|
|
346
424
|
}) : {};
|
|
347
425
|
const nextConfigResolved = await nextConfig;
|