Farm RFC:
- Farm 核心架构设计
- Farm 运行时模块管理
- Farm 部分打包
cli
cli基本可以看作整个项目的入口,包括dev、build。
代码位于farm仓库的packages/cli目录下。
入口代码src/index.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import { cac } from 'cac'; const cli = cac('farm');
cli .option('-c, --config <file>', 'use specified config file') .option('-m, --mode <mode>', 'set env mode') .option('--base <path>', 'public base path') .option('--clearScreen', 'allow/disable clear screen when logging', { default: true });
cli .command( '[root]', 'Compile the project in dev mode and serve it with farm dev server' ) .alias('start') .option('-l, --lazy', 'lazyCompilation') .option('--host <host>', 'specify host') .option('--port <port>', 'specify port') .option('--open', 'open browser on server start') .option('--hmr', 'enable hot module replacement') .option('--cors', 'enable cors') .option('--strictPort', 'specified port is already in use, exit with error') .action( async ( root: string, options: FarmCLIServerOptions & GlobalFarmCLIOptions ) => { const { start } = await resolveCore(); handleAsyncOperationErrors( start(defaultOptions), 'Failed to start server' ); } );
|
可以看出主要用了cac来作为参数解析库,全部链式操作设置参数提示、回调,非常方便。
同理在下面设置一好build、watch等命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function preventExperimentalWarning() { const defaultEmit = process.emit; process.emit = function (...args: any[]) { if (args[1].name === 'ExperimentalWarning') { return undefined; } return defaultEmit.call(this, ...args); }; }
preventExperimentalWarning()
cli.help();
cli.version(version);
cli.parse();
|
core
index
core包是编译器的核心部分,看看index.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| 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';
export async function start( inlineConfig: FarmCLIOptions & UserConfig ): Promise<void> { const logger = inlineConfig.logger ?? new Logger(); setProcessEnv('development');
try { const resolvedUserConfig = await resolveConfig( inlineConfig, logger, 'development' );
const compiler = await createCompiler(resolvedUserConfig);
const devServer = await createDevServer( compiler, resolvedUserConfig, logger );
createFileWatcher(devServer, resolvedUserConfig, inlineConfig, logger); resolvedUserConfig.jsPlugins.forEach((plugin: JsPlugin) => plugin.configureDevServer?.(devServer) ); await devServer.listen(); } catch (error) { logger.error(`Failed to start the server: ${error.stack}`); } }
export async function build( inlineConfig: FarmCLIOptions & UserConfig ): Promise<void> { const logger = inlineConfig.logger ?? new Logger(); setProcessEnv('production'); const resolvedUserConfig = await resolveConfig( inlineConfig, logger, 'production' );
setProcessEnv(resolvedUserConfig.compilation.mode);
try { await createBundleHandler(resolvedUserConfig); await copyPublicDirectory(resolvedUserConfig, inlineConfig, logger); } catch (err) { console.log(err); } }
export async function createBundleHandler( resolvedUserConfig: ResolvedUserConfig, watchMode = false ) { const compiler = await createCompiler(resolvedUserConfig);
await compilerHandler(async () => { compiler.removeOutputPathDir(); await compiler.compile(); compiler.writeResourcesToDisk(); }, resolvedUserConfig);
if (resolvedUserConfig.compilation?.watch || watchMode) { const watcher = new FileWatcher(compiler, resolvedUserConfig); await watcher.watch(); return watcher; } }
|
comolier
先来看看compiler写了什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
| export class Compiler { private _bindingCompiler: BindingCompiler; private _updateQueue: UpdateQueueItem[] = []; private _onUpdateFinishQueue: (() => void | Promise<void>)[] = [];
public compiling = false;
private logger: ILogger;
constructor(public config: Config) { this.logger = new Logger(); this._bindingCompiler = new BindingCompiler(this.config); }
async compile() { if (this.compiling) { this.logger.error('Already compiling', { exit: true }); }
this.compiling = true; if (process.env.FARM_PROFILE) { this._bindingCompiler.compileSync(); } else { await this._bindingCompiler.compile(); } this.compiling = false; }
async update( paths: string[], sync = false, ignoreCompilingCheck = false ): Promise<JsUpdateResult> { let resolve: (res: JsUpdateResult) => void;
const promise = new Promise<JsUpdateResult>((r) => { resolve = r; });
if (this.compiling && !ignoreCompilingCheck) { this._updateQueue.push({ paths, resolve }); return promise; }
this.compiling = true; try { const res = this._bindingCompiler.update( paths, async () => { const next = this._updateQueue.shift();
if (next) { await this.update(next.paths, true, true).then(next.resolve); } else { this.compiling = false; for (const cb of this._onUpdateFinishQueue) { await cb(); } this._onUpdateFinishQueue = []; } }, sync );
return res as JsUpdateResult; } catch (e) { this.compiling = false; throw e; } }
writeResourcesToDisk(base = ''): void { const resources = this.resources(); const configOutputPath = this.config.config.output.path; const outputPath = path.isAbsolute(configOutputPath) ? configOutputPath : path.join(this.config.config.root, configOutputPath);
for (const [name, resource] of Object.entries(resources)) { const nameWithoutQuery = name.split('?')[0]; const nameWithoutHash = nameWithoutQuery.split('#')[0];
const filePath = path.join(outputPath, base, nameWithoutHash);
if (!existsSync(path.dirname(filePath))) { mkdirSync(path.dirname(filePath), { recursive: true }); }
writeFileSync(filePath, resource); }
this.callWriteResourcesHook(); }
callWriteResourcesHook() { for (const jsPlugin of this.config.jsPlugins ?? []) { (jsPlugin as JsPlugin).writeResources?.executor?.({ resourcesMap: this._bindingCompiler.resourcesMap() as Record< string, Resource >, config: this.config.config }); } }
compileSync() { if (this.compiling) { this.logger.error('Already compiling', { exit: true }); } this.compiling = true; this._bindingCompiler.compileSync(); this.compiling = false; }
}
|
config
config部分主要用来处理用户配置,这部分代码很多,只看上面用到的resolveConfig方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
| export async function resolveConfig( inlineOptions: FarmCLIOptions, logger: Logger, mode?: CompilationMode ): Promise<ResolvedUserConfig> { checkClearScreen(inlineOptions); inlineOptions.mode = inlineOptions.mode ?? mode;
const getDefaultConfig = async () => { const mergedUserConfig = mergeInlineCliOptions({}, inlineOptions);
const resolvedUserConfig = await resolveMergedUserConfig( mergedUserConfig, undefined, inlineOptions.mode ?? mode ); resolvedUserConfig.server = normalizeDevServerOptions({}, mode); resolvedUserConfig.compilation = await normalizeUserCompilationConfig( resolvedUserConfig, logger, mode ); resolvedUserConfig.root = resolvedUserConfig.compilation.root; resolvedUserConfig.jsPlugins = []; resolvedUserConfig.rustPlugins = []; return resolvedUserConfig; }; const { configPath } = inlineOptions; if (!configPath) { return getDefaultConfig(); }
if (!path.isAbsolute(configPath)) { throw new Error('configPath must be an absolute path'); }
const loadedUserConfig = await loadConfigFile( configPath, inlineOptions, logger );
if (!loadedUserConfig) { return getDefaultConfig(); }
const { config: userConfig, configFilePath } = loadedUserConfig;
const { jsPlugins, rustPlugins } = await resolveFarmPlugins(userConfig);
const rawJsPlugins = (await resolveAsyncPlugins(jsPlugins || [])).filter( Boolean );
let vitePluginAdapters: JsPlugin[] = []; const vitePlugins = (userConfig?.vitePlugins ?? []).filter(Boolean); if (vitePlugins.length) { vitePluginAdapters = await handleVitePlugins( vitePlugins, userConfig, logger ); }
const sortFarmJsPlugins = getSortedPlugins([ ...rawJsPlugins, ...vitePluginAdapters ]);
const config = await resolveConfigHook(userConfig, sortFarmJsPlugins);
const mergedUserConfig = mergeInlineCliOptions(config, inlineOptions);
const resolvedUserConfig = await resolveMergedUserConfig( mergedUserConfig, configFilePath, inlineOptions.mode ?? mode );
resolvedUserConfig.server = normalizeDevServerOptions( resolvedUserConfig.server, mode );
const targetWeb = !( userConfig.compilation?.output?.targetEnv === 'node' || mode === 'production' );
try { targetWeb && (await Server.resolvePortConflict(resolvedUserConfig.server, logger)); } catch {}
resolvedUserConfig.compilation = await normalizeUserCompilationConfig( resolvedUserConfig, logger, mode ); resolvedUserConfig.root = resolvedUserConfig.compilation.root; resolvedUserConfig.jsPlugins = sortFarmJsPlugins; resolvedUserConfig.rustPlugins = rustPlugins;
await resolveConfigResolvedHook(resolvedUserConfig, sortFarmJsPlugins);
return resolvedUserConfig; }
|
看似很长,实际上是因为配置文件涉及到的很多的配置,需要对配置项规范化、设置默认值操作。
server
使用koa2提供开发服务端,src/index.ts的start其中调用了该文件里的createDevServer:
1 2 3 4 5 6 7 8 9 10
| export async function createDevServer( compiler: Compiler, resolvedUserConfig: ResolvedUserConfig, logger: Logger ) { const server = new Server({ compiler, logger }); await server.createDevServer(resolvedUserConfig.server);
return server; }
|
然后看看src/server/index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
| interface ImplDevServer { createServer(options: UserServerConfig): void; createDevServer(options: UserServerConfig): void; createPreviewServer(options: UserServerConfig): void; listen(): Promise<void>; close(): Promise<void>; getCompiler(): Compiler; }
export class Server implements ImplDevServer { private _app: Koa; private restart_promise: Promise<void> | null = null; private compiler: Compiler | null; public logger: Logger;
ws: WsServer; config: NormalizedServerConfig & UserPreviewServerConfig; hmrEngine?: HmrEngine; server?: httpServer; publicDir?: string; publicPath?: string; resolvedUrls?: ServerUrls; watcher: FileWatcher;
constructor({ compiler = null, logger }: { compiler?: Compiler | null; logger: Logger; }) { this.compiler = compiler; this.logger = logger ?? new Logger();
this.initializeKoaServer();
if (!compiler) return;
this.publicDir = normalizePublicDir(compiler?.config.config.root);
this.publicPath = normalizePublicPath( compiler.config.config.output?.publicPath, logger, false ) || '/'; } private initializeKoaServer() { this._app = new Koa(); } public async createDevServer(options: NormalizedServerConfig) { if (!this.compiler) { throw new Error('DevServer requires a compiler for development mode.'); }
await this.createServer(options);
this.hmrEngine = new HmrEngine(this.compiler, this, this.logger);
this.createWebSocket();
this.invalidateVite();
this.applyServerMiddlewares(options.middlewares); } public async createServer(options: NormalizedServerConfig) { const { https, host } = options; const protocol = https ? 'https' : 'http';
const hostname = await resolveHostname(host);
this.config = { ...options, protocol, hostname };
if (https) { this.server = http2.createSecureServer( { ...https, allowHTTP1: true }, this._app.callback() ); } else { this.server = http.createServer(this._app.callback()); } } public createWebSocket() { if (!this.server) { throw new Error('Websocket requires a server.'); } this.ws = new WsServer(this.server, this.config, this.hmrEngine); } async listen(): Promise<void> { if (!this.server) { this.logger.error('HTTP server is not created yet'); return; } const { port, open, protocol, hostname } = this.config;
const start = Date.now(); await this.compile(); bootstrap(Date.now() - start, this.compiler.config); await this.startServer(this.config);
!__FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ && (await this.displayServerUrls());
if (open) { const publicPath = this.publicPath === '/' ? this.publicPath : `/${this.publicPath}`; const serverUrl = `${protocol}://${hostname.name}:${port}${publicPath}`; openBrowser(serverUrl); } } async startServer(serverOptions: UserServerConfig) { const { port, hostname } = serverOptions; const listen = promisify(this.server.listen).bind(this.server); try { await listen(port, hostname.host); } catch (error) { this.handleServerError(error, port, hostname.host); } } }
|
可以看到server部分代码还是和compiler部分紧密耦合在一起的,可以说server部分就是在compiler部分外面包一层服务器,方便调用编译。
HmrEngine
在创建DevServer的过程中,还初始化了HmrEngine,看看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
| export class HmrEngine { private _updateQueue: string[] = [];
private _compiler: Compiler; private _devServer: Server; private _onUpdates: ((result: JsUpdateResult) => void)[]; private _lastModifiedTimestamp: Map<string, string>;
constructor(compiler: Compiler, devServer: Server, private _logger: Logger) { this._compiler = compiler; this._devServer = devServer; this._lastModifiedTimestamp = new Map(); }
callUpdates(result: JsUpdateResult) { this._onUpdates?.forEach((cb) => cb(result)); }
onUpdateFinish(cb: (result: JsUpdateResult) => void) { if (!this._onUpdates) { this._onUpdates = []; } this._onUpdates.push(cb); }
recompileAndSendResult = async (): Promise<JsUpdateResult> => { const queue = [...this._updateQueue];
if (queue.length === 0) { return; }
let updatedFilesStr = queue .map((item) => { if (isAbsolute(item)) { return relative(this._compiler.config.config.root, item); } else { const resolvedPath = this._compiler.transformModulePath( this._compiler.config.config.root, item ); return relative(this._compiler.config.config.root, resolvedPath); } }) .join(', '); if (updatedFilesStr.length >= 100) { updatedFilesStr = updatedFilesStr.slice(0, 100) + `...(${queue.length} files)`; }
try { clearScreen(); const start = Date.now(); const result = await this._compiler.update(queue); this._logger.info( `${bold(cyan(updatedFilesStr))} updated in ${bold( green(`${Date.now() - start}ms`) )}` );
this._updateQueue = this._updateQueue.filter( (item) => !queue.includes(item) );
let dynamicResourcesMap: Record<string, Resource[]> = null;
if (result.dynamicResourcesMap) { for (const [key, value] of Object.entries(result.dynamicResourcesMap)) { if (!dynamicResourcesMap) { dynamicResourcesMap = {} as Record<string, Resource[]>; } dynamicResourcesMap[key] = value.map((r) => ({ path: r[0], type: r[1] as 'script' | 'link' })); } }
const resultStr = `{ added: [${result.added .map((r) => `'${r.replaceAll('\\', '\\\\')}'`) .join(', ')}], changed: [${result.changed .map((r) => `'${r.replaceAll('\\', '\\\\')}'`) .join(', ')}], removed: [${result.removed .map((r) => `'${r.replaceAll('\\', '\\\\')}'`) .join(', ')}], immutableModules: ${JSON.stringify(result.immutableModules.trim())}, mutableModules: ${JSON.stringify(result.mutableModules.trim())}, boundaries: ${JSON.stringify(result.boundaries)}, dynamicResourcesMap: ${JSON.stringify(dynamicResourcesMap)} }`;
this.callUpdates(result);
this._devServer.ws.clients.forEach((client: WebSocketClient) => { client.rawSend(` { type: 'farm-update', result: ${resultStr} } `); });
this._compiler.onUpdateFinish(async () => { if (this._updateQueue.length > 0) { await this.recompileAndSendResult(); } }); } catch (e) { clearScreen(); throw e; } };
async hmrUpdate(absPath: string | string[], force = false) { const paths = Array.isArray(absPath) ? absPath : [absPath];
for (const path of paths) { if (this._compiler.hasModule(path) && !this._updateQueue.includes(path)) { const lastModifiedTimestamp = this._lastModifiedTimestamp.get(path); const currentTimestamp = (await stat(path)).mtime.toISOString(); if (!force && lastModifiedTimestamp === currentTimestamp) { continue; }
this._lastModifiedTimestamp.set(path, currentTimestamp); this._updateQueue.push(path); } }
if (!this._compiler.compiling && this._updateQueue.length > 0) { try { await this.recompileAndSendResult(); } catch (e) { const serialization = e.message.replace(/\x1b\[[0-9;]*m/g, ''); const errorStr = `${JSON.stringify({ message: serialization })}`; this._devServer.ws.clients.forEach((client: WebSocketClient) => { client.rawSend(` { type: 'error', err: ${errorStr} } `); }); this._logger.error(e); } } } }
|
watcher
这部分主要实现了文件修改后,执行编译并通过hmr更新页面。在上面我们看到了start命令调用了:createFileWatcher,文件位于src/index.ts,看看他的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| export async function createFileWatcher( devServer: Server, resolvedUserConfig: ResolvedUserConfig, inlineConfig: FarmCLIOptions & UserConfig, logger: Logger ) { if ( devServer.config.hmr && resolvedUserConfig.compilation.mode === 'production' ) { logger.error('HMR cannot be enabled in production mode.'); return; }
if (!devServer.config.hmr) { return; }
const fileWatcher = new FileWatcher(devServer, resolvedUserConfig); devServer.watcher = fileWatcher; await fileWatcher.watch();
const configFilePath = await getConfigFilePath(inlineConfig.configPath); const farmWatcher = new ConfigWatcher({ ...resolvedUserConfig, configFilePath }); farmWatcher.watch(async (files: string[]) => { clearScreen();
devServer.restart(async () => { logFileChanges(files, resolvedUserConfig.root, logger); farmWatcher?.close();
await devServer.close(); __FARM_GLOBAL__.__FARM_RESTART_DEV_SERVER__ = true; await start(inlineConfig); }); }); }
|
然后是watcher核心代码src/watcher/index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| interface ImplFileWatcher { watch(): Promise<void>; }
export class FileWatcher implements ImplFileWatcher { private _root: string; private _watcher: FSWatcher; private _logger: Logger; private _close = false;
constructor( public serverOrCompiler: Server | Compiler, public options: ResolvedUserConfig ) { this._root = options.root; this._logger = new Logger(); }
getInternalWatcher() { return this._watcher; }
async watch() { const compiler = this.getCompilerFromServerOrCompiler( this.serverOrCompiler );
const handlePathChange = async (path: string): Promise<void> => { if (this._close) { return; }
try { if (this.serverOrCompiler instanceof Server) { await this.serverOrCompiler.hmrEngine.hmrUpdate(path); }
if ( this.serverOrCompiler instanceof Compiler && this.serverOrCompiler.hasModule(path) ) { compilerHandler( async () => { const result = await compiler.update([path], true); handleUpdateFinish(result); compiler.writeResourcesToDisk(); }, this.options, { clear: true } ); } } catch (error) { this._logger.error(error); } };
const watchedFiles = [ ...compiler.resolvedModulePaths(this._root), ...compiler.resolvedWatchPaths() ].filter( (file) => !file.startsWith(this.options.root) && !file.includes('node_modules/') && existsSync(file) );
const files = [this.options.root, ...watchedFiles]; this._watcher = createWatcher(this.options, files);
this._watcher.on('change', (path) => { if (this._close) return; handlePathChange(path); });
const handleUpdateFinish = (updateResult: JsUpdateResult) => { const added = [ ...updateResult.added, ...updateResult.extraWatchResult.add ].map((addedModule) => { const resolvedPath = compiler.transformModulePath( this._root, addedModule ); return resolvedPath; }); const filteredAdded = added.filter( (file) => !file.startsWith(this.options.root) );
if (filteredAdded.length > 0) { this._watcher.add(filteredAdded); } };
if (this.serverOrCompiler instanceof Server) { this.serverOrCompiler.hmrEngine?.onUpdateFinish(handleUpdateFinish); } }
private getCompilerFromServerOrCompiler( serverOrCompiler: Server | Compiler ): Compiler { return serverOrCompiler instanceof Server ? serverOrCompiler.getCompiler() : serverOrCompiler; }
close() { this._close = false; this._watcher = null; this.serverOrCompiler = null; } }
|
可以看到这部分代码只是胶水代码,用来实现watcher和server交互的代码,看看watcher核心代码:src/watcher/create-watcher.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| import glob from 'fast-glob';
import chokidar, { FSWatcher, WatchOptions } from 'chokidar';
function resolveChokidarOptions( config: ResolvedUserConfig, insideChokidarOptions: WatchOptions ) { const { ignored = [], ...userChokidarOptions } = config.server?.hmr?.watchOptions ?? {}; let cacheDir = path.resolve(config.root, 'node_modules', '.farm', 'cache');
if ( typeof config.compilation?.persistentCache === 'object' && config.compilation.persistentCache.cacheDir ) { cacheDir = config.compilation.persistentCache.cacheDir;
if (!path.isAbsolute(cacheDir)) { cacheDir = path.resolve(config.root, cacheDir); } }
const options: WatchOptions = { ignored: [ '**/.git/**', '**/node_modules/**', '**/test-results/**', glob.escapePath(cacheDir) + '/**', glob.escapePath( path.resolve(config.root, config.compilation.output.path) ) + '/**', ...(Array.isArray(ignored) ? ignored : [ignored]) ], ignoreInitial: true, ignorePermissionErrors: true, awaitWriteFinish: process.platform === 'linux' ? undefined : { stabilityThreshold: 10, pollInterval: 10 }, ...userChokidarOptions, ...insideChokidarOptions };
return options; }
export function createWatcher( config: ResolvedUserConfig, files: string[], chokidarOptions?: WatchOptions ): FSWatcher { const options = resolveChokidarOptions(config, chokidarOptions);
return chokidar.watch(files, options); }
|
以上就是core部分的核心代码解析,下一篇继续看rust部分的core和compiler代码。