Skip to content
Closed
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
4 changes: 3 additions & 1 deletion packages/devtools-kit/src/_types/client-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export interface NuxtDevtoolsHostClient {
/**
* Refreshes the client
*/
refreshState(): NuxtDevtoolsHostClient
refreshState(iframe?: HTMLIFrameElement): NuxtDevtoolsHostClient
}

export interface NuxtDevtoolsClient {
Expand All @@ -74,6 +74,8 @@ export interface NuxtDevtoolsClient {
}

export interface NuxtDevtoolsIframeClient {
url: string
version: string
host: NuxtDevtoolsHostClient
devtools: NuxtDevtoolsClient
}
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools-ui-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"@nuxt/devtools": "workspace:*",
"@nuxt/devtools-kit": "workspace:*",
"@nuxt/kit": "^3.5.3",
"@nuxtjs/color-mode": "^3.2.0",
"@nuxtjs/color-mode": "^3.3.0",
"@unocss/core": "^0.53.1",
"@unocss/nuxt": "^0.53.1",
"@unocss/preset-attributify": "^0.53.1",
Expand Down
9 changes: 9 additions & 0 deletions packages/devtools-webext/extension/devtools.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<script src="./dist/devtools.js"></script>
</body>
</html>
Binary file added packages/devtools-webext/extension/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions packages/devtools-webext/extension/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style>
html, body {
font-family: sans-serif;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<script src="./dist/panel.js"></script>
</body>
</html>
15 changes: 15 additions & 0 deletions packages/devtools-webext/extension/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "Nuxt DevTools",
"description": "Unleash Nuxt Developer Experience.",
"version": "0.0.1",
"manifest_version": 3,
"devtools_page": "devtools.html",
"content_security_policy": {
"extension_pages": "script-src 'self' http://localhost:3000; object-src 'self';"
},
"icons": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
}
}
17 changes: 17 additions & 0 deletions packages/devtools-webext/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@nuxt/devtools-webext",
"private": true,
"type": "module",
"version": "0.6.1",
"license": "MIT",
"scripts": {
"build": "tsup",
"dev": "tsup --watch"
},
"devDependencies": {
"@types/webextension-polyfill": "^0.10.0",
"tsup": "^7.0.0",
"webext-bridge": "^6.0.1",
"webextension-polyfill": "^0.10.0"
}
}
Empty file.
3 changes: 3 additions & 0 deletions packages/devtools-webext/src/devtools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import browser from 'webextension-polyfill'

browser.devtools.panels.create('Nuxt', 'icon.png', 'index.html')
83 changes: 83 additions & 0 deletions packages/devtools-webext/src/panel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* eslint-disable eslint-comments/no-unlimited-disable */
import browser from 'webextension-polyfill'

async function run() {
const [url, isException] = await browser.devtools.inspectedWindow.eval('window.__NUXT_DEVTOOLS_URL__')
// eslint-disable-next-line no-console
console.log({ url, isException })

if (!url)
return noDevTools()

// #1 - inline html
// const origin = new URL(result.url).origin
// const html = await fetch(result.url).then(res => res.text())
// const body = html.match(/<body[^>]*>([\s\S]*)<\/body>/i)
// ?.[1]
// ?.replace(/ src="\//, ` src="${origin}/`)

// if (!body)
// return noDevTools()

// const div = document.createElement('div')
// div.innerHTML = body
// document.body.appendChild(div)

// #2 iframe
// const iframe = document.createElement('iframe')
// iframe.src = url
// iframe.style.border = 'none'
// iframe.style.outline = 'none'
// iframe.style.width = '100vw'
// iframe.style.height = '100vh'
// document.body.appendChild(iframe)

// #3 - Import inline
const html = await fetch(url).then(res => res.text())
const elements = Array.from((new DOMParser().parseFromString(html, 'text/html')).body.children)
const origin = new URL(url).origin

const div = document.createElement('div')
div.id = '__nuxt'
document.body.appendChild(div)

setupColorMode()
window.__NUXT_DEVTOOLS_SERVER_ORIGIN__ = origin
window.__NUXT__ ||= {}
window.__NUXT__.config = { public: { }, app: { baseURL: '/__nuxt_devtools__/client', buildAssetsDir: '/_nuxt/', cdnURL: '' } }

window.history.replaceState('', '', window.__NUXT__.config.app.baseURL)

for (const element of elements) {
if (element.tagName === 'SCRIPT') {
if ('src' in element && element.src) {
const url = new URL(element.src as string)
await import(origin + url.pathname)
}
else {
// Manifest V3 does not support eval, we might need to workaround this later
// const script = document.createElement('script')
// script.innerHTML = element.innerHTML
// document.body.appendChild(script)
}
}
else {
div.appendChild(element)
}
}
}

