Skip to content

Commit

Permalink
Properly parse Webpack 5 entry modules
Browse files Browse the repository at this point in the history
  • Loading branch information
th0r committed Nov 5, 2020
1 parent 7bbe89f commit c35bda3
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 20 deletions.
55 changes: 47 additions & 8 deletions src/analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
continue;
}

bundlesSources[statAsset.name] = bundleInfo.src;
bundlesSources[statAsset.name] = _.pick(bundleInfo, 'src', 'runtimeSrc');
_.assign(parsedModules, bundleInfo.modules);
}

Expand All @@ -94,19 +94,48 @@ function getViewerData(bundleStats, bundleDir, opts) {
const asset = result[statAsset.name] = _.pick(statAsset, 'size');

if (bundlesSources && _.has(bundlesSources, statAsset.name)) {
asset.parsedSize = Buffer.byteLength(bundlesSources[statAsset.name]);
asset.gzipSize = gzipSize.sync(bundlesSources[statAsset.name]);
asset.parsedSize = Buffer.byteLength(bundlesSources[statAsset.name].src);
asset.gzipSize = gzipSize.sync(bundlesSources[statAsset.name].src);
}

// Picking modules from current bundle script
asset.modules = _(modules)
.filter(statModule => assetHasModule(statAsset, statModule))
.each(statModule => {
if (parsedModules) {
const assetModules = modules.filter(statModule => assetHasModule(statAsset, statModule));

// Adding parsed sources
if (parsedModules) {
const unparsedEntryModules = [];

for (const statModule of assetModules) {
if (parsedModules[statModule.id]) {
statModule.parsedSrc = parsedModules[statModule.id];
} else if (isEntryModule(statModule)) {
unparsedEntryModules.push(statModule);
}
});
}

// Webpack 5 changed bundle format and now entry modules are concatenated and located at the end on the it.
// Because of this they basically become a concatenated module, for which we can't even precisely determine its
// parsed source as it's located in the same scope as all Webpack runtime helpers.
if (unparsedEntryModules.length) {
if (unparsedEntryModules.length === 1) {
// So if there is only one entry we consider its parsed source to be all the bundle code excluding code
// from parsed modules.
unparsedEntryModules[0].parsedSrc = bundlesSources[statAsset.name].runtimeSrc;
} else {
// If there are multiple entry points we move all of them under synthetic concatenated module.
_.pullAll(assetModules, unparsedEntryModules);
assetModules.unshift({
identifier: './entry modules',
name: './entry modules',
modules: unparsedEntryModules,
size: unparsedEntryModules.reduce((totalSize, module) => totalSize + module.size, 0),
parsedSrc: bundlesSources[statAsset.name].runtimeSrc
});
}
}
}

asset.modules = assetModules;
asset.tree = createModulesTree(asset.modules);
}, {});

Expand Down Expand Up @@ -148,6 +177,8 @@ function getBundleModules(bundleStats) {
.compact()
.flatten()
.uniqBy('id')
// Filtering out Webpack's runtime modules as they don't have ids and can't be parsed (introduced in Webpack 5)
.reject(isRuntimeModule)
.value();
}

Expand All @@ -158,6 +189,14 @@ function assetHasModule(statAsset, statModule) {
);
}

function isEntryModule(statModule) {
return statModule.depth === 0;
}

function isRuntimeModule(statModule) {
return statModule.moduleType === 'runtime';
}

function createModulesTree(modules) {
const root = new Folder('.');

Expand Down
48 changes: 36 additions & 12 deletions src/parseUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,19 @@ function parseBundle(bundlePath) {
// ...nor parameters
fn.callee.params.length === 0
) {
// Modules are stored in the very first variable as hash
const {body} = fn.callee.body;

if (
body.length &&
body[0].type === 'VariableDeclaration' &&
body[0].declarations.length &&
body[0].declarations[0].type === 'VariableDeclarator' &&
body[0].declarations[0].init.type === 'ObjectExpression'
) {
state.locations = getModulesLocations(body[0].declarations[0].init);
// Modules are stored in the very first variable declaration as hash
const firstVariableDeclaration = fn.callee.body.body.find(node => node.type === 'VariableDeclaration');

if (firstVariableDeclaration) {
for (const declaration of firstVariableDeclaration.declarations) {
if (declaration.init) {
state.locations = getModulesLocations(declaration.init);

if (state.locations) {
break;
}
}
}
}
}
}
Expand All @@ -66,6 +68,7 @@ function parseBundle(bundlePath) {

state.expressionStatementDepth--;
},

AssignmentExpression(node, state) {
if (state.locations) return;

Expand All @@ -82,6 +85,7 @@ function parseBundle(bundlePath) {
state.locations = getModulesLocations(right);
}
},

CallExpression(node, state, c) {
if (state.locations) return;

Expand Down Expand Up @@ -147,11 +151,31 @@ function parseBundle(bundlePath) {
}

return {
modules,
src: content,
modules
runtimeSrc: getBundleRuntime(content, walkState.locations)
};
}

/**
* Returns bundle source except modules
*/
function getBundleRuntime(content, modulesLocations) {
const sortedLocations = _(modulesLocations)
.values()
.sortBy('start');

let result = '';
let lastIndex = 0;

for (const {start, end} of sortedLocations) {
result += content.slice(lastIndex, start);
lastIndex = end;
}

return result + content.slice(lastIndex, content.length);
}

function isIIFE(node) {
return (
node.type === 'ExpressionStatement' &&
Expand Down

0 comments on commit c35bda3

Please sign in to comment.