UNPKG

fuse-box

Version:

Fuse-Box a bundler that does it right

195 lines (194 loc) • 7.91 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CodeSplittingPhase = exports.resolveSplitEntry = void 0; const package_1 = require("../../moduleResolver/package"); const ImportReference_1 = require("../module/ImportReference"); const SplitEntries_1 = require("../module/SplitEntries"); /** * Function to check if a module is only required thr * @param possibleSplitEntry */ function resolveDynamicImport(possibleSplitEntry) { const { moduleTree } = possibleSplitEntry; if (moduleTree.dependants.length === 0) return false; let isDynamic = true; for (const dependant of moduleTree.dependants) { // if all dependants require this module dynamic // then we have a splitEntry if (dependant.type !== ImportReference_1.ImportType.DYNAMIC_IMPORT) { isDynamic = false; break; } } return isDynamic; } /** * The core of the code splitting * * Provide it with a target and it will spit out a valid ISplitEntry * * @param productionContext * @param target */ function resolveSplitEntry(productionContext, target) { const entryModuleId = target.id; const subModules = [target]; const circularModules = {}; const visited = {}; const traversed = { [entryModuleId]: true, }; target.isSplit = true; const splitEntry = { circularModules, subModules, /** * traceCircularDependency accepts a target of type Module * it needs a reference to the parentId so we can safely trace back * * @param target * @param parentId */ traceCircularDependency: function (target, parentId) { let traced = false; const { id, moduleTree: { dependants }, } = target; // prevent infinite loop if (!!this.circularModules[parentId][id]) { return this.circularModules[parentId][id]; } for (const { module: dependant } of dependants) { /** * if we resolve to the parent or entryModuleId * we're safe to assume we're contained within the split * otherwise dive in to the dependant hierarchy */ traced = dependant.id === entryModuleId || dependant.id === parentId || this.traceCircularDependency(dependant, parentId); this.circularModules[parentId][id] = traced; if (!traced) break; } return traced; }, /** * traceOrigin accepts a target of type Module * it will validate against the already processed modules * and returns true if it origins back to the possibleSplitEntry Module * * @param target */ traceOrigin: function (target, parentId) { const { id, moduleTree: { dependants }, } = target; /** * This check is to validate possible circular dependencies * It's quite complex to keep track of all falsy code * so we create a stack trace for it to validate against */ if (!!this.visited[id]) { if (!(parentId in this.circularModules)) { this.circularModules[parentId] = {}; } const result = this.traceCircularDependency(target, parentId); // @todo: log circular trace to console // this.circularModules contains a stack trace return result; } this.visited[id] = true; let traced = false; for (const { module } of dependants) { if (module.id === entryModuleId) { traced = true; } else if (!!productionContext.splitEntries.ids[module.id]) { // the module is a dynamic module and not the entry, so false! // we flagAsCommonsEligible here! // target.isCommonsEligible = true; // if we return false, it will be excluded and added to the mainBundle traced = false; // if we return true, it will be included in every splitBundle // traced = true; } else { traced = this.traceOrigin(module, id); } if (!traced) { // @todo: config to flag all shared commons? // target.flagAsCommonsEligible(); break; } } return traced; }, /** * traverseDependencies accepts a target of type Module * it will check the modules that reference this module to validate isolation * if it's an isolated module (no other references) it will be included * in the splitted bundle. Also the dependencies of this module will be checked * to see if we can integrate those too * * @param target */ traverseDependencies: function (target, entry = false) { const { id, moduleTree: { importReferences: { references }, }, } = target; // we already traversed this module, so we can skip it // this happens in case of circular deps // we return true because we want this module to be excluded if (!entry && !!this.traversed[id]) return true; this.traversed[id] = true; // check if target is a dynamic module. If so, we want to exclude it const isDynamic = !entry ? !!productionContext.splitEntries.ids[target.id] : false; if (!isDynamic) { for (const { target: reference } of references) { if (this.traceOrigin(reference, id)) { const exclude = this.traverseDependencies(reference, false); reference.isSplit = true; if (!exclude) this.subModules.push(reference); } } } return isDynamic; }, traversed, visited, }; splitEntry.traverseDependencies(target, true); return SplitEntries_1.createSplitEntry({ module: target, productionContext, subModules: splitEntry.subModules, }); } exports.resolveSplitEntry = resolveSplitEntry; /** * CodeSplittingPhase * heavy lifting by validating all modules included in the context * @param productionContext */ function CodeSplittingPhase(productionContext) { // loop over all modules to extract the modules that can be splitted const splitEntries = []; for (const possibleSplitEntry of productionContext.modules) { if (!possibleSplitEntry.isEntry && possibleSplitEntry.pkg.type === package_1.PackageType.USER_PACKAGE) { // check if the current module is only imported through // dynamic imports const isDynamic = resolveDynamicImport(possibleSplitEntry); if (isDynamic) { // resolve the complete tree for this module and add it // to the split entries splitEntries.push(possibleSplitEntry); productionContext.splitEntries.ids[possibleSplitEntry.id] = true; } } } // we needed to traverse all modules first to improve splitEntry resolvement // so now we can parse all dynamicModules that we found. for (const splitEntry of splitEntries) { productionContext.splitEntries.register(resolveSplitEntry(productionContext, splitEntry)); } } exports.CodeSplittingPhase = CodeSplittingPhase;