Skip to content
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

feat: experimental build analyze #190

Merged
merged 22 commits into from May 17, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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