function noDevTools() {
const div = document.createElement('div')
div.innerText = 'Nuxt devtools not found'
document.body.appendChild(div)
}

function setupColorMode() {
/* eslint-disable */
// @ts-expect-error cast
const w=window,de=document.documentElement,knownColorSchemes=["dark","light"],preference=window.localStorage.getItem("nuxt-color-mode")||"system";let value=preference==="system"?getColorScheme():preference;const forcedColorMode=de.getAttribute("data-color-mode-forced");forcedColorMode&&(value=forcedColorMode),addColorScheme(value),w["__NUXT_COLOR_MODE__"]={preference,value,getColorScheme,addColorScheme,removeColorScheme};function addColorScheme(e){const o=""+e+"",t="";de.classList?de.classList.add(o):de.className+=" "+o,t&&de.setAttribute("data-"+t,e)}function removeColorScheme(e){const o=""+e+"",t="";de.classList?de.classList.remove(o):de.className=de.className.replace(new RegExp(o,"g"),""),t&&de.removeAttribute("data-"+t)}function prefersColorScheme(e){return w.matchMedia("(prefers-color-scheme"+e+")")}function getColorScheme(){if(w.matchMedia&&prefersColorScheme("").media!=="not all"){for(const e of knownColorSchemes)if(prefersColorScheme(":"+e).matches)return e}return"light"}
/* eslint-enable */
}

run()
12 changes: 12 additions & 0 deletions packages/devtools-webext/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from 'tsup'

