Skip to main content
Glama
withIntlayer.ts12.1 kB
import { join, relative, resolve } from 'node:path'; import { getComponentTransformPatternSync, prepareIntlayer, runOnce, } from '@intlayer/chokidar'; import { ANSIColors, colorize, compareVersions, type GetConfigurationOptions, getAlias, getAppLogger, getConfiguration, getProjectRequire, normalizePath, } from '@intlayer/config'; import { getDictionaries } from '@intlayer/dictionaries-entry'; import type { IntlayerConfig } from '@intlayer/types'; import { IntlayerPlugin } from '@intlayer/webpack'; import { defu } from 'defu'; import type { NextConfig } from 'next'; import type { NextJsWebpackConfig } from 'next/dist/server/config-shared'; import nextPackageJSON from 'next/package.json' with { type: 'json' }; const isGteNext13 = compareVersions(nextPackageJSON.version, '≥', '13.0.0'); const isGteNext15 = compareVersions(nextPackageJSON.version, '≥', '15.0.0'); const isGteNext16 = compareVersions(nextPackageJSON.version, '≥', '16.0.0'); const isTurbopackStable = compareVersions( nextPackageJSON.version, '≥', '15.3.0' ); const isTurbopackEnabledFromCommand = isGteNext16 ? // Next@16 enable turbopack by default, and offer the possibility to disable it if --webpack flag is used !process.env.npm_lifecycle_script?.includes('--webpack') : // Next@15 use --turbopack flag, Next@14 use --turbo flag process.env.npm_lifecycle_script?.includes('--turbo'); // Check if SWC plugin is available const getIsSwcPluginAvailable = (intlayerConfig: IntlayerConfig) => { try { const requireFunction = intlayerConfig.build?.require ?? getProjectRequire(); requireFunction.resolve('@intlayer/swc'); return true; } catch (_e) { return false; } }; const resolvePluginPath = ( pluginPath: string, intlayerConfig: IntlayerConfig, isTurbopackEnabled: boolean ): string => { const requireFunction = intlayerConfig.build?.require ?? getProjectRequire(); const pluginPathResolved = requireFunction?.resolve(pluginPath); if (isTurbopackEnabled) // Relative path for turbopack return normalizePath(`./${relative(process.cwd(), pluginPathResolved)}`); // Absolute path for webpack return pluginPathResolved; }; const getPruneConfig = ( intlayerConfig: IntlayerConfig, isBuildCommand: boolean, isTurbopackEnabled: boolean ): Partial<NextConfig> => { const { optimize, importMode } = intlayerConfig.build; const { dictionariesDir, unmergedDictionariesDir, dynamicDictionariesDir, fetchDictionariesDir, mainDir, baseDir, } = intlayerConfig.content; const logger = getAppLogger(intlayerConfig); if (optimize === false) { return {}; } if (optimize === undefined && !isBuildCommand) { return {}; } if (!isGteNext13) return {}; const isSwcPluginAvailable = getIsSwcPluginAvailable(intlayerConfig); runOnce( join(baseDir, '.intlayer', 'cache', 'intlayer-prune-plugin-enabled.lock'), () => { if (isSwcPluginAvailable) { logger('Build optimization enabled'); } else { logger([ colorize('Recommended: Install', ANSIColors.GREY), colorize('@intlayer/swc', ANSIColors.GREY_LIGHT), colorize( 'package to enable build optimization. See documentation: ', ANSIColors.GREY ), colorize( 'https://intlayer.org/docs/en/bundle_optimization', ANSIColors.GREY_LIGHT ), ]); } }, { cacheTimeoutMs: 1000 * 30, // 30 seconds } ); if (!isSwcPluginAvailable) { return {}; } const dictionariesEntryPath = join(mainDir, 'dictionaries.mjs'); const dynamicDictionariesEntryPath = join( mainDir, 'dynamic_dictionaries.mjs' ); const unmergedDictionariesEntryPath = join( mainDir, 'unmerged_dictionaries.mjs' ); const fetchDictionariesEntryPath = join(mainDir, 'fetch_dictionaries.mjs'); const filesListPattern = getComponentTransformPatternSync(intlayerConfig); const filesList = [ ...filesListPattern, dictionariesEntryPath, // should add dictionariesEntryPath to replace it by a empty object if import made dynamic unmergedDictionariesEntryPath, // should add dictionariesEntryPath to replace it by a empty object if import made dynamic ]; const dictionaries = getDictionaries(intlayerConfig); const liveSyncKeys = Object.values(dictionaries) .filter((dictionary) => dictionary.live) .map((dictionary) => dictionary.key); return { experimental: { swcPlugins: [ [ resolvePluginPath( '@intlayer/swc', intlayerConfig, isTurbopackEnabled ), { dictionariesDir, dictionariesEntryPath, unmergedDictionariesEntryPath, unmergedDictionariesDir, dynamicDictionariesDir, dynamicDictionariesEntryPath, fetchDictionariesDir, fetchDictionariesEntryPath, importMode, filesList, replaceDictionaryEntry: true, liveSyncKeys, } as any, ], ], }, }; }; const getCommandsEvent = () => { const lifecycleEvent = process.env.npm_lifecycle_event; const lifecycleScript = process.env.npm_lifecycle_script ?? ''; const isDevCommand = lifecycleEvent === 'dev' || process.argv.some((arg) => arg === 'dev') || /(^|\s)(next\s+)?dev(\s|$)/.test(lifecycleScript); const isBuildCommand = lifecycleEvent === 'build' || process.argv.some((arg) => arg === 'build') || /(^|\s)(next\s+)?build(\s|$)/.test(lifecycleScript); const isStartCommand = lifecycleEvent === 'start' || process.argv.some((arg) => arg === 'start') || /(^|\s)(next\s+)?start(\s|$)/.test(lifecycleScript); return { isDevCommand, isBuildCommand, isStartCommand, }; }; type WebpackParams = Parameters<NextJsWebpackConfig>; type WithIntlayerOptions = GetConfigurationOptions & { enableTurbopack?: boolean; }; /** * A Next.js plugin that adds the intlayer configuration to the webpack configuration * and sets the environment variables * * Usage: * * ```ts * // next.config.js * export default withIntlayerSync(nextConfig) * ``` */ export const withIntlayerSync = <T extends Partial<NextConfig>>( nextConfig: T = {} as T, configOptions?: WithIntlayerOptions ): NextConfig & T => { if (typeof nextConfig !== 'object') { nextConfig = {} as T; } const intlayerConfig = getConfiguration(configOptions); const logger = getAppLogger(intlayerConfig); const isTurbopackEnabled = configOptions?.enableTurbopack ?? isTurbopackEnabledFromCommand; if (isTurbopackEnabled && typeof nextConfig.webpack !== 'undefined') { logger( 'Turbopack is enabled but a custom webpack config is present. It will be ignored.' ); } const { isBuildCommand, isDevCommand } = getCommandsEvent(); // Only provide turbo-specific config if user explicitly sets it const turboConfig = { resolveAlias: getAlias({ configuration: intlayerConfig, formatter: (value: string) => `./${value}`, // prefix by './' to consider the path as relative to the project root. This is necessary for turbopack to work correctly. }), rules: { '*.node': { as: '*.node', loaders: ['node-loader'], }, }, }; const serverExternalPackages = [ 'esbuild', 'module', 'fs', 'chokidar', 'fsevents', ]; const getNewConfig = (): Partial<NextConfig> => { let config: Partial<NextConfig> = {}; if (isGteNext15) { config = { ...config, serverExternalPackages, }; } if (isGteNext13 && !isGteNext15) { config = { ...config, experimental: { ...(config?.experimental ?? {}), serverComponentsExternalPackages: serverExternalPackages, }, }; } if (isTurbopackEnabled) { if (isGteNext15 && isTurbopackStable) { config = { ...config, turbopack: turboConfig, }; } else { config = { ...config, experimental: { ...(config?.experimental ?? {}), // @ts-ignore exist in next@14 turbo: turboConfig, }, }; } } else { config = { ...config, webpack: (config: WebpackParams['0'], options: WebpackParams[1]) => { // Only add Intlayer plugin on server side (node runtime) const { isServer, nextRuntime } = options; // If the user has defined their own webpack config, call it if (typeof nextConfig.webpack === 'function') { config = nextConfig.webpack(config, options); } // Rspack set external as false by default // Overwrite it to allow pushing the desired externals if (config.externals === false) { config.externals = []; } // Mark these modules as externals config.externals.push({ esbuild: 'esbuild', module: 'module', fs: 'fs', chokidar: 'chokidar', fsevents: 'fsevents', }); // Use `node-loader` for any `.node` files config.module.rules.push({ test: /\.node$/, loader: 'node-loader', }); // Always alias on the server (node/edge) for stability. // On the client, alias only when not using live sync. config.resolve.alias = { ...config.resolve.alias, ...getAlias({ configuration: intlayerConfig, formatter: (value: string) => resolve(value), // get absolute path }), }; // Activate watch mode webpack plugin if (isDevCommand && isServer && nextRuntime === 'nodejs') { // Optional as rspack not support plugin yet config.plugins.push(new IntlayerPlugin(intlayerConfig)); } return config; }, }; } return config; }; const pruneConfig: Partial<NextConfig> = getPruneConfig( intlayerConfig, isBuildCommand, isTurbopackEnabled ?? false ); const intlayerNextConfig: Partial<NextConfig> = defu( getNewConfig(), pruneConfig ); // Merge the new config with the user's config const result = defu(intlayerNextConfig, nextConfig) as NextConfig & T; return result; }; /** * A Next.js plugin that adds the intlayer configuration to the webpack configuration * and sets the environment variables * * Usage: * * ```ts * // next.config.js * export default withIntlayer(nextConfig) * ``` * * > Node withIntlayer is a promise function. Use withIntlayerSync instead if you want to use it synchronously. * > Using the promise allows to prepare the intlayer dictionaries before the build starts. * */ export const withIntlayer = async <T extends Partial<NextConfig>>( nextConfig: T | Promise<T> = {} as T, configOptions?: WithIntlayerOptions ): Promise<NextConfig & T> => { const { isBuildCommand, isDevCommand } = getCommandsEvent(); const intlayerConfig = getConfiguration(configOptions); const { mode } = intlayerConfig.build; // Only call prepareIntlayer during `dev` or `build` (not during `start`) // If prod: clean and rebuild once // If dev: rebuild only once if it's more than 1 hour since last rebuild if (isDevCommand || isBuildCommand || mode === 'auto') { // prepareIntlayer use runOnce to ensure to run only once because will run twice on client and server side otherwise await prepareIntlayer(intlayerConfig, { clean: isBuildCommand, cacheTimeoutMs: isBuildCommand ? 1000 * 30 // 30 seconds for build (to ensure to rebuild all dictionaries) : 1000 * 60 * 60, // 1 hour for dev (default cache timeout) }); } const nextConfigResolved = await nextConfig; return withIntlayerSync(nextConfigResolved, configOptions); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server