@verbaly/next 0.20.0 → 0.21.0
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/dist/index.cjs +11 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.js +11 -12
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -13,6 +13,12 @@ function writeIfChanged(file, content) {
|
|
|
13
13
|
(0, node_fs.writeFileSync)(file, content);
|
|
14
14
|
return true;
|
|
15
15
|
}
|
|
16
|
+
function syncAndWrite(compiler, cfg, catalogs, registry, requestOptions) {
|
|
17
|
+
const { added } = compiler.syncCatalogs(cfg, catalogs, registry);
|
|
18
|
+
for (const locale of Object.keys(added)) compiler.writeCatalog(cfg, locale, catalogs[locale] ?? {});
|
|
19
|
+
compiler.writeDts(cfg, catalogs[cfg.sourceLocale] ?? {});
|
|
20
|
+
writeGeneratedModules(compiler, cfg, catalogs, requestOptions);
|
|
21
|
+
}
|
|
16
22
|
function writeGeneratedModules(compiler, cfg, catalogs, requestOptions = {}) {
|
|
17
23
|
const dir = generatedDir(cfg.root);
|
|
18
24
|
const localeDir = (0, node_path.join)(dir, "locale");
|
|
@@ -48,12 +54,7 @@ function startWatcher(compiler, cfg, requestOptions) {
|
|
|
48
54
|
}
|
|
49
55
|
running = true;
|
|
50
56
|
try {
|
|
51
|
-
|
|
52
|
-
const registry = await compiler.extractProject(cfg);
|
|
53
|
-
const { added } = compiler.syncCatalogs(cfg, catalogs, registry);
|
|
54
|
-
for (const locale of Object.keys(added)) compiler.writeCatalog(cfg, locale, catalogs[locale] ?? {});
|
|
55
|
-
compiler.writeDts(cfg, new Map(Object.entries(catalogs[cfg.sourceLocale] ?? {})));
|
|
56
|
-
writeGeneratedModules(compiler, cfg, catalogs, requestOptions);
|
|
57
|
+
syncAndWrite(compiler, cfg, compiler.loadCatalogs(cfg), await compiler.extractProject(cfg), requestOptions);
|
|
57
58
|
} catch (error) {
|
|
58
59
|
console.warn("[verbaly] live extraction failed:", error);
|
|
59
60
|
} finally {
|
|
@@ -104,14 +105,12 @@ function withVerbaly(nextConfig, options = {}) {
|
|
|
104
105
|
const catalogs = compiler.loadCatalogs(cfg);
|
|
105
106
|
const registry = await compiler.extractProject(cfg);
|
|
106
107
|
if (phase === BUILD_PHASE) {
|
|
107
|
-
|
|
108
|
+
compiler.runBuildGate(cfg, registry, failOnMissing);
|
|
109
|
+
writeGeneratedModules(compiler, cfg, catalogs, requestOptions);
|
|
108
110
|
} else {
|
|
109
|
-
|
|
110
|
-
for (const locale of Object.keys(added)) compiler.writeCatalog(cfg, locale, catalogs[locale] ?? {});
|
|
111
|
-
compiler.writeDts(cfg, new Map(Object.entries(catalogs[cfg.sourceLocale] ?? {})));
|
|
111
|
+
syncAndWrite(compiler, cfg, catalogs, registry, requestOptions);
|
|
112
112
|
startWatcher(compiler, cfg, requestOptions);
|
|
113
113
|
}
|
|
114
|
-
writeGeneratedModules(compiler, cfg, catalogs, requestOptions);
|
|
115
114
|
return composeConfig(base, cfg.root);
|
|
116
115
|
};
|
|
117
116
|
}
|
|
@@ -147,7 +146,7 @@ function composeConfig(base, root) {
|
|
|
147
146
|
config.module ??= {};
|
|
148
147
|
config.module.rules ??= [];
|
|
149
148
|
config.module.rules.push({
|
|
150
|
-
test:
|
|
149
|
+
test: SOURCE_PATH_RE,
|
|
151
150
|
exclude: /node_modules/,
|
|
152
151
|
enforce: "pre",
|
|
153
152
|
use: [{ loader: LOADER }]
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/codegen.ts","../src/watch.ts","../src/index.ts"],"sourcesContent":["import type { Catalogs, ResolvedConfig } from '@verbaly/compiler';\nimport { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type Compiler = typeof import('@verbaly/compiler');\n\nexport interface RequestOptions {\n cookie?: string | false;\n fallback?: string;\n}\n\nexport const GENERATED_DIR = '.verbaly';\n\nexport function generatedDir(root: string): string {\n return join(root, GENERATED_DIR);\n}\n\n// content compare: identical rewrites must not retrigger the bundler\nfunction writeIfChanged(file: string, content: string): boolean {\n try {\n if (readFileSync(file, 'utf8') === content) return false;\n } catch {\n // new file\n }\n writeFileSync(file, content);\n return true;\n}\n\n// real-file replacement for virtual:verbaly: Turbopack has no virtual modules\nexport function writeGeneratedModules(\n compiler: Compiler,\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n requestOptions: RequestOptions = {},\n): boolean {\n const dir = generatedDir(cfg.root);\n const localeDir = join(dir, 'locale');\n mkdirSync(localeDir, { recursive: true });\n\n let changed = writeIfChanged(join(dir, '.gitignore'), '*\\n');\n\n const runtime = compiler.generateRuntimeModule(cfg, {\n localeImport: (locale) => `./locale/${locale}.js`,\n extraExports: `export const requestOptions = ${JSON.stringify(requestOptions)};\\n`,\n });\n changed = writeIfChanged(join(dir, 'index.js'), runtime) || changed;\n\n const expected = new Set(cfg.locales.map((locale) => `${locale}.js`));\n for (const locale of cfg.locales) {\n changed =\n writeIfChanged(\n join(localeDir, `${locale}.js`),\n compiler.generateLocaleModule(catalogs[locale] ?? {}),\n ) || changed;\n }\n for (const file of readdirSync(localeDir)) {\n if (!expected.has(file)) {\n rmSync(join(localeDir, file));\n changed = true;\n }\n }\n return changed;\n}\n","import type { ResolvedConfig } from '@verbaly/compiler';\nimport { watch } from 'node:fs';\nimport { relative } from 'node:path';\nimport { GENERATED_DIR, writeGeneratedModules, type Compiler, type RequestOptions } from './codegen';\n\n// one watcher per project root: next.config can be evaluated more than once\nconst active = new Map<string, () => void>();\n\nexport function startWatcher(\n compiler: Compiler,\n cfg: ResolvedConfig,\n requestOptions: RequestOptions,\n): () => void {\n const existing = active.get(cfg.root);\n if (existing) return existing;\n\n const catalogDir = relative(cfg.root, cfg.dir).replaceAll('\\\\', '/');\n let timer: ReturnType<typeof setTimeout> | undefined;\n let running = false;\n let queued = false;\n\n async function refresh(): Promise<void> {\n if (running) {\n queued = true;\n return;\n }\n running = true;\n try {\n const catalogs = compiler.loadCatalogs(cfg);\n const registry = await compiler.extractProject(cfg);\n const { added } = compiler.syncCatalogs(cfg, catalogs, registry);\n for (const locale of Object.keys(added)) {\n compiler.writeCatalog(cfg, locale, catalogs[locale] ?? {});\n }\n compiler.writeDts(cfg, new Map(Object.entries(catalogs[cfg.sourceLocale] ?? {})));\n writeGeneratedModules(compiler, cfg, catalogs, requestOptions);\n } catch (error) {\n console.warn('[verbaly] live extraction failed:', error);\n } finally {\n running = false;\n if (queued) {\n queued = false;\n schedule();\n }\n }\n }\n\n function schedule(): void {\n clearTimeout(timer);\n timer = setTimeout(() => void refresh(), 150);\n }\n\n const watcher = watch(cfg.root, { recursive: true }, (_event, filename) => {\n if (!filename) return;\n const file = filename.replaceAll('\\\\', '/');\n if (\n file.startsWith(`${GENERATED_DIR}/`) ||\n file.startsWith('.next/') ||\n file.includes('node_modules/') ||\n file.endsWith('.d.ts')\n ) {\n return;\n }\n const isCatalog = file.startsWith(`${catalogDir}/`) && file.endsWith('.json');\n if (isCatalog || compiler.SOURCE_FILE_RE.test(file)) schedule();\n });\n watcher.unref?.();\n\n const dispose = (): void => {\n clearTimeout(timer);\n watcher.close();\n active.delete(cfg.root);\n };\n active.set(cfg.root, dispose);\n return dispose;\n}\n\n// test hook\nexport function stopWatcher(root: string): void {\n active.get(root)?.();\n}\n","import type { VerbalyConfig } from '@verbaly/compiler';\nimport { join } from 'node:path';\nimport {\n GENERATED_DIR,\n generatedDir,\n writeGeneratedModules,\n type Compiler,\n type RequestOptions,\n} from './codegen';\nimport { startWatcher } from './watch';\n\nexport type { VerbalyConfig } from '@verbaly/compiler';\nexport type { RequestOptions } from './codegen';\n\nexport interface NextVerbalyOptions extends VerbalyConfig {\n failOnMissing?: boolean;\n cookie?: string | false;\n fallback?: string;\n}\n\n// structural NextConfig subset: compat asserted against next's real type in tests.\n// No index signatures: Next's real interfaces have none and a target index\n// signature would reject them.\nexport interface WebpackConfigLike {\n resolve?: { alias?: Record<string, unknown>; [key: string]: unknown };\n module?: { rules?: unknown[]; [key: string]: unknown };\n plugins?: unknown[];\n [key: string]: unknown;\n}\n\ninterface WebpackContextLike {\n webpack?: {\n NormalModuleReplacementPlugin: new (test: RegExp, resource: string) => unknown;\n };\n}\n\n// context param is `never` so Next's real webpack fn (specific context type) stays assignable\nexport type WebpackFn = (config: WebpackConfigLike, context: never) => unknown;\n\nexport interface TurbopackLike {\n resolveAlias?: Record<string, unknown>;\n rules?: Record<string, unknown>;\n}\n\nexport interface NextConfigLike {\n webpack?: WebpackFn | null;\n turbopack?: TurbopackLike;\n}\n\n// constraint is `object` (not NextConfigLike): an all-optional target would\n// reject configs sharing no keys with it (TS weak-type rule)\nexport type NextConfigInput<C extends object> =\n | C\n | ((phase: string, context: { defaultConfig?: unknown }) => C | Promise<C>);\n\n// next/constants values: literal to keep this module import-free of next\nconst DEV_PHASE = 'phase-development-server';\nconst BUILD_PHASE = 'phase-production-build';\n\nconst LOADER = '@verbaly/next/loader';\n// compiler's SOURCE_FILE_RE (inlined: the compiler is only dynamically imported here).\n// Matching happens via `condition.path`: a bare extension glob also matches Next's\n// internal App Router entry and Turbopack panics reading it as a file.\nconst SOURCE_PATH_RE = /\\.[cm]?[jt]sx?$/;\n\nexport function withVerbaly<C extends object>(\n nextConfig?: NextConfigInput<C>,\n options: NextVerbalyOptions = {},\n): (phase: string, context?: { defaultConfig?: unknown }) => Promise<C> {\n const { failOnMissing, cookie, fallback, ...verbalyConfig } = options;\n const requestOptions: RequestOptions = { cookie, fallback };\n\n return async (phase, context = {}) => {\n const base: C =\n typeof nextConfig === 'function' ? await nextConfig(phase, context) : (nextConfig ?? ({} as C));\n const root = verbalyConfig.root ?? process.cwd();\n\n // production server / export: everything is bundled, so no FS work, config only\n if (phase !== DEV_PHASE && phase !== BUILD_PHASE) {\n return composeConfig(base, root);\n }\n\n // dynamic: the compiler is ESM-only and this entry is also consumed as CJS\n const compiler: Compiler = await import('@verbaly/compiler');\n\n const cfg = await compiler.loadConfig(root, verbalyConfig);\n const catalogs = compiler.loadCatalogs(cfg);\n const registry = await compiler.extractProject(cfg);\n\n if (phase === BUILD_PHASE) {\n if (failOnMissing !== false) compiler.runBuildGate(cfg, registry);\n } else {\n const { added } = compiler.syncCatalogs(cfg, catalogs, registry);\n for (const locale of Object.keys(added)) {\n compiler.writeCatalog(cfg, locale, catalogs[locale] ?? {});\n }\n compiler.writeDts(cfg, new Map(Object.entries(catalogs[cfg.sourceLocale] ?? {})));\n startWatcher(compiler, cfg, requestOptions);\n }\n\n writeGeneratedModules(compiler, cfg, catalogs, requestOptions);\n return composeConfig(base, cfg.root);\n };\n}\n\nfunction composeConfig<C extends object>(base: C, root: string): C {\n const runtimeModule = join(generatedDir(root), 'index.js');\n const { webpack: userWebpack, turbopack } = base as NextConfigLike;\n\n const rules: Record<string, unknown> = { ...turbopack?.rules };\n const verbalyRule = {\n condition: { all: [{ not: 'foreign' }, { path: SOURCE_PATH_RE }] },\n loaders: [LOADER],\n };\n const existing = rules['*'];\n rules['*'] = existing\n ? [...(Array.isArray(existing) ? existing : [existing]), verbalyRule]\n : verbalyRule;\n\n return {\n ...base,\n turbopack: {\n ...turbopack,\n resolveAlias: {\n ...turbopack?.resolveAlias,\n 'virtual:verbaly': `./${GENERATED_DIR}/index.js`,\n },\n rules,\n },\n webpack(config: WebpackConfigLike, context: unknown) {\n // webpack 5 treats 'virtual:' as a URI scheme: resolve.alias never fires,\n // module replacement does; the alias stays as a fallback for odd setups\n const { webpack: webpackInstance } = (context ?? {}) as WebpackContextLike;\n if (webpackInstance?.NormalModuleReplacementPlugin) {\n config.plugins ??= [];\n config.plugins.push(\n new webpackInstance.NormalModuleReplacementPlugin(/^virtual:verbaly$/, runtimeModule),\n );\n }\n config.resolve ??= {};\n config.resolve.alias ??= {};\n (config.resolve.alias as Record<string, unknown>)['virtual:verbaly'] = runtimeModule;\n config.module ??= {};\n config.module.rules ??= [];\n config.module.rules.push({\n test: /\\.[cm]?[jt]sx?$/,\n exclude: /node_modules/,\n enforce: 'pre',\n use: [{ loader: LOADER }],\n });\n return userWebpack ? userWebpack(config, context as never) : config;\n },\n } as C;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/codegen.ts","../src/watch.ts","../src/index.ts"],"sourcesContent":["import type { Catalogs, ResolvedConfig } from '@verbaly/compiler';\nimport { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type Compiler = typeof import('@verbaly/compiler');\n\nexport interface RequestOptions {\n cookie?: string | false;\n fallback?: string;\n}\n\nexport const GENERATED_DIR = '.verbaly';\n\nexport function generatedDir(root: string): string {\n return join(root, GENERATED_DIR);\n}\n\n// content compare: identical rewrites must not retrigger the bundler\nfunction writeIfChanged(file: string, content: string): boolean {\n try {\n if (readFileSync(file, 'utf8') === content) return false;\n } catch {\n // new file\n }\n writeFileSync(file, content);\n return true;\n}\n\n// dev-phase pipeline shared by withVerbaly and the watcher: sync catalogs to\n// the sources, refresh verbaly.d.ts, regenerate the runtime modules\nexport function syncAndWrite(\n compiler: Compiler,\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: Awaited<ReturnType<Compiler['extractProject']>>,\n requestOptions: RequestOptions,\n): void {\n const { added } = compiler.syncCatalogs(cfg, catalogs, registry);\n for (const locale of Object.keys(added)) {\n compiler.writeCatalog(cfg, locale, catalogs[locale] ?? {});\n }\n compiler.writeDts(cfg, catalogs[cfg.sourceLocale] ?? {});\n writeGeneratedModules(compiler, cfg, catalogs, requestOptions);\n}\n\n// real-file replacement for virtual:verbaly: Turbopack has no virtual modules\nexport function writeGeneratedModules(\n compiler: Compiler,\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n requestOptions: RequestOptions = {},\n): boolean {\n const dir = generatedDir(cfg.root);\n const localeDir = join(dir, 'locale');\n mkdirSync(localeDir, { recursive: true });\n\n let changed = writeIfChanged(join(dir, '.gitignore'), '*\\n');\n\n const runtime = compiler.generateRuntimeModule(cfg, {\n localeImport: (locale) => `./locale/${locale}.js`,\n extraExports: `export const requestOptions = ${JSON.stringify(requestOptions)};\\n`,\n });\n changed = writeIfChanged(join(dir, 'index.js'), runtime) || changed;\n\n const expected = new Set(cfg.locales.map((locale) => `${locale}.js`));\n for (const locale of cfg.locales) {\n changed =\n writeIfChanged(\n join(localeDir, `${locale}.js`),\n compiler.generateLocaleModule(catalogs[locale] ?? {}),\n ) || changed;\n }\n for (const file of readdirSync(localeDir)) {\n if (!expected.has(file)) {\n rmSync(join(localeDir, file));\n changed = true;\n }\n }\n return changed;\n}\n","import type { ResolvedConfig } from '@verbaly/compiler';\nimport { watch } from 'node:fs';\nimport { relative } from 'node:path';\nimport { GENERATED_DIR, syncAndWrite, type Compiler, type RequestOptions } from './codegen';\n\n// one watcher per project root: next.config can be evaluated more than once\nconst active = new Map<string, () => void>();\n\nexport function startWatcher(\n compiler: Compiler,\n cfg: ResolvedConfig,\n requestOptions: RequestOptions,\n): () => void {\n const existing = active.get(cfg.root);\n if (existing) return existing;\n\n const catalogDir = relative(cfg.root, cfg.dir).replaceAll('\\\\', '/');\n let timer: ReturnType<typeof setTimeout> | undefined;\n let running = false;\n let queued = false;\n\n async function refresh(): Promise<void> {\n if (running) {\n queued = true;\n return;\n }\n running = true;\n try {\n const catalogs = compiler.loadCatalogs(cfg);\n const registry = await compiler.extractProject(cfg);\n syncAndWrite(compiler, cfg, catalogs, registry, requestOptions);\n } catch (error) {\n console.warn('[verbaly] live extraction failed:', error);\n } finally {\n running = false;\n if (queued) {\n queued = false;\n schedule();\n }\n }\n }\n\n function schedule(): void {\n clearTimeout(timer);\n timer = setTimeout(() => void refresh(), 150);\n }\n\n const watcher = watch(cfg.root, { recursive: true }, (_event, filename) => {\n if (!filename) return;\n const file = filename.replaceAll('\\\\', '/');\n if (\n file.startsWith(`${GENERATED_DIR}/`) ||\n file.startsWith('.next/') ||\n file.includes('node_modules/') ||\n file.endsWith('.d.ts')\n ) {\n return;\n }\n const isCatalog = file.startsWith(`${catalogDir}/`) && file.endsWith('.json');\n if (isCatalog || compiler.SOURCE_FILE_RE.test(file)) schedule();\n });\n watcher.unref?.();\n\n const dispose = (): void => {\n clearTimeout(timer);\n watcher.close();\n active.delete(cfg.root);\n };\n active.set(cfg.root, dispose);\n return dispose;\n}\n\n// test hook\nexport function stopWatcher(root: string): void {\n active.get(root)?.();\n}\n","import type { PluginOptions } from '@verbaly/compiler';\nimport { join } from 'node:path';\nimport {\n GENERATED_DIR,\n generatedDir,\n syncAndWrite,\n writeGeneratedModules,\n type Compiler,\n type RequestOptions,\n} from './codegen';\nimport { startWatcher } from './watch';\n\nexport type { VerbalyConfig } from '@verbaly/compiler';\nexport type { RequestOptions } from './codegen';\n\nexport interface NextVerbalyOptions extends PluginOptions {\n cookie?: string | false;\n fallback?: string;\n}\n\n// structural NextConfig subset: compat asserted against next's real type in tests.\n// No index signatures: Next's real interfaces have none and a target index\n// signature would reject them.\nexport interface WebpackConfigLike {\n resolve?: { alias?: Record<string, unknown>; [key: string]: unknown };\n module?: { rules?: unknown[]; [key: string]: unknown };\n plugins?: unknown[];\n [key: string]: unknown;\n}\n\ninterface WebpackContextLike {\n webpack?: {\n NormalModuleReplacementPlugin: new (test: RegExp, resource: string) => unknown;\n };\n}\n\n// context param is `never` so Next's real webpack fn (specific context type) stays assignable\nexport type WebpackFn = (config: WebpackConfigLike, context: never) => unknown;\n\nexport interface TurbopackLike {\n resolveAlias?: Record<string, unknown>;\n rules?: Record<string, unknown>;\n}\n\nexport interface NextConfigLike {\n webpack?: WebpackFn | null;\n turbopack?: TurbopackLike;\n}\n\n// constraint is `object` (not NextConfigLike): an all-optional target would\n// reject configs sharing no keys with it (TS weak-type rule)\nexport type NextConfigInput<C extends object> =\n | C\n | ((phase: string, context: { defaultConfig?: unknown }) => C | Promise<C>);\n\n// next/constants values: literal to keep this module import-free of next\nconst DEV_PHASE = 'phase-development-server';\nconst BUILD_PHASE = 'phase-production-build';\n\nconst LOADER = '@verbaly/next/loader';\n// compiler's SOURCE_FILE_RE (inlined: the compiler is only dynamically imported here).\n// Matching happens via `condition.path`: a bare extension glob also matches Next's\n// internal App Router entry and Turbopack panics reading it as a file.\nconst SOURCE_PATH_RE = /\\.[cm]?[jt]sx?$/;\n\nexport function withVerbaly<C extends object>(\n nextConfig?: NextConfigInput<C>,\n options: NextVerbalyOptions = {},\n): (phase: string, context?: { defaultConfig?: unknown }) => Promise<C> {\n const { failOnMissing, cookie, fallback, ...verbalyConfig } = options;\n const requestOptions: RequestOptions = { cookie, fallback };\n\n return async (phase, context = {}) => {\n const base: C =\n typeof nextConfig === 'function' ? await nextConfig(phase, context) : (nextConfig ?? ({} as C));\n const root = verbalyConfig.root ?? process.cwd();\n\n // production server / export: everything is bundled, so no FS work, config only\n if (phase !== DEV_PHASE && phase !== BUILD_PHASE) {\n return composeConfig(base, root);\n }\n\n // dynamic: the compiler is ESM-only and this entry is also consumed as CJS\n const compiler: Compiler = await import('@verbaly/compiler');\n\n const cfg = await compiler.loadConfig(root, verbalyConfig);\n const catalogs = compiler.loadCatalogs(cfg);\n const registry = await compiler.extractProject(cfg);\n\n if (phase === BUILD_PHASE) {\n compiler.runBuildGate(cfg, registry, failOnMissing);\n writeGeneratedModules(compiler, cfg, catalogs, requestOptions);\n } else {\n syncAndWrite(compiler, cfg, catalogs, registry, requestOptions);\n startWatcher(compiler, cfg, requestOptions);\n }\n\n return composeConfig(base, cfg.root);\n };\n}\n\nfunction composeConfig<C extends object>(base: C, root: string): C {\n const runtimeModule = join(generatedDir(root), 'index.js');\n const { webpack: userWebpack, turbopack } = base as NextConfigLike;\n\n const rules: Record<string, unknown> = { ...turbopack?.rules };\n const verbalyRule = {\n condition: { all: [{ not: 'foreign' }, { path: SOURCE_PATH_RE }] },\n loaders: [LOADER],\n };\n const existing = rules['*'];\n rules['*'] = existing\n ? [...(Array.isArray(existing) ? existing : [existing]), verbalyRule]\n : verbalyRule;\n\n return {\n ...base,\n turbopack: {\n ...turbopack,\n resolveAlias: {\n ...turbopack?.resolveAlias,\n 'virtual:verbaly': `./${GENERATED_DIR}/index.js`,\n },\n rules,\n },\n webpack(config: WebpackConfigLike, context: unknown) {\n // webpack 5 treats 'virtual:' as a URI scheme: resolve.alias never fires,\n // module replacement does; the alias stays as a fallback for odd setups\n const { webpack: webpackInstance } = (context ?? {}) as WebpackContextLike;\n if (webpackInstance?.NormalModuleReplacementPlugin) {\n config.plugins ??= [];\n config.plugins.push(\n new webpackInstance.NormalModuleReplacementPlugin(/^virtual:verbaly$/, runtimeModule),\n );\n }\n config.resolve ??= {};\n config.resolve.alias ??= {};\n (config.resolve.alias as Record<string, unknown>)['virtual:verbaly'] = runtimeModule;\n config.module ??= {};\n config.module.rules ??= [];\n config.module.rules.push({\n test: SOURCE_PATH_RE,\n exclude: /node_modules/,\n enforce: 'pre',\n use: [{ loader: LOADER }],\n });\n return userWebpack ? userWebpack(config, context as never) : config;\n },\n } as C;\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PluginOptions, VerbalyConfig } from "@verbaly/compiler";
|
|
2
2
|
//#region src/codegen.d.ts
|
|
3
3
|
interface RequestOptions {
|
|
4
4
|
cookie?: string | false;
|
|
@@ -6,8 +6,7 @@ interface RequestOptions {
|
|
|
6
6
|
}
|
|
7
7
|
//#endregion
|
|
8
8
|
//#region src/index.d.ts
|
|
9
|
-
interface NextVerbalyOptions extends
|
|
10
|
-
failOnMissing?: boolean;
|
|
9
|
+
interface NextVerbalyOptions extends PluginOptions {
|
|
11
10
|
cookie?: string | false;
|
|
12
11
|
fallback?: string;
|
|
13
12
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PluginOptions, VerbalyConfig } from "@verbaly/compiler";
|
|
2
2
|
//#region src/codegen.d.ts
|
|
3
3
|
interface RequestOptions {
|
|
4
4
|
cookie?: string | false;
|
|
@@ -6,8 +6,7 @@ interface RequestOptions {
|
|
|
6
6
|
}
|
|
7
7
|
//#endregion
|
|
8
8
|
//#region src/index.d.ts
|
|
9
|
-
interface NextVerbalyOptions extends
|
|
10
|
-
failOnMissing?: boolean;
|
|
9
|
+
interface NextVerbalyOptions extends PluginOptions {
|
|
11
10
|
cookie?: string | false;
|
|
12
11
|
fallback?: string;
|
|
13
12
|
}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,12 @@ function writeIfChanged(file, content) {
|
|
|
12
12
|
writeFileSync(file, content);
|
|
13
13
|
return true;
|
|
14
14
|
}
|
|
15
|
+
function syncAndWrite(compiler, cfg, catalogs, registry, requestOptions) {
|
|
16
|
+
const { added } = compiler.syncCatalogs(cfg, catalogs, registry);
|
|
17
|
+
for (const locale of Object.keys(added)) compiler.writeCatalog(cfg, locale, catalogs[locale] ?? {});
|
|
18
|
+
compiler.writeDts(cfg, catalogs[cfg.sourceLocale] ?? {});
|
|
19
|
+
writeGeneratedModules(compiler, cfg, catalogs, requestOptions);
|
|
20
|
+
}
|
|
15
21
|
function writeGeneratedModules(compiler, cfg, catalogs, requestOptions = {}) {
|
|
16
22
|
const dir = generatedDir(cfg.root);
|
|
17
23
|
const localeDir = join(dir, "locale");
|
|
@@ -47,12 +53,7 @@ function startWatcher(compiler, cfg, requestOptions) {
|
|
|
47
53
|
}
|
|
48
54
|
running = true;
|
|
49
55
|
try {
|
|
50
|
-
|
|
51
|
-
const registry = await compiler.extractProject(cfg);
|
|
52
|
-
const { added } = compiler.syncCatalogs(cfg, catalogs, registry);
|
|
53
|
-
for (const locale of Object.keys(added)) compiler.writeCatalog(cfg, locale, catalogs[locale] ?? {});
|
|
54
|
-
compiler.writeDts(cfg, new Map(Object.entries(catalogs[cfg.sourceLocale] ?? {})));
|
|
55
|
-
writeGeneratedModules(compiler, cfg, catalogs, requestOptions);
|
|
56
|
+
syncAndWrite(compiler, cfg, compiler.loadCatalogs(cfg), await compiler.extractProject(cfg), requestOptions);
|
|
56
57
|
} catch (error) {
|
|
57
58
|
console.warn("[verbaly] live extraction failed:", error);
|
|
58
59
|
} finally {
|
|
@@ -103,14 +104,12 @@ function withVerbaly(nextConfig, options = {}) {
|
|
|
103
104
|
const catalogs = compiler.loadCatalogs(cfg);
|
|
104
105
|
const registry = await compiler.extractProject(cfg);
|
|
105
106
|
if (phase === BUILD_PHASE) {
|
|
106
|
-
|
|
107
|
+
compiler.runBuildGate(cfg, registry, failOnMissing);
|
|
108
|
+
writeGeneratedModules(compiler, cfg, catalogs, requestOptions);
|
|
107
109
|
} else {
|
|
108
|
-
|
|
109
|
-
for (const locale of Object.keys(added)) compiler.writeCatalog(cfg, locale, catalogs[locale] ?? {});
|
|
110
|
-
compiler.writeDts(cfg, new Map(Object.entries(catalogs[cfg.sourceLocale] ?? {})));
|
|
110
|
+
syncAndWrite(compiler, cfg, catalogs, registry, requestOptions);
|
|
111
111
|
startWatcher(compiler, cfg, requestOptions);
|
|
112
112
|
}
|
|
113
|
-
writeGeneratedModules(compiler, cfg, catalogs, requestOptions);
|
|
114
113
|
return composeConfig(base, cfg.root);
|
|
115
114
|
};
|
|
116
115
|
}
|
|
@@ -146,7 +145,7 @@ function composeConfig(base, root) {
|
|
|
146
145
|
config.module ??= {};
|
|
147
146
|
config.module.rules ??= [];
|
|
148
147
|
config.module.rules.push({
|
|
149
|
-
test:
|
|
148
|
+
test: SOURCE_PATH_RE,
|
|
150
149
|
exclude: /node_modules/,
|
|
151
150
|
enforce: "pre",
|
|
152
151
|
use: [{ loader: LOADER }]
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/codegen.ts","../src/watch.ts","../src/index.ts"],"sourcesContent":["import type { Catalogs, ResolvedConfig } from '@verbaly/compiler';\nimport { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type Compiler = typeof import('@verbaly/compiler');\n\nexport interface RequestOptions {\n cookie?: string | false;\n fallback?: string;\n}\n\nexport const GENERATED_DIR = '.verbaly';\n\nexport function generatedDir(root: string): string {\n return join(root, GENERATED_DIR);\n}\n\n// content compare: identical rewrites must not retrigger the bundler\nfunction writeIfChanged(file: string, content: string): boolean {\n try {\n if (readFileSync(file, 'utf8') === content) return false;\n } catch {\n // new file\n }\n writeFileSync(file, content);\n return true;\n}\n\n// real-file replacement for virtual:verbaly: Turbopack has no virtual modules\nexport function writeGeneratedModules(\n compiler: Compiler,\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n requestOptions: RequestOptions = {},\n): boolean {\n const dir = generatedDir(cfg.root);\n const localeDir = join(dir, 'locale');\n mkdirSync(localeDir, { recursive: true });\n\n let changed = writeIfChanged(join(dir, '.gitignore'), '*\\n');\n\n const runtime = compiler.generateRuntimeModule(cfg, {\n localeImport: (locale) => `./locale/${locale}.js`,\n extraExports: `export const requestOptions = ${JSON.stringify(requestOptions)};\\n`,\n });\n changed = writeIfChanged(join(dir, 'index.js'), runtime) || changed;\n\n const expected = new Set(cfg.locales.map((locale) => `${locale}.js`));\n for (const locale of cfg.locales) {\n changed =\n writeIfChanged(\n join(localeDir, `${locale}.js`),\n compiler.generateLocaleModule(catalogs[locale] ?? {}),\n ) || changed;\n }\n for (const file of readdirSync(localeDir)) {\n if (!expected.has(file)) {\n rmSync(join(localeDir, file));\n changed = true;\n }\n }\n return changed;\n}\n","import type { ResolvedConfig } from '@verbaly/compiler';\nimport { watch } from 'node:fs';\nimport { relative } from 'node:path';\nimport { GENERATED_DIR, writeGeneratedModules, type Compiler, type RequestOptions } from './codegen';\n\n// one watcher per project root: next.config can be evaluated more than once\nconst active = new Map<string, () => void>();\n\nexport function startWatcher(\n compiler: Compiler,\n cfg: ResolvedConfig,\n requestOptions: RequestOptions,\n): () => void {\n const existing = active.get(cfg.root);\n if (existing) return existing;\n\n const catalogDir = relative(cfg.root, cfg.dir).replaceAll('\\\\', '/');\n let timer: ReturnType<typeof setTimeout> | undefined;\n let running = false;\n let queued = false;\n\n async function refresh(): Promise<void> {\n if (running) {\n queued = true;\n return;\n }\n running = true;\n try {\n const catalogs = compiler.loadCatalogs(cfg);\n const registry = await compiler.extractProject(cfg);\n const { added } = compiler.syncCatalogs(cfg, catalogs, registry);\n for (const locale of Object.keys(added)) {\n compiler.writeCatalog(cfg, locale, catalogs[locale] ?? {});\n }\n compiler.writeDts(cfg, new Map(Object.entries(catalogs[cfg.sourceLocale] ?? {})));\n writeGeneratedModules(compiler, cfg, catalogs, requestOptions);\n } catch (error) {\n console.warn('[verbaly] live extraction failed:', error);\n } finally {\n running = false;\n if (queued) {\n queued = false;\n schedule();\n }\n }\n }\n\n function schedule(): void {\n clearTimeout(timer);\n timer = setTimeout(() => void refresh(), 150);\n }\n\n const watcher = watch(cfg.root, { recursive: true }, (_event, filename) => {\n if (!filename) return;\n const file = filename.replaceAll('\\\\', '/');\n if (\n file.startsWith(`${GENERATED_DIR}/`) ||\n file.startsWith('.next/') ||\n file.includes('node_modules/') ||\n file.endsWith('.d.ts')\n ) {\n return;\n }\n const isCatalog = file.startsWith(`${catalogDir}/`) && file.endsWith('.json');\n if (isCatalog || compiler.SOURCE_FILE_RE.test(file)) schedule();\n });\n watcher.unref?.();\n\n const dispose = (): void => {\n clearTimeout(timer);\n watcher.close();\n active.delete(cfg.root);\n };\n active.set(cfg.root, dispose);\n return dispose;\n}\n\n// test hook\nexport function stopWatcher(root: string): void {\n active.get(root)?.();\n}\n","import type { VerbalyConfig } from '@verbaly/compiler';\nimport { join } from 'node:path';\nimport {\n GENERATED_DIR,\n generatedDir,\n writeGeneratedModules,\n type Compiler,\n type RequestOptions,\n} from './codegen';\nimport { startWatcher } from './watch';\n\nexport type { VerbalyConfig } from '@verbaly/compiler';\nexport type { RequestOptions } from './codegen';\n\nexport interface NextVerbalyOptions extends VerbalyConfig {\n failOnMissing?: boolean;\n cookie?: string | false;\n fallback?: string;\n}\n\n// structural NextConfig subset: compat asserted against next's real type in tests.\n// No index signatures: Next's real interfaces have none and a target index\n// signature would reject them.\nexport interface WebpackConfigLike {\n resolve?: { alias?: Record<string, unknown>; [key: string]: unknown };\n module?: { rules?: unknown[]; [key: string]: unknown };\n plugins?: unknown[];\n [key: string]: unknown;\n}\n\ninterface WebpackContextLike {\n webpack?: {\n NormalModuleReplacementPlugin: new (test: RegExp, resource: string) => unknown;\n };\n}\n\n// context param is `never` so Next's real webpack fn (specific context type) stays assignable\nexport type WebpackFn = (config: WebpackConfigLike, context: never) => unknown;\n\nexport interface TurbopackLike {\n resolveAlias?: Record<string, unknown>;\n rules?: Record<string, unknown>;\n}\n\nexport interface NextConfigLike {\n webpack?: WebpackFn | null;\n turbopack?: TurbopackLike;\n}\n\n// constraint is `object` (not NextConfigLike): an all-optional target would\n// reject configs sharing no keys with it (TS weak-type rule)\nexport type NextConfigInput<C extends object> =\n | C\n | ((phase: string, context: { defaultConfig?: unknown }) => C | Promise<C>);\n\n// next/constants values: literal to keep this module import-free of next\nconst DEV_PHASE = 'phase-development-server';\nconst BUILD_PHASE = 'phase-production-build';\n\nconst LOADER = '@verbaly/next/loader';\n// compiler's SOURCE_FILE_RE (inlined: the compiler is only dynamically imported here).\n// Matching happens via `condition.path`: a bare extension glob also matches Next's\n// internal App Router entry and Turbopack panics reading it as a file.\nconst SOURCE_PATH_RE = /\\.[cm]?[jt]sx?$/;\n\nexport function withVerbaly<C extends object>(\n nextConfig?: NextConfigInput<C>,\n options: NextVerbalyOptions = {},\n): (phase: string, context?: { defaultConfig?: unknown }) => Promise<C> {\n const { failOnMissing, cookie, fallback, ...verbalyConfig } = options;\n const requestOptions: RequestOptions = { cookie, fallback };\n\n return async (phase, context = {}) => {\n const base: C =\n typeof nextConfig === 'function' ? await nextConfig(phase, context) : (nextConfig ?? ({} as C));\n const root = verbalyConfig.root ?? process.cwd();\n\n // production server / export: everything is bundled, so no FS work, config only\n if (phase !== DEV_PHASE && phase !== BUILD_PHASE) {\n return composeConfig(base, root);\n }\n\n // dynamic: the compiler is ESM-only and this entry is also consumed as CJS\n const compiler: Compiler = await import('@verbaly/compiler');\n\n const cfg = await compiler.loadConfig(root, verbalyConfig);\n const catalogs = compiler.loadCatalogs(cfg);\n const registry = await compiler.extractProject(cfg);\n\n if (phase === BUILD_PHASE) {\n if (failOnMissing !== false) compiler.runBuildGate(cfg, registry);\n } else {\n const { added } = compiler.syncCatalogs(cfg, catalogs, registry);\n for (const locale of Object.keys(added)) {\n compiler.writeCatalog(cfg, locale, catalogs[locale] ?? {});\n }\n compiler.writeDts(cfg, new Map(Object.entries(catalogs[cfg.sourceLocale] ?? {})));\n startWatcher(compiler, cfg, requestOptions);\n }\n\n writeGeneratedModules(compiler, cfg, catalogs, requestOptions);\n return composeConfig(base, cfg.root);\n };\n}\n\nfunction composeConfig<C extends object>(base: C, root: string): C {\n const runtimeModule = join(generatedDir(root), 'index.js');\n const { webpack: userWebpack, turbopack } = base as NextConfigLike;\n\n const rules: Record<string, unknown> = { ...turbopack?.rules };\n const verbalyRule = {\n condition: { all: [{ not: 'foreign' }, { path: SOURCE_PATH_RE }] },\n loaders: [LOADER],\n };\n const existing = rules['*'];\n rules['*'] = existing\n ? [...(Array.isArray(existing) ? existing : [existing]), verbalyRule]\n : verbalyRule;\n\n return {\n ...base,\n turbopack: {\n ...turbopack,\n resolveAlias: {\n ...turbopack?.resolveAlias,\n 'virtual:verbaly': `./${GENERATED_DIR}/index.js`,\n },\n rules,\n },\n webpack(config: WebpackConfigLike, context: unknown) {\n // webpack 5 treats 'virtual:' as a URI scheme: resolve.alias never fires,\n // module replacement does; the alias stays as a fallback for odd setups\n const { webpack: webpackInstance } = (context ?? {}) as WebpackContextLike;\n if (webpackInstance?.NormalModuleReplacementPlugin) {\n config.plugins ??= [];\n config.plugins.push(\n new webpackInstance.NormalModuleReplacementPlugin(/^virtual:verbaly$/, runtimeModule),\n );\n }\n config.resolve ??= {};\n config.resolve.alias ??= {};\n (config.resolve.alias as Record<string, unknown>)['virtual:verbaly'] = runtimeModule;\n config.module ??= {};\n config.module.rules ??= [];\n config.module.rules.push({\n test: /\\.[cm]?[jt]sx?$/,\n exclude: /node_modules/,\n enforce: 'pre',\n use: [{ loader: LOADER }],\n });\n return userWebpack ? userWebpack(config, context as never) : config;\n },\n } as C;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/codegen.ts","../src/watch.ts","../src/index.ts"],"sourcesContent":["import type { Catalogs, ResolvedConfig } from '@verbaly/compiler';\nimport { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type Compiler = typeof import('@verbaly/compiler');\n\nexport interface RequestOptions {\n cookie?: string | false;\n fallback?: string;\n}\n\nexport const GENERATED_DIR = '.verbaly';\n\nexport function generatedDir(root: string): string {\n return join(root, GENERATED_DIR);\n}\n\n// content compare: identical rewrites must not retrigger the bundler\nfunction writeIfChanged(file: string, content: string): boolean {\n try {\n if (readFileSync(file, 'utf8') === content) return false;\n } catch {\n // new file\n }\n writeFileSync(file, content);\n return true;\n}\n\n// dev-phase pipeline shared by withVerbaly and the watcher: sync catalogs to\n// the sources, refresh verbaly.d.ts, regenerate the runtime modules\nexport function syncAndWrite(\n compiler: Compiler,\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n registry: Awaited<ReturnType<Compiler['extractProject']>>,\n requestOptions: RequestOptions,\n): void {\n const { added } = compiler.syncCatalogs(cfg, catalogs, registry);\n for (const locale of Object.keys(added)) {\n compiler.writeCatalog(cfg, locale, catalogs[locale] ?? {});\n }\n compiler.writeDts(cfg, catalogs[cfg.sourceLocale] ?? {});\n writeGeneratedModules(compiler, cfg, catalogs, requestOptions);\n}\n\n// real-file replacement for virtual:verbaly: Turbopack has no virtual modules\nexport function writeGeneratedModules(\n compiler: Compiler,\n cfg: ResolvedConfig,\n catalogs: Catalogs,\n requestOptions: RequestOptions = {},\n): boolean {\n const dir = generatedDir(cfg.root);\n const localeDir = join(dir, 'locale');\n mkdirSync(localeDir, { recursive: true });\n\n let changed = writeIfChanged(join(dir, '.gitignore'), '*\\n');\n\n const runtime = compiler.generateRuntimeModule(cfg, {\n localeImport: (locale) => `./locale/${locale}.js`,\n extraExports: `export const requestOptions = ${JSON.stringify(requestOptions)};\\n`,\n });\n changed = writeIfChanged(join(dir, 'index.js'), runtime) || changed;\n\n const expected = new Set(cfg.locales.map((locale) => `${locale}.js`));\n for (const locale of cfg.locales) {\n changed =\n writeIfChanged(\n join(localeDir, `${locale}.js`),\n compiler.generateLocaleModule(catalogs[locale] ?? {}),\n ) || changed;\n }\n for (const file of readdirSync(localeDir)) {\n if (!expected.has(file)) {\n rmSync(join(localeDir, file));\n changed = true;\n }\n }\n return changed;\n}\n","import type { ResolvedConfig } from '@verbaly/compiler';\nimport { watch } from 'node:fs';\nimport { relative } from 'node:path';\nimport { GENERATED_DIR, syncAndWrite, type Compiler, type RequestOptions } from './codegen';\n\n// one watcher per project root: next.config can be evaluated more than once\nconst active = new Map<string, () => void>();\n\nexport function startWatcher(\n compiler: Compiler,\n cfg: ResolvedConfig,\n requestOptions: RequestOptions,\n): () => void {\n const existing = active.get(cfg.root);\n if (existing) return existing;\n\n const catalogDir = relative(cfg.root, cfg.dir).replaceAll('\\\\', '/');\n let timer: ReturnType<typeof setTimeout> | undefined;\n let running = false;\n let queued = false;\n\n async function refresh(): Promise<void> {\n if (running) {\n queued = true;\n return;\n }\n running = true;\n try {\n const catalogs = compiler.loadCatalogs(cfg);\n const registry = await compiler.extractProject(cfg);\n syncAndWrite(compiler, cfg, catalogs, registry, requestOptions);\n } catch (error) {\n console.warn('[verbaly] live extraction failed:', error);\n } finally {\n running = false;\n if (queued) {\n queued = false;\n schedule();\n }\n }\n }\n\n function schedule(): void {\n clearTimeout(timer);\n timer = setTimeout(() => void refresh(), 150);\n }\n\n const watcher = watch(cfg.root, { recursive: true }, (_event, filename) => {\n if (!filename) return;\n const file = filename.replaceAll('\\\\', '/');\n if (\n file.startsWith(`${GENERATED_DIR}/`) ||\n file.startsWith('.next/') ||\n file.includes('node_modules/') ||\n file.endsWith('.d.ts')\n ) {\n return;\n }\n const isCatalog = file.startsWith(`${catalogDir}/`) && file.endsWith('.json');\n if (isCatalog || compiler.SOURCE_FILE_RE.test(file)) schedule();\n });\n watcher.unref?.();\n\n const dispose = (): void => {\n clearTimeout(timer);\n watcher.close();\n active.delete(cfg.root);\n };\n active.set(cfg.root, dispose);\n return dispose;\n}\n\n// test hook\nexport function stopWatcher(root: string): void {\n active.get(root)?.();\n}\n","import type { PluginOptions } from '@verbaly/compiler';\nimport { join } from 'node:path';\nimport {\n GENERATED_DIR,\n generatedDir,\n syncAndWrite,\n writeGeneratedModules,\n type Compiler,\n type RequestOptions,\n} from './codegen';\nimport { startWatcher } from './watch';\n\nexport type { VerbalyConfig } from '@verbaly/compiler';\nexport type { RequestOptions } from './codegen';\n\nexport interface NextVerbalyOptions extends PluginOptions {\n cookie?: string | false;\n fallback?: string;\n}\n\n// structural NextConfig subset: compat asserted against next's real type in tests.\n// No index signatures: Next's real interfaces have none and a target index\n// signature would reject them.\nexport interface WebpackConfigLike {\n resolve?: { alias?: Record<string, unknown>; [key: string]: unknown };\n module?: { rules?: unknown[]; [key: string]: unknown };\n plugins?: unknown[];\n [key: string]: unknown;\n}\n\ninterface WebpackContextLike {\n webpack?: {\n NormalModuleReplacementPlugin: new (test: RegExp, resource: string) => unknown;\n };\n}\n\n// context param is `never` so Next's real webpack fn (specific context type) stays assignable\nexport type WebpackFn = (config: WebpackConfigLike, context: never) => unknown;\n\nexport interface TurbopackLike {\n resolveAlias?: Record<string, unknown>;\n rules?: Record<string, unknown>;\n}\n\nexport interface NextConfigLike {\n webpack?: WebpackFn | null;\n turbopack?: TurbopackLike;\n}\n\n// constraint is `object` (not NextConfigLike): an all-optional target would\n// reject configs sharing no keys with it (TS weak-type rule)\nexport type NextConfigInput<C extends object> =\n | C\n | ((phase: string, context: { defaultConfig?: unknown }) => C | Promise<C>);\n\n// next/constants values: literal to keep this module import-free of next\nconst DEV_PHASE = 'phase-development-server';\nconst BUILD_PHASE = 'phase-production-build';\n\nconst LOADER = '@verbaly/next/loader';\n// compiler's SOURCE_FILE_RE (inlined: the compiler is only dynamically imported here).\n// Matching happens via `condition.path`: a bare extension glob also matches Next's\n// internal App Router entry and Turbopack panics reading it as a file.\nconst SOURCE_PATH_RE = /\\.[cm]?[jt]sx?$/;\n\nexport function withVerbaly<C extends object>(\n nextConfig?: NextConfigInput<C>,\n options: NextVerbalyOptions = {},\n): (phase: string, context?: { defaultConfig?: unknown }) => Promise<C> {\n const { failOnMissing, cookie, fallback, ...verbalyConfig } = options;\n const requestOptions: RequestOptions = { cookie, fallback };\n\n return async (phase, context = {}) => {\n const base: C =\n typeof nextConfig === 'function' ? await nextConfig(phase, context) : (nextConfig ?? ({} as C));\n const root = verbalyConfig.root ?? process.cwd();\n\n // production server / export: everything is bundled, so no FS work, config only\n if (phase !== DEV_PHASE && phase !== BUILD_PHASE) {\n return composeConfig(base, root);\n }\n\n // dynamic: the compiler is ESM-only and this entry is also consumed as CJS\n const compiler: Compiler = await import('@verbaly/compiler');\n\n const cfg = await compiler.loadConfig(root, verbalyConfig);\n const catalogs = compiler.loadCatalogs(cfg);\n const registry = await compiler.extractProject(cfg);\n\n if (phase === BUILD_PHASE) {\n compiler.runBuildGate(cfg, registry, failOnMissing);\n writeGeneratedModules(compiler, cfg, catalogs, requestOptions);\n } else {\n syncAndWrite(compiler, cfg, catalogs, registry, requestOptions);\n startWatcher(compiler, cfg, requestOptions);\n }\n\n return composeConfig(base, cfg.root);\n };\n}\n\nfunction composeConfig<C extends object>(base: C, root: string): C {\n const runtimeModule = join(generatedDir(root), 'index.js');\n const { webpack: userWebpack, turbopack } = base as NextConfigLike;\n\n const rules: Record<string, unknown> = { ...turbopack?.rules };\n const verbalyRule = {\n condition: { all: [{ not: 'foreign' }, { path: SOURCE_PATH_RE }] },\n loaders: [LOADER],\n };\n const existing = rules['*'];\n rules['*'] = existing\n ? [...(Array.isArray(existing) ? existing : [existing]), verbalyRule]\n : verbalyRule;\n\n return {\n ...base,\n turbopack: {\n ...turbopack,\n resolveAlias: {\n ...turbopack?.resolveAlias,\n 'virtual:verbaly': `./${GENERATED_DIR}/index.js`,\n },\n rules,\n },\n webpack(config: WebpackConfigLike, context: unknown) {\n // webpack 5 treats 'virtual:' as a URI scheme: resolve.alias never fires,\n // module replacement does; the alias stays as a fallback for odd setups\n const { webpack: webpackInstance } = (context ?? {}) as WebpackContextLike;\n if (webpackInstance?.NormalModuleReplacementPlugin) {\n config.plugins ??= [];\n config.plugins.push(\n new webpackInstance.NormalModuleReplacementPlugin(/^virtual:verbaly$/, runtimeModule),\n );\n }\n config.resolve ??= {};\n config.resolve.alias ??= {};\n (config.resolve.alias as Record<string, unknown>)['virtual:verbaly'] = runtimeModule;\n config.module ??= {};\n config.module.rules ??= [];\n config.module.rules.push({\n test: SOURCE_PATH_RE,\n exclude: /node_modules/,\n enforce: 'pre',\n use: [{ loader: LOADER }],\n });\n return userWebpack ? userWebpack(config, context as never) : config;\n },\n } as C;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verbaly/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Next.js integration for Verbaly — App Router/RSC with per-request locale negotiation, Turbopack and webpack support, flash-free hydration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"i18n",
|
|
@@ -64,13 +64,13 @@
|
|
|
64
64
|
"access": "public"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@verbaly/compiler": "^0.
|
|
67
|
+
"@verbaly/compiler": "^0.21.0"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"next": "^15.0.0 || ^16.0.0",
|
|
71
71
|
"react": "^18.0.0 || ^19.0.0",
|
|
72
|
-
"@verbaly/react": "^0.
|
|
73
|
-
"verbaly": "^0.
|
|
72
|
+
"@verbaly/react": "^0.21.0",
|
|
73
|
+
"verbaly": "^0.21.0"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@types/react": "^19.2.17",
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
"next": "^16.2.10",
|
|
80
80
|
"react": "^19.2.7",
|
|
81
81
|
"react-dom": "^19.2.7",
|
|
82
|
-
"verbaly": "0.
|
|
83
|
-
"
|
|
82
|
+
"@verbaly/react": "0.21.0",
|
|
83
|
+
"verbaly": "0.21.0"
|
|
84
84
|
},
|
|
85
85
|
"scripts": {
|
|
86
86
|
"build": "tsdown",
|