This commit is contained in:
Connor Peet 2021-05-07 10:39:11 -07:00
Родитель 9f3b220c94
Коммит cf943d45d7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: CF8FD2EA0DBC61BD
40 изменённых файлов: 3802 добавлений и 5178 удалений

Просмотреть файл

@ -27,6 +27,7 @@ jobs:
action: "upload"
###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
app_build_command: "npm run build"
app_location: "/" # App source code path
api_location: "api" # Api source code path - optional
output_location: "/dist" # Built app content directory - optional

4
.gitignore поставляемый
Просмотреть файл

@ -42,8 +42,4 @@ jspm_packages
# Optional REPL history
.node_repl_history
/public/samples
!/public/samples/sample1.msp.gz
!/public/samples/sample2.msp.gz
/dist
/.vscode

6
.vscode/settings.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,6 @@
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
}

8545
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -8,8 +8,8 @@
"test": "mocha --opts mocha.opts && npm run test:fmt && npm run test:lint",
"test:fmt": "prettier --list-different \"src/**/*.{tsx,ts}\" || echo \"Run npm run fmt to fix formatting on these files\"",
"test:lint": "tslint --project tsconfig.json \"src/**/*.{ts,tsx}\"",
"build": "webpack --config webpack.production.js",
"start": "webpack-dev-server --open",
"build": "tsc -p tsconfig.package.json && webpack --config webpack.production.js",
"start": "webpack serve",
"prepare": "tsc -p tsconfig.package.json"
},
"repository": {
@ -30,75 +30,76 @@
},
"homepage": "https://github.com/mixer/webpack-bundle-compare#readme",
"dependencies": {
"bfj": "^6.1.1",
"js-base64": "^2.5.1",
"bfj": "^7.0.2",
"js-base64": "^3.6.0",
"msgpack-lite": "^0.1.26"
},
"devDependencies": {
"@mixer/retrieval": "^2.0.2",
"@types/chai": "^4.1.7",
"@types/cytoscape": "^3.4.3",
"@types/filesize": "^4.1.0",
"@types/fixed-data-table-2": "^0.8.3",
"@types/js-base64": "^2.3.1",
"@types/mocha": "^5.2.7",
"@types/msgpack-lite": "^0.1.6",
"@types/node": "^12.0.4",
"@mixer/retrieval": "^2.0.3",
"@types/chai": "^4.2.17",
"@types/cytoscape": "^3.14.15",
"@types/filesize": "^4.2.0",
"@types/fixed-data-table-2": "^0.8.4",
"@types/js-base64": "^3.0.0",
"@types/mocha": "^8.2.2",
"@types/msgpack-lite": "^0.1.7",
"@types/node": "^15.0.2",
"@types/pako": "^1.0.1",
"@types/react": "^16.8.19",
"@types/react-dom": "^16.8.4",
"@types/react-redux": "^7.0.9",
"@types/react-router-dom": "^4.3.3",
"@types/react-tabs": "^2.3.1",
"@types/react": "^17.0.5",
"@types/react-dom": "^17.0.3",
"@types/react-redux": "^7.1.16",
"@types/react-router-dom": "^5.1.7",
"@types/react-tabs": "^2.3.2",
"@types/sigmajs": "^1.0.27",
"@types/webpack": "^4.4.32",
"chai": "^4.2.0",
"copy-webpack-plugin": "^5.0.3",
"css-loader": "^2.1.1",
"cytoscape": "^3.7.0",
"cytoscape-dagre": "^2.2.2",
"cytoscape-fcose": "^1.0.0",
"dayjs": "^1.8.14",
"filesize": "^4.1.2",
"fixed-data-table-2": "^0.8.26",
"@types/webpack": "^5.28.0",
"chai": "^4.3.4",
"copy-webpack-plugin": "^8.1.1",
"css-loader": "^5.2.4",
"cytoscape": "^3.18.2",
"cytoscape-dagre": "^2.3.2",
"cytoscape-fcose": "^2.0.0",
"dayjs": "^1.10.4",
"filesize": "^6.3.0",
"fixed-data-table-2": "^1.1.3",
"flexboxgrid": "^6.3.1",
"fs-extra": "^8.0.1",
"html-webpack-plugin": "^3.2.0",
"mocha": "^6.1.4",
"node-sass": "^4.12.0",
"fs-extra": "^10.0.0",
"html-webpack-plugin": "^5.3.1",
"mocha": "^8.4.0",
"node-sass": "^5.0.0",
"normalize.css": "^8.0.1",
"pako": "^1.0.10",
"prettier": "^1.17.1",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-github-corner": "^2.3.0",
"react-icons": "^3.7.0",
"react-redux": "^7.0.3",
"react-router-dom": "^5.0.0",
"react-tabs": "^3.0.0",
"redux": "^4.0.1",
"redux-devtools-extension": "^2.13.8",
"redux-observable": "^1.1.0",
"pako": "^2.0.3",
"prettier": "^2.2.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-github-corner": "^2.5.0",
"react-icons": "^4.2.0",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"react-tabs": "^3.2.2",
"redux": "^4.1.0",
"redux-devtools-extension": "^2.13.9",
"redux-observable": "^1.2.0",
"reselect": "^4.0.0",
"rxjs": "^6.5.2",
"sass-loader": "^7.1.0",
"rxjs": "^6.0.0",
"sass-loader": "^11.0.1",
"sigma": "^1.2.1",
"style-loader": "^0.23.1",
"ts-loader": "^6.0.2",
"ts-node": "^8.2.0",
"tslint": "^5.17.0",
"style-loader": "^2.0.0",
"ts-loader": "^9.1.2",
"ts-node": "^9.1.1",
"tslint": "^5.20.1",
"tslint-config-prettier": "^1.18.0",
"typesafe-actions": "^4.4.0",
"typescript": "^3.5.1",
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.4.1",
"worker-loader": "^2.0.0"
"typesafe-actions": "^5.1.0",
"typescript": "^4.2.4",
"webpack": "^5.36.2",
"webpack-cli": "^4.7.0",
"webpack-dev-server": "^3.11.2",
"worker-loader": "^3.0.8"
},
"prettier": {
"trailingComma": "all",
"singleQuote": true,
"printWidth": 100,
"arrowParens": "avoid",
"tabWidth": 2
}
}