export default defineConfig({
entry: [
'./src/*.ts',
],
outDir: './extension/dist',
format: ['iife'],
outExtension() {
return { js: '.js' }
},
})
2 changes: 1 addition & 1 deletion packages/devtools/client/components/AssetFontPreview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const id = computed(() => `devtools-assets-${hash(props.asset)}`)
useStyleTag(computed(() => `
@font-face {
font-family: '${id.value}';
src: url('${props.asset.publicPath}');
src: url('${resolveAssetPath(props.asset.publicPath)}');
}
`))
</script>
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools/client/components/AssetPreview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defineProps<{
<template>
<div flex items-center justify-center of-hidden bg-active object-cover p1>
<template v-if="asset.type === 'image'">
<img :src="asset.publicPath">
<img :src="resolveAssetPath(asset.publicPath)">
</template>
<AssetFontPreview
v-else-if="asset.type === 'font'"
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools/client/components/TabIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ withDefaults(defineProps<{
height: '1em',
}"
v-bind="$attrs"
:src="icon"
:src="resolveAssetPath(icon)"
:alt="title"
>
<div
Expand Down
5 changes: 4 additions & 1 deletion packages/devtools/client/composables/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Lang } from 'shiki-es'
import type { NuxtDevtoolsClient, NuxtDevtoolsHostClient, NuxtDevtoolsIframeClient, VueInspectorData } from '@nuxt/devtools-kit/types'
import type { Unhead } from '@unhead/schema'
import { version } from '../../package.json'
import { renderMarkdown } from './client-services/markdown'
import { renderCodeHighlight } from './client-services/shiki'
import { extendedRpcMap, rpc } from './rpc'
Expand Down Expand Up @@ -41,7 +42,9 @@ export function useInjectionClient(): ComputedRef<NuxtDevtoolsIframeClient> {
const client = useClient()
const mode = useColorMode()

return computed(() => ({
return computed<NuxtDevtoolsIframeClient>(() => ({
url: location.href,
version,
host: client.value,
devtools: <NuxtDevtoolsClient>{
rpc,
Expand Down
7 changes: 7 additions & 0 deletions packages/devtools/client/composables/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { relative } from 'pathe'
import type { Ref } from 'vue'
import type { AsyncDataOptions } from '#app'
import type { NormalizedHeadTag, SocialPreviewCard, SocialPreviewResolved } from '~/../src/types'
import '../../src/runtime/shared/client.d'

export function isNodeModulePath(path: string) {
return !!path.match(/[/\\]node_modules[/\\]/) || isPackageName(path)
Expand Down Expand Up @@ -117,6 +118,12 @@ export function getSocialPreviewCard(
}
}

export function resolveAssetPath(path: string) {
if (window.__NUXT_DEVTOOLS_SERVER_ORIGIN__ && path.startsWith('/'))
return `${window.__NUXT_DEVTOOLS_SERVER_ORIGIN__}${path}`
return path
}

export function formatDuration(ms: number) {
if (Number.isNaN(ms) || ms < 0)
return '-'
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
"@iconify-json/tabler": "^1.1.79",
"@nuxt/content": "^2.7.0",
"@nuxt/devtools": "workspace:*",
"@nuxtjs/color-mode": "^3.2.0",
"@nuxtjs/color-mode": "^3.3.0",
"@parcel/watcher": "^2.1.0",
"@types/markdown-it-link-attributes": "^3.0.1",
"@types/ua-parser-js": "^0.7.36",
Expand Down
42 changes: 39 additions & 3 deletions packages/devtools/src/runtime/plugins/devtools.client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { createApp, h, markRaw, ref, shallowReactive, watchEffect } from 'vue'
import { createApp, h, markRaw, ref, shallowReactive, toRaw, watch, watchEffect } from 'vue'

import { setupHooksDebug } from '../shared/hooks'
import type { LoadingTimeMetric, NuxtDevtoolsHostClient, PluginMetric, VueInspectorClient } from '../../types'
import '../shared/client.d'

import { useClientColorMode } from './view/client'

// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
// @ts-ignore tsconfig
import { CLIENT_PATH } from './view/constants'
import { defineNuxtPlugin, useAppConfig, useRouter, useState } from '#imports'

export default defineNuxtPlugin((nuxt: any) => {
Expand All @@ -20,9 +22,13 @@ export default defineNuxtPlugin((nuxt: any) => {
return
}
catch (e) {
// Cross-origin
return
}
}

window.__NUXT_DEVTOOLS_URL__ = window.location.origin + CLIENT_PATH

const timeMetric = window.__NUXT_DEVTOOLS_TIME_METRIC__ = shallowReactive(window.__NUXT_DEVTOOLS_TIME_METRIC__ || {})
timeMetric.pluginInit = Date.now()

Expand Down Expand Up @@ -55,8 +61,8 @@ export default defineNuxtPlugin((nuxt: any) => {
const colorMode = useClientColorMode()
const client: NuxtDevtoolsHostClient = shallowReactive({
nuxt: markRaw(nuxt as any),
appConfig: useAppConfig() as any,
hooks: createHooks(),
appConfig: useAppConfig() as any,
getClientHooksMetrics: () => Object.values(clientHooks),
getClientPluginMetrics: () => {
return window.__NUXT_DEVTOOLS_PLUGINS_METRIC__ || []
Expand All @@ -68,13 +74,42 @@ export default defineNuxtPlugin((nuxt: any) => {
closeDevTools: closePanel,
inspector: getInspectorInstance(),
colorMode,
refreshState(): NuxtDevtoolsHostClient {
refreshState(iframe?: HTMLIFrameElement): NuxtDevtoolsHostClient {
if (!client.inspector)
client.inspector = getInspectorInstance()

window.__NUXT_DEVTOOLS_HOST_CLIENT__ = toRaw(client)
try {
iframe?.contentWindow?.__NUXT_DEVTOOLS_VIEW__?.setClient(client)
}
catch (e) {}

return client
},
})

function refreshReactivity() {
client.hooks.callHook('host:update:reactivity')
}

watch(() => [
client.nuxt.payload,
client.colorMode.value,
client.loadingTimeMetrics,
], () => {
refreshReactivity()
}, { deep: true })
// trigger update for route change
client.nuxt.vueApp.config.globalProperties?.$router?.afterEach(() => {
refreshReactivity()
})
// trigger update for app mounted
client.nuxt.hook('app:mounted', () => {
refreshReactivity()
})

client.refreshState()

function enableComponentInspector() {
window.__VUE_INSPECTOR__?.enable()
isInspecting.value = true
Expand Down Expand Up @@ -104,6 +139,7 @@ export default defineNuxtPlugin((nuxt: any) => {
})
}
}

return markRaw({
isEnabled: isInspecting,
enable: enableComponentInspector,
Expand Down
Loading