Skip to content

[pull] canary from vercel:canary #131

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/next/src/build/define-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ export function getDefineEnv({
'process.env.__NEXT_RELATIVE_DIST_DIR': config.distDir,
}
: {}),
'process.env.__NEXT_DEVTOOL_SEGMENT_EXPLORER':
config.experimental.devtoolSegmentExplorer ?? false,
}

const userDefines = config.compiler?.define ?? {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { TurbopackInfo } from './dev-tools-info/turbopack-info'
import { RouteInfo } from './dev-tools-info/route-info'
import GearIcon from '../../../icons/gear-icon'
import { UserPreferences } from './dev-tools-info/user-preferences'
import { SegmentsExplorer } from '../../overview/segment-explorer'
import {
MENU_CURVE,
MENU_DURATION_MS,
Expand Down Expand Up @@ -80,6 +81,7 @@ const OVERLAYS = {
Turbo: 'turbo',
Route: 'route',
Preferences: 'preferences',
SegmentExplorer: 'segment-explorer',
} as const

export type Overlays = (typeof OVERLAYS)[keyof typeof OVERLAYS]
Expand Down Expand Up @@ -123,6 +125,7 @@ function DevToolsPopover({
const isTurbopackInfoOpen = open === OVERLAYS.Turbo
const isRouteInfoOpen = open === OVERLAYS.Route
const isPreferencesOpen = open === OVERLAYS.Preferences
const isSegmentExplorerOpen = open === OVERLAYS.SegmentExplorer

const { mounted: menuMounted, rendered: menuRendered } = useDelayedRender(
isMenuOpen,
Expand Down Expand Up @@ -325,6 +328,16 @@ function DevToolsPopover({
setScale={setScale}
/>

{/* Page Segment Explorer */}
{process.env.__NEXT_DEVTOOL_SEGMENT_EXPLORER ? (
<SegmentsExplorer
isOpen={isSegmentExplorerOpen}
close={openRootMenu}
triggerRef={triggerRef}
style={popover}
/>
) : null}

{/* Dropdown Menu */}
{menuMounted && (
<div
Expand Down Expand Up @@ -390,6 +403,15 @@ function DevToolsPopover({
onClick={() => setOpen(OVERLAYS.Preferences)}
index={isTurbopack ? 2 : 3}
/>
{process.env.__NEXT_DEVTOOL_SEGMENT_EXPLORER ? (
<MenuItem
data-rendered-files
label="Segment Explorer"
value={<ChevronRight />}
onClick={() => setOpen(OVERLAYS.SegmentExplorer)}
index={isTurbopack ? 3 : 4}
/>
) : null}
</div>
</Context.Provider>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ export interface DevToolsInfoPropsCore {
}

export interface DevToolsInfoProps extends DevToolsInfoPropsCore {
title: string
title: React.ReactNode
children: React.ReactNode
learnMoreLink?: string
closeButton?: boolean
}

export function DevToolsInfo({
Expand All @@ -20,6 +21,7 @@ export function DevToolsInfo({
learnMoreLink,
isOpen,
triggerRef,
closeButton = true,
close,
...props
}: DevToolsInfoProps) {
Expand Down Expand Up @@ -55,25 +57,29 @@ export function DevToolsInfo({
<div className="dev-tools-info-container">
<h1 className="dev-tools-info-title">{title}</h1>
{children}
<div className="dev-tools-info-button-container">
<button
ref={closeButtonRef}
className="dev-tools-info-close-button"
onClick={close}
>
Close
</button>
{learnMoreLink && (
<a
className="dev-tools-info-learn-more-button"
href={learnMoreLink}
target="_blank"
rel="noreferrer noopener"
>
Learn More
</a>
)}
</div>
{(closeButton || learnMoreLink) && (
<div className="dev-tools-info-button-container">
{closeButton ? (
<button
ref={closeButtonRef}
className="dev-tools-info-close-button"
onClick={close}
>
Close
</button>
) : null}
{learnMoreLink && (
<a
className="dev-tools-info-learn-more-button"
href={learnMoreLink}
target="_blank"
rel="noreferrer noopener"
>
Learn More
</a>
)}
</div>
)}
</div>
</div>
)
Expand Down Expand Up @@ -112,6 +118,7 @@ export const DEV_TOOLS_INFO_STYLES = `

.dev-tools-info-container {
padding: 12px;
width: 100%;
}

.dev-tools-info-title {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import type { HTMLProps } from 'react'
import { css } from '../../../utils/css'
import type { DevToolsInfoPropsCore } from '../errors/dev-tools-indicator/dev-tools-info/dev-tools-info'
import { DevToolsInfo } from '../errors/dev-tools-indicator/dev-tools-info/dev-tools-info'
import { cx } from '../../utils/cx'
import { LeftArrow } from '../../icons/left-arrow'
import {
useSegmentTreeClientState,
type SegmentNode,
} from '../../../../../../shared/lib/devtool/app-segment-tree'
import type { Trie, TrieNode } from '../../../../../../shared/lib/devtool/trie'

const IconLayout = (props: React.SVGProps<SVGSVGElement>) => {
return (
<svg
{...props}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16 12.5L15.9873 12.7559C15.8677 13.9323 14.9323 14.8677 13.7559 14.9873L13.5 15H2.5L2.24414 14.9873C1.06772 14.8677 0.132274 13.9323 0.0126953 12.7559L0 12.5V1H16V12.5ZM1.5 6.25488V12.5C1.5 13.0523 1.94772 13.5 2.5 13.5H4.99512V6.25488H1.5ZM6.24512 6.25488V13.5H13.5C14.0523 13.5 14.5 13.0523 14.5 12.5V6.25488H6.24512ZM1.5 5.00488H14.5V2.5H1.5V5.00488Z"
fill="currentColor"
/>
</svg>
)
}

const IconPage = (props: React.SVGProps<SVGSVGElement>) => {
return (
<svg
{...props}
viewBox="0 0 16 16"
fill="none"
strokeLinejoin="round"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M14.5 6.5V13.5C14.5 14.8807 13.3807 16 12 16H4C2.61929 16 1.5 14.8807 1.5 13.5V1.5V0H3H8H9.08579C9.351 0 9.60536 0.105357 9.79289 0.292893L14.2071 4.70711C14.3946 4.89464 14.5 5.149 14.5 5.41421V6.5ZM13 6.5V13.5C13 14.0523 12.5523 14.5 12 14.5H4C3.44772 14.5 3 14.0523 3 13.5V1.5H8V5V6.5H9.5H13ZM9.5 2.12132V5H12.3787L9.5 2.12132Z"
fill="currentColor"
/>
</svg>
)
}

const ICONS = {
layout: <IconLayout width={16} />,
page: <IconPage width={16} />,
}

function PageSegmentTree({ tree }: { tree: Trie<SegmentNode> | undefined }) {
if (!tree) {
return null
}
return (
<div className="segment-explorer-content">
<PageSegmentTreeLayerPresentation
tree={tree}
node={tree.getRoot()}
level={0}
/>
</div>
)
}

function PageSegmentTreeLayerPresentation({
tree,
node,
level,
}: {
tree: Trie<SegmentNode>
node: TrieNode<SegmentNode>
level: number
}) {
const segments = node.value?.pagePath?.split('/') || []
const fileName = segments[segments.length - 1] || ''
const nodeName = node.value?.type
const pagePathPrefix = segments.slice(0, -1).join('/')

return (
<div className="segment-explorer-item">
{!fileName || level === 0 ? null : (
<div className="segment-explorer-item-row">
<div className="segment-explorer-line">
<div className={`segment-explorer-line-text-${nodeName}`}>
<span
className={cx(
'segment-explorer-line-icon',
`segment-explorer-line-icon-${nodeName}`
)}
>
{nodeName === 'layout' ? ICONS.layout : ICONS.page}
</span>
{pagePathPrefix === '' ? '' : `${pagePathPrefix}/`}
<span className="segment-explorer-filename-path">{fileName}</span>
</div>
</div>
</div>
)}

<div className="tree-node-expanded-rendered-children">
{Object.entries(node.children).map(
([key, child]) =>
child && (
<PageSegmentTreeLayerPresentation
key={key}
tree={tree}
node={child}
level={level + 1}
/>
)
)}
</div>
</div>
)
}

export function SegmentsExplorer(
props: DevToolsInfoPropsCore & HTMLProps<HTMLDivElement>
) {
const ctx = useSegmentTreeClientState()
if (!ctx) {
return null
}

return (
<DevToolsInfo
title={
<>
<button
className="segment-explorer-back-button"
onClick={props.close}
>
<LeftArrow />
</button>
{'Segment Explorer'}
</>
}
closeButton={false}
{...props}
>
<PageSegmentTree tree={ctx.tree} />
</DevToolsInfo>
)
}

export const DEV_TOOLS_INFO_RENDER_FILES_STYLES = css`
.segment-explorer-back-button {
margin-right: 12px;
color: var(--color-gray-1000);
}
.segment-explorer-back-button svg {
width: 20px;
height: 20px;
}

.segment-explorer-content {
overflow-y: auto;
padding: 0 12px;
font-size: var(--size-14);
}

.segment-explorer-item-row {
display: flex;
align-items: center;
gap: 8px;
padding: 2px 0;
}

.segment-explorer-filename-path {
display: inline-block;

&:hover {
color: var(--color-gray-1000);
text-decoration: none;
}
}

.segment-explorer-filename-path a {
color: inherit;
text-decoration: inherit;
}

.segment-explorer-line {
white-space: pre;
}

.segment-explorer-line-icon {
margin-right: 4px;
}
.segment-explorer-line-icon-page {
color: inherit;
}
.segment-explorer-line-icon-layout {
color: var(--color-gray-1-00);
}

.segment-explorer-line-text-page {
color: var(--color-blue-900);
font-weight: 500;
}
`
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { DEV_TOOLS_INFO_STYLES } from '../components/errors/dev-tools-indicator/
import { DEV_TOOLS_INFO_TURBOPACK_INFO_STYLES } from '../components/errors/dev-tools-indicator/dev-tools-info/turbopack-info'
import { DEV_TOOLS_INFO_ROUTE_INFO_STYLES } from '../components/errors/dev-tools-indicator/dev-tools-info/route-info'
import { DEV_TOOLS_INFO_USER_PREFERENCES_STYLES } from '../components/errors/dev-tools-indicator/dev-tools-info/user-preferences'
import { DEV_TOOLS_INFO_RENDER_FILES_STYLES } from '../components/overview/segment-explorer'
import { FADER_STYLES } from '../components/fader'

export function ComponentStyles() {
Expand Down Expand Up @@ -49,6 +50,7 @@ export function ComponentStyles() {
${DEV_TOOLS_INFO_TURBOPACK_INFO_STYLES}
${DEV_TOOLS_INFO_ROUTE_INFO_STYLES}
${DEV_TOOLS_INFO_USER_PREFERENCES_STYLES}
${DEV_TOOLS_INFO_RENDER_FILES_STYLES}
${FADER_STYLES}
`}
</style>
Expand Down
Loading
Loading