Skip to content

Commit

Permalink
feat: experimental build analyze (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed May 17, 2023
1 parent 2e9d824 commit 2344afd
Show file tree
Hide file tree
Showing 21 changed files with 646 additions and 44 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -21,6 +21,7 @@
"@nuxt/devtools": "workspace:*",
"@nuxt/devtools-ui-kit": "workspace:*",
"@nuxt/module-builder": "^0.3.1",
"@nuxt/schema": "^3.5.0",
"@types/markdown-it": "^12.2.3",
"@types/node": "^18.16.10",
"@types/pacote": "^11.1.5",
Expand Down
18 changes: 18 additions & 0 deletions packages/devtools-kit/src/_types/analyze-build.ts
@@ -0,0 +1,18 @@
import type { NuxtAnalyzeMeta } from '@nuxt/schema'

export interface AnalyzeBuildMeta extends NuxtAnalyzeMeta {
features: {
bundleClient: boolean
bundleNitro: boolean
viteInspect: boolean
}
size: {
clientBundle?: number
nitroBundle?: number
}
}

export interface AnalyzeBuildsInfo {
isBuilding: boolean
builds: AnalyzeBuildMeta[]
}
1 change: 1 addition & 0 deletions packages/devtools-kit/src/_types/index.ts
Expand Up @@ -7,5 +7,6 @@ export * from './integrations'
export * from './wizard'
export * from './rpc'
export * from './server-ctx'
export * from './analyze-build'
export * from './options'
export * from './common'
7 changes: 7 additions & 0 deletions packages/devtools-kit/src/_types/rpc.ts
Expand Up @@ -6,6 +6,7 @@ import type { ModuleCustomTab } from './custom-tabs'
import type { AssetInfo, AutoImportsWithMetadata, ComponentRelationship, HookInfo, ImageMeta, NpmCommandOptions, NpmCommandType, PackageManagerName, PackageUpdateInfo, ServerRouteInfo } from './integrations'
import type { TerminalAction, TerminalInfo } from './terminals'
import type { GetWizardArgs, WizardActions } from './wizard'
import type { AnalyzeBuildsInfo } from './analyze-build'
import type { InstallModuleReturn } from './server-ctx'

export interface ServerFunctions {
Expand Down Expand Up @@ -44,6 +45,12 @@ export interface ServerFunctions {
setStorageItem(key: string, value: StorageValue): Promise<void>
removeStorageItem(key: string): Promise<void>

// Analyze
getAnalyzeBuildInfo(): Promise<AnalyzeBuildsInfo>
generateAnalyzeBuildName(): Promise<string>
startAnalyzeBuild(name: string): Promise<string>
clearAnalyzeBuilds(names?: string[]): Promise<void>

// Queries
getImageMeta(filepath: string): Promise<ImageMeta | undefined>
getTextAssetContent(filepath: string, limit?: number): Promise<string | undefined>
Expand Down
3 changes: 3 additions & 0 deletions packages/devtools-kit/src/index.ts
Expand Up @@ -39,11 +39,14 @@ export function startSubprocess(
execaOptions.command,
execaOptions.args,
{
reject: false,
...execaOptions,
env: {
COLORS: 'true',
FORCE_COLOR: 'true',
...execaOptions.env,
// Force disable Nuxi CLI override
__CLI_ARGV__: undefined,
},
},
)
Expand Down
6 changes: 3 additions & 3 deletions packages/devtools-wizard/src/builtin.ts
@@ -1,5 +1,5 @@
import { existsSync } from 'node:fs'
import fs from 'node:fs/promises'
import fsp from 'node:fs/promises'
import { relative } from 'node:path'
import { consola } from 'consola'
import c from 'picocolors'
Expand Down Expand Up @@ -36,7 +36,7 @@ async function toggleConfig(cwd: string, value?: boolean) {
}

try {
const source = await fs.readFile(nuxtConfig, 'utf-8')
const source = await fsp.readFile(nuxtConfig, 'utf-8')
const mod = await parseModule(source, { sourceFileName: nuxtConfig })
const config = mod.exports.default.$type === 'function-call'
? mod.exports.default.$args[0]
Expand Down Expand Up @@ -71,7 +71,7 @@ async function toggleConfig(cwd: string, value?: boolean) {
if (!confirm)
return false

await fs.writeFile(nuxtConfig, `${generated.trimEnd()}\n`, 'utf-8')
await fsp.writeFile(nuxtConfig, `${generated.trimEnd()}\n`, 'utf-8')
}
}
catch (err) {
Expand Down
131 changes: 131 additions & 0 deletions packages/devtools/client/components/BuildAnalyzeDetails.vue
@@ -0,0 +1,131 @@
<script setup lang="ts">
import { formatTimeAgo } from '@vueuse/core'
import type { AnalyzeBuildMeta } from '../../src/types'
const props = defineProps<{
current: AnalyzeBuildMeta
prev?: AnalyzeBuildMeta
}>()
const ROUTE_ANALYZE = '/__nuxt_devtools__/analyze/'
const tabs = computed(() => {
const items = [
{ name: 'Overview', id: 'overview' },
]
if (props.current.features.bundleClient)
items.push({ name: 'Client Bundle', id: 'bundle-client' })
if (props.current.features.bundleNitro)
items.push({ name: 'Nitro Bundle', id: 'bundle-nitro' })
if (props.current.features.viteInspect)
items.push({ name: 'Vite Inspect', id: 'vite-inspect' })
return items
})
const selectedTab = ref(tabs.value[0])
const openInEditor = useOpenInEditor()
function formatFileSize(bytes: number) {
if (bytes < 1024)
return `${bytes}B`
if (bytes < 1024 * 1024)
return `${(bytes / 1024).toFixed(1)}KB`
if (bytes < 1024 * 1024 * 1024)
return `${(bytes / 1024 / 1024).toFixed(1)}MB`
return `${(bytes / 1024 / 1024 / 1024).toFixed(1)}GB`
}
function formatDuration(build: AnalyzeBuildMeta) {
return `${((build.endTime - build.startTime) / 1000).toFixed(1)}s`
}
</script>

<template>
<div h-full grid="~ rows-[max-content_1fr]">
<div flex="~ wrap" w-full>
<template v-for="tab, idx of tabs" :key="idx">
<button
px4 py2 border="r base"
hover="bg-active"
:class="tab.id === selectedTab.id ? '' : 'border-b'"
@click="selectedTab = tab"
>
<div :class="tab.id === selectedTab.id ? '' : 'op30' ">
{{ tab.name }}
</div>
</button>
</template>
<div border="b base" flex-auto />
</div>
<div
v-if="selectedTab.id === 'overview'"
flex="~ col gap-4 items-center justify-center" p4
>
<div flex-auto />
<div grid="~ cols-[30px_1fr] gap-x-2 gap-y-3 items-center justify-center" w-100>
<div i-carbon-commit text-xl />
<div>
<div text-sm op50>
Name
</div>
<div>{{ current.name }}</div>
</div>
<div i-carbon-time text-xl />
<div>
<div text-sm op50>
Build duration
</div>
<div>{{ formatDuration(current) }}</div>
</div>
<template v-if="current.size?.clientBundle">
<div i-carbon-cics-program text-xl />
<div>
<div text-sm op50>
Client bundle size
</div>
<div>{{ formatFileSize(current.size.clientBundle) }}</div>
</div>
</template>
<template v-if="current.size.nitroBundle">
<div i-carbon-bare-metal-server text-xl />
<div>
<div text-sm op50>
Nitro bundle size
</div>
<div>{{ formatFileSize(current.size.nitroBundle) }}</div>
</div>
</template>
<div i-carbon-edge-node text-xl />
<div>
<div text-sm op50>
Built
</div>
<div>{{ formatTimeAgo(new Date(current.endTime)) }}</div>
</div>
</div>
<NButton n="primary" icon="carbon-launch" @click="openInEditor(current.analyzeDir)">
Open in Editor
</NButton>
<div flex-auto />
<NButton n="rose" icon="carbon-delete" @click="rpc.clearAnalyzeBuilds([current.name])">
Delete this report
</NButton>
</div>
<iframe
v-lazy-show="selectedTab.id === 'bundle-client'"
:src="`${ROUTE_ANALYZE}${current.slug}/client.html`"
h-full w-full
/>
<iframe
v-lazy-show="selectedTab.id === 'bundle-nitro'"
:src="`${ROUTE_ANALYZE}${current.slug}/nitro.html`"
h-full w-full
/>
<iframe
v-lazy-show="selectedTab.id === 'vite-inspect'"
:src="`${ROUTE_ANALYZE}${current.slug}/.vite-inspect/`"
h-full w-full
/>
</div>
</template>
6 changes: 4 additions & 2 deletions packages/devtools/client/components/PanelLeftRight.vue
Expand Up @@ -6,6 +6,8 @@ const props = defineProps<{
* The key to use for storing the pane sizes in localStorage.
*/
storageKey?: string
leftSize?: number
}>()
const DEFAULT = 30
Expand All @@ -14,10 +16,10 @@ const state = useDevToolsPanelsState()
const key = props.storageKey
const size = key
? computed({
get: () => state.value[key] || DEFAULT,
get: () => state.value[key] || props.leftSize || DEFAULT,
set: (v) => { state.value[key] = v },
})
: ref(DEFAULT)
: ref(props.leftSize || DEFAULT)
</script>

<template>
Expand Down
12 changes: 12 additions & 0 deletions packages/devtools/client/composables/npm.ts
@@ -1,3 +1,4 @@
import semver from 'semver'
import type { NpmCommandOptions } from '../../src/types'

export type PackageUpdateState = 'idle' | 'running' | 'updated'
Expand All @@ -11,6 +12,17 @@ export function usePackageUpdate(name: string, options?: NpmCommandOptions): Ret
return map.get(key)
}

export function useNuxtVersion() {
return useAsyncData('npm:check:nuxt', () => rpc.checkForUpdateFor('nuxt')).data
}

export function satisfyNuxtVersion(range: string) {
const nuxt = useNuxtVersion()
if (!nuxt?.value?.current)
return false
return semver.satisfies(nuxt.value.current, range)
}

function getPackageUpdate(name: string, options?: NpmCommandOptions) {
const nuxt = useNuxtApp()
const info = useAsyncData(`npm:check:${name}`, () => rpc.checkForUpdateFor(name)).data
Expand Down
4 changes: 4 additions & 0 deletions packages/devtools/client/composables/state.ts
Expand Up @@ -147,6 +147,10 @@ export function useTerminals() {
return useAsyncState('getTerminals', () => rpc.getTerminals())
}

export function useAnalyzeBuildInfo() {
return useAsyncState('getAnalyzeBuildInfo', () => rpc.getAnalyzeBuildInfo())
}

export function useAllTabs() {
const customTabs = useCustomTabs()
const settings = useDevToolsOptions()
Expand Down
3 changes: 3 additions & 0 deletions packages/devtools/client/nuxt.config.ts
Expand Up @@ -45,6 +45,9 @@ export default defineNuxtConfig({
app: {
baseURL: '/__nuxt_devtools__/client',
},
experimental: {
watcher: 'parcel',
},
vite: {
define: {
'process.env.VSCODE_TEXTMATE_DEBUG': 'false',
Expand Down

0 comments on commit 2344afd

Please sign in to comment.