UNPKG

@velcro/bundler

Version:

Build a module graph of modules and serialize these to different formats

1,371 lines (1,361 loc) 68 kB
import { Base64, Uri, isThenable, checkCancellation, DependencyNotFoundError, EntryExcludedError, EntryNotFoundError, MapSet, DisposableStore, Emitter, CancellationTokenSource, isCanceledError } from '@velcro/common'; import MagicString, { Bundle } from 'magic-string'; import { parse as parse$2 } from 'acorn'; import { runtime } from '@velcro/runtime'; import { version as version$1 } from '@velcro/node-libs/package.json'; class BaseError extends Error { constructor() { super(...arguments); this.name = this.constructor.name; } } class GraphBuildError extends BaseError { constructor(errors) { super(`Graph building failed with errors:\n${errors.map((err) => ` ${err.message}`).join('\n')}`); this.errors = errors; } } var charToInteger = {}; var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; for (var i = 0; i < chars.length; i++) { charToInteger[chars.charCodeAt(i)] = i; } function decode(mappings) { var decoded = []; var line = []; var segment = [ 0, 0, 0, 0, 0, ]; var j = 0; for (var i = 0, shift = 0, value = 0; i < mappings.length; i++) { var c = mappings.charCodeAt(i); if (c === 44) { // "," segmentify(line, segment, j); j = 0; } else if (c === 59) { // ";" segmentify(line, segment, j); j = 0; decoded.push(line); line = []; segment[0] = 0; } else { var integer = charToInteger[c]; if (integer === undefined) { throw new Error('Invalid character (' + String.fromCharCode(c) + ')'); } var hasContinuationBit = integer & 32; integer &= 31; value += integer << shift; if (hasContinuationBit) { shift += 5; } else { var shouldNegate = value & 1; value >>>= 1; if (shouldNegate) { value = value === 0 ? -0x80000000 : -value; } segment[j] += value; j++; value = shift = 0; // reset } } } segmentify(line, segment, j); decoded.push(line); return decoded; } function segmentify(line, segment, j) { // This looks ugly, but we're creating specialized arrays with a specific // length. This is much faster than creating a new array (which v8 expands to // a capacity of 17 after pushing the first item), or slicing out a subarray // (which is slow). Length 4 is assumed to be the most frequent, followed by // length 5 (since not everything will have an associated name), followed by // length 1 (it's probably rare for a source substring to not have an // associated segment data). if (j === 4) line.push([segment[0], segment[1], segment[2], segment[3]]); else if (j === 5) line.push([segment[0], segment[1], segment[2], segment[3], segment[4]]); else if (j === 1) line.push([segment[0]]); } function encode(decoded) { var sourceFileIndex = 0; // second field var sourceCodeLine = 0; // third field var sourceCodeColumn = 0; // fourth field var nameIndex = 0; // fifth field var mappings = ''; for (var i = 0; i < decoded.length; i++) { var line = decoded[i]; if (i > 0) mappings += ';'; if (line.length === 0) continue; var generatedCodeColumn = 0; // first field var lineMappings = []; for (var _i = 0, line_1 = line; _i < line_1.length; _i++) { var segment = line_1[_i]; var segmentMappings = encodeInteger(segment[0] - generatedCodeColumn); generatedCodeColumn = segment[0]; if (segment.length > 1) { segmentMappings += encodeInteger(segment[1] - sourceFileIndex) + encodeInteger(segment[2] - sourceCodeLine) + encodeInteger(segment[3] - sourceCodeColumn); sourceFileIndex = segment[1]; sourceCodeLine = segment[2]; sourceCodeColumn = segment[3]; } if (segment.length === 5) { segmentMappings += encodeInteger(segment[4] - nameIndex); nameIndex = segment[4]; } lineMappings.push(segmentMappings); } mappings += lineMappings.join(','); } return mappings; } function encodeInteger(num) { var result = ''; num = num < 0 ? (-num << 1) | 1 : num << 1; do { var clamped = num & 31; num >>>= 5; if (num > 0) { clamped |= 32; } result += chars[clamped]; } while (num > 0); return result; } class SourceMap { constructor(input) { this.file = input.file; this.mappings = input.mappings; this.sourceRoot = input.sourceRoot; this.names = input.names; this.sources = input.sources; this.sourcesContent = input.sourcesContent; this.version = input.version; } toString() { return JSON.stringify(this); } toDataUri() { return `data:application/json;charset=utf-8;base64,${Base64.encode(this.toString())}`; } } function getSourceMappingUrlMatch(str) { const re = /(?:(?:\/\/|\/\*)[@#][\s]*(?:source)MappingURL=([^\s'"]+)[\s]*$)|(?:\/\*[@#][\s]*(?:source)MappingURL=([^\s*'"]+)[\s]*(?:\*\/)[\s]*$)/gm; // Keep executing the search to find the *last* sourceMappingURL to avoid // picking up sourceMappingURLs from comments, strings, etc. let lastMatch = null; let match; while ((match = re.exec(str))) lastMatch = match; return lastMatch; } function getSourceMappingUrl(str) { const lastMatch = getSourceMappingUrlMatch(str); if (!lastMatch) return ''; return lastMatch[1]; } function updateSourceMappingUrl(str, url) { const lastMatch = getSourceMappingUrlMatch(str); if (!lastMatch) return str; return str.slice(0, lastMatch.index) + str.slice(lastMatch.index).replace(lastMatch[1], url); } function decodeDataUriAsSourceMap(href) { const match = href.match(/^data:application\/json;(?:charset=([^;]+);)?base64,(.*)$/); if (match) { if (match[1] && match[1] !== 'utf-8') { return null; } try { const decoded = JSON.parse(Base64.decode(match[2])); if (decoded.mappings === '') { return { file: '', mappings: [], names: [], sources: [], sourcesContent: [], }; } if (typeof decoded.mappings === 'string') { decoded.mappings = decode(decoded.mappings); } return decoded; } catch (err) { return null; } } return null; } /** * Copyright (c) Rollup 2020 authors: https://github.com/rollup/rollup/graphs/contributors) * * Copied with light modifications from: * https://github.com/rollup/rollup/blob/36a4527473ea1fe678ed866c9f8dfd3c2542cd22/src/utils/collapseSourcemaps.ts */ class Source { constructor(filename, content) { this.filename = filename; this.content = content; } traceSegment(line, column, name) { return { line, column, name, source: this }; } } class Link { constructor(map, sources) { this.sources = sources; this.names = map.names; this.mappings = typeof map.mappings === 'string' ? decode(map.mappings) : map.mappings; } traceMappings() { return traceMappings(this); } traceSegment(line, column, name) { return traceSegment(this, line, column, name); } } class LazyLink { constructor(loadLink) { this.loadLink = loadLink; this.link = undefined; } getLink() { let link = this.link; if (!link) { link = this.loadLink(); this.link = link; } return link; } traceMappings() { return traceMappings(this.getLink()); } traceSegment(line, column, name) { return traceSegment(this.getLink(), line, column, name); } } function traceMappings(map) { const sources = []; const sourcesContent = []; const names = []; const mappings = []; for (const line of map.mappings) { const tracedLine = []; for (const segment of line) { if (segment.length == 1) continue; const source = map.sources[segment[1]]; if (!source) continue; const traced = source.traceSegment(segment[2], segment[3], segment.length === 5 ? map.names[segment[4]] : ''); if (traced) { // newer sources are more likely to be used, so search backwards. let sourceIndex = sources.lastIndexOf(traced.source.filename); if (sourceIndex === -1) { sourceIndex = sources.length; sources.push(traced.source.filename); sourcesContent[sourceIndex] = traced.source.content; } else if (sourcesContent[sourceIndex] == null) { sourcesContent[sourceIndex] = traced.source.content; } else if (traced.source.content != null && sourcesContent[sourceIndex] !== traced.source.content) { return new Error(`Multiple conflicting contents for sourcemap source ${traced.source.filename}`); } const tracedSegment = [ segment[0], sourceIndex, traced.line, traced.column, ]; if (traced.name) { let nameIndex = names.indexOf(traced.name); if (nameIndex === -1) { nameIndex = names.length; names.push(traced.name); } tracedSegment[4] = nameIndex; } tracedLine.push(tracedSegment); } } mappings.push(tracedLine); } return { sources, sourcesContent, names, mappings }; } function traceSegment(map, line, column, name) { const segments = map.mappings[line]; if (!segments) return null; // binary search through segments for the given column let i = 0; let j = segments.length - 1; while (i <= j) { const m = (i + j) >> 1; const segment = segments[m]; if (segment[0] === column) { if (segment.length == 1) return null; const source = map.sources[segment[1]]; if (!source) return null; return source.traceSegment(segment[2], segment[3], segment.length === 5 ? map.names[segment[4]] : name); } if (segment[0] > column) { j = m - 1; } else { i = m + 1; } } return null; } /** * This function attempts to compensate for the loss of precision when lower * layers of source maps have higher precision than upper layers, leading to * a loss of fidelity. * * The code was lifted from [Alec Larson](https://github.com/aleclarson)'s * [fork of sorcery](https://github.com/aleclarson/sorcery/blob/3934a3f38a6d8604fc9dbaa576cbb6e4d733040f/src/blend.js). * * NOTE: This function mutates the given node. * * @copyright [Alec Larson](https://github.com/aleclarson) 2018 */ // function blend(node: Link) { // let mappings: SourceMapSegment[][] = []; // traced lines // let sources: (Link | Source)[] = []; // traced sources // let names: string[] = []; // traced symbols // // Precompute which source/line/column triples are mapped by the given node. // // These references are useful when interweaving old segments. // const refs: number[][][] = Object.keys(node.sources).map(() => []); // for (const segments of node.mappings) { // let segment: SourceMapSegment; // let lines: number[][]; // let columns: number[]; // for (let i = 0; i < segments.length; i++) { // segment = segments[i]; // if (segment.length === 4 || segment.length === 5) { // lines = refs[segment[1]]; // if (!lines) refs[segment[1]] = lines = []; // columns = lines[segment[2]]; // if (columns) { // uniqueAscendingInsert(columns, segment[3]); // } else { // lines[segment[2]] = [segment[3]]; // } // } // } // } // let traced: SourceMapSegment[] | undefined = undefined; // the traced line mapping // let untraced: SourceMapSegment[] | undefined = undefined; // the untraced line mapping // function addSegment( // segment: SourceMapSegment, // source?: { names: string[]; sources: (Link | Source)[] } // ) { // if (source) { // segment[1] = uniq<Link | Source>(sources, source.sources[segment[1]!]); // if (segment.length === 5) { // segment[4] = uniq(names, source.names[segment[4]]); // } // } else if (segment.length === 5) { // segment[4] = uniq(names, node.names[segment[4]]); // } // traced!.push(segment); // } // let tracedLine: number; // the last traced line // let generatedLine = -1; // the current line // let sourceIndex: number | undefined = -1; // source of last traced segment // let sourceLine: number | undefined = undefined; // source line of last traced segment // // Find the next line with segments. // function nextLine() { // tracedLine = generatedLine; // while (++generatedLine < node.mappings.length) { // untraced = node.mappings[generatedLine]; // if (untraced.length) return true; // } // } // // Provide mappings for lines between the // // last traced line and the current line. // function fillSkippedLines() { // const skipped = generatedLine - (tracedLine + 1); // if (skipped !== 0) { // let line = tracedLine; // // Take line mappings from the current source. // if (sourceIndex !== -1) { // const source = node.sources[sourceIndex!]; // if (source instanceof Link) { // while (line < generatedLine - 1) { // if (++sourceLine! !== source.mappings.length) { // mappings[++line] = traced = []; // // Check referenced columns to avoid duplicate segments. // const columns = refs[sourceIndex!][sourceLine!] || []; // let prevColumn = -1; // // Interweave old segments from the current source. // const segments = source.mappings[sourceLine!]; // for (let i = 0; i < segments.length; i++) { // const segment = segments[i]; // if (!hasValueBetween(columns, prevColumn, segment[0] + 1)) { // addSegment([...segment] as SourceMapSegment, source); // prevColumn = segment[0]; // } else break; // } // } else { // // End of source file. // sourceIndex = -1; // break; // } // } // } // } // // Default to empty arrays for unmapped lines. // while (++line < generatedLine) { // mappings[line] = []; // } // } // } // while (nextLine()) { // fillSkippedLines(); // // Trace the segments of this generated line. // mappings[generatedLine] = traced = []; // // Interweave old segments before the first mapped column of each line. // const sourceColumn = untraced![0][3]; // if (sourceIndex !== -1 && sourceColumn !== 0) { // const source = node.sources[sourceIndex]; // if (source instanceof Link) { // const segments = // sourceLine! < source.mappings.length - 1 ? source.mappings[++sourceLine!] : []; // for (let i = 0; i < segments.length; i++) { // const segment = segments[i]; // if (segment[0] < sourceColumn!) { // addSegment(segment.slice(0) as SourceMapSegment, source); // } else break; // } // } // } // const last = untraced!.length - 1; // untraced!.forEach((curr: SourceMapSegment | null, i) => { // [, sourceIndex, sourceLine] = curr!; // const source = node.sources[sourceIndex!]; // if (source === null) { // curr![1] = uniq(sources, null); // return addSegment(curr!); // } // if (!(source instanceof Link)) { // curr![1] = uniq(sources, source); // return addSegment(curr!); // } // const next = i !== last ? untraced![i + 1] : null; // const sourceColumn = curr![3]; // const generatedColumn = curr![0]; // // Find the first segment with a greater column. // const segments = source.mappings[sourceLine!] || []; // let j = findGreaterColumn(segments, sourceColumn!); // // A "base segment" is required for tracing to a grand-parent. // let base; // if (--j !== -1) { // base = segments[j]; // curr![1] = uniq(sources, source.sources[base[1]!]); // curr![2] = base[2]; // curr![3] = base[3]! + sourceColumn! - base[0]; // if (base.length === 5) { // // Inherit the names of aligned base segments. // if (base[0] === sourceColumn) { // curr![4] = uniq(names, source.names[base[4]!]); // } // } else if (curr!.length === 5) { // // When our segment is named and the base segment is not, // // assume this segment cannot be traced to its original source. // if (base[0] !== sourceColumn) curr = null; // } // } else { // curr![1] = uniq(sources, null); // } // curr && addSegment(curr); // // Check referenced columns to avoid duplicate segments. // const columns = refs[sourceIndex!][sourceLine!] || []; // let baseColumn = base ? base[0] : -1; // // Interweave old segments between our current and next segments. // const nextColumn = next ? next[0] : 1 / 0; // while (++j < segments.length) { // let segment = segments[j]; // // The generated column is shifted to fit into the root source map. // const column = segment[0] + generatedColumn - sourceColumn!; // if (column >= nextColumn) break; // // Avoid duplicates by checking if this segment goes elsewhere. // if (!hasValueBetween(columns, baseColumn, segment[0] + 1)) { // baseColumn = segment[0]; // segment = segment.slice(0) as SourceMapSegment; // segment[0] = column; // addSegment(segment, source); // } else break; // } // }); // } // fillSkippedLines(); // node.mappings = mappings; // node.sources = sources; // node.names = names; // return node; // } // // Check if a value exists before pushing it to an array. // // Return the new or existing index of the value. // function uniq<T>(arr: T[], val: T): number { // const i = arr.indexOf(val); // return ~i ? i : arr.push(val) - 1; // } // // Get the first segment with a greater column. // function findGreaterColumn(segments: SourceMapSegment[], column: number) { // let low = 0, // high = segments.length; // while (low < high) { // const mid = (low + high) >>> 1; // segments[mid][0] <= column ? (low = mid + 1) : (high = mid); // } // return low; // } // // The range is exclusive. // function hasValueBetween(arr: number[], start: number, end: number) { // let low = 0, // high = arr.length; // while (low < high) { // const mid = (low + high) >>> 1; // const val = arr[mid]; // if (val <= start) { // low = mid + 1; // } else if (val >= end) { // high = mid; // } else { // return true; // } // } // return false; // } // // Insert unique values in ascending order. // function uniqueAscendingInsert(arr: number[], val: number) { // let low = 0, // high = arr.length; // while (low < high) { // const mid = (low + high) >>> 1; // const x = arr[mid]; // if (x === val) return; // if (x < val) { // low = mid + 1; // } else { // high = mid; // } // } // arr.splice(low, 0, val); // } class PluginManager { constructor(plugins) { this.plugins = plugins; this.plugins.push({ name: 'builtIn', load: async (ctx, id) => { const uri = Uri.parse(id); const readReturn = ctx.resolver.readFileContent(uri); const readResult = isThenable(readReturn) ? await checkCancellation(readReturn, ctx.token) : readReturn; return { code: ctx.resolver.decode(readResult.content), visited: readResult.visited, }; }, resolveDependency: async (ctx, dependency, fromSourceModule) => { const resolveReturn = ctx.resolver.resolve(dependency.spec, fromSourceModule.uri); const resolveResult = isThenable(resolveReturn) ? await checkCancellation(resolveReturn, ctx.token) : resolveReturn; if (!resolveResult.found) { throw new DependencyNotFoundError(dependency.spec, fromSourceModule); } if (!resolveResult.uri) { // TODO: Inject empty module throw new EntryExcludedError(dependency.spec); } return { uri: resolveResult.uri, rootUri: resolveResult.rootUri, visited: resolveResult.visited, }; }, resolveEntrypoint: async (ctx, uri) => { const resolveResult = await ctx.resolver.resolve(uri); if (!resolveResult.found) { throw new EntryNotFoundError(`Entry point not found: ${uri}`); } if (!resolveResult.uri) { throw new EntryExcludedError(uri); } return resolveResult; }, transform: async ({ createMagicString }, id) => { if (id.path.endsWith('.json')) { const magicString = createMagicString(); magicString.prepend('module.exports = '); return { code: magicString.toString(), sourceMap: magicString.generateDecodedMap(), }; } }, }); } async executeLoad(ctx, uri) { for (const plugin of this.plugins) { if (typeof plugin.load === 'function') { const loadReturn = plugin.load(ctx, uri.toString()); const loadResult = isThenable(loadReturn) ? await checkCancellation(loadReturn, ctx.token) : loadReturn; if (!loadResult) { continue; } return { code: loadResult.code, visited: loadResult.visited || [], }; } } throw new Error(`No plugin was found that was able to load the uri ${uri.toString()}`); } async executeResolveDependency(ctx, dependency, fromModule) { for (const plugin of this.plugins) { if (typeof plugin.resolveDependency === 'function') { const loadReturn = plugin.resolveDependency(ctx, dependency, fromModule); const loadResult = isThenable(loadReturn) ? await checkCancellation(loadReturn, ctx.token) : loadReturn; if (!loadResult) { continue; } return { uri: loadResult.uri, rootUri: loadResult.rootUri, visited: loadResult.visited || [], }; } } throw new Error(`No plugin was able to resolve the '${dependency.kind}' dependency, '${dependency.spec}' from '${fromModule.href}'`); } async executeResolveEntrypoint(ctx, uri) { for (const plugin of this.plugins) { if (typeof plugin.resolveEntrypoint === 'function') { const loadReturn = plugin.resolveEntrypoint(ctx, uri); const loadResult = isThenable(loadReturn) ? await checkCancellation(loadReturn, ctx.token) : loadReturn; if (!loadResult) { continue; } return { uri: loadResult.uri, rootUri: loadResult.rootUri, visited: loadResult.visited || [], }; } } throw new Error(`No plugin was able to resolve the entrypoint '${uri.toString()}'`); } async executeTransform(ctx, uri, code) { if (typeof code !== 'string') { code = ctx.resolver.decode(code); } const pluginCtx = Object.assign(ctx, { createMagicString() { return new MagicString(code); }, }); let sourceMapTree = new Source(uri.toString(), code); // Figure out if our original code, itself has a sourcemap. // For now, we will not recurse beyond that depth. const sourceMapRef = getSourceMappingUrl(code); if (sourceMapRef) { let sourceMap = decodeDataUriAsSourceMap(sourceMapRef); if (!sourceMap) { const sourceMapUri = Uri.joinPath(uri, `../${sourceMapRef}`); code = updateSourceMappingUrl(code, sourceMapUri.toString()); } if (sourceMap) { const sources = sourceMap.sources; const sourcesContent = sourceMap.sourcesContent || []; const baseSources = []; for (const idx in sources) { if (sources[idx] && sourcesContent[idx]) { baseSources.push(new Source(sources[idx], sourcesContent[idx])); } } sourceMapTree = new Link(sourceMap, baseSources); } } const visited = []; for (const plugin of this.plugins) { if (typeof plugin.transform === 'function') { const transformReturn = plugin.transform(pluginCtx, uri, code); const transformResult = isThenable(transformReturn) ? await checkCancellation(transformReturn, ctx.token) : transformReturn; if (transformResult === null || transformResult === undefined) { continue; } if (transformResult.sourceMap) { sourceMapTree = new Link(transformResult.sourceMap, [sourceMapTree]); } code = transformResult.code; if (transformResult.visited) { visited.push(...transformResult.visited); } } } return { code, sourceMapTree, visited, }; } } var SourceModuleDependencyKind; (function (SourceModuleDependencyKind) { SourceModuleDependencyKind["Entrypoint"] = "Entrypoint"; SourceModuleDependencyKind["Require"] = "Require"; SourceModuleDependencyKind["RequireResolve"] = "RequireResolve"; SourceModuleDependencyKind["GlobalObject"] = "GlobalObject"; })(SourceModuleDependencyKind || (SourceModuleDependencyKind = {})); class SourceModuleDependency { constructor(kind, spec, locations, options = {}) { this.kind = kind; this.spec = spec; this.locations = locations; this.options = options; } static areIdentical(l, r) { return l.kind === r.kind && l.spec === r.spec; } static fromEntrypoint(uri) { return new SourceModuleDependency(SourceModuleDependencyKind.Entrypoint, uri.toString(), []); } static fromGlobalObject(spec, locations, exportName) { return new SourceModuleDependency(SourceModuleDependencyKind.GlobalObject, spec, locations, { exportName, }); } static fromRequire(spec, locations) { return new SourceModuleDependency(SourceModuleDependencyKind.Require, spec, locations); } static fromRequireResolve(spec, locations) { return new SourceModuleDependency(SourceModuleDependencyKind.RequireResolve, spec, locations); } } function isArrowFunctionExpression(node) { return node.type === 'ArrowFunctionExpression'; } function isArrayPattern(node) { return node.type === 'ArrayPattern'; } function isAssignmentPattern(node) { return node.type === 'AssignmentPattern'; } function isBinaryExpression(node) { return node.type === 'BinaryExpression'; } function isBlockStatement(node) { return node.type === 'BlockStatement'; } function isCallExpression(node) { return node.type === 'CallExpression'; } function isClassDeclaration(node) { return node.type === 'ClassDeclaration'; } function isFunctionDeclaration(node) { return node.type === 'FunctionDeclaration'; } function isFunctionExpression(node) { return node.type === 'FunctionExpression'; } function isIdentifier(node) { return node.type === 'Identifier'; } function isIfStatement(node) { return node.type === 'IfStatement'; } function isLiteral(node) { return node.type === 'Literal'; } function isMemberExpression(node) { return node.type === 'MemberExpression'; } function isMethodDefinition(node) { return node.type === 'MethodDefinition'; } function isObjectPattern(node) { return node.type === 'ObjectPattern'; } function isProperty(node) { return node.type === 'Property'; } function isRestElement(node) { return node.type === 'RestElement'; } function isProgram(node) { return node.type === 'Program'; } function isTemplateLiteral(node) { return node.type === 'TemplateLiteral'; } function isThisExpression(node) { return node.type === 'ThisExpression'; } function isTryStatement(node) { return node.type === 'TryStatement'; } function isUnaryExpression(node) { return node.type === 'UnaryExpression'; } function isVariableDeclaration(node) { return node.type === 'VariableDeclaration'; } // Refinements or groups function isFunction(node) { return (isFunctionDeclaration(node) || isFunctionExpression(node) || isArrowFunctionExpression(node)); } function isStringLiteral(node) { return isLiteral(node) && typeof node.value === 'string'; } function parse(code, options) { return parse$2(code, { ...options, allowReturnOutsideFunction: true, sourceType: 'script', }); } function traverse(ast, ctx, { enter, leave }) { visit(ast, null, ctx, enter, leave); } let shouldSkip = false; const context = { skip: () => (shouldSkip = true) }; const childKeys = {}; function visit(node, parent, ctx, enter, leave // prop?: string, // index?: number ) { if (!node) return; node.parent = parent; if (enter) { const _shouldSkip = shouldSkip; shouldSkip = false; enter.call(context, node, parent, ctx); const skipped = shouldSkip; shouldSkip = _shouldSkip; if (skipped) return; } const keys = childKeys[node.type] || (childKeys[node.type] = Object.keys(node).filter((key) => key !== 'parent' && typeof node[key] === 'object')); const children = []; for (let i = 0; i < keys.length; i++) { const key = keys[i]; const value = node[key]; if (Array.isArray(value)) { for (let j = 0; j < value.length; j++) { if (value[j]) children.push(value[j]); } } else if (value && value.type) { children.push(value); } } children.sort((a, b) => a.start - b.start); for (const child of children) { visit(child, node, ctx, enter, leave); } if (leave) { leave(node, parent, ctx); } } const parse$1 = function parseJavaScript(uri, code, options) { const visitorCtx = { unboundSymbols: new Map(), locals: new Map(), magicString: new MagicString(code, { filename: uri.toString(), indentExclusionRanges: [] }), nodeEnv: options.nodeEnv, replacedSymbols: new Set(), requires: [], requireResolves: [], skip: new Set(), skipTransform: new Set(), }; const dependencies = []; try { // let lastToken: Token | undefined; const ast = parse(code, { // onComment: (_isBlock, _test, start, end) => { // result.changes.push({ type: 'remove', start, end }); // }, // onInsertedSemicolon(lastTokEnd) { // result.changes.push({ type: 'appendRight', position: lastTokEnd, value: ';' }); // }, // onToken: (token) => { // const start = lastToken ? lastToken.end + 1 : 0; // const end = token.start; // if (end > start) { // result.changes.push({ type: 'remove', start, end }); // } // lastToken = token; // }, }); traverse(ast, visitorCtx, scopingAndRequiresVisitor); traverse(ast, visitorCtx, collectGlobalsVisitor); } catch (err) { // console.debug(code); // console.trace(err); throw new Error(`Error parsing ${uri}: ${err.message}`); } // Handle explicit requires const requiresBySpec = new Map(); for (const requireDependency of visitorCtx.requires) { let locations = requiresBySpec.get(requireDependency.spec.value); if (!locations) { locations = []; requiresBySpec.set(requireDependency.spec.value, locations); } locations.push({ start: requireDependency.spec.start, end: requireDependency.spec.end }); } for (const [spec, locations] of requiresBySpec) { dependencies.push(SourceModuleDependency.fromRequire(spec, locations)); } // Handle require.resolve const requireResolvesBySpec = new Map(); for (const requireDependency of visitorCtx.requireResolves) { let locations = requiresBySpec.get(requireDependency.spec.value); if (!locations) { locations = []; requiresBySpec.set(requireDependency.spec.value, locations); } locations.push({ start: requireDependency.spec.start, end: requireDependency.spec.end }); } for (const [spec, locations] of requireResolvesBySpec) { dependencies.push(SourceModuleDependency.fromRequireResolve(spec, locations)); } for (const [symbolName, locations] of visitorCtx.unboundSymbols) { const shim = options.globalModules[symbolName]; if (shim) { dependencies.push(SourceModuleDependency.fromGlobalObject(shim.spec, locations, shim.export)); for (const location of locations) { visitorCtx.magicString.overwrite(location.start, location.end, `require(${JSON.stringify(`${shim.spec}`)})${shim.export ? `.${shim.export}` : ''}`); } } } return { code: visitorCtx.magicString, dependencies, }; }; const scopingAndRequiresVisitor = { enter(node, parent, ctx) { // Get AST-node level locations in the source map ctx.magicString.addSourcemapLocation(node.start); ctx.magicString.addSourcemapLocation(node.end); if (ctx.skip.has(node)) { return this.skip(); } visitAndCaptureScoping(node, parent, ctx); visitAndSkipBranches(node, parent, ctx); visitRequires(node, parent, ctx); }, leave(node, _parent, ctx) { let skipped = false; let nextCheck = node; while (nextCheck) { if (ctx.skipTransform.has(nextCheck)) { skipped = true; break; } nextCheck = nextCheck.parent; } if (!skipped && isMemberExpression(node) && memberExpressionMatches(node, 'process.env.NODE_ENV')) { ctx.magicString.overwrite(node.start, node.end, JSON.stringify(ctx.nodeEnv), { contentOnly: true, storeName: true, }); ctx.skip.add(node); ctx.skipTransform.add(node); } }, }; const collectGlobalsVisitor = { enter(node, _parent, ctx) { if (ctx.skip.has(node)) { return this.skip(); } if (isBindingIdentifier(node) && isIdentifier(node) && !isArgumentOfTypeOf(node)) { var name = node.name; if (name === 'undefined') return; if (ctx.replacedSymbols.has(node)) { return; } let foundBinding = false; let nextParent = node.parent; while (nextParent) { if (name === 'arguments' && declaresArguments(nextParent)) { foundBinding = true; break; } const locals = ctx.locals.get(nextParent); if (locals && locals[name]) { foundBinding = true; break; } nextParent = nextParent.parent; } if (!foundBinding) { let unboundSymbols = ctx.unboundSymbols.get(name); if (!unboundSymbols) { unboundSymbols = []; ctx.unboundSymbols.set(name, unboundSymbols); } unboundSymbols.push(node); } } else if (isThisExpression(node)) { let foundBinding = false; let nextParent = node.parent; while (nextParent) { if (declaresThis(nextParent)) { foundBinding = true; break; } nextParent = nextParent.parent; } if (!foundBinding) { let unboundSymbols = ctx.unboundSymbols.get('this'); if (!unboundSymbols) { unboundSymbols = []; ctx.unboundSymbols.set('this', unboundSymbols); } unboundSymbols.push(node); } } }, }; function visitAndCaptureScoping(node, _parent, ctx) { if (isVariableDeclaration(node)) { let parent; let nextParent = node.parent; while (nextParent) { if (node.kind === 'var' ? isScope(nextParent) : isBlockScope(nextParent)) { parent = nextParent; break; } nextParent = nextParent.parent; } if (!parent) { throw new Error(`Invariant violation: Failed to find a parent`); } let locals = ctx.locals.get(parent); if (!locals) { locals = {}; ctx.locals.set(parent, locals); } for (const declaration of node.declarations) { declarePattern(declaration.id, locals); } } else if (isFunctionDeclaration(node)) { let parent; let nextParent = node.parent; if (nextParent && nextParent.parent) { nextParent = nextParent.parent; } while (nextParent) { if (isScope(nextParent)) { parent = nextParent; break; } nextParent = nextParent.parent; } if (!parent) { throw new Error(`Invariant violation: Failed to find a parent`); } let locals = ctx.locals.get(parent); if (!locals) { locals = {}; ctx.locals.set(parent, locals); } declareFunction(node, locals); } else if (isFunction(node)) { let locals = ctx.locals.get(node); if (!locals) { locals = {}; ctx.locals.set(node, locals); } declareFunction(node, locals); } else if (isClassDeclaration(node) && node.id) { let parent; let nextParent = node.parent; if (nextParent && nextParent.parent) { nextParent = nextParent.parent; } while (nextParent) { if (isScope(nextParent)) { parent = nextParent; break; } nextParent = nextParent.parent; } if (!parent) { throw new Error(`Invariant violation: Failed to find a parent`); } let locals = ctx.locals.get(parent); if (!locals) { locals = {}; ctx.locals.set(parent, locals); } locals[node.id.name] = true; } else if (isTryStatement(node)) { if (node.handler) { let locals = ctx.locals.get(node.handler); if (!locals) { locals = {}; ctx.locals.set(node.handler, locals); } if (node.handler.param) { declarePattern(node.handler.param, locals); } } } } function visitAndSkipBranches(node, _parent, ctx) { if (isIfStatement(node) && isBinaryExpression(node.test)) { const tests = { '!=': (l, r) => l != r, '!==': (l, r) => l !== r, '==': (l, r) => l == r, '===': (l, r) => l === r, }; const test = tests[node.test.operator]; if (test) { if (isStringLiteral(node.test.left) && isMemberExpression(node.test.right) && memberExpressionMatches(node.test.right, 'process.env.NODE_ENV')) { let rootObject = node.test.right; while (isMemberExpression(rootObject.object)) { rootObject = rootObject.object; } if (isIdentifier(rootObject.object)) { ctx.replacedSymbols.add(rootObject.object); } ctx.skipTransform.add(node.test.right); // if ('development' === process.env.NODE_ENV) {} if (!test(node.test.left.value, ctx.nodeEnv)) { ctx.skip.add(node.consequent); // We can blow away the consequent ctx.magicString.remove(node.start, node.alternate ? node.alternate.start : node.consequent.end); } else { // We can blow away the test ctx.magicString.remove(node.start, node.consequent.start - 1); if (node.alternate) { ctx.skip.add(node.alternate); // We can blow away the alternate but we need to start and the end of the consequent + 1 char ctx.magicString.remove(node.consequent.end + 1, node.alternate.end); } } } else if (isStringLiteral(node.test.right) && isMemberExpression(node.test.left) && memberExpressionMatches(node.test.left, 'process.env.NODE_ENV')) { let rootObject = node.test.left; while (isMemberExpression(rootObject.object)) { rootObject = rootObject.object; } if (isIdentifier(rootObject.object)) { ctx.replacedSymbols.add(rootObject.object); } ctx.skipTransform.add(node.test.left); // if (process.env.NODE_ENV === 'development') {} if (!test(node.test.right.value, ctx.nodeEnv)) { ctx.skip.add(node.consequent); // We can blow away the consequent ctx.magicString.remove(node.start, node.alternate ? node.alternate.start : node.consequent.end); } else { // We can blow away the test and the alternate ctx.magicString.remove(node.start, node.consequent.start - 1); if (node.alternate) { ctx.skip.add(node.alternate); // We can blow away the alternate but we need to start and the end of the consequent + 1 char ctx.magicString.remove(node.consequent.end + 1, node.alternate.end); } } } } } } function visitRequires(node, _parent, ctx) { if (isCallExpression(node)) { const callee = node.callee; if (isIdentifier(callee) && callee.name === 'require') { const firstArg = node.arguments[0]; if (isStringLiteral(firstArg)) { ctx.requires.push({ spec: { start: firstArg.start, end: firstArg.end, value: firstArg.value }, callee: { start: callee.start, end: callee.end }, }); } else if (isTemplateLiteral(firstArg) && firstArg.expressions.length === 0 && firstArg.quasis.length === 1) { ctx.requires.push({ spec: { start: firstArg.quasis[0].start, end: firstArg.quasis[0].end, value: firstArg.quasis[0].value.raw, }, callee: { start: callee.start, end: callee.end }, }); } else { console.warn('Non string-literal first arg to require', firstArg); } } else if (isMemberExpression(callee) && isIdentifier(callee.object) && callee.object.name === 'require' && isIdentifier(callee.property) && callee.property.name === 'resolve') { const firstArg = node.arguments[0]; if (isStringLiteral(firstArg)) { ctx.requireResolves.push({ spec: { start: firstArg.start, end: firstArg.end, value: firstArg.value }, callee: { start: callee.start, end: callee.end }, }); } else { console.warn('Non string-literal first arg to require.resolve', firstArg); } } } } function declareFunction(node, locals) { node.params.forEach(function (node) { declarePattern(node, locals); }); if (node.id) { locals[node.id.name] = true; } } function declarePattern(node, locals) { if (isIdentifier(node)) { locals[node.name] = true; } else if (isObjectPattern(node)) { node.properties.forEach((node) => isRestElement(node) ? declarePattern(node.argument, locals) : declarePattern(node.value, locals)); } else if (isArrayPattern(node)) { node.elements.forEach((node) => node && declarePattern(node, locals)); } else if (isRestElement(node)) { declarePattern(node.argument, locals); } else if (isAssignmentPattern(node)) { declarePattern(node.left, locals); } else { throw new Error(`Invariant violation: Unexpected pattern type: ${node.type}`); } } function isArgumentOfTypeOf(node) { const parent = node.parent; return isUnaryExpression(parent) && parent.operator === 'typeof'; } function isBindingIdentifier(node) { return (isIdentifier(node) && !isPropertyOfMemberExpression(node) && !isKeyOfProperty(node) && !isKeyOfMethodDefinition(node)); } function isKeyOfProperty(node) { return node.parent && isProperty(node.parent) && node.parent.key === node; } function isPropertyOfMemberExpression(node) { return node.parent && isMemberExpression(node.parent) && node.parent.object !== node; } function isKeyOfMethodDefinition(node) { return node.parent && isMethodDefinition(node.parent); } function isScope(node) { return (isFunctionDeclaration(node) || isFunctionExpression(node) || isArrowFunctionExpression(node) || isProgram(node)); } function isBlockScope(node) { return isBlockStatement(node) || isScope(node); } function declaresArguments(node) { return isFunctionDeclaration(node) || isFunctionExpression(node); } function declaresThis(node) { return isFunctionDeclaration(node) || isFunctionExpression(node); } function memberExpressionMatches(node, pattern) { const memberParts = pattern.split('.'); if (memberParts.length < 2) { return false; } const object = memberParts.shift(); const property = memberParts.shift(); for (let i = memberParts.length - 1; i >= 0; i--) { if (!isIdentifier(node.property) || node.property.name !== memberParts[i]) { return false; } if (!isMemberExpression(node.object)) { return false; } node = node.object; } if (!isIdentifier(node.object) || !isIdentifier(node.property)) { return false; } return node.object.name === object && node.property.name === property; } class ChunkOutput { constructor(bundle, sourceMapTree, entrypoints) { this.bundle = bundle; this.sourceMapTree = sourceMapTree; this.entrypoints = entrypoints; } get code() { i