Двоичные данные
public/samples/sample1.msp.gz Normal file

Двоичный файл не отображается.

Двоичные данные
public/samples/sample2.msp.gz Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -1,6 +1,6 @@
# @mixer/webpack-bundle-compare
This is a tool that allows you to compare webpack bundle analysis files over time. Check it out [here](https://webpackbundlecomparison.z5.web.core.windows.net).
This is a tool that allows you to compare webpack bundle analysis files over time. Check it out [here](https://happy-water-0887b0b1e.azurestaticapps.net).
![](./screenshot.png)

Просмотреть файл

@ -6,7 +6,7 @@ import { connect } from 'react-redux';
import { fetchBundlephobiaData } from '../redux/actions';
import { getBundlephobiaData, IAppState } from '../redux/reducer';
import { IBundlephobiaStats } from '../redux/services/bundlephobia-api';
import * as styles from './bundlephobia-stats.component.scss';
import styles from './bundlephobia-stats.component.scss';
import { Errors } from './errors.component';
import { SideEffectHint, TreeShakeHint } from './hints/hints.component';
import { BasePanel } from './panels/base-panel.component';

Просмотреть файл

@ -1,5 +1,5 @@
import * as React from 'react';
import * as styles from './button.component.scss';
import styles from './button.component.scss';
import { classes } from './util';
type Props = React.DetailedHTMLProps<

Просмотреть файл

@ -1,6 +1,6 @@
import * as filesize from 'filesize';
import * as React from 'react';
import { Stats } from 'webpack';
import { StatsCompilation } from 'webpack';
import {
compareNodeModules,
getNodeModuleCount,
@ -19,8 +19,8 @@ import { formatPercent } from './util';
export const DashboardChunkPage: React.FC<{
chunk: number;
first: Stats.ToJsonOutput;
last: Stats.ToJsonOutput;
first: StatsCompilation;
last: StatsCompilation;
}> = ({ first, last, chunk }) => {
const firstObj = first.chunks!.find(c => c.id === chunk);
const lastSize = last.chunks!.find(c => c.id === chunk);

Просмотреть файл

@ -9,8 +9,8 @@ import {
IoIosWarning,
} from 'react-icons/io';
import { Link, withRouter } from 'react-router-dom';
import { Stats } from 'webpack';
import * as styles from './dashboard-header.component.scss';
import { StatsCompilation } from 'webpack';
import styles from './dashboard-header.component.scss';
const DashboardHeaderItem: React.FC<{ icon: IconType; href?: string | (() => void) }> = props => {
const inner = (
@ -34,8 +34,8 @@ const DashboardHeaderItem: React.FC<{ icon: IconType; href?: string | (() => voi
};
export const DashboardBuildDate: React.FC<{
first: Stats.ToJsonOutput;
last: Stats.ToJsonOutput;
first: StatsCompilation;
last: StatsCompilation;
}> = ({ first, last }) => {
const from = first.builtAt;
const to = last.builtAt;
@ -56,21 +56,21 @@ export const DashboardBuildDate: React.FC<{
);
};
export const DashboardWarningCount: React.FC<{ last: Stats.ToJsonOutput }> = ({ last }) => (
export const DashboardWarningCount: React.FC<{ last: StatsCompilation }> = ({ last }) => (
<DashboardHeaderItem
icon={IoIosWarning}
href={last.warnings.length ? '/dashboard/output' : undefined}
href={last.warnings?.length ? '/dashboard/output' : undefined}
>
{last.warnings.length} Warnings
{last.warnings?.length ?? 0} Warnings
</DashboardHeaderItem>
);
export const DashboardErrorCount: React.FC<{ last: Stats.ToJsonOutput }> = ({ last }) => (
export const DashboardErrorCount: React.FC<{ last: StatsCompilation }> = ({ last }) => (
<DashboardHeaderItem
icon={IoIosCloseCircleOutline}
href={last.errors.length ? '/dashboard/output' : undefined}
href={last.errors?.length ? '/dashboard/output' : undefined}
>
{last.errors.length} Errors
{last.errors?.length ?? 0} Errors
</DashboardHeaderItem>
);

Просмотреть файл

@ -1,5 +1,5 @@
import * as React from 'react';
import { Stats } from 'webpack';
import { StatsCompilation } from 'webpack';
import { getDirectImportsOfNodeModule } from '../stat-reducers';
import { BundlephobiaStats } from './bundlephobia-stats.component';
import { NodeModuleDependentGraph } from './graphs/dependent-graph.component';
@ -8,8 +8,8 @@ import { ImportsStatsRow } from './imports-stats-row.component';
export const DashboardNodeModulePage: React.FC<{
name: string;
first: Stats.ToJsonOutput;
last: Stats.ToJsonOutput;
first: StatsCompilation;
last: StatsCompilation;
}> = ({ first, last, name }) => {
const firstImports = getDirectImportsOfNodeModule(first, name);
const lastImports = getDirectImportsOfNodeModule(last, name);

Просмотреть файл

@ -1,6 +1,6 @@
import * as filesize from 'filesize';
import * as React from 'react';
import { Stats } from 'webpack';
import { StatsCompilation } from 'webpack';
import {
getAverageChunkSize,
getEntryChunkSize,
@ -10,12 +10,6 @@ import {
getTotalModuleCount,
getTreeShakablePercent,
} from '../stat-reducers';
import { ModuleTable } from './module-table.component';
import { OverviewSuggestions } from './overview-suggestions';
import { CounterPanel } from './panels/counter-panel.component';
import { PanelArrangement } from './panels/panel-arrangement.component';
import { formatDuration, formatPercent } from './util';
import { ChunkGraph } from './graphs/chunk-graph.component';
import {
AverageChunkSize,
@ -23,10 +17,15 @@ import {
TreeShakeHint,
WhatIsAnEntrypoint,
} from './hints/hints.component';
import { ModuleTable } from './module-table.component';
import { OverviewSuggestions } from './overview-suggestions';
import { CounterPanel } from './panels/counter-panel.component';
import { PanelArrangement } from './panels/panel-arrangement.component';
import { formatDuration, formatPercent } from './util';
export const DashboardOverview: React.FC<{
first: Stats.ToJsonOutput;
last: Stats.ToJsonOutput;
first: StatsCompilation;
last: StatsCompilation;
}> = ({ first, last }) => {
return (
<>

Просмотреть файл

@ -1,6 +1,6 @@
import * as filesize from 'filesize';
import * as React from 'react';
import { Stats } from 'webpack';
import { StatsCompilation } from 'webpack';
import {
getImportsOfName,
getWebpackModulesMap,
@ -14,8 +14,8 @@ import { PanelArrangement } from './panels/panel-arrangement.component';
export const DashboardOwnModulePage: React.FC<{
name: string;
first: Stats.ToJsonOutput;
last: Stats.ToJsonOutput;
first: StatsCompilation;
last: StatsCompilation;
}> = ({ first, last, name }) => {
const firstRoot = getWebpackModulesMap(first)[name];
const lastRoot = getWebpackModulesMap(last)[name];
@ -34,7 +34,7 @@ export const DashboardOwnModulePage: React.FC<{
<PanelArrangement>
<CounterPanel
title="Total Size"
value={lastRoot ? lastRoot.size : 0}
value={lastRoot?.size ?? 0}
oldValue={firstRoot ? firstRoot.size : 0}
formatter={filesize}
/>

Просмотреть файл

@ -1,7 +1,8 @@
import { Base64 } from 'js-base64';
import * as React from 'react';
import { connect } from 'react-redux';
import { Redirect, Route, RouteChildrenProps } from 'react-router';
import { Stats } from 'webpack';
import { StatsCompilation } from 'webpack';
import { getKnownStats, IAppState } from '../redux/reducer';
import { DashboardChunkPage } from './dashboard-chunk-page.component';
import {
@ -13,10 +14,10 @@ import {
import { DashboardNodeModulePage } from './dashboard-node-module-page.component';
import { DashboardOverview } from './dashboard-overview';
import { DashboardOwnModulePage } from './dashboard-own-module-page.component';
import * as styles from './dashboard.component.scss';
import styles from './dashboard.component.scss';
interface IProps {
stats: Stats.ToJsonOutput[];
stats: StatsCompilation[];
}
class DashboardComponent extends React.PureComponent<IProps> {

Просмотреть файл

@ -12,7 +12,7 @@ import {
IAppState,
} from '../redux/reducer';
import { Button } from './button.component';
import * as styles from './enter-urls.component.scss';
import styles from './enter-urls.component.scss';
import { Errors } from './errors.component';
import { ProgressBar } from './progress-bar.component';

Просмотреть файл

@ -1,6 +1,6 @@
import { IError, IRetrievalError } from '@mixer/retrieval';
import * as React from 'react';
import * as styles from './errors.component.scss';
import styles from './errors.component.scss';
import { classes } from './util';
interface IProps {

Просмотреть файл

@ -1,7 +1,7 @@
import * as cytoscape from 'cytoscape';
import * as React from 'react';
import { IoIosContract, IoIosExpand } from 'react-icons/io';
import * as styles from './base-graph.component.scss';
import styles from './base-graph.component.scss';
import { filterUnattachedEdges } from './graph-tool';
// tslint:disable-next-line
@ -197,10 +197,7 @@ export default class BaseGraph extends React.PureComponent<IProps, { filter: Fil
let lastPath: cytoscape.CollectionReturnValue | null = null;
graph.on('mouseover', 'node', ev => {
lastPath = graph
.elements()
.dijkstra({ root: ev.target, directed: true })
.pathTo(root);
lastPath = graph.elements().dijkstra({ root: ev.target, directed: true }).pathTo(root);
lastPath.addClass('highlighted');
});

Просмотреть файл

@ -2,15 +2,15 @@ import * as cytoscape from 'cytoscape';
import { Base64 } from 'js-base64';
import * as React from 'react';
import { RouteComponentProps, withRouter } from 'react-router';
import { Stats } from 'webpack';
import { StatsCompilation } from 'webpack';
import { compareAllModules, getNodeModuleFromIdentifier } from '../../stat-reducers';
import { Placeholder } from '../placeholder.component';
import { linkToModule, linkToNodeModule } from '../util';
import { expandModuleComparison, LazyBaseGraph } from './graph-tool';
interface IProps {
previous: Stats.ToJsonOutput;
stats: Stats.ToJsonOutput;
previous: StatsCompilation;
stats: StatsCompilation;
chunkId: number;
}

Просмотреть файл

@ -1,12 +1,12 @@
import * as cytoscape from 'cytoscape';
import * as React from 'react';
import { RouteComponentProps, withRouter } from 'react-router';
import { Stats } from 'webpack';
import { StatsCompilation } from 'webpack';
import { fileSizeNode, LazyBaseGraph } from './graph-tool';
interface IProps {
previous: Stats.ToJsonOutput;
stats: Stats.ToJsonOutput;
previous: StatsCompilation;
stats: StatsCompilation;
maxBubbleArea?: number;
minBubbleArea?: number;
}
@ -50,7 +50,8 @@ export const ChunkGraph = withRouter(
const previous = this.props.previous.chunks!.find(c => c.id === chunk.id);
// If the chunk has a friendly name, render that in the chart. Otherwise, list the chunk id.
const chunkLabel = chunk.names.length > 0 ? chunk.names[0] : `Chunk ${chunk.id}`;
const chunkLabel =
chunk.names && chunk.names.length > 0 ? chunk.names[0] : `Chunk ${chunk.id}`;
return {
data: fileSizeNode({
@ -68,7 +69,7 @@ export const ChunkGraph = withRouter(
const edges: cytoscape.EdgeDefinition[] = [];
for (const chunk of chunks) {
for (const parent of chunk.parents) {
for (const parent of chunk.parents ?? []) {
edges.push({
data: {
id: `edge${parent}to${chunk.id}`,

Просмотреть файл

@ -2,21 +2,21 @@ import * as cytoscape from 'cytoscape';
import { Base64 } from 'js-base64';
import * as React from 'react';
import { RouteComponentProps, withRouter } from 'react-router';
import { Stats } from 'webpack';
import { StatsCompilation, StatsModule } from 'webpack';
import {
compareAllModules,
getDirectImportsOfNodeModule,
getImportsOfName,
getNodeModuleFromIdentifier,
normalizeName,
replaceLoaderInIdentifier,
replaceLoaderInIdentifier
} from '../../stat-reducers';
import { color, linkToModule, linkToNodeModule } from '../util';
import { expandModuleComparison, LazyBaseGraph } from './graph-tool';
interface IProps {
previous: Stats.ToJsonOutput;
stats: Stats.ToJsonOutput;
previous: StatsCompilation;
stats: StatsCompilation;
chunkId?: number;
}
@ -27,7 +27,7 @@ interface IState {
}
const createDependentGraph = <P extends {}>(
rootFinder: (props: IProps & P) => Stats.FnModules[],
rootFinder: (props: IProps & P) => StatsModule[],
rootLabel: (props: IProps & P) => string,
) =>
withRouter(
@ -91,6 +91,10 @@ const createDependentGraph = <P extends {}>(
});
for (const direct of directImports) {
if (!direct.name) {
continue;
}
const directId = Base64.encodeURI(direct.name);
edges.push({
data: {
@ -123,7 +127,7 @@ export const NodeModuleDependentGraph = createDependentGraph<{ name: string }>(
/**
* Graphs the dependent tree for a node module.
*/
export const GenericDependentGraph = createDependentGraph<{ root: Stats.FnModules }>(
props => getImportsOfName(props.stats, props.root.name),
export const GenericDependentGraph = createDependentGraph<{ root: StatsModule }>(
props => props.root.name ? getImportsOfName(props.stats, props.root.name) : [],
props => replaceLoaderInIdentifier(props.root.name),
);

Просмотреть файл

@ -2,7 +2,7 @@ import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { IoIosInformationCircleOutline } from 'react-icons/io';
import { classes } from '../util';
import * as styles from './hint-button.component.scss';
import styles from './hint-button.component.scss';
export class HintButton extends React.PureComponent<
{ hint: React.ComponentType<{}>; className?: string },

Просмотреть файл

@ -1,5 +1,5 @@
import * as React from 'react';
import { Stats } from 'webpack';
import { Stats, StatsModule } from 'webpack';
import {
getConcatenationParent,
getImportType,
@ -7,14 +7,14 @@ import {
replaceLoaderInIdentifier,
} from '../stat-reducers';
import { ButWaitTheresMore } from './but-wait-theres-more.component';
import * as styles from './imports-list.component.scss';
import styles from './imports-list.component.scss';
import { ModuleTypeBadge } from './panels/node-module-panel.component';
import { Placeholder } from './placeholder.component';
/**
* Prints the list of modules that import the target modules.
*/
export const ImportsList: React.FC<{ targets: Stats.FnModules[] }> = ({ targets }) =>
export const ImportsList: React.FC<{ targets: StatsModule[] }> = ({ targets }) =>
targets.length === 0 ? (
<Placeholder>This module is not imported in the lastest build.</Placeholder>
) : (
@ -25,7 +25,8 @@ export const ImportsList: React.FC<{ targets: Stats.FnModules[] }> = ({ targets
return (
<div key={i} className={styles.importBox}>
<div className={styles.title}>
{target.name} in chunk {getConcatenationParent(target).chunks.join(', ')}
{target.name} in chunk{' '}
{getConcatenationParent(target).chunks?.join(', ') ?? '<anonymous>'}
<span style={{ flex: 1 }} />
<ModuleTypeBadge type={getImportType(target)} />
</div>
@ -41,7 +42,7 @@ export const ImportsList: React.FC<{ targets: Stats.FnModules[] }> = ({ targets
/**
* Prints the list of issuers that import any of the target modules.
*/
export const IssuerTree: React.FC<{ targets: Stats.FnModules[] }> = ({ targets }) =>
export const IssuerTree: React.FC<{ targets: StatsModule[] }> = ({ targets }) =>
targets.length === 0 ? (
<Placeholder>This module is not imported in the lastest build.</Placeholder>
) : (
@ -52,7 +53,8 @@ export const IssuerTree: React.FC<{ targets: Stats.FnModules[] }> = ({ targets }
target.issuerPath && (
<div key={i} className={styles.importBox}>
<div className={styles.title}>
{target.name} in chunk {getConcatenationParent(target).chunks.join(', ')}
{target.name} in chunk{' '}
{getConcatenationParent(target).chunks?.join(', ') ?? '<anonymous>'}
<span style={{ flex: 1 }} />
<ModuleTypeBadge type={getImportType(target)} />
</div>

Просмотреть файл

@ -1,6 +1,6 @@
import * as filesize from 'filesize';
import * as React from 'react';
import { Stats } from 'webpack';
import { StatsModule } from 'webpack';
import { getReasons } from '../stat-reducers';
import { DependentModules, TotalNodeModuleSize, UniqueEntrypoints } from './hints/hints.component';
import { CounterPanel } from './panels/counter-panel.component';
@ -11,15 +11,15 @@ import { color } from './util';
* Prints the list of modules that import the target modules.
*/
export const ImportsStatsRow: React.FC<{
oldTargets: Stats.FnModules[];
newTargets: Stats.FnModules[];
oldTargets: StatsModule[];
newTargets: StatsModule[];
}> = ({ oldTargets, newTargets }) => (
<PanelArrangement>
<CounterPanel
title="Total Size"
hint={TotalNodeModuleSize}
value={newTargets.reduce((acc, t) => acc + t.size, 0)}
oldValue={oldTargets.reduce((acc, t) => acc + t.size, 0)}
value={newTargets.reduce((acc, t) => acc + (t.size || 0), 0)}
oldValue={oldTargets.reduce((acc, t) => acc + (t.size || 0), 0)}
formatter={filesize}
/>
<CounterPanel

Просмотреть файл

@ -1,18 +1,17 @@
import * as filesize from 'filesize';
import { Cell, Column, ColumnCellProps, Table } from 'fixed-data-table-2';
import 'fixed-data-table-2/dist/fixed-data-table.css';
import * as React from 'react';
import { Stats } from 'webpack';
import { IoIosArrowRoundDown, IoIosArrowRoundUp } from 'react-icons/io';
import { RouteComponentProps, withRouter } from 'react-router';
import { StatsCompilation } from 'webpack';
import {
compareAllModules,
getNodeModuleFromIdentifier,
IWebpackModuleComparisonOutput,
replaceLoaderInIdentifier,
} from '../stat-reducers';
import * as styles from './module-table.component.scss';
import 'fixed-data-table-2/dist/fixed-data-table.css';
import { IoIosArrowRoundDown, IoIosArrowRoundUp } from 'react-icons/io';
import { RouteComponentProps, withRouter } from 'react-router';
import styles from './module-table.component.scss';
import {
formatDifference,
formatPercentageDifference,
@ -21,8 +20,8 @@ import {
} from './util';
interface IProps {
first: Stats.ToJsonOutput;
last: Stats.ToJsonOutput;
first: StatsCompilation;
last: StatsCompilation;
inChunk?: number;
}

Просмотреть файл

@ -1,24 +1,24 @@
import * as filesize from 'filesize';
import * as React from 'react';
import { IoIosInformationCircleOutline, IoIosThumbsUp } from 'react-icons/io';
import { Stats } from 'webpack';
import { StatsCompilation } from 'webpack';
import {
getEntryChunkSize,
getNodeModuleSize,
getTotalChunkSize,
getTreeShakablePercent,
} from '../stat-reducers';
import * as styles from './overview-suggestions.component.scss';
import styles from './overview-suggestions.component.scss';
import { classes, formatPercent } from './util';
interface IProps {
first: Stats.ToJsonOutput;
last: Stats.ToJsonOutput;
first: StatsCompilation;
last: StatsCompilation;
}
const epsilon = 1024 * 2;
function nodeModuleSizeTip(first: Stats.ToJsonOutput, last: Stats.ToJsonOutput) {
function nodeModuleSizeTip(first: StatsCompilation, last: StatsCompilation) {
const firstNodeModuleSize = getNodeModuleSize(first);
const lastNodeModuleSize = getNodeModuleSize(last);
if (lastNodeModuleSize > firstNodeModuleSize + epsilon) {
@ -51,7 +51,7 @@ function nodeModuleSizeTip(first: Stats.ToJsonOutput, last: Stats.ToJsonOutput)
return null;
}
function entrypointTip(last: Stats.ToJsonOutput) {
function entrypointTip(last: StatsCompilation) {
const totalSize = getTotalChunkSize(last);
const entrySize = getEntryChunkSize(last);
const isMajority = entrySize > totalSize / 2;
@ -83,7 +83,7 @@ function entrypointTip(last: Stats.ToJsonOutput) {
return null;
}
function treeShakeTip(last: Stats.ToJsonOutput) {
function treeShakeTip(last: StatsCompilation) {
const percent = getTreeShakablePercent(last);
if (percent > 0.8) {
return;

Просмотреть файл

@ -1,7 +1,7 @@
import * as React from 'react';
import { HintButton } from '../hints/hint-button.component';
import { classes } from '../util';
import * as styles from './panels.component.scss';
import styles from './panels.component.scss';
export const enum ArrowDirection {
Up,

Просмотреть файл

@ -1,7 +1,7 @@
import * as React from 'react';
import { color, formatValue } from '../util';
import { BasePanel } from './base-panel.component';
import * as styles from './panels.component.scss';
import styles from './panels.component.scss';
import { StatDelta } from './stat-delta.component';
interface IProps {

Просмотреть файл

@ -5,7 +5,7 @@ import { RouteComponentProps, withRouter } from 'react-router';
import { ImportType, INodeModule, INodeModuleComparisonOutput } from '../../stat-reducers';
import { classes, color, linkToNodeModule } from '../util';
import { BasePanel } from './base-panel.component';
import * as styles from './panels.component.scss';
import styles from './panels.component.scss';
import { StatDelta } from './stat-delta.component';
interface IProps {
@ -24,16 +24,16 @@ const getSizeInChunk = (nodeModule?: INodeModule, chunk?: number) => {
let sum = 0;
for (const m of nodeModule.modules) {
if (m.chunks.includes(chunk)) {
sum += m.size;
if (m.chunks?.includes(chunk)) {
sum += m.size || 0;
}
}
return sum;
};
export const NodeModulePanel = withRouter<IProps & RouteComponentProps<{}>>(
({ comparison, inChunk, history }) => {
export const NodeModulePanel = withRouter(
({ comparison, inChunk, history }: IProps & RouteComponentProps<{}>) => {
const oldSize = getSizeInChunk(comparison.old, inChunk);
const newSize = getSizeInChunk(comparison.new, inChunk);

Просмотреть файл

@ -1,5 +1,5 @@
import * as React from 'react';
import * as styles from './panels.component.scss';
import styles from './panels.component.scss';
export const PanelArrangement: React.FC = props => (
<div className={styles.arrangement}>{props.children}</div>

Просмотреть файл

@ -1,5 +1,5 @@
import * as React from 'react';
import * as styles from './placeholder.component.scss';
import styles from './placeholder.component.scss';
export const Placeholder: React.FC = props => (
<div className={styles.placeholder}>{props.children}</div>

Просмотреть файл

@ -1,5 +1,5 @@
import * as React from 'react';
import * as styles from './progress-bar.component.scss';
import styles from './progress-bar.component.scss';
import { classes } from './util';
interface IProps {

Просмотреть файл

@ -1,5 +1,5 @@
import { Base64 } from 'js-base64';
import * as styles from './util.component.scss';
import styles from './util.component.scss';
export const classes = (...classList: Array<string | null | undefined>) => {
let str = '';

Просмотреть файл

@ -1,6 +1,6 @@
import { IError } from '@mixer/retrieval';
import { ActionType, createAsyncAction, createStandardAction } from 'typesafe-actions';
import { Stats } from 'webpack';
import { ActionType, createAction, createAsyncAction } from 'typesafe-actions';
import { StatsCompilation } from 'webpack';
import { IBundlephobiaStats } from './services/bundlephobia-api';
export interface ILoadableResource {
@ -18,7 +18,7 @@ export const doAnalysis = createAsyncAction(
'doAnalysisCancel',
)<
{ resource: ILoadableResource },
{ resource: ILoadableResource; data: Stats.ToJsonOutput },
{ resource: ILoadableResource; data: StatsCompilation },
IError & { resource: ILoadableResource },
{ resource: ILoadableResource }
>();
@ -35,19 +35,19 @@ export const fetchBundlephobiaData = createAsyncAction(
/**
* Loads bundles for all the requested urls.
*/
export const loadAllUrls = createStandardAction('loadAllUrls')<{
export const loadAllUrls = createAction('loadAllUrls')<{
resources: ILoadableResource[];
}>();
/**
* Clears loaded bundles.
*/
export const clearLoadedBundles = createStandardAction('clearLoadedBundles')();
export const clearLoadedBundles = createAction('clearLoadedBundles')();
/**
* Indicates that a webworker errored.
*/
export const webworkerErrored = createStandardAction('webworkerErrored')<IError>();
export const webworkerErrored = createAction('webworkerErrored')<IError>();
export type CompareAction = ActionType<
| typeof doAnalysis

Просмотреть файл

@ -5,17 +5,17 @@ import {
Retrieval,
RetrievalState,
success,
workingRetrival,
workingRetrival
} from '@mixer/retrieval';
import { createSelector } from 'reselect';
import { getType } from 'typesafe-actions';
import { Stats } from 'webpack';
import { StatsCompilation } from 'webpack';
import {
clearLoadedBundles,
CompareAction,
doAnalysis,
fetchBundlephobiaData,
loadAllUrls,
loadAllUrls
} from './actions';
import { IBundlephobiaStats } from './services/bundlephobia-api';
@ -31,7 +31,7 @@ export interface IAppState {
/**
* List of bundle loading states.
*/
bundles: Readonly<{ [url: string]: Retrieval<Stats.ToJsonOutput> }>;
bundles: Readonly<{ [url: string]: Retrieval<StatsCompilation> }>;
/**
* Mapping of bundlephobia dependency information.
@ -130,7 +130,7 @@ export const getBundleData = (url: string) => (state: IAppState) =>
export const getKnownStats = createSelector(
getBundleMap,
map => {
const output: Stats.ToJsonOutput[] = [];
const output: StatsCompilation[] = [];
for (const key of Object.keys(map)) {
const value = map[key];
if (value.state === RetrievalState.Succeeded) {

Просмотреть файл

@ -1,4 +1,4 @@
import { Stats } from 'webpack';
import { Stats, StatsCompilation, StatsModule } from 'webpack';
const anyPathSeparator = /\/|\\/;
@ -62,14 +62,14 @@ const cacheByArg = <T extends Function>(fn: T): T => {
/**
* Returns the size of all chunks from the stats.
*/
export const getTotalChunkSize = cacheByArg((stats: Stats.ToJsonOutput) =>
export const getTotalChunkSize = cacheByArg((stats: StatsCompilation) =>
stats.chunks!.reduce((sum, chunk) => sum + chunk.size, 0),
);
/**
* Returns the size of the entry chunk(s).
*/
export const getEntryChunkSize = cacheByArg((stats: Stats.ToJsonOutput) =>
export const getEntryChunkSize = cacheByArg((stats: StatsCompilation) =>
stats.chunks!.filter(c => c.entry).reduce((sum, chunk) => sum + chunk.size, 0),
);
@ -99,7 +99,7 @@ export interface INodeModule {
/**
* Modules imported from this dependency.
*/
modules: Stats.FnModules[];
modules: StatsModule[];
/**
* Type of the node module.
@ -129,26 +129,26 @@ export const getNodeModuleFromIdentifier = (identifier: string): string | null =
return null;
};
const concatParents = new WeakMap<Stats.FnModules, Stats.FnModules>();
const concatParents = new WeakMap<StatsModule, StatsModule>();
/**
* Returns the concatenation parent in which the given module resides. Will
* returnt he module itself if it's already top-level.
*/
export const getConcatenationParent = (m: Stats.FnModules): Stats.FnModules =>
export const getConcatenationParent = (m: StatsModule): StatsModule =>
concatParents.get(m) || m;
/**
* Get webpack modules, either globally ro in a single chunk.
*/
export const getWebpackModules = cacheByArg((stats: Stats.ToJsonOutput, filterToChunk?: number) => {
const modules: Stats.FnModules[] = [];
export const getWebpackModules = cacheByArg((stats: StatsCompilation, filterToChunk?: number) => {
const modules: StatsModule[] = [];
if (!stats.modules) {
return modules;
}
for (const parent of stats.modules) {
if (filterToChunk !== undefined && !parent.chunks.includes(filterToChunk)) {
if (filterToChunk !== undefined && !parent.chunks?.includes(filterToChunk)) {
continue;
}
@ -172,8 +172,8 @@ export const getWebpackModules = cacheByArg((stats: Stats.ToJsonOutput, filterTo
* Returns a mapping of normalized identifiers in the chunk to module data.
*/
export const getWebpackModulesMap = cacheByArg(
(stats: Stats.ToJsonOutput, filterToChunk?: number) => {
const mapping: { [name: string]: Stats.FnModules } = {};
(stats: StatsCompilation, filterToChunk?: number) => {
const mapping: { [name: string]: StatsModule } = {};
for (const m of getWebpackModules(stats, filterToChunk)) {
mapping[normalizeName(m.name)] = m;
}
@ -185,7 +185,7 @@ export const getWebpackModulesMap = cacheByArg(
/**
* Gets the type of import of the given module.
*/
export const getImportType = (importedModule: Stats.FnModules) => {
export const getImportType = (importedModule: StatsModule) => {
let flags = 0;
for (const reason of getReasons(importedModule)) {
if (reason.type) {
@ -203,11 +203,11 @@ export const getImportType = (importedModule: Stats.FnModules) => {
/**
* Returns the number of dependencies.
*/
export const getNodeModules = cacheByArg((stats: Stats.ToJsonOutput, inChunk?: number) => {
export const getNodeModules = cacheByArg((stats: StatsCompilation, inChunk?: number) => {
const modules: { [key: string]: INodeModule } = {};
for (const importedModule of getWebpackModules(stats, inChunk)) {
const packageName = getNodeModuleFromIdentifier(importedModule.identifier);
const packageName = getNodeModuleFromIdentifier(importedModule.identifier!);
if (!packageName) {
continue;
}
@ -217,12 +217,12 @@ export const getNodeModules = cacheByArg((stats: Stats.ToJsonOutput, inChunk?: n
if (!previous) {
modules[packageName] = {
name: packageName,
totalSize: importedModule.size,
totalSize: importedModule.size || 0,
modules: [importedModule],
importType: moduleType,
};
} else {
previous.totalSize += importedModule.size;
previous.totalSize += importedModule.size || 0;
previous.importType |= moduleType;
previous.modules.push(importedModule);
}
@ -269,16 +269,16 @@ export interface IWebpackModuleComparisonOutput {
fromSize: number;
toSize: number;
nodeModule?: string;
old?: Stats.FnModules;
new?: Stats.FnModules;
old?: StatsModule;
new?: StatsModule;
}
/**
* Returns a grouped comparison of the old and new modules from the stats.
*/
export const compareAllModules = (
oldStats: Stats.ToJsonOutput,
newStats: Stats.ToJsonOutput,
oldStats: StatsCompilation,
newStats: StatsCompilation,
inChunk?: number,
) => {
const oldModules = getWebpackModules(oldStats, inChunk);
@ -286,29 +286,37 @@ export const compareAllModules = (
const output: { [name: string]: IWebpackModuleComparisonOutput } = {};
for (const m of oldModules) {
if (!m.identifier) {
continue;
}
const normalized = normalizeName(m.name);
output[normalized] = {
name: normalized,
type: identifyModuleType(m.identifier),
nodeModule: getNodeModuleFromIdentifier(m.identifier) || undefined,
toSize: 0,
fromSize: m.size,
fromSize: m.size || 0,
old: m,
};
}
for (const m of newModules) {
if (!m.identifier) {
continue;
}
const normalized = normalizeName(m.name);
if (output[normalized]) {
output[normalized].new = m;
output[normalized].toSize = m.size;
output[normalized].toSize = m.size || 0;
} else {
output[normalized] = {
name: normalized,
type: identifyModuleType(m.identifier),
nodeModule: getNodeModuleFromIdentifier(m.identifier) || undefined,
fromSize: 0,
toSize: m.size,
toSize: m.size || 0,
new: m,
};
}
@ -330,8 +338,8 @@ export interface INodeModuleComparisonOutput {
* Returns a grouped comparison of the old and new modules from the stats.
*/
export const compareNodeModules = (
oldStats: Stats.ToJsonOutput,
newStats: Stats.ToJsonOutput,
oldStats: StatsCompilation,
newStats: StatsCompilation,
inChunk?: number,
) => {
const oldModules = getNodeModules(oldStats, inChunk);
@ -356,7 +364,7 @@ export const compareNodeModules = (
/**
* Gets the number of node modules.
*/
export const getNodeModuleSize = cacheByArg((stats: Stats.ToJsonOutput, inChunk?: number) => {
export const getNodeModuleSize = cacheByArg((stats: StatsCompilation, inChunk?: number) => {
let total = 0;
const modules = getNodeModules(stats, inChunk);
for (const key of Object.keys(modules)) {
@ -369,13 +377,13 @@ export const getNodeModuleSize = cacheByArg((stats: Stats.ToJsonOutput, inChunk?
/**
* Gets the number of node modules.
*/
export const getNodeModuleCount = (stats: Stats.ToJsonOutput, inChunk?: number) =>
export const getNodeModuleCount = (stats: StatsCompilation, inChunk?: number) =>
Object.keys(getNodeModules(stats, inChunk)).length;
/**
* Gets the number of node modules.
*/
export const getTreeShakablePercent = cacheByArg((stats: Stats.ToJsonOutput, inChunk?: number) => {
export const getTreeShakablePercent = cacheByArg((stats: StatsCompilation, inChunk?: number) => {
const modules = Object.values(getNodeModules(stats, inChunk));
if (modules.length === 0) {
return 1;
@ -394,13 +402,13 @@ export const getTreeShakablePercent = cacheByArg((stats: Stats.ToJsonOutput, inC
/**
* Gets the number of node modules.
*/
export const getTotalModuleCount = (stats: Stats.ToJsonOutput, inChunk?: number) =>
export const getTotalModuleCount = (stats: StatsCompilation, inChunk?: number) =>
getWebpackModules(stats, inChunk)!.length;
/**
* Gets the number of node modules.
*/
export const getAverageChunkSize = (stats: Stats.ToJsonOutput) => {
export const getAverageChunkSize = (stats: StatsCompilation) => {
let sum = 0;
for (const chunk of stats.chunks!) {
sum += chunk.size;
@ -434,18 +442,18 @@ export interface IModuleTreeNode {
/**
* Gets the reasons that the module was imported. Fix for broken webpack typings here.
*/
export const getReasons = (m: Stats.FnModules): Stats.Reason[] => m.reasons as any;
export const getReasons = (m: StatsModule): Stats.Reason[] => m.reasons as any;
/**
* Gets all direct imports of the given node module.
*/
export const getDirectImportsOfNodeModule = (stats: Stats.ToJsonOutput, name: string) =>
stats.modules!.filter(m => getNodeModuleFromIdentifier(m.name) === name);
export const getDirectImportsOfNodeModule = (stats: StatsCompilation, name: string) =>
stats.modules!.filter(m => m.name && getNodeModuleFromIdentifier(m.name) === name);
/**
* Gets all direct imports of the given node module.
*/
export const getImportsOfName = (stats: Stats.ToJsonOutput, name: string): Stats.FnModules[] => {
export const getImportsOfName = (stats: StatsCompilation, name: string): StatsModule[] => {
const modules = getWebpackModulesMap(stats);
const root = modules[name];
if (!root) {

Просмотреть файл

@ -1,7 +1,7 @@
import { decode } from 'msgpack-lite';
import { inflate } from 'pako';
import { from, Observable } from 'rxjs';
import { Stats } from 'webpack';
import { StatsCompilation } from 'webpack';
import { CompareAction, doAnalysis, ILoadableResource } from '../redux/actions';
import { ErrorCode } from '../redux/reducer';
import { Semaphore } from './semaphore';
@ -52,14 +52,14 @@ export function download(resource: ILoadableResource): Observable<CompareAction>
);
}
function processDownload(rawResponse: ArrayBuffer): Stats.ToJsonOutput {
function processDownload(rawResponse: ArrayBuffer): StatsCompilation {
const asArray = new Uint8Array(rawResponse);
const data = asArray[0] === 0x1f && asArray[1] === 0x8b ? inflate(asArray) : asArray;
const stats: Stats.ToJsonOutput =
const stats: StatsCompilation =
data[0] === '{'.charCodeAt(0) ? JSON.parse(new TextDecoder().decode(data)) : decode(data);
if (stats.modules) {
stats.modules = stats.modules.filter(m => m.chunks.length !== 0);
stats.modules = stats.modules.filter(m => m.chunks && m.chunks.length !== 0);
}
return stats;

Просмотреть файл

@ -3,7 +3,7 @@ import { createWriteStream } from 'fs';
import { createEncodeStream } from 'msgpack-lite';
import { resolve } from 'path';
import { PassThrough } from 'stream';
import { Compiler, Stats } from 'webpack';
import { Compiler, Stats, StatsCompilation } from 'webpack';
import { createGzip } from 'zlib';
export const enum StatsFormat {
@ -51,7 +51,7 @@ const defaultFilename = (gzip: boolean, format: StatsFormat) => {
const writeToStream = (
options: IPluginOptions,
data: Stats.ToJsonOutput,
data: StatsCompilation,
stream: NodeJS.WritableStream,
) => {
if (options.format === StatsFormat.MsgPack) {
@ -129,7 +129,8 @@ export class BundleComparisonPlugin {
if (compiler.hooks) {
compiler.hooks.done.tapAsync(BundleComparisonPlugin.name, handler);
} else {
compiler.plugin('done', handler);
// webpack 3
(compiler as any).plugin('done', handler);
}
}
}

Просмотреть файл

@ -35,7 +35,6 @@ module.exports = {
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[local]__[hash:base64:5]',
},
},
{
@ -50,7 +49,9 @@ module.exports = {
new HtmlWebpackPlugin({
title: 'Webpack Bundle Compare',
}),
new CopyPlugin([{ from: path.join(__dirname, 'public'), to: 'public' }]),
new CopyPlugin({
patterns: [{ from: 'public/**/*.*', to: 'public' }],
}),
new DefinePlugin({
INITIAL_FILES: process.env.WBC_FILES
? JSON.stringify(process.env.WBC_FILES.split(','))