diff --git a/tools/isobuild/package-source.js b/tools/isobuild/package-source.js index 210f6d03e8..ec0998d9fc 100644 --- a/tools/isobuild/package-source.js +++ b/tools/isobuild/package-source.js @@ -860,6 +860,9 @@ _.extend(PackageSource.prototype, { const testModulesByArch = projectContext.meteorConfig.getTestModulesByArch(); + const nodeModulesToRecompileByArch = + projectContext.meteorConfig.getNodeModulesToRecompileByArch(); + projectWatchSet.merge(projectContext.meteorConfig.watchSet); _.each(compiler.ALL_ARCHES, function (arch) { @@ -871,10 +874,13 @@ _.extend(PackageSource.prototype, { } const mainModule = projectContext.meteorConfig - .getMainModuleForArch(arch, mainModulesByArch); + .getMainModule(arch, mainModulesByArch); const testModule = projectContext.meteorConfig - .getTestModuleForArch(arch, testModulesByArch); + .getTestModule(arch, testModulesByArch); + + const nodeModulesToRecompile = projectContext.meteorConfig + .getNodeModulesToRecompile(arch, nodeModulesToRecompileByArch); // XXX what about /web.browser/* etc, these directories could also // be for specific client targets. @@ -895,6 +901,7 @@ _.extend(PackageSource.prototype, { ignoreFiles, isApp: true, testModule, + nodeModulesToRecompile, }; // If this architecture has a mainModule defined in @@ -1094,6 +1101,7 @@ _.extend(PackageSource.prototype, { isApp, sourceArch, testModule, + nodeModulesToRecompile = new Set, loopChecker = new SymlinkLoopChecker(this.sourceRoot), ignoreFiles = [] }) { @@ -1254,7 +1262,7 @@ _.extend(PackageSource.prototype, { // If we're in a node_modules directory, cache the results of the // find function for the duration of the process. - const cacheKey = inNodeModules && makeCacheKey(dir); + let cacheKey = inNodeModules && makeCacheKey(dir); if (cacheKey && cacheKey in self._findSourcesCache) { return self._findSourcesCache[cacheKey]; @@ -1273,14 +1281,22 @@ _.extend(PackageSource.prototype, { } } - const pkgJson = optimisticLookupPackageJson(self.sourceRoot, dir); - const hasModuleEntryPoint = pkgJson && ( - isWeb ? typeof pkgJson.module === "string" : pkgJson.type === "module" - ); + let readOptions = sourceReadOptions; + if (inNodeModules) { + const pkgJson = optimisticLookupPackageJson(self.sourceRoot, dir); + const shouldRecompile = + pkgJson && nodeModulesToRecompile.has(pkgJson.name); + const hasModuleEntryPoint = pkgJson && ( + typeof pkgJson.module === "string" || pkgJson.type === "module" + ); - const readOptions = inNodeModules && !hasModuleEntryPoint - ? nodeModulesReadOptions - : sourceReadOptions; + if (hasModuleEntryPoint || shouldRecompile) { + // Avoid caching node_modules code recompiled by Meteor. + cacheKey = false; + } else { + readOptions = nodeModulesReadOptions; + } + } const sources = _.difference( self._readAndWatchDirectory(dir, watchSet, readOptions), diff --git a/tools/project-context.js b/tools/project-context.js index 7806436baf..fdc136b9f2 100644 --- a/tools/project-context.js +++ b/tools/project-context.js @@ -1649,9 +1649,47 @@ export class MeteorConfig { } } - // Call this first if you plan to call getMainModuleForArch multiple + getNodeModulesToRecompileByArch() { + const packageNamesByArch = Object.create(null); + const recompile = this.get("nodeModules", "recompile"); + + if (recompile && typeof recompile === "object") { + const get = arch => packageNamesByArch[arch] || ( + packageNamesByArch[arch] = new Set); + + Object.keys(recompile).forEach(packageName => { + const info = recompile[packageName]; + if (! info) return; + if (info === true) { + get("web").add(packageName); + get("os").add(packageName); + } else if (typeof info === "string") { + mapWhereToArches(info).forEach(arch => { + get(arch).add(packageName); + }); + } else if (Array.isArray(info)) { + info.forEach(where => { + mapWhereToArches(where).forEach(arch => { + get(arch).add(packageName); + }); + }); + } + }); + } + + return packageNamesByArch; + } + + getNodeModulesToRecompile( + arch, + packageNamesByArch = this.getNodeModulesToRecompileByArch(), + ) { + return packageNamesByArch[arch]; + } + + // Call this first if you plan to call getMainModule multiple // times, so that you can avoid repeating this work each time. - getMainModulesByArch(arch) { + getMainModulesByArch() { return this._getEntryModulesByArch("mainModule"); } @@ -1659,24 +1697,24 @@ export class MeteorConfig { // that architecture. For example, if this.config.mainModule.client is // defined, then because mapWhereToArch("client") === "web", and "web" // matches web.browser, return this.config.mainModule.client. - getMainModuleForArch( + getMainModule( arch, mainModulesByArch = this.getMainModulesByArch(), ) { - return this._getEntryModuleForArch(arch, mainModulesByArch); + return this._getEntryModule(arch, mainModulesByArch); } // Analogous to getMainModulesByArch, except for this.config.testModule. - getTestModulesByArch(arch) { + getTestModulesByArch() { return this._getEntryModulesByArch("testModule"); } - // Analogous to getMainModuleForArch, except for this.config.testModule. - getTestModuleForArch( + // Analogous to getMainModule, except for this.config.testModule. + getTestModule( arch, testModulesByArch = this.getTestModulesByArch(), ) { - return this._getEntryModuleForArch(arch, testModulesByArch); + return this._getEntryModule(arch, testModulesByArch); } _getEntryModulesByArch(...keys) { @@ -1703,7 +1741,7 @@ export class MeteorConfig { return entryModulesByArch; } - _getEntryModuleForArch( + _getEntryModule( arch, entryModulesByArch, ) {