UNPKG

@parcel/core

Version:
257 lines (237 loc) • 7.06 kB
// @flow strict-local import type { ASTGenerator, BundleBehavior, FilePath, GenerateOutput, Meta, PackageName, Stats, Symbol, SourceLocation, Transformer, } from '@parcel/types'; import type { Asset, RequestInvalidation, Dependency, Environment, ParcelOptions, } from './types'; import {Readable} from 'stream'; import {PluginLogger} from '@parcel/logger'; import nullthrows from 'nullthrows'; import CommittedAsset from './CommittedAsset'; import UncommittedAsset from './UncommittedAsset'; import loadPlugin from './loadParcelPlugin'; import {Asset as PublicAsset} from './public/Asset'; import PluginOptions from './public/PluginOptions'; import {blobToStream, hashFile} from '@parcel/utils'; import {hashFromOption, toInternalSourceLocation} from './utils'; import {createBuildCache} from './buildCache'; import { type ProjectPath, fromProjectPath, fromProjectPathRelative, } from './projectPath'; import {hashString} from '@parcel/hash'; import {BundleBehavior as BundleBehaviorMap} from './types'; import {PluginTracer} from '@parcel/profiler'; type AssetOptions = {| id?: string, committed?: boolean, hash?: ?string, idBase?: ?string, filePath: ProjectPath, query?: ?string, type: string, contentKey?: ?string, mapKey?: ?string, astKey?: ?string, astGenerator?: ?ASTGenerator, dependencies?: Map<string, Dependency>, bundleBehavior?: ?BundleBehavior, isBundleSplittable?: ?boolean, isSource: boolean, env: Environment, meta?: Meta, outputHash?: ?string, pipeline?: ?string, stats: Stats, symbols?: ?Map<Symbol, {|local: Symbol, loc: ?SourceLocation, meta?: ?Meta|}>, sideEffects?: boolean, uniqueKey?: ?string, plugin?: PackageName, configPath?: ProjectPath, configKeyPath?: string, |}; export function createAssetIdFromOptions(options: AssetOptions): string { let uniqueKey = options.uniqueKey ?? ''; let idBase = options.idBase != null ? options.idBase : fromProjectPathRelative(options.filePath); return hashString( idBase + options.type + options.env.id + uniqueKey + ':' + (options.pipeline ?? '') + ':' + (options.query ?? ''), ); } export function createAsset( projectRoot: FilePath, options: AssetOptions, ): Asset { return { id: options.id != null ? options.id : createAssetIdFromOptions(options), committed: options.committed ?? false, hash: options.hash, filePath: options.filePath, query: options.query, bundleBehavior: options.bundleBehavior ? BundleBehaviorMap[options.bundleBehavior] : null, isBundleSplittable: options.isBundleSplittable ?? true, type: options.type, contentKey: options.contentKey, mapKey: options.mapKey, astKey: options.astKey, astGenerator: options.astGenerator, dependencies: options.dependencies || new Map(), isSource: options.isSource, outputHash: options.outputHash, pipeline: options.pipeline, env: options.env, meta: options.meta || {}, stats: options.stats, symbols: options.symbols && new Map( [...options.symbols].map(([k, v]) => [ k, { local: v.local, meta: v.meta, loc: toInternalSourceLocation(projectRoot, v.loc), }, ]), ), sideEffects: options.sideEffects ?? true, uniqueKey: options.uniqueKey, plugin: options.plugin, configPath: options.configPath, configKeyPath: options.configKeyPath, }; } const generateResults: WeakMap<Asset, Promise<GenerateOutput>> = new WeakMap(); export function generateFromAST( asset: CommittedAsset | UncommittedAsset, ): Promise<GenerateOutput> { let output = generateResults.get(asset.value); if (output == null) { output = _generateFromAST(asset); generateResults.set(asset.value, output); } return output; } async function _generateFromAST(asset: CommittedAsset | UncommittedAsset) { let ast = await asset.getAST(); if (ast == null) { throw new Error('Asset has no AST'); } let pluginName = nullthrows(asset.value.plugin); let {plugin} = await loadPlugin<Transformer<mixed>>( pluginName, fromProjectPath( asset.options.projectRoot, nullthrows(asset.value.configPath), ), nullthrows(asset.value.configKeyPath), asset.options, ); let generate = plugin.generate?.bind(plugin); if (!generate) { throw new Error(`${pluginName} does not have a generate method`); } let {content, map} = await generate({ asset: new PublicAsset(asset), ast, options: new PluginOptions(asset.options), logger: new PluginLogger({origin: pluginName}), tracer: new PluginTracer({origin: pluginName, category: 'asset-generate'}), }); let mapBuffer = map?.toBuffer(); // Store the results in the cache so we can avoid generating again next time await Promise.all([ asset.options.cache.setStream( nullthrows(asset.value.contentKey), blobToStream(content), ), mapBuffer != null && asset.options.cache.setBlob(nullthrows(asset.value.mapKey), mapBuffer), ]); return { content: content instanceof Readable ? asset.options.cache.getStream(nullthrows(asset.value.contentKey)) : content, map, }; } export function getInvalidationId(invalidation: RequestInvalidation): string { switch (invalidation.type) { case 'file': return 'file:' + fromProjectPathRelative(invalidation.filePath); case 'env': return 'env:' + invalidation.key; case 'option': return 'option:' + invalidation.key; default: throw new Error('Unknown invalidation type: ' + invalidation.type); } } const hashCache = createBuildCache(); export async function getInvalidationHash( invalidations: Array<RequestInvalidation>, options: ParcelOptions, ): Promise<string> { if (invalidations.length === 0) { return ''; } let sortedInvalidations = invalidations .slice() .sort((a, b) => (getInvalidationId(a) < getInvalidationId(b) ? -1 : 1)); let hashes = ''; for (let invalidation of sortedInvalidations) { switch (invalidation.type) { case 'file': { // Only recompute the hash of this file if we haven't seen it already during this build. let fileHash = hashCache.get(invalidation.filePath); if (fileHash == null) { fileHash = hashFile( options.inputFS, fromProjectPath(options.projectRoot, invalidation.filePath), ); hashCache.set(invalidation.filePath, fileHash); } hashes += await fileHash; break; } case 'env': hashes += invalidation.key + ':' + (options.env[invalidation.key] || ''); break; case 'option': hashes += invalidation.key + ':' + hashFromOption(options[invalidation.key]); break; default: throw new Error('Unknown invalidation type: ' + invalidation.type); } } return hashString(hashes); }