mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
feature: Adds logs chart with stacked charts with total errors (#32742)
* add logschart with stacked bars * use ui charts pkg * fix contrast * move to ui-patterns * undo areachart * add recharts peerDep * update pckgjson and lock w recharts * feat: enable client-side rendering for LogsBarChart component * fix registry
This commit is contained in:
1 parent
c7ebd91740
commit
a2aa5bc83d
17 files changed
+252
-151
No files matched your search
@@ -2161,6 +2161,17 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"logs-bar-chart": {
|
||||
name: "logs-bar-chart",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/logs-bar-chart")),
|
||||
source: "",
|
||||
files: ["registry/default/example/logs-bar-chart.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"chart-area-axes": {
|
||||
name: "chart-area-axes",
|
||||
type: "components:block",
|
||||
|
||||
@@ -117,6 +117,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/fragments/multi-select',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Logs Bar Chart',
|
||||
href: '/docs/fragments/logs-bar-chart',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Logs Bar Chart
|
||||
description: A stacked bar chart that displays logs errors and successes.
|
||||
fragment: true
|
||||
---
|
||||
|
||||
<ComponentPreview name="logs-bar-chart" peekCode wide />
|
||||
|
||||
## Usage
|
||||
|
||||
This component is used to display the bar chart in logs pages.
|
||||
|
||||
It requires that the `data` prop is an array of objects with the following shape:
|
||||
|
||||
```ts
|
||||
type LogsBarChartDatum = {
|
||||
timestamp: string
|
||||
ok_count: number
|
||||
error_count: number
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
import { LogsBarChart } from 'ui-patterns/LogsBarChart'
|
||||
|
||||
export default function LogsBarChartDemo() {
|
||||
const data = Array.from({ length: 100 }, (_, i) => {
|
||||
const date = new Date()
|
||||
date.setMinutes(date.getMinutes() - i * 5) // Each point 5 minutes apart
|
||||
|
||||
return {
|
||||
timestamp: date.toISOString(),
|
||||
ok_count: Math.floor(Math.random() * 100), // Random value 0-99
|
||||
error_count: Math.floor(Math.random() * 50), // Random value 0-50
|
||||
}
|
||||
}).reverse()
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<LogsBarChart data={data} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1201,4 +1201,9 @@ export const examples: Registry = [
|
||||
type: 'docs:example',
|
||||
files: ['example/expanding-textarea-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'logs-bar-chart',
|
||||
type: 'components:example',
|
||||
files: ['example/logs-bar-chart.tsx'],
|
||||
},
|
||||
]
|
||||
@@ -1,27 +0,0 @@
|
||||
import BarChart from 'components/ui/Charts/BarChart'
|
||||
import type { Datum } from 'components/ui/Charts/Charts.types'
|
||||
import type { EventChartData } from './Logs.types'
|
||||
|
||||
export interface LogEventChartProps {
|
||||
data: EventChartData[]
|
||||
onBarClick: (isoTimestamp: string) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
const LogEventChart = ({ data, onBarClick, className }: LogEventChartProps) => (
|
||||
<BarChart
|
||||
className={className}
|
||||
minimalHeader
|
||||
size="tiny"
|
||||
yAxisKey="count"
|
||||
xAxisKey="timestamp"
|
||||
data={data}
|
||||
title="Logs / Time"
|
||||
onBarClick={(datum: Datum | EventChartData) => {
|
||||
if (!datum?.timestamp) return
|
||||
onBarClick(datum.timestamp as string)
|
||||
}}
|
||||
customDateFormat="MMM D, HH:mm:s"
|
||||
/>
|
||||
)
|
||||
export default LogEventChart
|
||||
@@ -673,6 +673,16 @@ export const LOGS_TAILWIND_CLASSES = {
|
||||
}
|
||||
|
||||
export const PREVIEWER_DATEPICKER_HELPERS: DatetimeHelper[] = [
|
||||
{
|
||||
text: 'Last 15 minutes',
|
||||
calcFrom: () => dayjs().subtract(15, 'minute').startOf('minute').toISOString(),
|
||||
calcTo: () => '',
|
||||
},
|
||||
{
|
||||
text: 'Last 30 minutes',
|
||||
calcFrom: () => dayjs().subtract(30, 'minute').startOf('minute').toISOString(),
|
||||
calcTo: () => '',
|
||||
},
|
||||
{
|
||||
text: 'Last hour',
|
||||
calcFrom: () => dayjs().subtract(1, 'hour').startOf('hour').toISOString(),
|
||||
|
||||
@@ -51,7 +51,8 @@ export interface CountData {
|
||||
}
|
||||
|
||||
export interface EventChartData extends Datum {
|
||||
count: number
|
||||
error_count: number
|
||||
ok_count: number
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
|
||||
@@ -321,14 +321,16 @@ export const genChartQuery = (
|
||||
) => {
|
||||
const [startOffset, trunc] = calcChartStart(params)
|
||||
const where = genWhereStatement(table, filters)
|
||||
const errorCondition = getErrorCondition(table)
|
||||
|
||||
let joins = genCrossJoinUnnests(table)
|
||||
|
||||
return `
|
||||
const q = `
|
||||
SELECT
|
||||
-- log-event-chart
|
||||
timestamp_trunc(t.timestamp, ${trunc}) as timestamp,
|
||||
count(t.timestamp) as count
|
||||
count(CASE WHEN NOT (${errorCondition}) THEN 1 END) as ok_count,
|
||||
count(CASE WHEN ${errorCondition} THEN 1 END) as error_count,
|
||||
FROM
|
||||
${table} t
|
||||
${joins}
|
||||
@@ -342,6 +344,7 @@ timestamp
|
||||
ORDER BY
|
||||
timestamp ASC
|
||||
`
|
||||
return q
|
||||
}
|
||||
|
||||
type TsPair = [string | '', string | '']
|
||||
@@ -579,3 +582,20 @@ export function checkForWildcard(query: string) {
|
||||
const wildcardRegex = /\*/
|
||||
return wildcardRegex.test(queryWithoutCount)
|
||||
}
|
||||
|
||||
function getErrorCondition(table: LogsTableName): string {
|
||||
switch (table) {
|
||||
case 'edge_logs':
|
||||
return 'response.status_code >= 400'
|
||||
case 'postgres_logs':
|
||||
return "parsed.error_severity IN ('ERROR', 'FATAL', 'PANIC')"
|
||||
case 'auth_logs':
|
||||
return "metadata.level = 'error' OR metadata.status >= 400"
|
||||
case 'function_edge_logs':
|
||||
return 'response.status_code >= 400'
|
||||
case 'function_logs':
|
||||
return "metadata.level IN ('error', 'fatal')"
|
||||
default:
|
||||
return 'false' // Default to no errors if table type is unknown
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
|
||||
import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import { Button, cn } from 'ui'
|
||||
import LogEventChart from './LogEventChart'
|
||||
import LogTable from './LogTable'
|
||||
import { LOGS_TABLES, LOG_ROUTES_WITH_REPLICA_SUPPORT, LogsTableName } from './Logs.constants'
|
||||
import type { Filters, LogSearchCallback, LogTemplate, QueryType } from './Logs.types'
|
||||
@@ -21,6 +20,8 @@ import { ensureNoTimestampConflict, maybeShowUpgradePrompt } from './Logs.utils'
|
||||
import UpgradePrompt from './UpgradePrompt'
|
||||
import { useSelectedLog } from 'hooks/analytics/useSelectedLog'
|
||||
import useSingleLog from 'hooks/analytics/useSingleLog'
|
||||
import { LogsBarChart } from 'ui-patterns/LogsBarChart'
|
||||
import NoDataPlaceholder from 'components/ui/Charts/NoDataPlaceholder'
|
||||
|
||||
/**
|
||||
* Acts as a container component for the entire log display
|
||||
@@ -93,21 +94,6 @@ export const LogsPreviewer = ({
|
||||
params.iso_timestamp_start as string
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
search_query: s,
|
||||
database: db,
|
||||
}))
|
||||
if (ite || its) {
|
||||
setParams((prev) => ({
|
||||
...prev,
|
||||
iso_timestamp_start: its || '',
|
||||
iso_timestamp_end: ite || '',
|
||||
}))
|
||||
}
|
||||
}, [db, s, ite, its])
|
||||
|
||||
// Show the prompt on page load based on query params
|
||||
useEffect(() => {
|
||||
if (its) {
|
||||
@@ -239,23 +225,28 @@ export const LogsPreviewer = ({
|
||||
<div
|
||||
className={
|
||||
'transition-all duration-500 ' +
|
||||
(showChart && logData.length > 0 ? 'mb-4 h-28 opacity-100' : 'h-0 opacity-0')
|
||||
(showChart && logData.length > 0 ? 'mb-2 mt-1 opacity-100' : 'h-0 opacity-0')
|
||||
}
|
||||
>
|
||||
<div className={condensedLayout ? 'px-3' : ''}>
|
||||
{showChart && (
|
||||
<LogEventChart
|
||||
className={cn({
|
||||
'opacity-40': isLoading,
|
||||
})}
|
||||
<LogsBarChart
|
||||
data={eventChartData}
|
||||
onBarClick={(isoTimestamp) => {
|
||||
onBarClick={(datum) => {
|
||||
if (!datum?.timestamp) return
|
||||
handleSearch('event-chart-bar-click', {
|
||||
query: filters.search_query as string,
|
||||
to: isoTimestamp as string,
|
||||
to: datum.timestamp.toString(),
|
||||
from: null,
|
||||
})
|
||||
}}
|
||||
EmptyState={
|
||||
<NoDataPlaceholder
|
||||
message={'No data'}
|
||||
description="It may take up to 24 hours for data to refresh"
|
||||
size={'tiny'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,8 @@ export const CHART_COLORS = {
|
||||
AXIS: 'hsl(var(--background-overlay-hover))',
|
||||
GREEN_1: 'hsl(var(--brand-default))', // #3ECF8E
|
||||
GREEN_2: 'hsl(var(--brand-500))',
|
||||
RED_1: 'hsl(var(--destructive-default))',
|
||||
RED_2: 'hsl(var(--destructive-500))',
|
||||
}
|
||||
|
||||
// refer to packages/ui/radix-colors.js for full list of colors
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { afterEach, beforeEach, test, vi } from 'vitest'
|
||||
|
||||
import { screen } from '@testing-library/react'
|
||||
import LogEventChart from 'components/interfaces/Settings/Logs/LogEventChart'
|
||||
import { render } from '../../helpers'
|
||||
|
||||
const { ResizeObserver } = window
|
||||
|
||||
beforeEach(() => {
|
||||
delete (window as any).ResizeObserver
|
||||
window.ResizeObserver = vi.fn().mockImplementation(() => ({
|
||||
observe: vi.fn(),
|
||||
unobserve: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
}))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
window.ResizeObserver = ResizeObserver
|
||||
})
|
||||
|
||||
test('renders chart', async () => {
|
||||
const mockFn = vi.fn()
|
||||
const tsMicro = new Date().getTime() * 1000
|
||||
render(
|
||||
<LogEventChart
|
||||
data={[
|
||||
{ timestamp: tsMicro.toString(), count: 1 },
|
||||
{ timestamp: (tsMicro + 1).toString(), count: 2 },
|
||||
]}
|
||||
onBarClick={mockFn}
|
||||
/>
|
||||
)
|
||||
await screen.findByText(/Logs \/ Time/)
|
||||
})
|
||||
@@ -0,0 +1,131 @@
|
||||
'use client'
|
||||
import dayjs from 'dayjs'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import { Bar, Cell, BarChart as RechartBarChart, XAxis, YAxis } from 'recharts'
|
||||
import type { CategoricalChartState } from 'recharts/types/chart/types'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, cn } from 'ui'
|
||||
|
||||
const CHART_COLORS = {
|
||||
TICK: 'hsl(var(--background-overlay-hover))',
|
||||
AXIS: 'hsl(var(--background-overlay-hover))',
|
||||
GREEN_1: 'hsl(var(--brand-default))',
|
||||
GREEN_2: 'hsl(var(--brand-500))',
|
||||
RED_1: 'hsl(var(--destructive-default))',
|
||||
RED_2: 'hsl(var(--destructive-500))',
|
||||
}
|
||||
type LogsBarChartDatum = {
|
||||
timestamp: string
|
||||
error_count: number
|
||||
ok_count: number
|
||||
}
|
||||
export const LogsBarChart = ({
|
||||
data,
|
||||
onBarClick,
|
||||
EmptyState,
|
||||
DateTimeFormat = 'MMM D, YYYY, hh:mma',
|
||||
}: {
|
||||
data: LogsBarChartDatum[]
|
||||
onBarClick?: (datum: LogsBarChartDatum, tooltipData?: CategoricalChartState) => void
|
||||
EmptyState?: ReactNode
|
||||
DateTimeFormat?: string
|
||||
}) => {
|
||||
const [focusDataIndex, setFocusDataIndex] = useState<number | null>(null)
|
||||
|
||||
if (data.length === 0) {
|
||||
if (EmptyState) return EmptyState
|
||||
return null
|
||||
}
|
||||
|
||||
const startDate = dayjs(data[0]['timestamp']).format(DateTimeFormat)
|
||||
const endDate = dayjs(data[data?.length - 1]?.['timestamp']).format(DateTimeFormat)
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-y-3')}>
|
||||
<ChartContainer
|
||||
config={
|
||||
{
|
||||
error_count: {
|
||||
label: 'Errors',
|
||||
},
|
||||
ok_count: {
|
||||
label: 'Ok',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
}
|
||||
className="h-[80px]"
|
||||
>
|
||||
<RechartBarChart
|
||||
data={data}
|
||||
onMouseMove={(e: any) => {
|
||||
if (e.activeTooltipIndex !== focusDataIndex) {
|
||||
setFocusDataIndex(e.activeTooltipIndex)
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => setFocusDataIndex(null)}
|
||||
onClick={(tooltipData) => {
|
||||
const datum = tooltipData?.activePayload?.[0]?.payload
|
||||
if (onBarClick) onBarClick(datum, tooltipData)
|
||||
}}
|
||||
>
|
||||
<YAxis
|
||||
tick={false}
|
||||
width={0}
|
||||
axisLine={{ stroke: CHART_COLORS.AXIS }}
|
||||
tickLine={{ stroke: CHART_COLORS.AXIS }}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="timestamp"
|
||||
interval={data.length - 2}
|
||||
tick={false}
|
||||
axisLine={{ stroke: CHART_COLORS.AXIS }}
|
||||
tickLine={{ stroke: CHART_COLORS.AXIS }}
|
||||
/>
|
||||
<ChartTooltip
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
className="text-foreground-light"
|
||||
labelFormatter={(v) => dayjs(v).format(DateTimeFormat)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Error bars */}
|
||||
<Bar dataKey="error_count" fill={CHART_COLORS.RED_1} maxBarSize={24} stackId="stack">
|
||||
{data?.map((_entry: LogsBarChartDatum, index: number) => (
|
||||
<Cell
|
||||
className="cursor-pointer transition-colors"
|
||||
key={`error-${index}`}
|
||||
fill={
|
||||
focusDataIndex === index || focusDataIndex === null
|
||||
? CHART_COLORS.RED_1
|
||||
: CHART_COLORS.RED_2
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Bar>
|
||||
|
||||
{/* Success bars */}
|
||||
<Bar dataKey="ok_count" fill={CHART_COLORS.GREEN_1} maxBarSize={24} stackId="stack">
|
||||
{data?.map((_entry: LogsBarChartDatum, index: number) => (
|
||||
<Cell
|
||||
className="cursor-pointer transition-colors"
|
||||
key={`success-${index}`}
|
||||
fill={
|
||||
focusDataIndex === index || focusDataIndex === null
|
||||
? CHART_COLORS.GREEN_1
|
||||
: CHART_COLORS.GREEN_2
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Bar>
|
||||
</RechartBarChart>
|
||||
</ChartContainer>
|
||||
{data && (
|
||||
<div className="text-foreground-lighter -mt-10 flex items-center justify-between text-[10px] font-mono">
|
||||
<span>{startDate}</span>
|
||||
<span>{endDate}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -22,3 +22,4 @@ export * from './PromoToast'
|
||||
export * from './admonition'
|
||||
export * from './ComputeBadge'
|
||||
export * from './TimestampInfo'
|
||||
export * from './LogsBarChart'
|
||||
@@ -34,6 +34,7 @@
|
||||
"react-tooltip": "*",
|
||||
"react-use": "^17.5.0",
|
||||
"reactflow": "*",
|
||||
"recharts": "^2.8.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"sonner": "^1.5.0",
|
||||
"sql-formatter": "^15.3.1",
|
||||
|
||||
@@ -216,7 +216,7 @@ const ChartTooltipContent = React.forwardRef<
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-foreground-muted">
|
||||
<span className="text-foreground-light">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Generated
+6
-62
@@ -206,7 +206,7 @@ importers:
|
||||
version: 14.2.3
|
||||
'@next/mdx':
|
||||
specifier: ^14.2.3
|
||||
version: 14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0))(@mdx-js/react@2.3.0(react@18.2.0))
|
||||
version: 14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1))(@mdx-js/react@2.3.0(react@18.2.0))
|
||||
'@octokit/auth-app':
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
@@ -988,7 +988,7 @@ importers:
|
||||
version: 14.2.3
|
||||
'@next/mdx':
|
||||
specifier: ^14.2.3
|
||||
version: 14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0(esbuild@0.20.2)))(@mdx-js/react@2.3.0(react@18.2.0))
|
||||
version: 14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1))(@mdx-js/react@2.3.0(react@18.2.0))
|
||||
'@octokit/auth-app':
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
@@ -1852,6 +1852,9 @@ importers:
|
||||
reactflow:
|
||||
specifier: '*'
|
||||
version: 11.10.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
recharts:
|
||||
specifier: ^2.8.0
|
||||
version: 2.12.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
remark-gfm:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0(supports-color@8.1.1)
|
||||
@@ -14005,15 +14008,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0(esbuild@0.20.2))':
|
||||
dependencies:
|
||||
'@mdx-js/mdx': 2.3.0(supports-color@8.1.1)
|
||||
source-map: 0.7.4
|
||||
webpack: 5.94.0(esbuild@0.20.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
'@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0)':
|
||||
dependencies:
|
||||
'@mdx-js/mdx': 2.3.0(supports-color@8.1.1)
|
||||
@@ -14155,14 +14149,7 @@ snapshots:
|
||||
dependencies:
|
||||
glob: 10.3.10
|
||||
|
||||
'@next/mdx@14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0(esbuild@0.20.2)))(@mdx-js/react@2.3.0(react@18.2.0))':
|
||||
dependencies:
|
||||
source-map: 0.7.4
|
||||
optionalDependencies:
|
||||
'@mdx-js/loader': 2.3.0(supports-color@8.1.1)(webpack@5.94.0(esbuild@0.20.2))
|
||||
'@mdx-js/react': 2.3.0(react@18.2.0)
|
||||
|
||||
'@next/mdx@14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1)(webpack@5.94.0))(@mdx-js/react@2.3.0(react@18.2.0))':
|
||||
'@next/mdx@14.2.3(@mdx-js/loader@2.3.0(supports-color@8.1.1))(@mdx-js/react@2.3.0(react@18.2.0))':
|
||||
dependencies:
|
||||
source-map: 0.7.4
|
||||
optionalDependencies:
|
||||
@@ -24787,18 +24774,6 @@ snapshots:
|
||||
mkdirp: 1.0.4
|
||||
yallist: 4.0.0
|
||||
|
||||
terser-webpack-plugin@5.3.10(esbuild@0.20.2)(webpack@5.94.0(esbuild@0.20.2)):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
jest-worker: 27.5.1
|
||||
schema-utils: 3.3.0
|
||||
serialize-javascript: 6.0.2
|
||||
terser: 5.32.0
|
||||
webpack: 5.94.0(esbuild@0.20.2)
|
||||
optionalDependencies:
|
||||
esbuild: 0.20.2
|
||||
optional: true
|
||||
|
||||
terser-webpack-plugin@5.3.10(webpack@5.94.0):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
@@ -25653,37 +25628,6 @@ snapshots:
|
||||
- esbuild
|
||||
- uglify-js
|
||||
|
||||
webpack@5.94.0(esbuild@0.20.2):
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@webassemblyjs/ast': 1.12.1
|
||||
'@webassemblyjs/wasm-edit': 1.12.1
|
||||
'@webassemblyjs/wasm-parser': 1.12.1
|
||||
acorn: 8.11.3
|
||||
acorn-import-attributes: 1.9.5(acorn@8.11.3)
|
||||
browserslist: 4.23.3
|
||||
chrome-trace-event: 1.0.3
|
||||
enhanced-resolve: 5.17.1
|
||||
es-module-lexer: 1.5.4
|
||||
eslint-scope: 5.1.1
|
||||
events: 3.3.0
|
||||
glob-to-regexp: 0.4.1
|
||||
graceful-fs: 4.2.11
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
loader-runner: 4.3.0
|
||||
mime-types: 2.1.35
|
||||
neo-async: 2.6.2
|
||||
schema-utils: 3.3.0
|
||||
tapable: 2.2.1
|
||||
terser-webpack-plugin: 5.3.10(esbuild@0.20.2)(webpack@5.94.0(esbuild@0.20.2))
|
||||
watchpack: 2.4.2
|
||||
webpack-sources: 3.2.3
|
||||
transitivePeerDependencies:
|
||||
- '@swc/core'
|
||||
- esbuild
|
||||
- uglify-js
|
||||
optional: true
|
||||
|
||||
whatwg-encoding@2.0.0:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
|
||||
Reference in new issue
Block a user