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.

286 lines 11.6 kB
export * from './compiler/index.js'; export * from './config/index.js'; export * from './server/index.js'; export * from './plugin/type.js'; export * from './utils/index.js'; import { statSync } from 'node:fs'; import fs from 'node:fs/promises'; import path from 'node:path'; import fse from 'fs-extra'; import { Compiler } from './compiler/index.js'; import { loadEnv, setProcessEnv } from './config/env.js'; import { checkClearScreen, getConfigFilePath, normalizePublicDir, resolveConfig } from './config/index.js'; import { Server } from './server/index.js'; import { compilerHandler } from './utils/build.js'; import { colors } from './utils/color.js'; import { Logger } from './utils/logger.js'; import { FileWatcher } from './watcher/index.js'; import { __FARM_GLOBAL__ } from './config/_global.js'; import { logError } from './server/error.js'; import { lazyCompilation } from './server/middlewares/lazy-compilation.js'; import { ConfigWatcher } from './watcher/config-watcher.js'; import { resolveHostname } from './utils/http.js'; export async function start(inlineConfig) { inlineConfig = inlineConfig ?? {}; const logger = inlineConfig.logger ?? new Logger(); setProcessEnv('development'); try { const resolvedUserConfig = await resolveConfig(inlineConfig, 'development', logger); if (resolvedUserConfig.compilation.lazyCompilation && typeof resolvedUserConfig.server?.host === 'string') { setLazyCompilationDefine(resolvedUserConfig); } const compiler = await createCompiler(resolvedUserConfig, logger); const devServer = await createDevServer(compiler, resolvedUserConfig, logger); await devServer.listen(); } catch (error) { logger.error('Failed to start the server', { exit: true, error }); } } export async function build(inlineConfig) { inlineConfig = inlineConfig ?? {}; const logger = inlineConfig.logger ?? new Logger(); setProcessEnv('production'); const resolvedUserConfig = await resolveConfig(inlineConfig, 'production', logger, false); try { await createBundleHandler(resolvedUserConfig, logger); // copy resources under publicDir to output.path await copyPublicDirectory(resolvedUserConfig, logger); } catch (err) { logger.error(`Failed to build: ${err}`, { exit: true }); } } export async function preview(inlineConfig) { inlineConfig = inlineConfig ?? {}; const logger = inlineConfig.logger ?? new Logger(); const resolvedUserConfig = await resolveConfig(inlineConfig, 'production', logger); const { root, output } = resolvedUserConfig.compilation; const distDir = path.resolve(root, output.path); try { statSync(distDir); } catch (err) { if (err.code === 'ENOENT') { throw new Error(`The directory "${distDir}" does not exist. Did you build your project?`); } } // reusing port conflict check from DevServer const serverConfig = { ...resolvedUserConfig.server, host: inlineConfig.host ?? true, port: inlineConfig.port ?? (Number(process.env.FARM_DEFAULT_SERVER_PORT) || 1911) }; await Server.resolvePortConflict(serverConfig, logger); const port = serverConfig.port; const host = serverConfig.host; const previewOptions = { ...serverConfig, distDir, output: { path: output.path, publicPath: output.publicPath }, port, host }; const server = new Server({ logger }); server.createPreviewServer(previewOptions); } export async function watch(inlineConfig) { var _a; inlineConfig = inlineConfig ?? {}; const logger = inlineConfig.logger ?? new Logger(); setProcessEnv('development'); inlineConfig.server ?? (inlineConfig.server = {}); (_a = inlineConfig.server).hmr ?? (_a.hmr = false); const resolvedUserConfig = await resolveConfig(inlineConfig, 'development', logger, false); const lazyEnabled = resolvedUserConfig.compilation?.lazyCompilation; if (lazyEnabled) { setLazyCompilationDefine(resolvedUserConfig); } const compilerFileWatcher = await createBundleHandler(resolvedUserConfig, logger, true); let devServer; // create dev server for lazy compilation if (lazyEnabled) { devServer = new Server({ logger, compiler: compilerFileWatcher.serverOrCompiler }); devServer.createServer(resolvedUserConfig.server); devServer.applyMiddlewares([lazyCompilation]); await devServer.startServer(resolvedUserConfig.server); } async function handleFileChange(files) { logFileChanges(files, resolvedUserConfig.root, logger); try { farmWatcher.close(); if (lazyEnabled && devServer) { devServer.close(); } __FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ = true; compilerFileWatcher?.close(); await watch(inlineConfig); } catch (error) { logger.error(`Error restarting the watcher: ${error.message}`); } } const farmWatcher = new ConfigWatcher(resolvedUserConfig).watch(handleFileChange); } export async function clean(rootPath, recursive) { // TODO After optimizing the reading of config, put the clean method into compiler const logger = new Logger(); const nodeModulesFolders = recursive ? await findNodeModulesRecursively(rootPath) : [path.join(rootPath, 'node_modules')]; await Promise.all(nodeModulesFolders.map(async (nodeModulesPath) => { // TODO Bug .farm cacheDir folder not right const farmFolderPath = path.join(nodeModulesPath, '.farm'); try { const stats = await fs.stat(farmFolderPath); if (stats.isDirectory()) { await fs.rm(farmFolderPath, { recursive: true, force: true }); // TODO optimize nodeModulePath path e.g: /Users/xxx/node_modules/.farm/cache logger.info(`Cache cleaned at ${colors.bold(colors.green(nodeModulesPath))}`); } } catch (error) { if (error.code === 'ENOENT') { logger.warn(`No cached files found in ${colors.bold(colors.green(nodeModulesPath))}`); } else { logger.error(`Error cleaning cache in ${colors.bold(colors.green(nodeModulesPath))}: ${error.message}`); } } })); } async function findNodeModulesRecursively(rootPath) { const result = []; async function traverse(currentPath) { const items = await fs.readdir(currentPath); for (const item of items) { const fullPath = path.join(currentPath, item); const stats = await fs.stat(fullPath); if (stats.isDirectory()) { if (item === 'node_modules') { result.push(fullPath); } else { await traverse(fullPath); } } } } await traverse(rootPath); return result; } export async function createBundleHandler(resolvedUserConfig, logger, watchMode = false) { const compiler = await createCompiler(resolvedUserConfig, logger); await compilerHandler(async () => { if (resolvedUserConfig.compilation?.output?.clean) { compiler.removeOutputPathDir(); } try { await compiler.compile(); } catch (err) { throw new Error(logError(err)); } compiler.writeResourcesToDisk(); }, resolvedUserConfig, logger); if (resolvedUserConfig.compilation?.watch || watchMode) { const watcher = new FileWatcher(compiler, resolvedUserConfig, logger); await watcher.watch(); return watcher; } } export async function createCompiler(resolvedUserConfig, logger) { const { jsPlugins, rustPlugins, compilation: compilationConfig } = resolvedUserConfig; const compiler = new Compiler({ config: compilationConfig, jsPlugins, rustPlugins }, logger); for (const plugin of jsPlugins) { await plugin.configureCompiler?.(compiler); } return compiler; } async function copyPublicDirectory(resolvedUserConfig, logger) { const absPublicDirPath = normalizePublicDir(resolvedUserConfig.root, resolvedUserConfig.publicDir); try { if (await fse.pathExists(absPublicDirPath)) { const files = await fse.readdir(absPublicDirPath); const outputPath = resolvedUserConfig.compilation.output.path; for (const file of files) { const publicFile = path.join(absPublicDirPath, file); const destFile = path.join(outputPath, file); if (await fse.pathExists(destFile)) { continue; } await fse.copy(publicFile, destFile); } logger.info(`Public directory resources copied ${colors.bold(colors.green('successfully'))}.`); } } catch (error) { logger.error(`Error copying public directory: ${error.message}`); } } export async function createDevServer(compiler, resolvedUserConfig, logger) { const server = new Server({ compiler, logger }); await server.createDevServer(resolvedUserConfig.server); await createFileWatcher(server, resolvedUserConfig, logger); // call configureDevServer hook after both server and watcher are ready resolvedUserConfig.jsPlugins.forEach((plugin) => plugin.configureDevServer?.(server)); return server; } export async function createFileWatcher(devServer, resolvedUserConfig, logger = new Logger()) { if (devServer.config.hmr && resolvedUserConfig.compilation.mode === 'production') { logger.error('HMR cannot be enabled in production mode.'); return; } if (!devServer.config.hmr) { return; } if (devServer.watcher) { return; } const fileWatcher = new FileWatcher(devServer, resolvedUserConfig, logger); devServer.watcher = fileWatcher; await fileWatcher.watch(); const configFilePath = await getConfigFilePath(resolvedUserConfig.root); const farmWatcher = new ConfigWatcher({ ...resolvedUserConfig, configFilePath }); farmWatcher.watch(async (files) => { checkClearScreen(resolvedUserConfig); devServer.restart(async () => { logFileChanges(files, resolvedUserConfig.root, logger); farmWatcher?.close(); await devServer.close(); __FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ = true; // TODO: resolvedUserConfig params type check await start(resolvedUserConfig); }); }); return fileWatcher; } export function logFileChanges(files, root, logger) { const changedFiles = files .map((file) => path.relative(root, file)) .join(', '); logger.info(colors.bold(colors.green(`${changedFiles} changed, server will restart.`))); } function setLazyCompilationDefine(resolvedUserConfig) { const hostname = resolveHostname(resolvedUserConfig.server.host); resolvedUserConfig.compilation.define = { ...(resolvedUserConfig.compilation.define ?? {}), FARM_LAZY_COMPILE_SERVER_URL: `${resolvedUserConfig.server.protocol || 'http'}://${hostname.host || 'localhost'}:${resolvedUserConfig.server.port}` }; } export { defineFarmConfig as defineConfig } from './config/index.js'; export { loadEnv }; //# sourceMappingURL=index.js.map