"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ScopeHoistingPackager = void 0; function _utils() { const data = require("@parcel/utils"); _utils = function () { return data; }; return data; } function _sourceMap2() { const data = _interopRequireDefault(require("@parcel/source-map")); _sourceMap2 = function () { return data; }; return data; } function _nullthrows() { const data = _interopRequireDefault(require("nullthrows")); _nullthrows = function () { return data; }; return data; } function _assert() { const data = _interopRequireDefault(require("assert")); _assert = function () { return data; }; return data; } function _diagnostic() { const data = _interopRequireDefault(require("@parcel/diagnostic")); _diagnostic = function () { return data; }; return data; } function _globals() { const data = _interopRequireDefault(require("globals")); _globals = function () { return data; }; return data; } var _ESMOutputFormat = require("./ESMOutputFormat"); var _CJSOutputFormat = require("./CJSOutputFormat"); var _GlobalOutputFormat = require("./GlobalOutputFormat"); var _helpers = require("./helpers"); var _utils2 = require("./utils"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // https://262.ecma-international.org/6.0/#sec-names-and-keywords const IDENTIFIER_RE = /^[$_\p{ID_Start}][$_\u200C\u200D\p{ID_Continue}]*$/u; const ID_START_RE = /^[$_\p{ID_Start}]/u; const NON_ID_CONTINUE_RE = /[^$_\u200C\u200D\p{ID_Continue}]/gu; // General regex used to replace imports with the resolved code, references with resolutions, // and count the number of newlines in the file for source maps. const REPLACEMENT_RE = /\n|import\s+"([0-9a-f]{16}:.+?)";|(?:\$[0-9a-f]{16}\$exports)|(?:\$[0-9a-f]{16}\$(?:import|importAsync|require)\$[0-9a-f]+(?:\$[0-9a-f]+)?)/g; const BUILTINS = Object.keys(_globals().default.builtin); const GLOBALS_BY_CONTEXT = { browser: new Set([...BUILTINS, ...Object.keys(_globals().default.browser)]), 'web-worker': new Set([...BUILTINS, ...Object.keys(_globals().default.worker)]), 'service-worker': new Set([...BUILTINS, ...Object.keys(_globals().default.serviceworker)]), worklet: new Set([...BUILTINS]), node: new Set([...BUILTINS, ...Object.keys(_globals().default.node)]), 'electron-main': new Set([...BUILTINS, ...Object.keys(_globals().default.node)]), 'electron-renderer': new Set([...BUILTINS, ...Object.keys(_globals().default.node), ...Object.keys(_globals().default.browser)]) }; const OUTPUT_FORMATS = { esmodule: _ESMOutputFormat.ESMOutputFormat, commonjs: _CJSOutputFormat.CJSOutputFormat, global: _GlobalOutputFormat.GlobalOutputFormat }; class ScopeHoistingPackager { exportedSymbols = new Map(); externals = new Map(); topLevelNames = new Map(); seenAssets = new Set(); wrappedAssets = new Set(); hoistedRequires = new Map(); needsPrelude = false; usedHelpers = new Set(); constructor(options, bundleGraph, bundle, parcelRequireName) { this.options = options; this.bundleGraph = bundleGraph; this.bundle = bundle; this.parcelRequireName = parcelRequireName; let OutputFormat = OUTPUT_FORMATS[this.bundle.env.outputFormat]; this.outputFormat = new OutputFormat(this); this.isAsyncBundle = this.bundleGraph.hasParentBundleOfType(this.bundle, 'js') && !this.bundle.env.isIsolated() && this.bundle.bundleBehavior !== 'isolated'; this.globalNames = GLOBALS_BY_CONTEXT[bundle.env.context]; } async package() { var _sourceMap; let wrappedAssets = await this.loadAssets(); this.buildExportedSymbols(); // If building a library, the target is actually another bundler rather // than the final output that could be loaded in a browser. So, loader // runtimes are excluded, and instead we add imports into the entry bundle // of each bundle group pointing at the sibling bundles. These can be // picked up by another bundler later at which point runtimes will be added. if (this.bundle.env.isLibrary || this.bundle.env.outputFormat === 'commonjs') { let bundles = this.bundleGraph.getReferencedBundles(this.bundle); for (let b of bundles) { this.externals.set((0, _utils().relativeBundlePath)(this.bundle, b), new Map()); } } let res = ''; let lineCount = 0; let sourceMap = null; let processAsset = asset => { let [content, map, lines] = this.visitAsset(asset); if (sourceMap && map) { sourceMap.addSourceMap(map, lineCount); } else if (this.bundle.env.sourceMap) { sourceMap = map; } res += content + '\n'; lineCount += lines + 1; }; // Hoist wrapped asset to the top of the bundle to ensure that they are registered // before they are used. for (let asset of wrappedAssets) { if (!this.seenAssets.has(asset.id)) { processAsset(asset); } } // Add each asset that is directly connected to the bundle. Dependencies will be handled // by replacing `import` statements in the code. this.bundle.traverseAssets((asset, _, actions) => { if (this.seenAssets.has(asset.id)) { actions.skipChildren(); return; } processAsset(asset); actions.skipChildren(); }); let [prelude, preludeLines] = this.buildBundlePrelude(); res = prelude + res; lineCount += preludeLines; (_sourceMap = sourceMap) === null || _sourceMap === void 0 ? void 0 : _sourceMap.offsetLines(1, preludeLines); let entries = this.bundle.getEntryAssets(); let mainEntry = this.bundle.getMainEntry(); if (this.isAsyncBundle) { // In async bundles we don't want the main entry to execute until we require it // as there might be dependencies in a sibling bundle that hasn't loaded yet. entries = entries.filter(a => { var _mainEntry; return a.id !== ((_mainEntry = mainEntry) === null || _mainEntry === void 0 ? void 0 : _mainEntry.id); }); mainEntry = null; } // If any of the entry assets are wrapped, call parcelRequire so they are executed. for (let entry of entries) { if (this.wrappedAssets.has(entry.id) && !this.isScriptEntry(entry)) { var _entry$symbols$get; let parcelRequire = `parcelRequire(${JSON.stringify(this.bundleGraph.getAssetPublicId(entry))});\n`; let entryExports = (_entry$symbols$get = entry.symbols.get('*')) === null || _entry$symbols$get === void 0 ? void 0 : _entry$symbols$get.local; if (entryExports && entry === mainEntry && this.exportedSymbols.has(entryExports)) { res += `\nvar ${entryExports} = ${parcelRequire}`; } else { res += `\n${parcelRequire}`; } lineCount += 2; } } let [postlude, postludeLines] = this.outputFormat.buildBundlePostlude(); res += postlude; lineCount += postludeLines; // The entry asset of a script bundle gets hoisted outside the bundle wrapper so that // its top-level variables become globals like a real browser script. We need to replace // all dependency references for runtimes with a parcelRequire call. if (this.bundle.env.outputFormat === 'global' && this.bundle.env.sourceType === 'script') { res += '\n'; lineCount++; let mainEntry = (0, _nullthrows().default)(this.bundle.getMainEntry()); let { code, map: mapBuffer } = (0, _nullthrows().default)(this.assetOutputs.get(mainEntry.id)); let map; if (mapBuffer) { map = new (_sourceMap2().default)(this.options.projectRoot, mapBuffer); } res += (0, _utils2.replaceScriptDependencies)(this.bundleGraph, this.bundle, code, map, this.parcelRequireName); if (sourceMap && map) { sourceMap.addSourceMap(map, lineCount); } } return { contents: res, map: sourceMap }; } async loadAssets() { let queue = new (_utils().PromiseQueue)({ maxConcurrent: 32 }); let wrapped = []; this.bundle.traverseAssets((asset, shouldWrap) => { queue.add(async () => { let [code, map] = await Promise.all([asset.getCode(), this.bundle.env.sourceMap ? asset.getMapBuffer() : null]); return [asset.id, { code, map }]; }); if (shouldWrap || asset.meta.shouldWrap || this.isAsyncBundle || this.bundle.env.sourceType === 'script' || this.bundleGraph.isAssetReferenced(this.bundle, asset) || this.bundleGraph.getIncomingDependencies(asset).some(dep => dep.meta.shouldWrap && dep.specifierType !== 'url')) { this.wrappedAssets.add(asset.id); wrapped.push(asset); return true; } }); this.assetOutputs = new Map(await queue.run()); return wrapped; } buildExportedSymbols() { if (this.isAsyncBundle || !this.bundle.env.isLibrary || this.bundle.env.outputFormat !== 'esmodule') { return; } // TODO: handle ESM exports of wrapped entry assets... let entry = this.bundle.getMainEntry(); if (entry && !this.wrappedAssets.has(entry.id)) { for (let { asset, exportAs, symbol, exportSymbol } of this.bundleGraph.getExportedSymbols(entry)) { if (typeof symbol === 'string') { var _this$exportedSymbols, _entry$symbols$get2; let symbols = (_this$exportedSymbols = this.exportedSymbols.get(symbol === '*' ? (0, _nullthrows().default)((_entry$symbols$get2 = entry.symbols.get('*')) === null || _entry$symbols$get2 === void 0 ? void 0 : _entry$symbols$get2.local) : symbol)) === null || _this$exportedSymbols === void 0 ? void 0 : _this$exportedSymbols.exportAs; if (!symbols) { symbols = []; this.exportedSymbols.set(symbol, { asset, exportSymbol, local: symbol, exportAs: symbols }); } if (exportAs === '*') { exportAs = 'default'; } symbols.push(exportAs); } else if (symbol === null) {// TODO `meta.exportsIdentifier[exportSymbol]` should be exported // let relativePath = relative(options.projectRoot, asset.filePath); // throw getThrowableDiagnosticForNode( // md`${relativePath} couldn't be statically analyzed when importing '${exportSymbol}'`, // entry.filePath, // loc, // ); } else if (symbol !== false) {// let relativePath = relative(options.projectRoot, asset.filePath); // throw getThrowableDiagnosticForNode( // md`${relativePath} does not export '${exportSymbol}'`, // entry.filePath, // loc, // ); } } } } getTopLevelName(name) { name = name.replace(NON_ID_CONTINUE_RE, ''); if (!ID_START_RE.test(name) || this.globalNames.has(name)) { name = '_' + name; } let count = this.topLevelNames.get(name); if (count == null) { this.topLevelNames.set(name, 1); return name; } this.topLevelNames.set(name, count + 1); return name + count; } getPropertyAccess(obj, property) { if (IDENTIFIER_RE.test(property)) { return `${obj}.${property}`; } return `${obj}[${JSON.stringify(property)}]`; } visitAsset(asset) { (0, _assert().default)(!this.seenAssets.has(asset.id), 'Already visited asset'); this.seenAssets.add(asset.id); let { code, map } = (0, _nullthrows().default)(this.assetOutputs.get(asset.id)); return this.buildAsset(asset, code, map); } buildAsset(asset, code, map) { let shouldWrap = this.wrappedAssets.has(asset.id); let deps = this.bundleGraph.getDependencies(asset); let sourceMap = this.bundle.env.sourceMap && map ? new (_sourceMap2().default)(this.options.projectRoot, map) : null; // If this asset is skipped, just add dependencies and not the asset's content. if (this.shouldSkipAsset(asset)) { let depCode = ''; let lineCount = 0; for (let dep of deps) { let resolved = this.bundleGraph.getResolvedAsset(dep, this.bundle); let skipped = this.bundleGraph.isDependencySkipped(dep); if (!resolved || skipped) { continue; } if (this.bundle.hasAsset(resolved) && !this.seenAssets.has(resolved.id)) { let [code, map, lines] = this.visitAsset(resolved); depCode += code + '\n'; if (sourceMap && map) { sourceMap.addSourceMap(map, lineCount); } lineCount += lines + 1; } } return [depCode, sourceMap, lineCount]; } // TODO: maybe a meta prop? if (code.includes('$parcel$global')) { this.usedHelpers.add('$parcel$global'); } let [depMap, replacements] = this.buildReplacements(asset, deps); let [prepend, prependLines, append] = this.buildAssetPrelude(asset, deps); if (prependLines > 0) { sourceMap === null || sourceMap === void 0 ? void 0 : sourceMap.offsetLines(1, prependLines); code = prepend + code; } code += append; let lineCount = 0; let depContent = []; if (depMap.size === 0 && replacements.size === 0) { // If there are no dependencies or replacements, use a simple function to count the number of lines. lineCount = (0, _utils().countLines)(code) - 1; } else { // Otherwise, use a regular expression to perform replacements. // We need to track how many newlines there are for source maps, replace // all import statements with dependency code, and perform inline replacements // of all imported symbols with their resolved export symbols. This is all done // in a single regex so that we only do one pass over the whole code. let offset = 0; let columnStartIndex = 0; code = code.replace(REPLACEMENT_RE, (m, d, i) => { var _replacements$get; if (m === '\n') { columnStartIndex = i + offset + 1; lineCount++; return '\n'; } // If we matched an import, replace with the source code for the dependency. if (d != null) { let dep = depMap.get(d); if (!dep) { return m; } let resolved = this.bundleGraph.getResolvedAsset(dep, this.bundle); let skipped = this.bundleGraph.isDependencySkipped(dep); if (resolved && !skipped) { // Hoist variable declarations for the referenced parcelRequire dependencies // after the dependency is declared. This handles the case where the resulting asset // is wrapped, but the dependency in this asset is not marked as wrapped. This means // that it was imported/required at the top-level, so its side effects should run immediately. let [res, lines] = this.getHoistedParcelRequires(asset, dep, resolved); let map; if (this.bundle.hasAsset(resolved) && !this.seenAssets.has(resolved.id)) { // If this asset is wrapped, we need to hoist the code for the dependency // outside our parcelRequire.register wrapper. This is safe because all // assets referenced by this asset will also be wrapped. Otherwise, inline the // asset content where the import statement was. if (shouldWrap) { depContent.push(this.visitAsset(resolved)); } else { let [depCode, depMap, depLines] = this.visitAsset(resolved); res = depCode + '\n' + res; lines += 1 + depLines; map = depMap; } } // Push this asset's source mappings down by the number of lines in the dependency // plus the number of hoisted parcelRequires. Then insert the source map for the dependency. if (sourceMap) { if (lines > 0) { sourceMap.offsetLines(lineCount + 1, lines); } if (map) { sourceMap.addSourceMap(map, lineCount); } } lineCount += lines; return res; } return ''; } // If it wasn't a dependency, then it was an inline replacement (e.g. $id$import$foo -> $id$export$foo). let replacement = (_replacements$get = replacements.get(m)) !== null && _replacements$get !== void 0 ? _replacements$get : m; if (sourceMap) { // Offset the source map columns for this line if the replacement was a different length. // This assumes that the match and replacement both do not contain any newlines. let lengthDifference = replacement.length - m.length; if (lengthDifference !== 0) { sourceMap.offsetColumns(lineCount + 1, i + offset - columnStartIndex + m.length, lengthDifference); offset += lengthDifference; } } return replacement; }); } // If the asset is wrapped, we need to insert the dependency code outside the parcelRequire.register // wrapper. Dependencies must be inserted AFTER the asset is registered so that circular dependencies work. if (shouldWrap) { // Offset by one line for the parcelRequire.register wrapper. sourceMap === null || sourceMap === void 0 ? void 0 : sourceMap.offsetLines(1, 1); lineCount++; code = `parcelRequire.register(${JSON.stringify(this.bundleGraph.getAssetPublicId(asset))}, function(module, exports) { ${code} }); `; lineCount += 2; for (let [depCode, map, lines] of depContent) { if (!depCode) continue; code += depCode + '\n'; if (sourceMap && map) { sourceMap.addSourceMap(map, lineCount); } lineCount += lines + 1; } this.needsPrelude = true; } return [code, sourceMap, lineCount]; } buildReplacements(asset, deps) { let assetId = asset.meta.id; (0, _assert().default)(typeof assetId === 'string'); // Build two maps: one of import specifiers, and one of imported symbols to replace. // These will be used to build a regex below. let depMap = new Map(); let replacements = new Map(); for (let dep of deps) { depMap.set(`${assetId}:${(0, _utils2.getSpecifier)(dep)}`, dep); let asyncResolution = this.bundleGraph.resolveAsyncDependency(dep, this.bundle); let resolved = (asyncResolution === null || asyncResolution === void 0 ? void 0 : asyncResolution.type) === 'asset' ? // Prefer the underlying asset over a runtime to load it. It will // be wrapped in Promise.resolve() later. asyncResolution.value : this.bundleGraph.getResolvedAsset(dep, this.bundle); if (!resolved && !dep.isOptional && !this.bundleGraph.isDependencySkipped(dep)) { let external = this.addExternal(dep); for (let [imported, { local }] of dep.symbols) { // If already imported, just add the already renamed variable to the mapping. let renamed = external.get(imported); if (renamed && local !== '*') { replacements.set(local, renamed); continue; } // For CJS output, always use a property lookup so that exports remain live. // For ESM output, use named imports which are always live. if (this.bundle.env.outputFormat === 'commonjs') { renamed = external.get('*'); if (!renamed) { renamed = this.getTopLevelName(`$${this.bundle.publicId}$${dep.specifier}`); external.set('*', renamed); } if (local !== '*') { let replacement; if (imported === '*') { replacement = renamed; } else if (imported === 'default') { replacement = `($parcel$interopDefault(${renamed}))`; this.usedHelpers.add('$parcel$interopDefault'); } else { replacement = this.getPropertyAccess(renamed, imported); } replacements.set(local, replacement); } } else { // Rename the specifier so that multiple local imports of the same imported specifier // are deduplicated. We have to prefix the imported name with the bundle id so that // local variables do not shadow it. if (this.exportedSymbols.has(local)) { renamed = local; } else if (imported === 'default' || imported === '*') { renamed = this.getTopLevelName(`$${this.bundle.publicId}$${dep.specifier}`); } else { renamed = this.getTopLevelName(`$${this.bundle.publicId}$${imported}`); } external.set(imported, renamed); if (local !== '*') { replacements.set(local, renamed); } } } } if (!resolved) { continue; } for (let [imported, { local }] of dep.symbols) { if (local === '*') { continue; } let symbol = this.getSymbolResolution(asset, resolved, imported, dep); replacements.set(local, // If this was an internalized async asset, wrap in a Promise.resolve. (asyncResolution === null || asyncResolution === void 0 ? void 0 : asyncResolution.type) === 'asset' ? `Promise.resolve(${symbol})` : symbol); } // Async dependencies need a namespace object even if all used symbols were statically analyzed. // This is recorded in the promiseSymbol meta property set by the transformer rather than in // symbols so that we don't mark all symbols as used. if (dep.priority === 'lazy' && dep.meta.promiseSymbol) { let promiseSymbol = dep.meta.promiseSymbol; (0, _assert().default)(typeof promiseSymbol === 'string'); let symbol = this.getSymbolResolution(asset, resolved, '*', dep); replacements.set(promiseSymbol, (asyncResolution === null || asyncResolution === void 0 ? void 0 : asyncResolution.type) === 'asset' ? `Promise.resolve(${symbol})` : symbol); } } // If this asset is wrapped, we need to replace the exports namespace with `module.exports`, // which will be provided to us by the wrapper. if (this.wrappedAssets.has(asset.id) || this.bundle.env.outputFormat === 'commonjs' && asset === this.bundle.getMainEntry()) { var _asset$symbols$get; let exportsName = ((_asset$symbols$get = asset.symbols.get('*')) === null || _asset$symbols$get === void 0 ? void 0 : _asset$symbols$get.local) || `$${assetId}$exports`; replacements.set(exportsName, 'module.exports'); } return [depMap, replacements]; } addExternal(dep) { if (this.bundle.env.outputFormat === 'global') { throw new (_diagnostic().default)({ diagnostic: { message: 'External modules are not supported when building for browser', codeFrames: [{ filePath: (0, _nullthrows().default)(dep.sourcePath), codeHighlights: dep.loc ? [{ start: dep.loc.start, end: dep.loc.end }] : [] }] } }); } // Map of DependencySpecifier -> Map> let external = this.externals.get(dep.specifier); if (!external) { external = new Map(); this.externals.set(dep.specifier, external); } return external; } getSymbolResolution(parentAsset, resolved, imported, dep) { var _resolvedAsset$symbol; let { asset: resolvedAsset, exportSymbol, symbol } = this.bundleGraph.getSymbolResolution(resolved, imported, this.bundle); if (resolvedAsset.type !== 'js') { // Graceful fallback for non-js imports return '{}'; } let isWrapped = !this.bundle.hasAsset(resolvedAsset) || this.wrappedAssets.has(resolvedAsset.id) && resolvedAsset !== parentAsset; let staticExports = resolvedAsset.meta.staticExports !== false; let publicId = this.bundleGraph.getAssetPublicId(resolvedAsset); // If the rsolved asset is wrapped, but imported at the top-level by this asset, // then we hoist parcelRequire calls to the top of this asset so side effects run immediately. if (isWrapped && dep && !(dep !== null && dep !== void 0 && dep.meta.shouldWrap) && symbol !== false) { let hoisted = this.hoistedRequires.get(dep.id); if (!hoisted) { hoisted = new Map(); this.hoistedRequires.set(dep.id, hoisted); } hoisted.set(resolvedAsset.id, `var $${publicId} = parcelRequire(${JSON.stringify(publicId)});`); } if (isWrapped) { this.needsPrelude = true; } // If this is an ESM default import of a CJS module with a `default` symbol, // and no __esModule flag, we need to resolve to the namespace instead. let isDefaultInterop = exportSymbol === 'default' && staticExports && !isWrapped && ((dep === null || dep === void 0 ? void 0 : dep.meta.kind) === 'Import' || (dep === null || dep === void 0 ? void 0 : dep.meta.kind) === 'Export') && resolvedAsset.symbols.hasExportSymbol('*') && resolvedAsset.symbols.hasExportSymbol('default') && !resolvedAsset.symbols.hasExportSymbol('__esModule'); // Find the namespace object for the resolved module. If wrapped and this // is an inline require (not top-level), use a parcelRequire call, otherwise // the hoisted variable declared above. Otherwise, if not wrapped, use the // namespace export symbol. let assetId = resolvedAsset.meta.id; (0, _assert().default)(typeof assetId === 'string'); let obj = isWrapped && (!dep || dep !== null && dep !== void 0 && dep.meta.shouldWrap) ? // Wrap in extra parenthesis to not change semantics, e.g.`new (parcelRequire("..."))()`. `(parcelRequire(${JSON.stringify(publicId)}))` : isWrapped && dep ? `$${publicId}` : ((_resolvedAsset$symbol = resolvedAsset.symbols.get('*')) === null || _resolvedAsset$symbol === void 0 ? void 0 : _resolvedAsset$symbol.local) || `$${assetId}$exports`; if (imported === '*' || exportSymbol === '*' || isDefaultInterop) { // Resolve to the namespace object if requested or this is a CJS default interop reqiure. return obj; } else if ((!staticExports || isWrapped || !symbol) && resolvedAsset !== parentAsset) { // If the resolved asset is wrapped or has non-static exports, // we need to use a member access off the namespace object rather // than a direct reference. If importing default from a CJS module, // use a helper to check the __esModule flag at runtime. let kind = dep === null || dep === void 0 ? void 0 : dep.meta.kind; if ((!dep || kind === 'Import' || kind === 'Export') && exportSymbol === 'default' && resolvedAsset.symbols.hasExportSymbol('*') && this.needsDefaultInterop(resolvedAsset)) { this.usedHelpers.add('$parcel$interopDefault'); return `(/*@__PURE__*/$parcel$interopDefault(${obj}))`; } else { return this.getPropertyAccess(obj, exportSymbol); } } else if (!symbol) { (0, _assert().default)(false, 'Asset was skipped or not found.'); } else { return symbol; } } getHoistedParcelRequires(parentAsset, dep, resolved) { if (resolved.type !== 'js') { return ['', 0]; } let hoisted = this.hoistedRequires.get(dep.id); let res = ''; let lineCount = 0; let isWrapped = !this.bundle.hasAsset(resolved) || this.wrappedAssets.has(resolved.id) && resolved !== parentAsset; // If the resolved asset is wrapped and is imported in the top-level by this asset, // we need to run side effects when this asset runs. If the resolved asset is not // the first one in the hoisted requires, we need to insert a parcelRequire here // so it runs first. if (isWrapped && !dep.meta.shouldWrap && (!hoisted || hoisted.keys().next().value !== resolved.id) && !this.bundleGraph.isDependencySkipped(dep) && !this.shouldSkipAsset(resolved)) { this.needsPrelude = true; res += `parcelRequire(${JSON.stringify(this.bundleGraph.getAssetPublicId(resolved))});`; } if (hoisted) { this.needsPrelude = true; res += '\n' + [...hoisted.values()].join('\n'); lineCount += hoisted.size; } return [res, lineCount]; } buildAssetPrelude(asset, deps) { let prepend = ''; let prependLineCount = 0; let append = ''; let shouldWrap = this.wrappedAssets.has(asset.id); let usedSymbols = (0, _nullthrows().default)(this.bundleGraph.getUsedSymbols(asset)); let assetId = asset.meta.id; (0, _assert().default)(typeof assetId === 'string'); // If the asset has a namespace export symbol, it is CommonJS. // If there's no __esModule flag, and default is a used symbol, we need // to insert an interop helper. let defaultInterop = asset.symbols.hasExportSymbol('*') && usedSymbols.has('default') && !asset.symbols.hasExportSymbol('__esModule'); let usedNamespace = // If the asset has * in its used symbols, we might need the exports namespace. // The one case where this isn't true is in ESM library entries, where the only // dependency on * is the entry dependency. In this case, we will use ESM exports // instead of the namespace object. usedSymbols.has('*') && (this.bundle.env.outputFormat !== 'esmodule' || !this.bundle.env.isLibrary || asset !== this.bundle.getMainEntry() || this.bundleGraph.getIncomingDependencies(asset).some(dep => !dep.isEntry && (0, _nullthrows().default)(this.bundleGraph.getUsedSymbols(dep)).has('*'))) || // If a symbol is imported (used) from a CJS asset but isn't listed in the symbols, // we fallback on the namespace object. asset.symbols.hasExportSymbol('*') && [...usedSymbols].some(s => !asset.symbols.hasExportSymbol(s)) || // If the exports has this asset's namespace (e.g. ESM output from CJS input), // include the namespace object for the default export. this.exportedSymbols.has(`$${assetId}$exports`); // If the asset doesn't have static exports, should wrap, the namespace is used, // or we need default interop, then we need to synthesize a namespace object for // this asset. if (asset.meta.staticExports === false || shouldWrap || usedNamespace || defaultInterop) { // Insert a declaration for the exports namespace object. If the asset is wrapped // we don't need to do this, because we'll use the `module.exports` object provided // by the wrapper instead. This is also true of CommonJS entry assets, which will use // the `module.exports` object provided by CJS. if (!shouldWrap && (this.bundle.env.outputFormat !== 'commonjs' || asset !== this.bundle.getMainEntry())) { prepend += `var $${assetId}$exports = {};\n`; prependLineCount++; } // Insert the __esModule interop flag for this module if it has a `default` export // and the namespace symbol is used. // TODO: only if required by CJS? if (asset.symbols.hasExportSymbol('default') && usedSymbols.has('*')) { prepend += `\n$parcel$defineInteropFlag($${assetId}$exports);\n`; prependLineCount += 2; this.usedHelpers.add('$parcel$defineInteropFlag'); } // Find the used exports of this module. This is based on the used symbols of // incoming dependencies rather than the asset's own used exports so that we include // re-exported symbols rather than only symbols declared in this asset. let incomingDeps = this.bundleGraph.getIncomingDependencies(asset); let usedExports = [...asset.symbols.exportSymbols()].filter(symbol => { if (symbol === '*') { return false; } // If we need default interop, then all symbols are needed because the `default` // symbol really maps to the whole namespace. if (defaultInterop) { return true; } let unused = incomingDeps.every(d => { let symbols = (0, _nullthrows().default)(this.bundleGraph.getUsedSymbols(d)); return !symbols.has(symbol) && !symbols.has('*'); }); return !unused; }); if (usedExports.length > 0) { // Insert $parcel$export calls for each of the used exports. This creates a getter/setter // for the symbol so that when the value changes the object property also changes. This is // required to simulate ESM live bindings. It's easier to do it this way rather than inserting // additional assignments after each mutation of the original binding. prepend += `\n${usedExports.map(exp => { let resolved = this.getSymbolResolution(asset, asset, exp); let get = this.buildFunctionExpression([], resolved); let set = asset.meta.hasCJSExports ? ', ' + this.buildFunctionExpression(['v'], `${resolved} = v`) : ''; return `$parcel$export($${assetId}$exports, ${JSON.stringify(exp)}, ${get}${set});`; }).join('\n')}\n`; this.usedHelpers.add('$parcel$export'); prependLineCount += 1 + usedExports.length; } // Find wildcard re-export dependencies, and make sure their exports are also included in ours. for (let dep of deps) { let resolved = this.bundleGraph.getResolvedAsset(dep, this.bundle); if (dep.isOptional || this.bundleGraph.isDependencySkipped(dep)) { continue; } let isWrapped = resolved && resolved.meta.shouldWrap; for (let [imported, { local }] of dep.symbols) { if (imported === '*' && local === '*') { if (!resolved) { // Re-exporting an external module. This should have already been handled in buildReplacements. let external = (0, _nullthrows().default)((0, _nullthrows().default)(this.externals.get(dep.specifier)).get('*')); append += `$parcel$exportWildcard($${assetId}$exports, ${external});\n`; this.usedHelpers.add('$parcel$exportWildcard'); continue; } // If the resolved asset has an exports object, use the $parcel$exportWildcard helper // to re-export all symbols. Otherwise, if there's no namespace object available, add // $parcel$export calls for each used symbol of the dependency. if (isWrapped || resolved.meta.staticExports === false || (0, _nullthrows().default)(this.bundleGraph.getUsedSymbols(resolved)).has('*') || // an empty asset !resolved.meta.hasCJSExports && resolved.symbols.hasExportSymbol('*')) { let obj = this.getSymbolResolution(asset, resolved, '*', dep); append += `$parcel$exportWildcard($${assetId}$exports, ${obj});\n`; this.usedHelpers.add('$parcel$exportWildcard'); } else { for (let symbol of (0, _nullthrows().default)(this.bundleGraph.getUsedSymbols(dep))) { if (symbol === 'default' || // `export * as ...` does not include the default export symbol === '__esModule') { continue; } let resolvedSymbol = this.getSymbolResolution(asset, resolved, symbol); let get = this.buildFunctionExpression([], resolvedSymbol); let set = asset.meta.hasCJSExports ? ', ' + this.buildFunctionExpression(['v'], `${resolvedSymbol} = v`) : ''; prepend += `$parcel$export($${assetId}$exports, ${JSON.stringify(symbol)}, ${get}${set});\n`; this.usedHelpers.add('$parcel$export'); prependLineCount++; } } } } } } return [prepend, prependLineCount, append]; } buildBundlePrelude() { let enableSourceMaps = this.bundle.env.sourceMap; let res = ''; let lines = 0; // Add hashbang if the entry asset recorded an interpreter. let mainEntry = this.bundle.getMainEntry(); if (mainEntry && !this.isAsyncBundle && !this.bundle.target.env.isBrowser()) { let interpreter = mainEntry.meta.interpreter; (0, _assert().default)(interpreter == null || typeof interpreter === 'string'); if (interpreter != null) { res += `#!${interpreter}\n`; lines++; } } // The output format may have specific things to add at the start of the bundle (e.g. imports). let [outputFormatPrelude, outputFormatLines] = this.outputFormat.buildBundlePrelude(); res += outputFormatPrelude; lines += outputFormatLines; // Add used helpers. if (this.needsPrelude) { this.usedHelpers.add('$parcel$global'); } for (let helper of this.usedHelpers) { res += _helpers.helpers[helper]; if (enableSourceMaps) { lines += (0, _utils().countLines)(_helpers.helpers[helper]) - 1; } } if (this.needsPrelude) { // Add the prelude if this is potentially the first JS bundle to load in a // particular context (e.g. entry scripts in HTML, workers, etc.). let parentBundles = this.bundleGraph.getParentBundles(this.bundle); let mightBeFirstJS = parentBundles.length === 0 || parentBundles.some(b => b.type !== 'js') || this.bundleGraph.getBundleGroupsContainingBundle(this.bundle).some(g => this.bundleGraph.isEntryBundleGroup(g)) || this.bundle.env.isIsolated() || this.bundle.bundleBehavior === 'isolated'; if (mightBeFirstJS) { let preludeCode = (0, _helpers.prelude)(this.parcelRequireName); res += preludeCode; if (enableSourceMaps) { lines += (0, _utils().countLines)(preludeCode) - 1; } } else { // Otherwise, get the current parcelRequire global. res += `var parcelRequire = $parcel$global[${JSON.stringify(this.parcelRequireName)}];\n`; lines++; } } // Add importScripts for sibling bundles in workers. if (this.bundle.env.isWorker() || this.bundle.env.isWorklet()) { let importScripts = ''; let bundles = this.bundleGraph.getReferencedBundles(this.bundle); for (let b of bundles) { if (this.bundle.env.outputFormat === 'esmodule') { // importScripts() is not allowed in native ES module workers. importScripts += `import "${(0, _utils().relativeBundlePath)(this.bundle, b)}";\n`; } else { importScripts += `importScripts("${(0, _utils().relativeBundlePath)(this.bundle, b)}");\n`; } } res += importScripts; lines += bundles.length; } return [res, lines]; } needsDefaultInterop(asset) { if (asset.symbols.hasExportSymbol('*') && !asset.symbols.hasExportSymbol('default')) { let deps = this.bundleGraph.getIncomingDependencies(asset); return deps.some(dep => this.bundle.hasDependency(dep) && // dep.meta.isES6Module && dep.symbols.hasExportSymbol('default')); } return false; } shouldSkipAsset(asset) { if (this.isScriptEntry(asset)) { return true; } return asset.sideEffects === false && (0, _nullthrows().default)(this.bundleGraph.getUsedSymbols(asset)).size == 0 && !this.bundleGraph.isAssetReferenced(this.bundle, asset); } isScriptEntry(asset) { return this.bundle.env.outputFormat === 'global' && this.bundle.env.sourceType === 'script' && asset === this.bundle.getMainEntry(); } buildFunctionExpression(args, expr) { return this.bundle.env.supports('arrow-functions', true) ? `(${args.join(', ')}) => ${expr}` : `function (${args.join(', ')}) { return ${expr}; }`; } } exports.ScopeHoistingPackager = ScopeHoistingPackager;