UNPKG

@parcel/core

Version:
301 lines (274 loc) • 9.47 kB
// @flow strict-local import type {ContentKey} from '@parcel/graph'; import type {Dependency, NamedBundle as INamedBundle} from '@parcel/types'; import type {SharedReference} from '@parcel/workers'; import type { Asset, AssetGroup, Bundle as InternalBundle, Config, DevDepRequest, DevDepRequestRef, ParcelOptions, } from './types'; import type ParcelConfig from './ParcelConfig'; import type PluginOptions from './public/PluginOptions'; import type {RequestResult, RunAPI} from './RequestTracker'; import path from 'path'; import assert from 'assert'; import invariant from 'assert'; import nullthrows from 'nullthrows'; import {nodeFromAssetGroup} from './AssetGraph'; import BundleGraph from './public/BundleGraph'; import InternalBundleGraph, {bundleGraphEdgeTypes} from './BundleGraph'; import {NamedBundle} from './public/Bundle'; import {PluginLogger} from '@parcel/logger'; import {hashString} from '@parcel/rust'; import ThrowableDiagnostic, {errorToDiagnostic} from '@parcel/diagnostic'; import {dependencyToInternalDependency} from './public/Dependency'; import {mergeEnvironments} from './Environment'; import createAssetGraphRequest from './requests/AssetGraphRequest'; import {createDevDependency, runDevDepRequest} from './requests/DevDepRequest'; import {toProjectPath, fromProjectPathRelative} from './projectPath'; import {tracer, PluginTracer} from '@parcel/profiler'; type RuntimeConnection = {| bundle: InternalBundle, assetGroup: AssetGroup, dependency: ?Dependency, isEntry: ?boolean, shouldReplaceResolution: ?boolean, |}; export default async function applyRuntimes<TResult: RequestResult>({ bundleGraph, config, options, pluginOptions, api, optionsRef, previousDevDeps, devDepRequests, configs, }: {| bundleGraph: InternalBundleGraph, config: ParcelConfig, options: ParcelOptions, optionsRef: SharedReference, pluginOptions: PluginOptions, api: RunAPI<TResult>, previousDevDeps: Map<string, string>, devDepRequests: Map<string, DevDepRequest | DevDepRequestRef>, configs: Map<string, Config>, |}): Promise<Map<string, Asset>> { let runtimes = await config.getRuntimes(); let connections: Array<RuntimeConnection> = []; let bundles = bundleGraph.getBundles({includeInline: true}); let publicBundleGraph = new BundleGraph<INamedBundle>( bundleGraph, NamedBundle.get.bind(NamedBundle), options, ); for (let bundle of bundles) { for (let runtime of runtimes) { let measurement; try { const namedBundle = NamedBundle.get(bundle, bundleGraph, options); measurement = tracer.createMeasurement( runtime.name, 'applyRuntime', namedBundle.displayName, ); let applied = await runtime.plugin.apply({ bundle: namedBundle, bundleGraph: publicBundleGraph, config: configs.get(runtime.name)?.result, options: pluginOptions, logger: new PluginLogger({origin: runtime.name}), tracer: new PluginTracer({ origin: runtime.name, category: 'applyRuntime', }), }); if (applied) { let runtimeAssets = Array.isArray(applied) ? applied : [applied]; for (let { code, dependency, filePath, isEntry, env, shouldReplaceResolution, } of runtimeAssets) { let sourceName = path.join( path.dirname(filePath), `runtime-${hashString(code)}${path.extname(filePath)}`, ); let assetGroup = { code, filePath: toProjectPath(options.projectRoot, sourceName), env: mergeEnvironments(options.projectRoot, bundle.env, env), // Runtime assets should be considered source, as they should be // e.g. compiled to run in the target environment isSource: true, }; connections.push({ bundle, assetGroup, dependency, isEntry, shouldReplaceResolution, }); } } } catch (e) { throw new ThrowableDiagnostic({ diagnostic: errorToDiagnostic(e, { origin: runtime.name, }), }); } finally { measurement && measurement.end(); } } } // Add dev deps for runtime plugins AFTER running them, to account for lazy require(). for (let runtime of runtimes) { let devDepRequest = await createDevDependency( { specifier: runtime.name, resolveFrom: runtime.resolveFrom, }, previousDevDeps, options, ); devDepRequests.set( `${devDepRequest.specifier}:${fromProjectPathRelative( devDepRequest.resolveFrom, )}`, devDepRequest, ); await runDevDepRequest(api, devDepRequest); } let {assetGraph: runtimesAssetGraph, changedAssets} = await reconcileNewRuntimes(api, connections, optionsRef); let runtimesGraph = InternalBundleGraph.fromAssetGraph( runtimesAssetGraph, options.mode === 'production', bundleGraph._publicIdByAssetId, bundleGraph._assetPublicIds, ); // Merge the runtimes graph into the main bundle graph. bundleGraph.merge(runtimesGraph); for (let [assetId, publicId] of runtimesGraph._publicIdByAssetId) { bundleGraph._publicIdByAssetId.set(assetId, publicId); bundleGraph._assetPublicIds.add(publicId); } for (let { bundle, assetGroup, dependency, isEntry, shouldReplaceResolution, } of connections) { let assetGroupNode = nodeFromAssetGroup(assetGroup); let assetGroupAssetNodeIds = runtimesAssetGraph.getNodeIdsConnectedFrom( runtimesAssetGraph.getNodeIdByContentKey(assetGroupNode.id), ); invariant(assetGroupAssetNodeIds.length === 1); let runtimeNodeId = assetGroupAssetNodeIds[0]; let runtimeNode = nullthrows(runtimesAssetGraph.getNode(runtimeNodeId)); invariant(runtimeNode.type === 'asset'); let resolution = dependency && bundleGraph.getResolvedAsset( dependencyToInternalDependency(dependency), bundle, ); let runtimesGraphRuntimeNodeId = runtimesGraph._graph.getNodeIdByContentKey( runtimeNode.id, ); let duplicatedContentKeys: Set<ContentKey> = new Set(); runtimesGraph._graph.traverse((nodeId, _, actions) => { let node = nullthrows(runtimesGraph._graph.getNode(nodeId)); if (node.type !== 'dependency') { return; } let assets = runtimesGraph._graph .getNodeIdsConnectedFrom(nodeId) .map(assetNodeId => { let assetNode = nullthrows(runtimesGraph._graph.getNode(assetNodeId)); invariant(assetNode.type === 'asset'); return assetNode.value; }); for (let asset of assets) { if ( bundleGraph.isAssetReachableFromBundle(asset, bundle) || resolution?.id === asset.id ) { duplicatedContentKeys.add(asset.id); actions.skipChildren(); } } }, runtimesGraphRuntimeNodeId); let bundleNodeId = bundleGraph._graph.getNodeIdByContentKey(bundle.id); let bundleGraphRuntimeNodeId = bundleGraph._graph.getNodeIdByContentKey( runtimeNode.id, ); // the node id is not constant between graphs runtimesGraph._graph.traverse((nodeId, _, actions) => { let node = nullthrows(runtimesGraph._graph.getNode(nodeId)); if (node.type === 'asset' || node.type === 'dependency') { if (duplicatedContentKeys.has(node.id)) { actions.skipChildren(); return; } const bundleGraphNodeId = bundleGraph._graph.getNodeIdByContentKey( node.id, ); // the node id is not constant between graphs bundleGraph._graph.addEdge( bundleNodeId, bundleGraphNodeId, bundleGraphEdgeTypes.contains, ); } }, runtimesGraphRuntimeNodeId); if (isEntry) { bundleGraph._graph.addEdge(bundleNodeId, bundleGraphRuntimeNodeId); bundle.entryAssetIds.unshift(runtimeNode.id); } if (dependency == null) { // Verify this asset won't become an island assert( bundleGraph._graph.getNodeIdsConnectedTo(bundleGraphRuntimeNodeId) .length > 0, 'Runtime must have an inbound dependency or be an entry', ); } else { let dependencyNodeId = bundleGraph._graph.getNodeIdByContentKey( dependency.id, ); bundleGraph._graph.addEdge(dependencyNodeId, bundleGraphRuntimeNodeId); if (shouldReplaceResolution && resolution) { let resolutionNodeId = bundleGraph._graph.getNodeIdByContentKey( resolution.id, ); bundleGraph._graph.removeEdge(dependencyNodeId, resolutionNodeId); bundleGraph._graph.addEdge(dependencyNodeId, resolutionNodeId); // TODO: remove asset from bundle? } } } return changedAssets; } function reconcileNewRuntimes<TResult: RequestResult>( api: RunAPI<TResult>, connections: Array<RuntimeConnection>, optionsRef: SharedReference, ) { let assetGroups = connections.map(t => t.assetGroup); let request = createAssetGraphRequest({ name: 'Runtimes', assetGroups, optionsRef, }); // rebuild the graph return api.runRequest(request, {force: true}); }