diff --git a/README.md b/README.md index f08efa1..a14c385 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ## Features - [x] Highlight the results of Vue SFC compiler parse. -- [ ] Display Vue AST nodes in a tree view. +- [x] Display Vue AST nodes in a tree view. ## Contribution diff --git a/app/assets/example.vue b/app/assets/example.vue index ac93f05..69338e1 100644 --- a/app/assets/example.vue +++ b/app/assets/example.vue @@ -1,9 +1,3 @@ - - + + + + + + \ No newline at end of file diff --git a/app/components/ast-json-preview.tsx b/app/components/ast-json-preview.tsx deleted file mode 100644 index 9c0dd2a..0000000 --- a/app/components/ast-json-preview.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import type { SFCParseResult } from "@vue/compiler-sfc"; -import { codeToHtml } from "shiki"; - -interface AstJsonPreviewProps { - ast: SFCParseResult; -} - -const AstJsonPreview = ({ ast }: AstJsonPreviewProps) => { - const [highlightedAstJson, setHighlightedAstJson] = useState(""); - const jsonCode = JSON.stringify(ast, null, 2); - codeToHtml(jsonCode, { - lang: "json", - themes: { - light: "github-light-high-contrast", - dark: "github-dark-high-contrast", - }, - colorReplacements: { - "github-dark-high-contrast": { - "#0a0c10": "#0d1117", - }, - }, - }).then(setHighlightedAstJson); - - return ( -
-
- - {/* biome-ignore lint/security/noDangerouslySetInnerHtml: Shiki has escaped html. */} -
-
-
- ); -}; - -export default AstJsonPreview; - -const CopyTextButton = ({ text, className }: { text: string; className: string }) => { - const [copied, setCopied] = useState(false); - return ( - - ); -}; diff --git a/app/components/collapsible-tree-view.tsx b/app/components/collapsible-tree-view.tsx index f9b645f..2733ef3 100644 --- a/app/components/collapsible-tree-view.tsx +++ b/app/components/collapsible-tree-view.tsx @@ -14,17 +14,22 @@ const CollapsibleTreeView = ({ ast }: CollapsibleTreeViewProps) => { {ast.descriptor.template && } {ast.descriptor.scriptSetup && ( - {ast.descriptor.scriptSetup.type} +
+
{ast.descriptor.scriptSetup.type}
+
setup
)} {ast.descriptor.script && ( - {ast.descriptor.script.type} +
+
{ast.descriptor.script.type}
)} {ast.descriptor.styles.map((style) => ( - {style.type} +
+ +
{style.type}
))}
@@ -35,17 +40,20 @@ export default CollapsibleTreeView; interface CollapsibleButtonProps { children: React.ReactNode; node: Node | SFCBlock; + depth?: number; } -const CollapsibleButton = ({ children, node }: CollapsibleButtonProps) => { +const CollapsibleButton = ({ children, node, depth = 1 }: CollapsibleButtonProps) => { const { updateSelectedNode, node: selectedNode } = useSelectedNodeStore(); return ( @@ -57,39 +65,65 @@ interface TemplateTreeRootNodeProps { } const TemplateTreeRootNode = ({ instance }: TemplateTreeRootNodeProps) => { + const [collapsed, setCollapsed] = useState(true); return (
- {instance.type} -
- {instance.ast?.children.map((child) => ( - - ))} -
+ +
); }; interface TemplateTreeNodeProps { instance: TemplateChildNode; + depth?: number; } -const TemplateTreeNode = ({ instance }: TemplateTreeNodeProps) => { +const TemplateTreeNode = ({ instance, depth = 2 }: TemplateTreeNodeProps) => { // COMPOUND_EXPRESSION is a node that is only generated when the compiler optimizes. // Impossible to appear in the use case. It's excluded here just to eliminate TSC warning. const hasChildren = instance.type !== NodeTypes.COMPOUND_EXPRESSION && "children" in instance; + const [collapsed, setCollapsed] = useState(depth <= 3); return (
-
- {NodeTypes[instance.type]} - {hasChildren && ( -
- {instance.children.map((child) => ( - - ))} -
+ + {hasChildren ? ( +
); }; diff --git a/app/components/global-options.tsx b/app/components/global-options.tsx deleted file mode 100644 index 508d72e..0000000 --- a/app/components/global-options.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useGlobalOptionsStore } from "~/lib/store"; - -const GlobalOptions = () => { - const { view, updateView } = useGlobalOptionsStore(); - - const handleChangeView = (value: typeof view) => { - if (value !== view) { - updateView(value); - } - }; - - return ( - - - - - -
-
-

Global options

-

- These options are persistent by default. -

-
-
-
-
View
- -
- - -
-
- - -
-
-
-
-
-
-
- ); -}; - -export default GlobalOptions; diff --git a/app/components/header.tsx b/app/components/header.tsx index 14c2e3e..6b7e238 100644 --- a/app/components/header.tsx +++ b/app/components/header.tsx @@ -4,8 +4,6 @@ import { version } from "@vue/compiler-sfc"; import Logo from "~/assets/logo.svg?react"; import { repository } from "@/package.json"; - -import GlobalOptions from "~/components/global-options"; import ThemesSwitcher from "~/components/themes-switcher"; import { cn } from "~/lib/utils"; @@ -45,7 +43,6 @@ const Header = ({ className }: { className?: string }) => { GitHub -
); diff --git a/app/components/selected-node-view.tsx b/app/components/selected-node-view.tsx index 64b6ab0..5804314 100644 --- a/app/components/selected-node-view.tsx +++ b/app/components/selected-node-view.tsx @@ -18,11 +18,37 @@ const SelectedNodeView = () => { }, }).then(setHighlightedAstJson); return ( -
- {/* biome-ignore lint/security/noDangerouslySetInnerHtml: Shiki has escaped html. */} -
+
+
+ + {/* biome-ignore lint/security/noDangerouslySetInnerHtml: Shiki has escaped html. */} +
+
); }; export default SelectedNodeView; + +const CopyTextButton = ({ text, className }: { text: string; className: string }) => { + const [copied, setCopied] = useState(false); + return ( + + ); +}; diff --git a/app/components/ui/input.tsx b/app/components/ui/input.tsx deleted file mode 100644 index 5f92edd..0000000 --- a/app/components/ui/input.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from "react"; - -import { cn } from "~/lib/utils"; - -export interface InputProps extends React.InputHTMLAttributes {} - -const Input = React.forwardRef( - ({ className, type, ...props }, ref) => { - return ( - - ); - }, -); -Input.displayName = "Input"; - -export { Input }; diff --git a/app/components/ui/label.tsx b/app/components/ui/label.tsx deleted file mode 100644 index b4c77ea..0000000 --- a/app/components/ui/label.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import * as LabelPrimitive from "@radix-ui/react-label"; -import { type VariantProps, cva } from "class-variance-authority"; -import * as React from "react"; - -import { cn } from "~/lib/utils"; - -const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", -); - -const Label = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & VariantProps ->(({ className, ...props }, ref) => ( - -)); -Label.displayName = LabelPrimitive.Root.displayName; - -export { Label }; diff --git a/app/components/ui/popover.tsx b/app/components/ui/popover.tsx deleted file mode 100644 index 0faf72a..0000000 --- a/app/components/ui/popover.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import * as PopoverPrimitive from "@radix-ui/react-popover"; -import * as React from "react"; - -import { cn } from "~/lib/utils"; - -const Popover = PopoverPrimitive.Root; - -const PopoverTrigger = PopoverPrimitive.Trigger; - -const PopoverAnchor = PopoverPrimitive.Anchor; - -const PopoverContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( - - - -)); -PopoverContent.displayName = PopoverPrimitive.Content.displayName; - -export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; diff --git a/app/components/ui/radio-group.tsx b/app/components/ui/radio-group.tsx deleted file mode 100644 index 595fea5..0000000 --- a/app/components/ui/radio-group.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"; -import * as React from "react"; - -import { cn } from "~/lib/utils"; - -const RadioGroup = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => { - return ; -}); -RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; - -const RadioGroupItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => { - return ( - - - - - - ); -}); -RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName; - -export { RadioGroup, RadioGroupItem }; diff --git a/app/lib/store.ts b/app/lib/store.ts index 63a618b..8da88f4 100644 --- a/app/lib/store.ts +++ b/app/lib/store.ts @@ -1,45 +1,18 @@ import type { Node } from "@vue/compiler-core"; import type { SFCBlock } from "@vue/compiler-sfc"; import { create } from "zustand"; -import { persist } from "zustand/middleware"; - -type GlobalOptionsState = { - view: "JSON" | "tree"; -}; - -type GlobalOptionsAction = { - updateView: (view: GlobalOptionsState["view"]) => void; -}; - -type GlobalOptionsStore = GlobalOptionsState & GlobalOptionsAction; - -export const useGlobalOptionsStore = create()( - persist( - (set) => ({ - view: "tree", - updateView: (view) => set({ view }), - }), - { - name: "zustand-global-options", - }, - ), -); type SelectedNodeState = { node: Node | SFCBlock | null; - valid: boolean; }; type SelectedNodeAction = { updateSelectedNode: (node: SelectedNodeState["node"]) => void; - updateValid: (valid: SelectedNodeState["valid"]) => void; }; type SelectedNodeStore = SelectedNodeState & SelectedNodeAction; export const useSelectedNodeStore = create((set) => ({ node: null, - valid: true, updateSelectedNode: (node) => set(() => ({ node })), - updateValid: (valid) => set(() => ({ valid })), })); diff --git a/app/lib/utils.tsx b/app/lib/utils.tsx index 050b7ca..aa55f07 100644 --- a/app/lib/utils.tsx +++ b/app/lib/utils.tsx @@ -10,10 +10,6 @@ function subscribe() { return () => {}; } -export function useMount(fn: () => void) { - useEffect(fn, []); -} - /** * Return a boolean indicating if the JS has been hydrated already. * When doing Server-Side Rendering, the result will always be false. diff --git a/package.json b/package.json index d4d139c..0dd3bf3 100644 --- a/package.json +++ b/package.json @@ -35,10 +35,7 @@ "@netlify/edge-functions": "^2.11.0", "@netlify/remix-edge-adapter": "^3.4.2", "@netlify/remix-runtime": "^2.3.1", - "@radix-ui/react-context-menu": "^2.2.2", "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-radio-group": "^1.2.1", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94ac3bd..ec78d76 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,18 +23,9 @@ importers: '@netlify/remix-runtime': specifier: ^2.3.1 version: 2.3.1(@remix-run/server-runtime@2.13.1(typescript@5.6.3)) - '@radix-ui/react-context-menu': - specifier: ^2.2.2 - version: 2.2.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) '@radix-ui/react-dropdown-menu': specifier: ^2.1.2 version: 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-label': - specifier: ^2.1.0 - version: 2.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-popover': - specifier: ^1.1.2 - version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) '@radix-ui/react-radio-group': specifier: ^1.2.1 version: 1.2.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) @@ -927,19 +918,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-context-menu@2.2.2': - resolution: {integrity: sha512-99EatSTpW+hRYHt7m8wdDlLtkmTovEe8Z/hnxUPV+SKuuNL5HWNhQI4QSdjZqNSgXHay2z4M3Dym73j9p2Gx5Q==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-context@1.1.0': resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} peerDependencies: @@ -1024,19 +1002,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-label@2.1.0': - resolution: {integrity: sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-menu@2.1.2': resolution: {integrity: sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==} peerDependencies: @@ -1050,19 +1015,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popover@1.1.2': - resolution: {integrity: sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-popper@1.2.0': resolution: {integrity: sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==} peerDependencies: @@ -4869,20 +4821,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.11 - '@radix-ui/react-context-menu@2.2.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014)': - dependencies: - '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-menu': 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@19.0.0-rc-6cf85185-20241014) - react: 19.0.0-rc-6cf85185-20241014 - react-dom: 19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014) - optionalDependencies: - '@types/react': 18.3.11 - '@types/react-dom': 18.3.1 - '@radix-ui/react-context@1.1.0(@types/react@18.3.11)(react@19.0.0-rc-6cf85185-20241014)': dependencies: react: 19.0.0-rc-6cf85185-20241014 @@ -4953,15 +4891,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.11 - '@radix-ui/react-label@2.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014)': - dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) - react: 19.0.0-rc-6cf85185-20241014 - react-dom: 19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014) - optionalDependencies: - '@types/react': 18.3.11 - '@types/react-dom': 18.3.1 - '@radix-ui/react-menu@2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -4988,29 +4917,6 @@ snapshots: '@types/react': 18.3.11 '@types/react-dom': 18.3.1 - '@radix-ui/react-popover@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014)': - dependencies: - '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.11)(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@19.0.0-rc-6cf85185-20241014) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@19.0.0-rc-6cf85185-20241014) - aria-hidden: 1.2.4 - react: 19.0.0-rc-6cf85185-20241014 - react-dom: 19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014) - react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@19.0.0-rc-6cf85185-20241014) - optionalDependencies: - '@types/react': 18.3.11 - '@types/react-dom': 18.3.1 - '@radix-ui/react-popper@1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014)': dependencies: '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0-rc-6cf85185-20241014(react@19.0.0-rc-6cf85185-20241014))(react@19.0.0-rc-6cf85185-20241014) diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..b047eb0 --- /dev/null +++ b/renovate.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [":semanticCommitTypeAll(chore)", "group:allNonMajor"], + "packageRules": [ + { + "matchPackageNames": ["@vue/compiler-core", "@vue/compiler-sfc"], + "enabled": true + }, + { + "matchPackagePatterns": ["*"], + "enabled": false + } + ] +}