diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2405f357..2008d678 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,8 @@ jobs: run: npm i -g @antfu/ni - name: Install run: nci + - name: Test + run: nr test - name: Build run: nr build - name: Lint diff --git a/README.md b/README.md index c4eadf95..732e8228 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,13 @@ export default defineConfig({ ### Options ```ts +interface AnalyzeOptions { + /** + * @default true + */ + rerenderTrace: boolean +} + interface VitePluginVueDevToolsOptions { /** * append an import to the module id ending with `appendTo` instead of adding a script into body @@ -122,6 +129,14 @@ interface VitePluginVueDevToolsOptions { * WARNING: only set this if you know exactly what it does. */ appendTo?: string | RegExp + /** + * Enable Vue DevTools to analyze the codebase by using Babel + * @default + * { + * rerenderTrace: true, // enable rerenderTrace feature + * } + */ + analyze?: Partial } ``` diff --git a/package.json b/package.json index 8e4c89aa..70739bac 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,8 @@ "prepublishOnly": "npm run build", "release": "bumpp -r", "dep:up": "taze -I major", - "prepare": "simple-git-hooks" + "prepare": "simple-git-hooks", + "test": "vitest" }, "peerDependencies": { "vite": "^3.1.0 || ^4.0.0-0" @@ -76,6 +77,7 @@ "typescript": "^5.1.6", "unbuild": "^1.2.1", "vite": "^4.4.4", + "vitest": "^0.33.0", "vue": "^3.3.4" }, "simple-git-hooks": { diff --git a/packages/client/App.vue b/packages/client/App.vue index d042c223..873ba4d8 100644 --- a/packages/client/App.vue +++ b/packages/client/App.vue @@ -17,12 +17,12 @@ hookApi.hook.on('init:vue:app', () => { // mark client as loaded client.value.markClientLoaded() // listen hook - hookApi.produce() + hookApi.subscribe() // perf timeline // close perf timeline to avoid performance issue (#9) // initPerfTimeline(categorizedHookBuffer.perf) - // consume hook buffer - hookApi.consume(categorizedHookBuffer.component ?? []) + // publish hook buffer + hookApi.publish(categorizedHookBuffer.component ?? []) // init routes initRoutes(categorizedHookBuffer.router ?? []) // init pinia diff --git a/packages/client/logic/components/data.ts b/packages/client/logic/components/data.ts index e16590ac..11fe1ddb 100644 --- a/packages/client/logic/components/data.ts +++ b/packages/client/logic/components/data.ts @@ -167,7 +167,7 @@ function processState(instance: any): any { })) } -function processSetupState(instance: any) { +export function processSetupState(instance: any) { const raw = instance.devtoolsRawSetupState || {} return Object.keys(instance.setupState) .filter(key => !vueBuiltins.includes(key) && key.split(/(?=[A-Z])/)[0] !== 'use') @@ -226,14 +226,14 @@ function isReadOnly(raw: any): boolean { return !!raw.__v_isReadonly } -function toRaw(value: any) { +export function toRaw(value: any) { if (value?.__v_raw) return value.__v_raw return value } -function getSetupStateInfo(raw: any) { +export function getSetupStateInfo(raw: any) { return { ref: isRef(raw), computed: isComputed(raw), diff --git a/packages/client/logic/components/index.ts b/packages/client/logic/components/index.ts index 7b8857ba..0a72a3a9 100644 --- a/packages/client/logic/components/index.ts +++ b/packages/client/logic/components/index.ts @@ -1,4 +1,4 @@ export { ComponentWalker, InstanceMap } from './tree' -export { getInstanceState, getInstanceDetails } from './data' +export { getInstanceState, processSetupState, getInstanceDetails, getSetupStateInfo } from './data' export { getInstanceOrVnodeRect, getRootElementsFromComponentInstance } from './el' export { getInstanceName } from './util' diff --git a/packages/client/logic/format-state.ts b/packages/client/logic/format-state.ts index aba6efa1..f8da29e3 100644 --- a/packages/client/logic/format-state.ts +++ b/packages/client/logic/format-state.ts @@ -27,9 +27,6 @@ export interface StateType { rawDisplay?: string recursive: boolean } -// function isReactive(raw: any): boolean { -// return !!raw.__ob__ -// } export function formatStateType(value: unknown): StateType { // Vue diff --git a/packages/client/logic/hook.ts b/packages/client/logic/hook.ts index fa9372d2..a6b6e735 100644 --- a/packages/client/logic/hook.ts +++ b/packages/client/logic/hook.ts @@ -1,25 +1,18 @@ +import { DevToolsHooks } from '@vite-plugin-vue-devtools/core' import { updatePinia } from './pinia' import { instance, updateApp, app as vueApp } from './app' import { useDevToolsClient } from './client' -enum DevtoolsHooks { - APP_INIT = 'app:init', - COMPONENT_UPDATED = 'component:updated', - COMPONENT_ADDED = 'component:added', - COMPONENT_REMOVED = 'component:removed', - COMPONENT_EMIT = 'component:emit', -} - function hideInDevtools(component) { return component?.root?.type?.devtools?.hide } const client = useDevToolsClient() -function produceHook() { +function subscribeHook() { const client = useDevToolsClient() const hook = client.value.hook - hook.on(DevtoolsHooks.APP_INIT, (app) => { + hook.on(DevToolsHooks.APP_INIT, (app) => { if (app?._vueDevtools_hidden_) return vueApp.value = app @@ -30,7 +23,7 @@ function produceHook() { return (!app || (typeof uid !== 'number' && !uid) || !component || hideInDevtools(component)) } - hook.on(DevtoolsHooks.COMPONENT_UPDATED, (app, uid, parentUid, component) => { + hook.on(DevToolsHooks.COMPONENT_UPDATED, (app, uid, parentUid, component) => { updatePinia(component) if (skipCollect(app, uid, component)) @@ -39,7 +32,7 @@ function produceHook() { updateApp(app, component) }) - hook.on(DevtoolsHooks.COMPONENT_ADDED, (app, uid, parentUid, component) => { + hook.on(DevToolsHooks.COMPONENT_ADDED, (app, uid, parentUid, component) => { updatePinia(component) if (skipCollect(app, uid, component)) @@ -49,7 +42,7 @@ function produceHook() { updateApp(app, component) }) - hook.on(DevtoolsHooks.COMPONENT_REMOVED, (app, uid, parentUid, component) => { + hook.on(DevToolsHooks.COMPONENT_REMOVED, (app, uid, parentUid, component) => { updatePinia(component) if (skipCollect(app, uid, component)) @@ -60,7 +53,7 @@ function produceHook() { updateApp(app, component) }) - hook.on(DevtoolsHooks.COMPONENT_EMIT, (app, uid, parentUid, component) => { + hook.on(DevToolsHooks.COMPONENT_EMIT, (app, uid, parentUid, component) => { updatePinia(component) if (skipCollect(app, uid, component)) @@ -72,7 +65,7 @@ function produceHook() { }) } -function ConsumeHook(buffer: [string, Record][]) { +function publishHook(buffer: [string, Record][]) { buffer.forEach(([_, { app, component }]) => { updatePinia(component) updateApp(app, component) @@ -81,6 +74,6 @@ function ConsumeHook(buffer: [string, Record][]) { export const hookApi = { hook: client.value.hook, - produce: produceHook, - consume: ConsumeHook, + subscribe: subscribeHook, + publish: publishHook, } diff --git a/packages/client/pages/rerender-trace.vue b/packages/client/pages/rerender-trace.vue new file mode 100644 index 00000000..67b90253 --- /dev/null +++ b/packages/client/pages/rerender-trace.vue @@ -0,0 +1,209 @@ + + + + + diff --git a/packages/client/store/tab.ts b/packages/client/store/tab.ts index 54e92a3d..65f395bd 100644 --- a/packages/client/store/tab.ts +++ b/packages/client/store/tab.ts @@ -21,6 +21,12 @@ export const builtinTabs: BuiltinTab[] = [ icon: 'i-carbon-assembly-cluster', group: 'app', }, + { + path: 'rerender-trace', + title: 'Rerender trace', + icon: 'i-ic:outline-track-changes', + group: 'app', + }, { path: 'assets', title: 'Assets', @@ -101,6 +107,13 @@ function getInitialTabs() { // ---- States ---- const allTabs = useLocalStorage(TABS_STORAGE_KEY, getInitialTabs(), { shallow: true, + mergeDefaults(storageValue, defaults) { + // force update with builtin tabs avoid the client tabs is outdated + return defaults.map((item) => { + const storageItem = storageValue.find(i => i.title === item.title) + return storageItem ?? item + }) + }, }) const enabledTabs = computed(() => allTabs.value.filter(item => !item.disabled)) diff --git a/packages/client/uno.config.ts b/packages/client/uno.config.ts index eea43825..4c2829e9 100644 --- a/packages/client/uno.config.ts +++ b/packages/client/uno.config.ts @@ -84,5 +84,6 @@ export default defineConfig({ 'i-mdi:eyedropper', 'i-carbon-close-outline', 'i-carbon:checkmark', + 'i-ic:outline-track-changes', ], }) diff --git a/packages/core/build.config.ts b/packages/core/build.config.ts index be31769f..a74bc26d 100644 --- a/packages/core/build.config.ts +++ b/packages/core/build.config.ts @@ -5,8 +5,11 @@ export default defineBuildConfig({ 'src/index', ], externals: [ - // 'vue', + '@vue/compiler-sfc', + '@babel/parser', + 'estree-walker', + 'magic-string', ], declaration: true, rollup: { diff --git a/packages/core/package.json b/packages/core/package.json index 8b5e7505..93e38c5c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -17,12 +17,10 @@ } }, "main": "./dist/index.cjs", + "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "files": [ - "dist", - "*.d.ts", - "*.cjs", - "*.mjs" + "dist" ], "scripts": { "build": "unbuild", @@ -33,7 +31,14 @@ "vite": "^3.1.0 || ^4.0.0-0" }, "dependencies": { + "@babel/parser": "^7.22.7", "birpc": "^0.2.12", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.1", "vite-hot-client": "^0.2.1" + }, + "devDependencies": { + "@babel/types": "^7.22.5", + "@vue/compiler-sfc": "^3.3.4" } } diff --git a/packages/core/src/compiler/__test__/__snapshots__/analyze.test.ts.snap b/packages/core/src/compiler/__test__/__snapshots__/analyze.test.ts.snap new file mode 100644 index 00000000..5658a540 --- /dev/null +++ b/packages/core/src/compiler/__test__/__snapshots__/analyze.test.ts.snap @@ -0,0 +1,204 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`analyzeCode - rerender - [jt]sx? > js 1`] = ` +" +;import { getCurrentInstance as __VUE_DEVTOOLS_$getCurrentInstance__, onRenderTracked as __VUE_DEVTOOLS_$onRenderTracked__, onRenderTriggered as __VUE_DEVTOOLS_$onRenderTriggered__ } from 'vue' +; + + import { ref, h } from 'vue' + const comp = defineComponent({ + setup() { + const a = ref(1) + + +;__VUE_DEVTOOLS_$onRenderTriggered__((e) => { + const instance = __VUE_DEVTOOLS_$getCurrentInstance__() + window.__VUE_DEVTOOLS_GLOBAL_HOOK__?.emit?.('render:triggered', e, instance) + }); + + +;__VUE_DEVTOOLS_$onRenderTracked__((e) => { + const instance = __VUE_DEVTOOLS_$getCurrentInstance__() + window.__VUE_DEVTOOLS_GLOBAL_HOOK__?.emit?.('render:tracked', e, instance) + }); +return () => h('div', '1') + } + }) + " +`; + +exports[`analyzeCode - rerender - [jt]sx? > jsx 1`] = ` +" +;import { getCurrentInstance as __VUE_DEVTOOLS_$getCurrentInstance__, onRenderTracked as __VUE_DEVTOOLS_$onRenderTracked__, onRenderTriggered as __VUE_DEVTOOLS_$onRenderTriggered__ } from 'vue' +; + + import { ref } from 'vue' + const comp = defineComponent({ + setup() { + const a = ref(1) + + +;__VUE_DEVTOOLS_$onRenderTriggered__((e) => { + const instance = __VUE_DEVTOOLS_$getCurrentInstance__() + window.__VUE_DEVTOOLS_GLOBAL_HOOK__?.emit?.('render:triggered', e, instance) + }); + + +;__VUE_DEVTOOLS_$onRenderTracked__((e) => { + const instance = __VUE_DEVTOOLS_$getCurrentInstance__() + window.__VUE_DEVTOOLS_GLOBAL_HOOK__?.emit?.('render:tracked', e, instance) + }); +return () =>
+ } + }) + " +`; + +exports[`analyzeCode - rerender - [jt]sx? > ts 1`] = ` +" +;import { getCurrentInstance as __VUE_DEVTOOLS_$getCurrentInstance__, onRenderTracked as __VUE_DEVTOOLS_$onRenderTracked__, onRenderTriggered as __VUE_DEVTOOLS_$onRenderTriggered__ } from 'vue' +; + + import { ref, h } from 'vue' + const comp = defineComponent({ + setup() { + const a = ref(1) + + +;__VUE_DEVTOOLS_$onRenderTriggered__((e) => { + const instance = __VUE_DEVTOOLS_$getCurrentInstance__() + window.__VUE_DEVTOOLS_GLOBAL_HOOK__?.emit?.('render:triggered', e, instance) + }); + + +;__VUE_DEVTOOLS_$onRenderTracked__((e) => { + const instance = __VUE_DEVTOOLS_$getCurrentInstance__() + window.__VUE_DEVTOOLS_GLOBAL_HOOK__?.emit?.('render:tracked', e, instance) + }); +return () => h('div', '1') + } + }) + " +`; + +exports[`analyzeCode - rerender - [jt]sx? > tsx 1`] = ` +" +;import { getCurrentInstance as __VUE_DEVTOOLS_$getCurrentInstance__, onRenderTracked as __VUE_DEVTOOLS_$onRenderTracked__, onRenderTriggered as __VUE_DEVTOOLS_$onRenderTriggered__ } from 'vue' +; + + import { ref } from 'vue' + const comp = defineComponent({ + setup() { + const a = ref(1) + + +;__VUE_DEVTOOLS_$onRenderTriggered__((e) => { + const instance = __VUE_DEVTOOLS_$getCurrentInstance__() + window.__VUE_DEVTOOLS_GLOBAL_HOOK__?.emit?.('render:triggered', e, instance) + }); + + +;__VUE_DEVTOOLS_$onRenderTracked__((e) => { + const instance = __VUE_DEVTOOLS_$getCurrentInstance__() + window.__VUE_DEVTOOLS_GLOBAL_HOOK__?.emit?.('render:tracked', e, instance) + }); +return () =>
+ } + }) + " +`; + +exports[`analyzeCode - rerender - sfc > only script 1`] = ` +" + + + " +`; + +exports[`analyzeCode - rerender - sfc > script setup 1`] = ` +" + + + " +`; + +exports[`analyzeCode - rerender - sfc > script setup with script 1`] = ` +" + + + + " +`; diff --git a/packages/core/src/compiler/__test__/analyze.test.ts b/packages/core/src/compiler/__test__/analyze.test.ts new file mode 100644 index 00000000..57c014a5 --- /dev/null +++ b/packages/core/src/compiler/__test__/analyze.test.ts @@ -0,0 +1,140 @@ +import type { AnalyzeOptions } from '..' +import { analyzeCode } from '..' + +const baseConfig: AnalyzeOptions = { + rerenderTrace: true, +} + +describe('analyzeCode - exclude', () => { + test('not acceptable lang', () => { + expect(analyzeCode('', 'test.txt', baseConfig)).toBeNull() + }) + test('excluded path', () => { + expect(analyzeCode('', 'node_modules/test.js', baseConfig)).toBeNull() + }) + test('not enabled', () => { + expect(analyzeCode('', 'test.js', { rerenderTrace: false })).toBeNull() + }) + test('should execute', () => { + expect(analyzeCode(` + import { ref, h } from 'vue' + const comp = defineComponent({ + setup() { + const a = ref(1) + return () => h('div', '1') + } + }) + `, 'test.js', baseConfig)).not.toBeNull() + }) +}) + +describe('analyzeCode - rerender - sfc', () => { + test('script setup', () => { + const code = ` + + + ` + const result = analyzeCode(code, 'test.vue', baseConfig) + expect(result?.code).toMatchSnapshot() + }) + test('script setup with script', () => { + const code = ` + + + + ` + const result = analyzeCode(code, 'test.vue', baseConfig) + expect(result?.code).toMatchSnapshot() + }) + test('only script', () => { + const code = ` + + + ` + const result = analyzeCode(code, 'test.vue', baseConfig) + expect(result?.code).toMatchSnapshot() + }) +}) + +describe('analyzeCode - rerender - [jt]sx?', () => { + test('jsx', () => { + const code = ` + import { ref } from 'vue' + const comp = defineComponent({ + setup() { + const a = ref(1) + return () =>
+ } + }) + ` + const result = analyzeCode(code, 'test.jsx', baseConfig) + expect(result?.code).toMatchSnapshot() + }) + test('tsx', () => { + const code = ` + import { ref } from 'vue' + const comp = defineComponent({ + setup() { + const a = ref(1) + return () =>
+ } + }) + ` + const result = analyzeCode(code, 'test.tsx', baseConfig) + expect(result?.code).toMatchSnapshot() + }) + test('js', () => { + const code = ` + import { ref, h } from 'vue' + const comp = defineComponent({ + setup() { + const a = ref(1) + return () => h('div', '1') + } + }) + ` + const result = analyzeCode(code, 'test.js', baseConfig) + expect(result?.code).toMatchSnapshot() + }) + test('ts', () => { + const code = ` + import { ref, h } from 'vue' + const comp = defineComponent({ + setup() { + const a = ref(1) + return () => h('div', '1') + } + }) + ` + const result = analyzeCode(code, 'test.ts', baseConfig) + expect(result?.code).toMatchSnapshot() + }) +}) diff --git a/packages/core/src/compiler/__test__/common.test.ts b/packages/core/src/compiler/__test__/common.test.ts new file mode 100644 index 00000000..5e7d7480 --- /dev/null +++ b/packages/core/src/compiler/__test__/common.test.ts @@ -0,0 +1,43 @@ +import { getBabelParsePlugins, parseSFC } from '../common' + +describe('compiler:common', () => { + test.each([ + ['ts', ['typescript']], + ['tsx', ['typescript', 'jsx']], + ['js', []], + ['jsx', ['jsx']], + ])('getBabelParsePlugins(%s) === %s', (lang, expected) => { + expect(getBabelParsePlugins(lang)).toEqual(expected) + }) +}) + +describe('compiler:common:parseSFC', () => { + test('should parse + + + ` + expect(() => { + parseSFC(code, 'test.vue') + }).not.toThrow() + }) + test('should throw if + + + ` + expect(() => { + parseSFC(code, 'test.vue') + }).toThrow() + }) +}) diff --git a/packages/core/src/compiler/__test__/import.test.ts b/packages/core/src/compiler/__test__/import.test.ts new file mode 100644 index 00000000..05eacd3c --- /dev/null +++ b/packages/core/src/compiler/__test__/import.test.ts @@ -0,0 +1,111 @@ +import MagicString from 'magic-string' +import { IMPORT_ALIAS, PURE_IMPORT_RE, collectAllImports, ensureImport } from '../common' + +describe('compiler:imports', () => { + test('collect imports', () => { + const code = ` + import { ref } from 'vue' + import {useRouter,type Router} from 'vue-router' + const a = 1 + import * from 'vue-i18n' + import * as pinia from 'pinia' + import 'index.css' + import { reactive as vReactive, onMounted } from 'vue' + import { + shallowRef as sref, + shallowReactive, + } from 'vue' + ` + expect(collectAllImports(code)).toMatchInlineSnapshot(` + { + "index.css": { + "all": true, + "allAlias": null, + }, + "pinia": { + "all": true, + "allAlias": "*", + }, + "vue": [ + { + "alias": null, + "raw": "ref", + }, + { + "alias": "vReactive", + "raw": "reactive", + }, + { + "alias": null, + "raw": "onMounted", + }, + { + "alias": "sref", + "raw": "shallowRef", + }, + { + "alias": null, + "raw": "shallowReactive", + }, + ], + "vue-i18n": { + "all": true, + "allAlias": null, + }, + "vue-router": [ + { + "alias": null, + "raw": "useRouter", + }, + ], + } + `) + }) + test.each([ + ['{ref', 'ref'], + ['ref', 'ref'], + ['ref}', 'ref'], + ['{ ref', 'ref'], + ['{type ref', 'type ref'], + ['a as b', 'a as b'], + [`{ + ref }`, 'ref'], + [`{ + ref`, 'ref'], + [`{ + ref, + }`, 'ref'], + ])('pure import re <%s> <%s>', (input, expected) => { + expect(PURE_IMPORT_RE.exec(input)?.[1]).toEqual(expected) + }) +}) + +describe('compiler:import:alias', () => { + test.each([ + ['* as b', ['*', 'b']], + ['* as $Foo_aFOo', ['*', '$Foo_aFOo']], + ['a as b', ['a', 'b']], + ['a as $Foo_aFOo', ['a', '$Foo_aFOo']], + ])('IMPORT_ALL_ALIAS <%s> <%s>', (input, expected) => { + const r = IMPORT_ALIAS.exec(input)! + expect([r[1], r[2]]).toEqual(expected) + }) +}) + +test('compiler:import:ensureImport', () => { + const ms = ensureImport(new MagicString(''), { + vue: [{ + id: 'reactive', + alias: '$$reactive', + }, { + id: 'onMounted', + alias: '$$onMounted', + }], + }, 0) + expect(ms.toString()).toMatchInlineSnapshot(` + " + ;import { reactive as $$reactive, onMounted as $$onMounted } from 'vue' + ; + " + `) +}) diff --git a/packages/core/src/compiler/__test__/index.test.ts b/packages/core/src/compiler/__test__/index.test.ts new file mode 100644 index 00000000..a40e4781 --- /dev/null +++ b/packages/core/src/compiler/__test__/index.test.ts @@ -0,0 +1,24 @@ +import MagicString from 'magic-string' +import { analyzeByTraceRerender } from '../trace-rerender' + +test('trace-rerender', () => { + const ms = analyzeByTraceRerender(new MagicString(''), { start: 0, end: 0 }) + expect(ms.toString()).toMatchInlineSnapshot(` + " + ;import { getCurrentInstance as __VUE_DEVTOOLS_$getCurrentInstance__, onRenderTracked as __VUE_DEVTOOLS_$onRenderTracked__, onRenderTriggered as __VUE_DEVTOOLS_$onRenderTriggered__ } from 'vue' + ; + + + ;__VUE_DEVTOOLS_$onRenderTriggered__((e) => { + const instance = __VUE_DEVTOOLS_$getCurrentInstance__() + window.__VUE_DEVTOOLS_GLOBAL_HOOK__?.emit?.('render:triggered', e, instance) + }); + + + ;__VUE_DEVTOOLS_$onRenderTracked__((e) => { + const instance = __VUE_DEVTOOLS_$getCurrentInstance__() + window.__VUE_DEVTOOLS_GLOBAL_HOOK__?.emit?.('render:tracked', e, instance) + }); + " + `) +}) diff --git a/packages/core/src/compiler/__test__/lang.test.ts b/packages/core/src/compiler/__test__/lang.test.ts new file mode 100644 index 00000000..1646940d --- /dev/null +++ b/packages/core/src/compiler/__test__/lang.test.ts @@ -0,0 +1,41 @@ +import { isAcceptableLang, isJSX, isTS, isVUE } from '../common' + +describe('compiler:lang', () => { + test.each([ + ['ts', true], + ['tsx', true], + ['js', false], + ['jsx', false], + ['vue', false], + ])('isTS(%s) === %s', (lang, expected) => { + expect(isTS(lang)).toBe(expected) + }) + test.each([ + ['ts', false], + ['tsx', true], + ['js', false], + ['jsx', true], + ['vue', false], + ])('isJSX(%s) === %s', (lang, expected) => { + expect(isJSX(lang)).toBe(expected) + }) + test.each([ + ['a.vue', true], + ['vue', true], + ['avue', false], + ['a.vue.js', false], + ])('isVUE(%s) === %s', (filename, expected) => { + expect(isVUE(filename)).toBe(expected) + }) + test.each([ + ['.vue', true], + ['.jsx', true], + ['.tsx', true], + ['.js', true], + ['.ts', true], + ['.rs', false], + ['.go', false], + ])('isAcceptableLang(%s) === %s', (filename, expected) => { + expect(isAcceptableLang(filename)).toBe(expected) + }) +}) diff --git a/packages/core/src/compiler/common/analyze.ts b/packages/core/src/compiler/common/analyze.ts new file mode 100644 index 00000000..7dc82f59 --- /dev/null +++ b/packages/core/src/compiler/common/analyze.ts @@ -0,0 +1,79 @@ +import type { Identifier, Node, ObjectMethod, ObjectProperty } from '@babel/types' +import { getObjectFnBodyLocation, isCallOf, isObjectFn, walkAST } from './ast' +import type { InsertLocation } from './parse' +import { babelParse, parseSFC } from './parse' +import { DEFINE_COMPONENT, SETUP_FN } from './constants' + +function isSetupFn(node: Node): node is ObjectMethod | ObjectProperty { + return isObjectFn(node) && (node.key as Identifier).name === SETUP_FN +} + +/** + * @returns insert code location + */ +export function analyzeVueSFC(code: string, filename: string) { + const { + scriptSetup, + scriptSetupLocation, + script, + scriptLocation, + getScriptAST, + } = parseSFC(code, filename) + + if (!scriptSetup && !script) + return null + + // script setup: start: after + // script only: start: after diff --git a/packages/playground/vite.config.ts b/packages/playground/vite.config.ts index ab4dd292..0066f226 100644 --- a/packages/playground/vite.config.ts +++ b/packages/playground/vite.config.ts @@ -1,4 +1,5 @@ import { resolve } from 'node:path' +import type { Plugin } from 'vite' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import VueDevTools from 'vite-plugin-vue-devtools' @@ -7,13 +8,13 @@ import VueDevTools from 'vite-plugin-vue-devtools' export default defineConfig({ resolve: { alias: { - // Custom aliasuntil Jiti issue is resolved + // Custom alias until Jiti issue is resolved // https://github.com/unjs/jiti/issues/136 '@vite-plugin-vue-devtools/core': resolve(__dirname, '../core/src/index'), }, }, plugins: [ - VueDevTools(), + VueDevTools() as Plugin[], vue(), ], }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5270a060..f8f2ba44 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: vite: specifier: ^4.4.4 version: 4.4.4(@types/node@20.4.2) + vitest: + specifier: ^0.33.0 + version: 0.33.0 vue: specifier: ^3.3.4 version: 3.3.4 @@ -189,15 +192,31 @@ importers: packages/core: dependencies: + '@babel/parser': + specifier: ^7.22.7 + version: 7.22.7 birpc: specifier: ^0.2.12 version: 0.2.12 + estree-walker: + specifier: ^3.0.3 + version: 3.0.3 + magic-string: + specifier: ^0.30.1 + version: 0.30.1 vite: specifier: ^3.1.0 || ^4.0.0-0 version: 4.4.2(@types/node@20.4.2) vite-hot-client: specifier: ^0.2.1 version: 0.2.1(vite@4.4.2) + devDependencies: + '@babel/types': + specifier: ^7.22.5 + version: 7.22.5 + '@vue/compiler-sfc': + specifier: ^3.3.4 + version: 3.3.4 packages/node: dependencies: @@ -544,10 +563,10 @@ packages: '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1) '@babel/helper-module-transforms': 7.22.1 '@babel/helpers': 7.22.3 - '@babel/parser': 7.22.4 + '@babel/parser': 7.22.7 '@babel/template': 7.21.9 '@babel/traverse': 7.22.4 - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 convert-source-map: 1.9.0 debug: 4.3.4 gensync: 1.0.0-beta.2 @@ -560,7 +579,7 @@ packages: resolution: {integrity: sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.18 jsesc: 2.5.2 @@ -569,7 +588,7 @@ packages: resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 /@babel/helper-compilation-targets@7.22.1(@babel/core@7.22.1): resolution: {integrity: sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==} @@ -612,25 +631,25 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.21.9 - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 /@babel/helper-hoist-variables@7.18.6: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 /@babel/helper-member-expression-to-functions@7.22.3: resolution: {integrity: sha512-Gl7sK04b/2WOb6OPVeNy9eFKeD3L6++CzL3ykPOWqTn08xgYYK0wz4TUh2feIImDXxcVW3/9WQ1NMKY66/jfZA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 /@babel/helper-module-imports@7.21.4: resolution: {integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 /@babel/helper-module-transforms@7.22.1: resolution: {integrity: sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw==} @@ -640,10 +659,10 @@ packages: '@babel/helper-module-imports': 7.21.4 '@babel/helper-simple-access': 7.21.5 '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.19.1 + '@babel/helper-validator-identifier': 7.22.5 '@babel/template': 7.21.9 '@babel/traverse': 7.22.4 - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 transitivePeerDependencies: - supports-color @@ -651,7 +670,7 @@ packages: resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 /@babel/helper-plugin-utils@7.21.5: resolution: {integrity: sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==} @@ -666,7 +685,7 @@ packages: '@babel/helper-optimise-call-expression': 7.18.6 '@babel/template': 7.21.9 '@babel/traverse': 7.22.4 - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 transitivePeerDependencies: - supports-color @@ -674,26 +693,22 @@ packages: resolution: {integrity: sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 /@babel/helper-skip-transparent-expression-wrappers@7.20.0: resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 /@babel/helper-split-export-declaration@7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.4 - - /@babel/helper-string-parser@7.21.5: - resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==} - engines: {node: '>=6.9.0'} + '@babel/types': 7.22.5 - /@babel/helper-validator-identifier@7.19.1: - resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + /@babel/helper-string-parser@7.22.5: + resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} engines: {node: '>=6.9.0'} /@babel/helper-validator-identifier@7.22.5: @@ -710,7 +725,7 @@ packages: dependencies: '@babel/template': 7.21.9 '@babel/traverse': 7.22.4 - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 transitivePeerDependencies: - supports-color @@ -722,12 +737,12 @@ packages: chalk: 2.4.2 js-tokens: 4.0.0 - /@babel/parser@7.22.4: - resolution: {integrity: sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==} + /@babel/parser@7.22.7: + resolution: {integrity: sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.22.1): resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} @@ -780,8 +795,8 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.21.4 - '@babel/parser': 7.22.4 - '@babel/types': 7.22.4 + '@babel/parser': 7.22.7 + '@babel/types': 7.22.5 /@babel/traverse@7.22.4: resolution: {integrity: sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==} @@ -793,19 +808,19 @@ packages: '@babel/helper-function-name': 7.21.0 '@babel/helper-hoist-variables': 7.18.6 '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.22.4 - '@babel/types': 7.22.4 + '@babel/parser': 7.22.7 + '@babel/types': 7.22.5 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: - supports-color - /@babel/types@7.22.4: - resolution: {integrity: sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==} + /@babel/types@7.22.5: + resolution: {integrity: sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.21.5 - '@babel/helper-validator-identifier': 7.19.1 + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.5 to-fast-properties: 2.0.0 /@egjs/hammerjs@2.0.17: @@ -1290,6 +1305,13 @@ packages: wrap-ansi-cjs: /wrap-ansi@7.0.0 dev: true + /@jest/schemas@29.6.0: + resolution: {integrity: sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} @@ -1567,6 +1589,10 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: true + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + /@tootallnate/once@2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -1585,6 +1611,16 @@ packages: minimatch: 9.0.3 dev: true + /@types/chai-subset@1.3.3: + resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} + dependencies: + '@types/chai': 4.3.5 + dev: true + + /@types/chai@4.3.5: + resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==} + dev: true + /@types/debug@4.1.8: resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==} dependencies: @@ -2055,6 +2091,44 @@ packages: vue: 3.3.4 dev: true + /@vitest/expect@0.33.0: + resolution: {integrity: sha512-sVNf+Gla3mhTCxNJx+wJLDPp/WcstOe0Ksqz4Vec51MmgMth/ia0MGFEkIZmVGeTL5HtjYR4Wl/ZxBxBXZJTzQ==} + dependencies: + '@vitest/spy': 0.33.0 + '@vitest/utils': 0.33.0 + chai: 4.3.7 + dev: true + + /@vitest/runner@0.33.0: + resolution: {integrity: sha512-UPfACnmCB6HKRHTlcgCoBh6ppl6fDn+J/xR8dTufWiKt/74Y9bHci5CKB8tESSV82zKYtkBJo9whU3mNvfaisg==} + dependencies: + '@vitest/utils': 0.33.0 + p-limit: 4.0.0 + pathe: 1.1.1 + dev: true + + /@vitest/snapshot@0.33.0: + resolution: {integrity: sha512-tJjrl//qAHbyHajpFvr8Wsk8DIOODEebTu7pgBrP07iOepR5jYkLFiqLq2Ltxv+r0uptUb4izv1J8XBOwKkVYA==} + dependencies: + magic-string: 0.30.1 + pathe: 1.1.1 + pretty-format: 29.6.1 + dev: true + + /@vitest/spy@0.33.0: + resolution: {integrity: sha512-Kv+yZ4hnH1WdiAkPUQTpRxW8kGtH8VRTnus7ZTGovFYM1ZezJpvGtb9nPIjPnptHbsyIAxYZsEpVPYgtpjGnrg==} + dependencies: + tinyspy: 2.1.1 + dev: true + + /@vitest/utils@0.33.0: + resolution: {integrity: sha512-pF1w22ic965sv+EN6uoePkAOTkAPWM03Ri/jXNyMIKBb/XHLDPfhLvf/Fa9g0YECevAIz56oVYXhodLvLQ/awA==} + dependencies: + diff-sequences: 29.4.3 + loupe: 2.3.6 + pretty-format: 29.6.1 + dev: true + /@vue/babel-helper-vue-transform-on@1.0.2: resolution: {integrity: sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==} @@ -2065,7 +2139,7 @@ packages: '@babel/plugin-syntax-jsx': 7.21.4(@babel/core@7.22.1) '@babel/template': 7.21.9 '@babel/traverse': 7.22.4 - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 '@vue/babel-helper-vue-transform-on': 1.0.2 camelcase: 6.3.0 html-tags: 3.3.1 @@ -2077,7 +2151,7 @@ packages: /@vue/compiler-core@3.3.4: resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==} dependencies: - '@babel/parser': 7.22.4 + '@babel/parser': 7.22.7 '@vue/shared': 3.3.4 estree-walker: 2.0.2 source-map-js: 1.0.2 @@ -2091,15 +2165,15 @@ packages: /@vue/compiler-sfc@2.7.14: resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==} dependencies: - '@babel/parser': 7.22.4 - postcss: 8.4.24 + '@babel/parser': 7.22.7 + postcss: 8.4.26 source-map: 0.6.1 dev: true /@vue/compiler-sfc@3.3.4: resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==} dependencies: - '@babel/parser': 7.22.4 + '@babel/parser': 7.22.7 '@vue/compiler-core': 3.3.4 '@vue/compiler-dom': 3.3.4 '@vue/compiler-ssr': 3.3.4 @@ -2107,7 +2181,7 @@ packages: '@vue/shared': 3.3.4 estree-walker: 2.0.2 magic-string: 0.30.1 - postcss: 8.4.24 + postcss: 8.4.26 source-map-js: 1.0.2 /@vue/compiler-ssr@3.3.4: @@ -2122,7 +2196,7 @@ packages: /@vue/reactivity-transform@3.3.4: resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==} dependencies: - '@babel/parser': 7.22.4 + '@babel/parser': 7.22.7 '@vue/compiler-core': 3.3.4 '@vue/shared': 3.3.4 estree-walker: 2.0.2 @@ -2298,7 +2372,7 @@ packages: '@types/node': ^18.6.3 pug: ^3.0.2 dependencies: - '@babel/parser': 7.22.4 + '@babel/parser': 7.22.7 '@babel/traverse': 7.22.4 '@types/node': 20.4.2 pug: 3.0.2 @@ -2337,6 +2411,11 @@ packages: acorn: 8.10.0 dev: true + /acorn-walk@8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + /acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} engines: {node: '>=0.4.0'} @@ -2450,6 +2529,11 @@ packages: color-convert: 2.0.1 dev: true + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -2506,6 +2590,10 @@ packages: resolution: {integrity: sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==} dev: false + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + /astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -2520,7 +2608,7 @@ packages: resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==} engines: {node: '>= 10.0.0'} dependencies: - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 dev: false /balanced-match@1.0.2: @@ -2738,6 +2826,19 @@ packages: upper-case-first: 2.0.2 dev: false + /chai@4.3.7: + resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.2 + deep-eql: 4.1.3 + get-func-name: 2.0.0 + loupe: 2.3.6 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + /chalk-template@0.4.0: resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} engines: {node: '>=12'} @@ -2811,6 +2912,10 @@ packages: resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} dev: true + /check-error@1.0.2: + resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} + dev: true + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -2978,8 +3083,8 @@ packages: /constantinople@4.0.1: resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==} dependencies: - '@babel/parser': 7.22.4 - '@babel/types': 7.22.4 + '@babel/parser': 7.22.7 + '@babel/types': 7.22.5 dev: false /content-disposition@0.5.2: @@ -3066,6 +3171,13 @@ packages: dependencies: ms: 2.1.2 + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + /deep-equal@2.2.1: resolution: {integrity: sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==} dependencies: @@ -3155,6 +3267,11 @@ packages: engines: {node: '>=12.20'} dev: true + /diff-sequences@29.4.3: + resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -3796,6 +3913,12 @@ packages: /estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.1 + dev: false + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -4026,6 +4149,10 @@ packages: engines: {node: 6.* || 8.* || >= 10.*} dev: true + /get-func-name@2.0.0: + resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} + dev: true + /get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} dependencies: @@ -4882,6 +5009,12 @@ packages: wrap-ansi: 6.2.0 dev: true + /loupe@2.3.6: + resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} + dependencies: + get-func-name: 2.0.0 + dev: true + /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: @@ -5536,6 +5669,13 @@ packages: dependencies: yocto-queue: 0.1.0 + /p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + yocto-queue: 1.0.0 + dev: true + /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -5707,6 +5847,10 @@ packages: /pathe@1.1.1: resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + /perfect-debounce@0.1.3: resolution: {integrity: sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ==} dev: true @@ -5789,6 +5933,7 @@ packages: nanoid: 3.3.6 picocolors: 1.0.0 source-map-js: 1.0.2 + dev: false /postcss@8.4.26: resolution: {integrity: sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==} @@ -5808,6 +5953,15 @@ packages: engines: {node: ^14.13.1 || >=16.0.0} dev: true + /pretty-format@29.6.1: + resolution: {integrity: sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.0 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + /proc-log@3.0.0: resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -5970,6 +6124,10 @@ packages: strip-json-comments: 2.0.1 dev: true + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + /read-package-json-fast@3.0.2: resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -6295,6 +6453,10 @@ packages: object-inspect: 1.12.3 dev: true + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -6458,6 +6620,14 @@ packages: minipass: 3.3.6 dev: true + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + + /std-env@3.3.3: + resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==} + dev: true + /stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} engines: {node: '>= 0.4'} @@ -6652,6 +6822,20 @@ packages: resolution: {integrity: sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==} dev: true + /tinybench@2.5.0: + resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==} + dev: true + + /tinypool@0.6.0: + resolution: {integrity: sha512-FdswUUo5SxRizcBc6b1GSuLpLjisa8N8qMyYoP3rl+bym+QauhtJP5bvZY1ytt8krKGmMLYIRl36HBZfeAoqhQ==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@2.1.1: + resolution: {integrity: sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==} + engines: {node: '>=14.0.0'} + dev: true + /titleize@3.0.0: resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} engines: {node: '>=12'} @@ -6992,7 +7176,7 @@ packages: dependencies: '@babel/core': 7.22.1 '@babel/standalone': 7.21.4 - '@babel/types': 7.22.4 + '@babel/types': 7.22.5 defu: 6.1.2 jiti: 1.19.1 mri: 1.2.0 @@ -7127,6 +7311,28 @@ packages: vite: 4.4.4(@types/node@20.4.2) dev: false + /vite-node@0.33.0(@types/node@20.4.2): + resolution: {integrity: sha512-19FpHYbwWWxDr73ruNahC+vtEdza52kA90Qb3La98yZ0xULqV8A5JLNPUff0f5zID4984tW7l3DH2przTJUZSw==} + engines: {node: '>=v14.18.0'} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + mlly: 1.4.0 + pathe: 1.1.1 + picocolors: 1.0.0 + vite: 4.4.4(@types/node@20.4.2) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite-plugin-inspect@0.7.33(rollup@3.26.2)(vite@4.4.2): resolution: {integrity: sha512-cQRLQKa/+Ua++5hN0IZfqNn1JYXBg2eCQOSUatPTwhXMO7nwfSvhhSc45E1nXfBBEhzLLOxgr1OdbDu55PiDDA==} engines: {node: '>=14'} @@ -7278,6 +7484,71 @@ packages: optionalDependencies: fsevents: 2.3.2 + /vitest@0.33.0: + resolution: {integrity: sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==} + engines: {node: '>=v14.18.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + happy-dom: '*' + jsdom: '*' + playwright: '*' + safaridriver: '*' + webdriverio: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + dependencies: + '@types/chai': 4.3.5 + '@types/chai-subset': 1.3.3 + '@types/node': 20.4.2 + '@vitest/expect': 0.33.0 + '@vitest/runner': 0.33.0 + '@vitest/snapshot': 0.33.0 + '@vitest/spy': 0.33.0 + '@vitest/utils': 0.33.0 + acorn: 8.10.0 + acorn-walk: 8.2.0 + cac: 6.7.14 + chai: 4.3.7 + debug: 4.3.4 + local-pkg: 0.4.3 + magic-string: 0.30.1 + pathe: 1.1.1 + picocolors: 1.0.0 + std-env: 3.3.3 + strip-literal: 1.0.1 + tinybench: 2.5.0 + tinypool: 0.6.0 + vite: 4.4.4(@types/node@20.4.2) + vite-node: 0.33.0(@types/node@20.4.2) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} @@ -7479,6 +7750,15 @@ packages: isexe: 2.0.0 dev: true + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + /wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: @@ -7496,8 +7776,8 @@ packages: resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==} engines: {node: '>= 10.0.0'} dependencies: - '@babel/parser': 7.22.4 - '@babel/types': 7.22.4 + '@babel/parser': 7.22.7 + '@babel/types': 7.22.5 assert-never: 1.2.1 babel-walk: 3.0.0-canary-5 dev: false @@ -7597,3 +7877,8 @@ packages: /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + + /yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true diff --git a/tsconfig.json b/tsconfig.json index 60f6b6ec..10d0000d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,8 @@ "resolveJsonModule": true, "noImplicitAny": false, "types": [ - "vite/client" + "vite/client", + "vitest/globals" ] }, "exclude": ["node_modules", "dist", "src/node/app.js"] diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..47cdb031 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + }, +})