/
githubmirror
/
meteor
Обзор
Документация
Войти
/
githubmirror
/
meteor
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
devel
tools/tool-env/isopackets.js
344 строки
13 KB
denihs
Look deeper in the node modules to find more dependencies
05 июл 2023, 23:10
05 июл 2023, 23:10
9a3e70f
Код
Авторство
О чём код?
var assert = require('assert'); var _ = require('underscore'); var bundler = require('../isobuild/bundler.js'); import Builder from '../isobuild/builder.js'; var compiler = require('../isobuild/compiler.js'); var isopackCacheModule = require('../isobuild/isopack-cache.js'); var buildmessage = require('../utils/buildmessage.js'); var files = require('../fs/files'); var config = require('../meteor-services/config.js'); var watch = require('../fs/watch'); var Console = require('../console/console.js').Console; var packageMapModule = require('../packaging/package-map.js'); var archinfo = require('../utils/archinfo'); var Profile = require('./profile').Profile; // TL;DR: Isopacket is a set of isopacks. Isopackets are used only inside // meteor-tool. // An isopacket is a predefined set of isopackages which the meteor command-line // tool can load into its process. This is how we use the DDP client and many // other packages inside the tool. The isopackets are listed below in the // ISOPACKETS constant. // // All packages that are in isopackets and all of their transitive dependencies // must be part of the core Meteor git checkout (not loaded from troposphere). // // The requested packages will be loaded together with all of their // dependencies. If you request to load the same isopacket more than once, you // will efficiently get the same pre-loaded isopacket. On the other hand, two // different loaded isopackets contain distinct copies of all of their packages // copy of all of the packages. The return value is an object that maps package // name to package exports (that is, it is the Package object from inside the // sandbox created for the newly loaded packages). // // For built releases, all of the isopackets are pre-compiled (as JsImages, // similar to a plugin or a server program) into the tool. // // When run from a checkout, all isopackets are re-compiled early in the startup // process if any of their sources have changed. // // Example usage: // var DDP = require('./isopackets.js').loadIsopackage('ddp-client').DDP; // var reverse = DDP.connect('reverse.meteor.com'); // Console.info(reverse.call('reverse', 'hello world')); // All of the defined isopackets. Whenever they are being built, they will be // built in the order listed here. export const ISOPACKETS = { // These packages used to be divided up into distinct isopackets, but // that resulted in extremely wasteful duplication of transitive // dependencies, so now we have only one isopacket that combines all the // dependencies of every former isopacket. combined: [ // ddp 'ddp-client', // mongo 'npm-mongo', // ejson 'ejson', // constraint-solver 'constraint-solver', // cordova-support 'boilerplate-generator', 'webapp-hashing', // cordova-support, logging 'logging', // support for childProcess.sendMessage(topic, payload) 'inter-process-messaging', ] }; // Caches isopackets in memory (each isopacket only needs to be loaded // once). This is a map from isopacket name to either: // // - The 'Package' dictionary, if the isopacket has already been loaded // into memory // - null, if the isopacket hasn't been loaded into memory but its on-disk // instance is known to be ready // // The subtlety here is that when running from a checkout, we don't want to // accidentally load an isopacket before ensuring that it doesn't need to be // rebuilt. We used to need to load a "js-analyze" isopacket as part // of building other isopackets in ensureIsopacketsLoadable which made this // more important, though we've simplified it now by moving that code into // the tool itself. var loadedIsopackets = {}; // The main entry point: loads the specified isopacket ("combined" by // default) from cache or from disk, and returns the requested package // dependency, complaining if the package does not exist. Note that // ensureIsopacketsLoadable must be called first, as this function does // not trigger any building. export async function loadIsopackage(packageName, isopacketName = "combined") { // Small but necessary hack: because archinfo.host() calls execFileSync, // it yields the first time we call it, which is a problem for the // fiberHelpers.noYieldsAllowed block below. Calling it here ensures the // result is cached, so no yielding occurs later. assert.strictEqual((archinfo.host()).split(".", 1)[0], "os"); async function load() { if (_.has(loadedIsopackets, isopacketName)) { if (loadedIsopackets[isopacketName]) { return loadedIsopackets[isopacketName]; } // This is the case where the isopacket is up to date on disk but not // loaded. const loaded = await loadIsopacketFromDisk(isopacketName); loadedIsopackets[isopacketName] = loaded; return loaded; } if (_.has(ISOPACKETS, isopacketName)) { throw Error("Can't load isopacket before it has been verified: " + isopacketName); } throw Error("Unknown isopacket: " + isopacketName); } const isopacket = await load(); if (!_.has(isopacket, packageName)) { throw new Error("Unknown isopacket dependency: " + packageName); } return isopacket[packageName]; } var isopacketPath = function (isopacketName) { return files.pathJoin(config.getIsopacketRoot(), isopacketName); }; // ensureIsopacketsLoadable is called at startup and ensures that all isopackets // exist on disk as up-to-date loadable programs. var calledEnsure = false; export async function ensureIsopacketsLoadable() { if (calledEnsure) { throw Error("can't ensureIsopacketsLoadable twice!"); } calledEnsure = true; // If we're not running from checkout, then there's nothing to build and we // can declare that all isopackets are loadable. if (!files.inCheckout()) { _.each(ISOPACKETS, function (packages, name) { loadedIsopackets[name] = null; }); return; } // We make this object lazily later. var isopacketBuildContext = null; var failedPackageBuild = false; // Look at each isopacket. Check to see if it's on disk and up to date. If // not, build it. We rebuild them in the order listed in ISOPACKETS. var messages = await Console.withProgressDisplayVisible(function () { return buildmessage.capture(async function () { for (const [isopacketName, packages] of Object.entries(ISOPACKETS)) { if (failedPackageBuild) { continue; } var isopacketRoot = isopacketPath(isopacketName); var existingBuildinfo = files.readJSONOrNull( files.pathJoin(isopacketRoot, 'isopacket-buildinfo.json')); var needRebuild = !existingBuildinfo; if (!needRebuild && existingBuildinfo.builtBy !== compiler.BUILT_BY) { needRebuild = true; } if (!needRebuild) { var watchSet = watch.WatchSet.fromJSON(existingBuildinfo.watchSet); if (!watch.isUpToDate(watchSet)) { needRebuild = true; } } if (!needRebuild) { // Great, it's loadable without a rebuild. loadedIsopackets[isopacketName] = null; continue; } // We're going to need to build! Make a catalog and loader if we haven't // yet. if (!isopacketBuildContext) { isopacketBuildContext = await makeIsopacketBuildContext(); } await buildmessage.enterJob({ title: "bundling " + isopacketName + " packages for the tool" }, async function () { // Build the packages into the in-memory IsopackCache. await isopacketBuildContext.isopackCache.buildLocalPackages(packages); if (buildmessage.jobHasMessages()) { return; } // Now bundle them into a program. var built; try { built = await bundler.buildJsImage({ name: "isopacket-" + isopacketName, packageMap: isopacketBuildContext.packageMap, isopackCache: isopacketBuildContext.isopackCache, use: packages }); } catch (e) { console.error("Error trying to buildJsImage", e); throw e; } if (buildmessage.jobHasMessages()) { return; } var builder = new Builder({ outputPath: isopacketRoot }); await builder.init(); await builder.writeJson('isopacket-buildinfo.json', { builtBy: compiler.BUILT_BY, watchSet: built.watchSet.toJSON() }); await built.image.write(builder); await builder.complete(); // It's loadable now. loadedIsopackets[isopacketName] = null; }); } }); }); // This is a build step ... but it's one that only happens in development, so // it can just crash the app instead of being handled nicely. if (messages.hasMessages()) { Console.error("Errors prevented isopacket build:"); Console.printMessages(messages); throw new Error("isopacket build failed?"); } } // Returns a new all-local-packages catalog to be used for building isopackets. var newIsopacketBuildingCatalog = async function () { if (!files.inCheckout()) { throw Error("No need to build isopackets unless in checkout!"); } var catalogLocal = require('../packaging/catalog/catalog-local.js'); var isopacketCatalog = new catalogLocal.LocalCatalog; var messages = await buildmessage.capture( { title: "scanning local core packages" }, async function () { const packagesDir = files.pathJoin(files.getCurrentToolsDir(), 'packages'); // When running from a checkout, isopacket building does use local // packages, but *ONLY THOSE FROM THE CHECKOUT*: not app packages or // $PACKAGE_DIRS packages. One side effect of this: we really really // expect them to all build, and we're fine with dying if they don't // (there's no worries about needing to springboard). await isopacketCatalog.initialize({ localPackageSearchDirs: [ packagesDir, files.pathJoin(packagesDir, "non-core", "*", "packages"), ], buildingIsopackets: true }); }); if (messages.hasMessages()) { Console.arrowError("Errors while scanning core packages:"); Console.printMessages(messages); throw new Error("isopacket scan failed?"); } return isopacketCatalog; }; export async function makeIsopacketBuildContext() { var context = {}; var catalog = await newIsopacketBuildingCatalog(); var versions = {}; _.each(catalog.getAllPackageNames(), function (packageName) { versions[packageName] = catalog.getLatestVersion(packageName).version; }); context.packageMap = new packageMapModule.PackageMap(versions, { localCatalog: catalog }); // Make an isopack cache that doesn't save isopacks to disk and has no // access to versioned packages. context.isopackCache = new isopackCacheModule.IsopackCache({ packageMap: context.packageMap, includeCordovaUnibuild: false, // When linking JS files, don't include the padding spaces and line number // comments. Since isopackets are loaded as part of possibly very short // 'meteor' tool command invocations, we care more about startup time than // legibility, and the difference is actually observable (eg 25% speedup // loading constraint-solver). noLineNumbers: true, }); return context; } // Loads a built isopacket from disk. Always loads (the cache is in 'load', not // this function). Does not run a build process; it must already be built. var loadIsopacketFromDisk = async function (isopacketName) { var image = await bundler.readJsImage( files.pathJoin(isopacketPath(isopacketName), 'program.json')); // An incredibly minimalist version of the environment from // tools/server/boot.js. Kind of a hack. var env = { __meteor_bootstrap__: { startupHooks: [], isFibersDisabled: true }, __meteor_runtime_config__: { meteorRelease: "ISOPACKET" } }; env.Profile = Profile; var ret; var messages = await buildmessage.capture({ title: "loading isopacket `" + isopacketName + "`" }, async function () { ret = await image.load(env); }); // This is a build step ... but it's one that only happens in development, so // it can just crash the app instead of being handled nicely. if (messages.hasMessages()) { Console.error("Errors prevented isopacket load:"); Console.printMessages(messages); throw new Error("isopacket load failed?"); } // Run any user startup hooks. while (env.__meteor_bootstrap__.startupHooks.length) { var hook = env.__meteor_bootstrap__.startupHooks.shift(); await hook(); } // Setting this to null tells Meteor.startup to call hooks immediately. env.__meteor_bootstrap__.startupHooks = null; return ret; };