Skip to content

Commit

Permalink
refactor(kit): stub Vue runtime API (#456)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexzhang1030 authored Jun 19, 2024
1 parent 382402e commit 993982a
Show file tree
Hide file tree
Showing 14 changed files with 1,063 additions and 190 deletions.
10 changes: 10 additions & 0 deletions eslint-plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Linter } from 'eslint'
import noVueRuntime from './rules/no-vue-runtime-import'

export default {
rules: {
'no-vue-runtime-import': noVueRuntime,
},
} satisfies Linter.FlatConfig<{
'no-vue-runtime-import': any
}>
42 changes: 42 additions & 0 deletions eslint-plugins/rules/no-vue-runtime-import/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { createRule, isTypeImport } from '../../utils'

export default createRule<'no-vue-runtime-import', [{ prefer: string }]>({
meta: {
type: 'problem',
messages: {
'no-vue-runtime-import': 'Please consider import runtime APIs from {{prefer}}.',
},
schema: [
{
type: 'object',
properties: {
prefer: {
type: 'string',
},
},
},
],
},
create(context) {
const options = context.options[0]

return {
ImportDeclaration(node) {
const importSource = node.source.value

const shouldSkip = isTypeImport(node) || importSource !== 'vue'

if (shouldSkip)
return

context.report({
node: node.source,
messageId: 'no-vue-runtime-import',
data: {
prefer: options?.prefer ?? '<set prefer in your eslint config>',
},
})
},
}
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { run } from '../../test-utils'
import rule from '.'

run({
name: 'no-vue-runtime-import',
rule,

valid: [
'import Vue from "abc"',
'import type { Vue } from "vue"',
'import { type bar, type baz } from "vue"',
],

invalid: [
{
code: 'import Vue from "vue"',
errors: [{ messageId: 'no-vue-runtime-import' }],
},
{
code: 'import { abc } from "vue"',
errors: [{ messageId: 'no-vue-runtime-import' }],
},
{
code: 'import * as Vue from "vue"',
errors: [{ messageId: 'no-vue-runtime-import' }],
},
{
code: 'import { foo, type bar } from "vue"',
errors: [{ messageId: 'no-vue-runtime-import' }],
},
],
})
21 changes: 21 additions & 0 deletions eslint-plugins/test-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import tsParser from '@typescript-eslint/parser'

import type { RuleTesterInitOptions, TestCasesOptions } from 'eslint-vitest-rule-tester'
import { run as _run } from 'eslint-vitest-rule-tester'

export * from 'eslint-vitest-rule-tester'

export { unindent as $ } from 'eslint-vitest-rule-tester'

export interface ExtendedRuleTesterOptions extends RuleTesterInitOptions, TestCasesOptions {
lang?: 'js' | 'ts'
}

export function run(options: ExtendedRuleTesterOptions) {
return _run({
recursive: false,
verifyAfterFix: false,
...(options.lang === 'js' ? {} : { parser: tsParser as any }),
...options,
})
}
19 changes: 19 additions & 0 deletions eslint-plugins/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { TSESLint, TSESTree } from '@typescript-eslint/utils'
import type { Rule } from 'eslint'

export function createRule<MessageIds extends string, RuleOptions extends any[]>(
rule: Omit<TSESLint.RuleModule<MessageIds, RuleOptions>, 'defaultOptions'>,
) {
return rule as unknown as Rule.RuleModule
}

export function isTypeImport(node: TSESTree.ImportDeclaration) {
return node.importKind === 'type'
|| node.specifiers.filter((s) => {
if (s.type !== 'ImportSpecifier')
return true
if (s.importKind !== 'type')
return true
return false
}).length === 0
}
11 changes: 11 additions & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import antfu from '@antfu/eslint-config'
import devtools from './eslint-plugins'

export default antfu({
// force enable vue and typescript rules
Expand Down Expand Up @@ -26,4 +27,14 @@ export default antfu({
'ts/consistent-type-imports': 'off',
'ts/ban-types': 'off',
},
}, {
files: ['packages/devtools-kit/**/*.ts'],
plugins: {
devtools,
},
rules: {
'devtools/no-vue-runtime-import': ['error', {
prefer: 'shared/stub-vue',
}],
},
})
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@
"@types/degit": "^2.8.6",
"@types/fs-extra": "^11.0.4",
"@types/node": "^20.14.5",
"@typescript-eslint/parser": "^7.13.1",
"@typescript-eslint/utils": "^7.13.1",
"@unocss/eslint-plugin": "^0.61.0",
"@vue/devtools-core": "workspace:^",
"@vue/devtools-kit": "workspace:^",
Expand All @@ -91,6 +93,7 @@
"eslint": "npm:[email protected]",
"eslint-plugin-format": "^0.1.2",
"eslint-ts-patch": "8.57.0-0",
"eslint-vitest-rule-tester": "^0.3.2",
"execa": "^8.0.1",
"fast-glob": "^3.3.2",
"fs-extra": "^11.2.0",
Expand Down
3 changes: 0 additions & 3 deletions packages/devtools-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@
"prepare:type": "tsup --dts-only",
"stub": "tsup --watch --onSuccess 'tsup --dts-only'"
},
"peerDependencies": {
"vue": "^3.0.0"
},
"dependencies": {
"@vue/devtools-shared": "workspace:^",
"birpc": "^0.2.17",
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools-kit/src/core/component/state/editor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { MaybeRef, Ref } from 'vue'
import { isReactive, isRef, toRaw } from 'vue'
import { isReactive, isRef, toRaw } from '../../../shared/stub-vue'
import { getComponentInstance } from '../utils'
import { activeAppRecord } from '../../../ctx'
import { EditStatePayload } from '../../../types'
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools-kit/src/core/component/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { basename, classify } from '@vue/devtools-shared'
import { Fragment } from 'vue'
import { Fragment } from '../../../shared/stub-vue'
import type { AppRecord, VueAppInstance } from '../../../types'

function getComponentTypeName(options: VueAppInstance['type']) {
Expand Down
75 changes: 75 additions & 0 deletions packages/devtools-kit/src/shared/stub-vue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Only import vue types
*/
import type { Ref, VNodeProps } from 'vue'

/**
* To prevent include a **HUGE** vue package in the final bundle of chrome ext / electron
* we stub the necessary vue module.
* This implementation is based on the 1c3327a0fa5983aa9078e3f7bb2330f572435425 commit
*/

/**
* @from [@vue/reactivity](https://github.com/vuejs/core/blob/1c3327a0fa5983aa9078e3f7bb2330f572435425/packages/reactivity/src/constants.ts#L17-L23)
*/
export enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
IS_SHALLOW = '__v_isShallow',
RAW = '__v_raw',
}

/**
* @from [@vue/reactivity](https://github.com/vuejs/core/blob/1c3327a0fa5983aa9078e3f7bb2330f572435425/packages/reactivity/src/reactive.ts#L18-L24)
*/
export interface Target {
[ReactiveFlags.SKIP]?: boolean
[ReactiveFlags.IS_REACTIVE]?: boolean
[ReactiveFlags.IS_READONLY]?: boolean
[ReactiveFlags.IS_SHALLOW]?: boolean
[ReactiveFlags.RAW]?: any
}

/**
* @from [@vue/reactivity](https://github.com/vuejs/core/blob/1c3327a0fa5983aa9078e3f7bb2330f572435425/packages/reactivity/src/reactive.ts#L330-L332)
*/
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}

/**
* @from [@vue/reactivity](https://github.com/vuejs/core/blob/1c3327a0fa5983aa9078e3f7bb2330f572435425/packages/reactivity/src/reactive.ts#L312-L317)
*/
export function isReactive(value: unknown): boolean {
if (isReadonly(value)) {
return isReactive((value as Target)[ReactiveFlags.RAW])
}
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}

/**
* @from [@vue/reactivity](https://github.com/vuejs/core/blob/1c3327a0fa5983aa9078e3f7bb2330f572435425/packages/reactivity/src/ref.ts#L99-L102)
*/
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref {
return !!(r && r.__v_isRef === true)
}

/**
* @from [@vue/reactivity](https://github.com/vuejs/core/blob/1c3327a0fa5983aa9078e3f7bb2330f572435425/packages/reactivity/src/reactive.ts#L372-L375)
*/
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}

/**
* @from [@vue/runtime-core](https://github.com/vuejs/core/blob/1c3327a0fa5983aa9078e3f7bb2330f572435425/packages/runtime-core/src/vnode.ts#L63-L68)
*/
export const Fragment = Symbol.for('v-fgt') as any as {
__isFragment: true
new (): {
$props: VNodeProps
}
}
3 changes: 0 additions & 3 deletions packages/devtools-kit/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ export default defineConfig({
entryPoints: [
'src/index.ts',
],
external: [
'vue',
],
noExternal: ['speakingurl', 'superjson'],
clean: true,
format: ['esm', 'cjs'],
Expand Down
Loading

0 comments on commit 993982a

Please sign in to comment.