diff --git a/tools/fs/files.ts b/tools/fs/files.ts index 08383a0ed4..b33a1ca20c 100644 --- a/tools/fs/files.ts +++ b/tools/fs/files.ts @@ -9,6 +9,7 @@ import fs from "fs"; import path from "path"; import os from "os"; import { spawn, execFile } from "child_process"; +import { Slot } from "@wry/context"; const _ = require('underscore'); const Fiber = require("fibers"); @@ -43,13 +44,15 @@ import { pathSep, } from "./mini-files"; +const { hasOwnProperty } = Object.prototype; + const parsedSourceMaps: Record = {}; let nextStackFilenameCounter = 1; // Use the source maps specified to runJavaScript function useParsedSourceMap(pathForSourceMap: string) { // Check our fancy source map data structure, used for isopacks - if (_.has(parsedSourceMaps, pathForSourceMap)) { + if (hasOwnProperty.call(parsedSourceMaps, pathForSourceMap)) { return {map: parsedSourceMaps[pathForSourceMap]}; } @@ -752,7 +755,7 @@ export function extractTarGz( const tempDir = pathJoin(parentDir, '.tmp' + utils.randomToken()); mkdir_p(tempDir); - if (! _.has(options, "verbose")) { + if (! hasOwnProperty.call(options, "verbose")) { options.verbose = require("../console/console.js").Console.verbose; } @@ -1574,6 +1577,12 @@ export function readBufferWithLengthAndOffset( // A helpful file to import for this purpose is colon-converter.js, which also // knows how to convert various configuration file formats. +type wrapFsFuncOptions = { + cached?: boolean; + modifyReturnValue?: (result: TResult) => any; + dirty?: (...args: TArgs) => any; +} + function wrapFsFunc< TArgs extends any[], TResult, @@ -1581,10 +1590,7 @@ function wrapFsFunc< >( fn: F, pathArgIndices: number[], - options?: { - modifyReturnValue?: (result: TResult) => any; - dirty?: (...args: TArgs) => any; - }, + options?: wrapFsFuncOptions, ): F { return function wrapper(...args: TArgs) { for (let j = pathArgIndices.length - 1; j >= 0; --j) { @@ -1592,22 +1598,51 @@ function wrapFsFunc< args[i] = convertToOSPath(args[i]); } - const result = fn.apply(fs, args); - - if (! options) { - return result; + let cacheKey: string | null = null; + if (options && options.cached) { + const cache = withCacheSlot.getValue(); + if (cache) { + const strings = [fnName]; + const allStrings = args.every(arg => { + if (typeof arg === "string") { + strings.push(arg); + return true; + } + return false; + }); + if (allStrings) { + cacheKey = JSON.stringify(strings); + if (hasOwnProperty.call(cache, cacheKey)) { + return cache[cacheKey]; + } + } + } } - if (options.dirty) { + const result = fn.apply(fs, args); + + if (options && options.dirty) { options.dirty(...args); } - return options.modifyReturnValue + const finalResult = options && options.modifyReturnValue ? options.modifyReturnValue(result) : result; + + if (cacheKey) { + withCacheSlot.getValue()![cacheKey] = finalResult; + } + + return finalResult; } as F; } +const withCacheSlot = new Slot>(); +export function withCache(fn: () => R): R { + const cache = withCacheSlot.getValue(); + return cache ? fn() : withCacheSlot.withValue(Object.create(null), fn); +} + let dependOnPathSalt = 0; import { wrap } from "optimism"; export const dependOnPath = wrap( @@ -1626,8 +1661,10 @@ function wrapDestructiveFsFunc< >( fn: F, pathArgIndices: number[] = [0], + options?: wrapFsFuncOptions, ): F { return wrapFsFunc(fn, pathArgIndices, { + ...options, dirty(...args: TArgs) { pathArgIndices.forEach(i => dependOnPath.dirty(args[i])); } @@ -1692,10 +1729,12 @@ export const rename = isWindowsLikeFilesystem() ? function (from: string, to: st // Warning: doesn't convert slashes in the second 'cache' arg export const realpath = wrapFsFunc(fs.realpathSync, [0], { - modifyReturnValue: convertToStandardPath + cached: true, + modifyReturnValue: convertToStandardPath, }); export const readdir = wrapFsFunc(fs.readdirSync, [0], { + cached: true, modifyReturnValue(entries: string[]) { return entries.map(convertToStandardPath); }, @@ -1706,13 +1745,13 @@ export const chmod = wrapDestructiveFsFunc(fs.chmodSync); export const close = wrapFsFunc(fs.closeSync, []); export const createReadStream = wrapFsFunc(fs.createReadStream, [0]); export const createWriteStream = wrapFsFunc(fs.createWriteStream, [0]); -export const lstat = wrapFsFunc(fs.lstatSync, [0]); +export const lstat = wrapFsFunc(fs.lstatSync, [0], { cached: true }); export const mkdir = wrapDestructiveFsFunc(fs.mkdirSync); export const open = wrapFsFunc(fs.openSync, [0]); export const read = wrapFsFunc(fs.readSync, []); export const readlink = wrapFsFunc(fs.readlinkSync, [0]); export const rmdir = wrapDestructiveFsFunc(fs.rmdirSync); -export const stat = wrapFsFunc(fs.statSync, [0]); +export const stat = wrapFsFunc(fs.statSync, [0], { cached: true }); export const symlink = wrapFsFunc(fs.symlinkSync, [0, 1]); export const unlink = wrapDestructiveFsFunc(fs.unlinkSync); export const unwatchFile = wrapFsFunc(fs.unwatchFile, [0]); diff --git a/tools/isobuild/bundler.js b/tools/isobuild/bundler.js index 7569c13f75..e32b74708f 100644 --- a/tools/isobuild/bundler.js +++ b/tools/isobuild/bundler.js @@ -3109,7 +3109,9 @@ Find out more about Meteor at meteor.com. * you are testing! */ -exports.bundle = Profile("bundler.bundle", bundle); +exports.bundle = Profile("bundler.bundle", function (options) { + return files.withCache(() => bundle(options)); +}); function bundle({ projectContext, diff --git a/tools/isobuild/compiler.js b/tools/isobuild/compiler.js index 95ba6681fe..051d3930b6 100644 --- a/tools/isobuild/compiler.js +++ b/tools/isobuild/compiler.js @@ -182,15 +182,17 @@ compiler.compile = Profile(function (packageSource, options) { return; } - var unibuildResult = compileUnibuild({ - isopack: isopk, - sourceArch: architecture, - isopackCache: isopackCache, - nodeModulesPath: nodeModulesPath, - }); + files.withCache(() => { + var unibuildResult = compileUnibuild({ + isopack: isopk, + sourceArch: architecture, + isopackCache: isopackCache, + nodeModulesPath: nodeModulesPath, + }); - _.extend(pluginProviderPackageNames, - unibuildResult.pluginProviderPackageNames); + _.extend(pluginProviderPackageNames, + unibuildResult.pluginProviderPackageNames); + }); }); if (options.includePluginProviderPackageMap) { diff --git a/tools/isobuild/package-source.js b/tools/isobuild/package-source.js index e9296c2f92..bc146a7060 100644 --- a/tools/isobuild/package-source.js +++ b/tools/isobuild/package-source.js @@ -1353,7 +1353,7 @@ _.extend(PackageSource.prototype, { return sources; } - return find("", 0, false); + return files.withCache(() => find("", 0, false)); }), _findAssets({