UNPKG

@farmfe/core

Version:

Farm is a extremely fast web build tool written in Rust. Farm can start a project in milliseconds and perform HMR within 10ms, making it much faster than similar tools like webpack and vite.

698 lines 29.5 kB
import crypto from 'node:crypto'; import fs from 'node:fs'; import module from 'node:module'; import path, { isAbsolute, join } from 'node:path'; import { pathToFileURL } from 'node:url'; import { bindingPath } from '../../binding/index.js'; import { getSortedPlugins, handleVitePlugins, resolveAsyncPlugins, resolveConfigHook, resolveConfigResolvedHook, resolveFarmPlugins } from '../plugin/index.js'; import { Server } from '../server/index.js'; import { Logger, bold, clearScreen, colors, getAliasEntries, green, isArray, isEmptyObject, isNodeEnv, isObject, isWindows, normalizeBasePath, normalizePath, transformAliasWithVite } from '../utils/index.js'; import { traceDependencies } from '../utils/trace-dependencies.js'; import { __FARM_GLOBAL__ } from './_global.js'; import { getExistsEnvFiles, loadEnv, setProcessEnv } from './env.js'; import { getValidPublicPath, normalizeOutput } from './normalize-config/normalize-output.js'; import { normalizePersistentCache } from './normalize-config/normalize-persistent-cache.js'; import { parseUserConfig } from './schema.js'; import { externalAdapter } from '../plugin/js/external-adapter.js'; import { convertErrorMessage } from '../utils/error.js'; import merge from '../utils/merge.js'; import { CUSTOM_KEYS, DEFAULT_CONFIG_NAMES, FARM_DEFAULT_NAMESPACE } from './constants.js'; import { mergeConfig, mergeFarmCliConfig } from './mergeConfig.js'; import { normalizeAsset } from './normalize-config/normalize-asset.js'; import { normalizeCss } from './normalize-config/normalize-css.js'; import { normalizeExternal } from './normalize-config/normalize-external.js'; import { normalizeResolve } from './normalize-config/normalize-resolve.js'; export * from './types.js'; export function defineFarmConfig(config) { return config; } async function getDefaultConfig(config, inlineOptions, mode, logger) { logger = logger ?? new Logger(); const resolvedUserConfig = await resolveMergedUserConfig(config, undefined, inlineOptions.mode ?? mode, logger); resolvedUserConfig.server = normalizeDevServerConfig(inlineOptions.server, mode); resolvedUserConfig.compilation = await normalizeUserCompilationConfig(resolvedUserConfig, config, logger, mode, true); resolvedUserConfig.root = resolvedUserConfig.compilation.root; resolvedUserConfig.jsPlugins = []; resolvedUserConfig.rustPlugins = []; return resolvedUserConfig; } async function handleServerPortConflict(resolvedUserConfig, logger, mode) { // check port availability: auto increment the port if a conflict occurs try { mode !== 'production' && (await Server.resolvePortConflict(resolvedUserConfig.server, logger)); // eslint-disable-next-line no-empty } catch { } } /** * Resolve and load user config from the specified path * @param configPath */ export async function resolveConfig(inlineOptions = {}, mode, logger, isHandleServerPortConflict = true) { // Clear the console according to the cli command checkClearScreen(inlineOptions); logger = logger ?? new Logger(); inlineOptions.mode = inlineOptions.mode ?? mode; // configPath may be file or directory let { configPath } = inlineOptions; let rawConfig = mergeFarmCliConfig(inlineOptions, {}); // if the config file can not found, just merge cli options and return default if (configPath) { if (!path.isAbsolute(configPath)) { throw new Error('configPath must be an absolute path'); } const loadedUserConfig = await loadConfigFile(configPath, inlineOptions, mode, logger); if (loadedUserConfig) { configPath = loadedUserConfig.configFilePath; rawConfig = mergeConfig(rawConfig, loadedUserConfig.config); } rawConfig.compilation.mode = loadedUserConfig?.config?.compilation?.mode ?? mode; } else { mergeConfig(rawConfig, await getDefaultConfig(rawConfig, inlineOptions, mode, logger)); } const { config: userConfig, configFilePath } = { configFilePath: configPath, config: rawConfig }; const { jsPlugins, vitePlugins, rustPlugins, vitePluginAdapters } = await resolvePlugins(userConfig, logger, mode); const sortFarmJsPlugins = getSortedPlugins([ ...jsPlugins, ...vitePluginAdapters, externalAdapter() ]); const config = await resolveConfigHook(userConfig, sortFarmJsPlugins); const mergedUserConfig = mergeFarmCliConfig(inlineOptions, config); const resolvedUserConfig = await resolveMergedUserConfig(mergedUserConfig, configFilePath, inlineOptions.mode ?? mode, logger); // normalize server config first cause it may be used in normalizeUserCompilationConfig resolvedUserConfig.server = normalizeDevServerConfig(resolvedUserConfig.server, mode); if (isHandleServerPortConflict) { await handleServerPortConflict(resolvedUserConfig, logger, mode); } resolvedUserConfig.compilation = await normalizeUserCompilationConfig(resolvedUserConfig, mergedUserConfig, logger, mode); // normalize root path resolvedUserConfig.root = normalizeBasePath(resolvedUserConfig.compilation.root); resolvedUserConfig.jsPlugins = sortFarmJsPlugins; resolvedUserConfig.rustPlugins = rustPlugins; // Temporarily dealing with alias objects and arrays in js will be unified in rust in the future.] if (vitePlugins.length) { resolvedUserConfig.compilation.resolve.alias = getAliasEntries(resolvedUserConfig.compilation.resolve.alias); } await resolveConfigResolvedHook(resolvedUserConfig, sortFarmJsPlugins); // Fix: Await the Promise<void> and pass the resolved value to the function. // TODO Temporarily solve the problem of alias adaptation to vite if (resolvedUserConfig.compilation?.resolve?.alias && vitePlugins.length) { resolvedUserConfig.compilation.resolve.alias = transformAliasWithVite(resolvedUserConfig.compilation.resolve.alias); } return resolvedUserConfig; } /** * Normalize user config and transform it to rust compiler compatible config * * * ResolvedUserConfig is a parameter passed to rust Compiler, * and ResolvedUserConfig is generated from UserConfig. * When UserConfig is different from ResolvedUserConfig, * a legal value should be given to the ResolvedUserConfig field here, * and converted from UserConfig in the subsequent process. * * @param config * @returns resolved config that parsed to rust compiler */ export async function normalizeUserCompilationConfig(resolvedUserConfig, userConfig, logger, mode = 'development', isDefault = false) { const { compilation, root = process.cwd(), clearScreen } = resolvedUserConfig; // resolve root path const resolvedRootPath = normalizePath(root); resolvedUserConfig.root = resolvedRootPath; if (!userConfig.compilation) { userConfig.compilation = {}; } // if normalize default config, skip check input option const inputIndexConfig = !isDefault ? checkCompilationInputValue(userConfig, logger) : {}; const resolvedCompilation = merge({}, DEFAULT_COMPILATION_OPTIONS, { input: inputIndexConfig, root: resolvedRootPath }, { clearScreen }, compilation); const isProduction = mode === 'production'; const isDevelopment = mode === 'development'; resolvedCompilation.mode = resolvedCompilation.mode ?? mode; resolvedCompilation.coreLibPath = bindingPath; normalizeOutput(resolvedCompilation, isProduction, logger); normalizeExternal(userConfig, resolvedCompilation); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore do not check type for this internal option if (!resolvedCompilation.assets?.publicDir) { if (!resolvedCompilation.assets) { resolvedCompilation.assets = {}; } const userPublicDir = resolvedUserConfig.publicDir ? resolvedUserConfig.publicDir : join(resolvedCompilation.root, 'public'); if (isAbsolute(userPublicDir)) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore do not check type for this internal option resolvedCompilation.assets.publicDir = userPublicDir; } else { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore do not check type for this internal option resolvedCompilation.assets.publicDir = join(resolvedCompilation.root, userPublicDir); } } resolvedCompilation.define = Object.assign({ // skip self define ['FARM' + '_PROCESS_ENV']: resolvedUserConfig.env, FARM_RUNTIME_TARGET_ENV: JSON.stringify(resolvedCompilation.output?.targetEnv) }, resolvedCompilation?.define, // for node target, we should not define process.env.NODE_ENV resolvedCompilation.output?.targetEnv === 'node' ? {} : Object.keys(resolvedUserConfig.env || {}).reduce((env, key) => { env[`$__farm_regex:(global(This)?\\.)?process\\.env\\.${key}`] = JSON.stringify(resolvedUserConfig.env[key]); return env; }, {})); const require = module.createRequire(import.meta.url); const hmrClientPluginPath = require.resolve('@farmfe/runtime-plugin-hmr'); const ImportMetaPluginPath = require.resolve('@farmfe/runtime-plugin-import-meta'); if (!resolvedCompilation.runtime) { resolvedCompilation.runtime = { path: require.resolve('@farmfe/runtime'), plugins: [] }; } if (!resolvedCompilation.runtime.path) { resolvedCompilation.runtime.path = require.resolve('@farmfe/runtime'); } if (!resolvedCompilation.runtime.swcHelpersPath) { resolvedCompilation.runtime.swcHelpersPath = path.dirname(require.resolve('@swc/helpers/package.json')); } if (!resolvedCompilation.runtime.plugins) { resolvedCompilation.runtime.plugins = []; } else { // make sure all plugin paths are absolute resolvedCompilation.runtime.plugins = resolvedCompilation.runtime.plugins.map((plugin) => { if (!path.isAbsolute(plugin)) { if (!plugin.startsWith('.')) { // resolve plugin from node_modules return require.resolve(plugin); } else { return path.resolve(resolvedRootPath, plugin); } } return plugin; }); } // set namespace to package.json name field's hash if (!resolvedCompilation.runtime.namespace) { // read package.json name field const packageJsonPath = path.resolve(resolvedRootPath, 'package.json'); const packageJsonExists = fs.existsSync(packageJsonPath); const namespaceName = packageJsonExists ? JSON.parse(fs.readFileSync(packageJsonPath, { encoding: 'utf-8' })) ?.name ?? FARM_DEFAULT_NAMESPACE : FARM_DEFAULT_NAMESPACE; resolvedCompilation.runtime.namespace = crypto .createHash('md5') .update(namespaceName) .digest('hex'); } if (isProduction) { resolvedCompilation.lazyCompilation = false; } else if (resolvedCompilation.lazyCompilation === undefined) { if (isDevelopment) { resolvedCompilation.lazyCompilation = true; } else { resolvedCompilation.lazyCompilation = false; } } if (resolvedCompilation.mode === undefined) { resolvedCompilation.mode = mode; } setProcessEnv(resolvedCompilation.mode); // TODO add targetEnv `lib-browser` and `lib-node` support if (resolvedCompilation.output.targetEnv !== 'node' && isArray(resolvedCompilation.runtime.plugins) && resolvedUserConfig.server?.hmr && !resolvedCompilation.runtime.plugins.includes(hmrClientPluginPath)) { const publicPath = getValidPublicPath(resolvedCompilation.output.publicPath); const serverOptions = resolvedUserConfig.server; const defineHmrPath = normalizeBasePath(path.join(publicPath, resolvedUserConfig.server.hmr.path)); resolvedCompilation.runtime.plugins.push(hmrClientPluginPath); // TODO optimize get hmr logic resolvedCompilation.define.FARM_HMR_PORT = String((serverOptions.hmr.port || undefined) ?? serverOptions.port ?? DEFAULT_DEV_SERVER_OPTIONS.port); resolvedCompilation.define.FARM_HMR_HOST = JSON.stringify(resolvedUserConfig.server.hmr.host); resolvedCompilation.define.FARM_HMR_PROTOCOL = JSON.stringify(resolvedUserConfig.server.hmr.protocol); resolvedCompilation.define.FARM_HMR_PATH = JSON.stringify(defineHmrPath); } if (isArray(resolvedCompilation.runtime.plugins) && !resolvedCompilation.runtime.plugins.includes(ImportMetaPluginPath)) { resolvedCompilation.runtime.plugins.push(ImportMetaPluginPath); } // we should not deep merge compilation.input if (compilation?.input && Object.keys(compilation.input).length > 0) { // Add ./ if userConfig.input is relative path without ./ const input = {}; for (const [key, value] of Object.entries(compilation.input)) { if (!value && (value ?? true)) continue; if (!path.isAbsolute(value) && !value.startsWith('./')) { input[key] = `./${value}`; } else { input[key] = value; } } resolvedCompilation.input = input; } if (resolvedCompilation.treeShaking === undefined) { if (isProduction) { resolvedCompilation.treeShaking = true; } else { resolvedCompilation.treeShaking = false; } } if (resolvedCompilation.script?.plugins?.length) { logger.info(`Swc plugins are configured, note that Farm uses ${colors.yellow('swc_core v0.96')}, please make sure the plugin is ${colors.green('compatible')} with swc_core ${colors.yellow('swc_core v0.96')}. Otherwise, it may exit unexpectedly.`); } // lazyCompilation should be disabled in production mode // so, it only happens in development mode // https://github.com/farm-fe/farm/issues/962 if (resolvedCompilation.treeShaking && resolvedCompilation.lazyCompilation) { logger.error('treeShaking option is not supported in lazyCompilation mode, lazyCompilation will be disabled.'); resolvedCompilation.lazyCompilation = false; } if (resolvedCompilation.minify === undefined) { if (isProduction) { resolvedCompilation.minify = true; } else { resolvedCompilation.minify = false; } } if (resolvedCompilation.presetEnv === undefined) { if (isProduction) { resolvedCompilation.presetEnv = true; } else { resolvedCompilation.presetEnv = false; } } // setting the custom configuration resolvedCompilation.custom = { ...(resolvedCompilation.custom || {}), [CUSTOM_KEYS.runtime_isolate]: `${!!resolvedCompilation.runtime.isolate}` }; // Auto enable decorator by default when `script.decorators` is enabled if (resolvedCompilation.script?.decorators !== undefined) if (resolvedCompilation.script.parser === undefined) { resolvedCompilation.script.parser = { esConfig: { decorators: true }, tsConfig: { decorators: true } }; } else { if (resolvedCompilation.script.parser.esConfig !== undefined) resolvedCompilation.script.parser.esConfig.decorators = true; else resolvedCompilation.script.parser.esConfig = { decorators: true }; if (resolvedCompilation.script.parser.tsConfig !== undefined) resolvedCompilation.script.parser.tsConfig.decorators = true; else userConfig.compilation.script.parser.tsConfig = { decorators: true }; } // normalize persistent cache at last await normalizePersistentCache(resolvedCompilation, resolvedUserConfig, logger); normalizeResolve(userConfig, resolvedCompilation); normalizeCss(userConfig, resolvedCompilation); normalizeAsset(userConfig, resolvedCompilation); return resolvedCompilation; } export const DEFAULT_HMR_OPTIONS = { host: true, port: (process.env.FARM_DEFAULT_HMR_PORT && Number(process.env.FARM_DEFAULT_HMR_PORT)) ?? undefined, path: '/__hmr', overlay: true, protocol: '', watchOptions: {} }; export const DEFAULT_DEV_SERVER_OPTIONS = { headers: {}, port: (process.env.FARM_DEFAULT_SERVER_PORT && Number(process.env.FARM_DEFAULT_SERVER_PORT)) || 9000, https: undefined, protocol: 'http', hostname: { name: 'localhost', host: undefined }, host: true, proxy: {}, hmr: DEFAULT_HMR_OPTIONS, open: false, strictPort: false, cors: false, spa: true, middlewares: [], writeToDisk: false }; export const DEFAULT_COMPILATION_OPTIONS = { output: { path: './dist' }, sourcemap: true, resolve: { extensions: [ 'tsx', 'mts', 'cts', 'ts', 'jsx', 'mjs', 'js', 'cjs', 'json', 'html', 'css', 'mts', 'cts' ] } }; // eslint-disable-next-line @typescript-eslint/no-explicit-any function tryAsFileRead(value) { if (typeof value === 'string' && fs.existsSync(value)) { return fs.readFileSync(path.resolve(value.toString())); } return value; } export function normalizeDevServerConfig(options, mode) { const { host, port, hmr: hmrConfig, https } = options || {}; const isProductionMode = mode === 'production'; const hmr = isProductionMode || hmrConfig === false ? false : merge({}, DEFAULT_HMR_OPTIONS, { host: host ?? DEFAULT_DEV_SERVER_OPTIONS.host, port: port ?? DEFAULT_DEV_SERVER_OPTIONS.port }, hmrConfig === true ? {} : hmrConfig); return merge({}, DEFAULT_DEV_SERVER_OPTIONS, options, { hmr, https: https ? { ...https, ca: tryAsFileRead(options.https.ca), cert: tryAsFileRead(options.https.cert), key: tryAsFileRead(options.https.key), pfx: tryAsFileRead(options.https.pfx) } : undefined }); } const formatFromExt = { cjs: 'cjs', mjs: 'esm', cts: 'cjs', mts: 'esm', js: 'esm' }; const formatToExt = { cjs: 'cjs', esm: 'mjs' }; async function readConfigFile(inlineOptions, configFilePath, logger, mode = 'development') { if (fs.existsSync(configFilePath)) { !__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ && logger.info(`Using config file at ${bold(green(configFilePath))}`); const format = process.env.FARM_CONFIG_FORMAT ? process.env.FARM_CONFIG_FORMAT === 'cjs' ? 'cjs' : 'esm' : formatFromExt[path.extname(configFilePath).slice(1)] ?? 'esm'; // we need transform all type farm.config with __dirname and __filename const Compiler = (await import('../compiler/index.js')).Compiler; const outputPath = path.join(path.dirname(configFilePath), 'node_modules', '.farm'); const fileName = `farm.config.bundle-${Date.now()}-${Math.random() .toString(16) .split('.') .join('')}.${formatToExt[format]}`; const tsDefaultUserConfig = { root: inlineOptions.root, compilation: { input: { [fileName]: configFilePath }, output: { entryFilename: '[entryName]', path: outputPath, format, targetEnv: 'library-node' }, external: [ ...(process.env.FARM_CONFIG_FULL_BUNDLE ? [] : ['!^(\\./|\\.\\./|[A-Za-z]:\\\\|/).*']), '^@farmfe/core$' ], partialBundling: { enforceResources: [ { name: fileName, test: ['.+'] } ] }, watch: false, sourcemap: false, treeShaking: false, minify: false, presetEnv: false, lazyCompilation: false, persistentCache: false, progress: false } }; const tsDefaultResolvedUserConfig = await resolveMergedUserConfig(tsDefaultUserConfig, undefined, mode, logger); const normalizedConfig = await normalizeUserCompilationConfig(tsDefaultResolvedUserConfig, tsDefaultUserConfig, logger, mode); const replaceDirnamePlugin = await import('farm-plugin-replace-dirname').then((mod) => mod.default); const compiler = new Compiler({ config: normalizedConfig, jsPlugins: [], rustPlugins: [[replaceDirnamePlugin, '{}']] }, logger); const FARM_PROFILE = process.env.FARM_PROFILE; // disable FARM_PROFILE in farm_config if (FARM_PROFILE) { process.env.FARM_PROFILE = ''; } await compiler.compile(); if (FARM_PROFILE) { process.env.FARM_PROFILE = FARM_PROFILE; } compiler.writeResourcesToDisk(); const filePath = isWindows ? pathToFileURL(path.join(outputPath, fileName)) : path.join(outputPath, fileName); // Change to vm.module of node or loaders as far as it is stable const userConfig = (await import(filePath)).default; try { fs.unlink(filePath, () => void 0); // remove parent dir if empty const isEmpty = fs.readdirSync(outputPath).length === 0; if (isEmpty) { fs.rmSync(outputPath); } } catch { /** do nothing */ } const configEnv = { mode: inlineOptions.mode ?? process.env.NODE_ENV }; const config = await (typeof userConfig === 'function' ? userConfig(configEnv) : userConfig); if (!config.root) { config.root = inlineOptions.root; } if (!isObject(config)) { throw new Error(`config must export or return an object.`); } return config; } } export function normalizePublicDir(root, userPublicDir) { const publicDir = userPublicDir ?? 'public'; const absPublicDirPath = path.isAbsolute(publicDir) ? publicDir : path.join(root, publicDir); return absPublicDirPath; } export function checkClearScreen(inlineConfig) { if (inlineConfig?.clearScreen && !__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__) { clearScreen(); } } export async function resolveMergedUserConfig(mergedUserConfig, configFilePath, mode, logger = new Logger()) { const resolvedUserConfig = { ...mergedUserConfig, compilation: { ...mergedUserConfig.compilation, external: [] } }; // set internal config resolvedUserConfig.envMode = mode; if (configFilePath) { const dependencies = await traceDependencies(configFilePath, logger); dependencies.sort(); resolvedUserConfig.configFileDependencies = dependencies; resolvedUserConfig.configFilePath = configFilePath; } const resolvedRootPath = resolvedUserConfig.root ?? process.cwd(); const resolvedEnvPath = resolvedUserConfig.envDir ? resolvedUserConfig.envDir : resolvedRootPath; const userEnv = loadEnv(resolvedUserConfig.envMode ?? mode, resolvedEnvPath, resolvedUserConfig.envPrefix); const existsEnvFiles = getExistsEnvFiles(resolvedUserConfig.envMode ?? mode, resolvedEnvPath); resolvedUserConfig.envFiles = [ ...(Array.isArray(resolvedUserConfig.envFiles) ? resolvedUserConfig.envFiles : []), ...existsEnvFiles ]; resolvedUserConfig.env = { ...userEnv, NODE_ENV: mergedUserConfig.compilation.mode ?? mode, mode: mode }; return resolvedUserConfig; } /** * Load config file from the specified path and return the config and config file path * @param configPath the config path, could be a directory or a file * @param logger custom logger * @returns loaded config and config file path */ export async function loadConfigFile(configPath, inlineOptions, mode = 'development', logger = new Logger()) { // if configPath points to a directory, try to find a config file in it using default config try { const configFilePath = await getConfigFilePath(configPath); if (configFilePath) { const config = await readConfigFile(inlineOptions, configFilePath, logger, mode); return { config: config && parseUserConfig(config), configFilePath: configFilePath }; } } catch (error) { // In this place, the original use of throw caused emit to the outermost catch // callback, causing the code not to execute. If the internal catch compiler's own // throw error can solve this problem, it will not continue to affect the execution of // external code. We just need to return the default config. const errorMessage = convertErrorMessage(error); const stackTrace = error.code === 'GenericFailure' ? '' : `\n${error.stack}`; if (inlineOptions.mode === 'production') { logger.error(`Failed to load config file: ${errorMessage} \n${stackTrace}`, { exit: true }); } const potentialSolution = 'Potential solutions: \n1. Try set `FARM_CONFIG_FORMAT=cjs`(default to esm)\n2. Try set `FARM_CONFIG_FULL_BUNDLE=1`'; throw new Error(`Failed to load farm config file: ${errorMessage}. \n ${potentialSolution} \n ${error.stack}`); } } function checkCompilationInputValue(userConfig, logger) { const { compilation } = userConfig; const targetEnv = compilation?.output?.targetEnv; const isTargetNode = isNodeEnv(targetEnv); const defaultHtmlPath = './index.html'; let inputIndexConfig = { index: '' }; let errorMessage = ''; // Check if input is specified if (!isEmptyObject(compilation?.input)) { inputIndexConfig = compilation?.input; } else { if (isTargetNode) { // If input is not specified, try to find index.js or index.ts const entryFiles = ['./index.js', './index.ts']; for (const entryFile of entryFiles) { try { if (fs.statSync(path.resolve(userConfig?.root, entryFile))) { inputIndexConfig = { index: entryFile }; break; } } catch (error) { errorMessage = error.stack; } } } else { try { if (fs.statSync(path.resolve(userConfig?.root, defaultHtmlPath))) { inputIndexConfig = { index: defaultHtmlPath }; } } catch (error) { errorMessage = error.stack; } } // If no index file is found, throw an error if (!inputIndexConfig.index) { logger.error(`Build failed due to errors: Can not resolve ${isTargetNode ? 'index.js or index.ts' : 'index.html'} from ${userConfig.root}. \n${errorMessage}`, { exit: true }); } } return inputIndexConfig; } export async function getConfigFilePath(configPath) { if (fs.statSync(configPath).isDirectory()) { for (const name of DEFAULT_CONFIG_NAMES) { const resolvedPath = path.join(configPath, name); const isFile = fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isFile(); if (isFile) { return resolvedPath; } } } else if (fs.statSync(configPath).isFile()) { return configPath; } return undefined; } export async function resolvePlugins(userConfig, logger, mode) { const { jsPlugins, rustPlugins } = await resolveFarmPlugins(userConfig); const rawJsPlugins = (await resolveAsyncPlugins(jsPlugins || [])).filter(Boolean); let vitePluginAdapters = []; const vitePlugins = (userConfig?.vitePlugins ?? []).filter(Boolean); if (vitePlugins.length) { vitePluginAdapters = await handleVitePlugins(vitePlugins, userConfig, logger, mode); } return { jsPlugins: rawJsPlugins, vitePlugins, rustPlugins, vitePluginAdapters }; } //# sourceMappingURL=index.js.map