Repository: martinascharrer/vuensight Branch: main Commit: e3f6681dacdd Files: 96 Total size: 110.7 KB Directory structure: gitextract_z_w9sqhw/ ├── .editorconfig ├── .eslintrc ├── .github/ │ └── workflows/ │ └── node.js.yml ├── .gitignore ├── .nvmrc ├── LICENSE.txt ├── README.md ├── package.json ├── packages/ │ ├── app/ │ │ ├── .browserslistrc │ │ ├── .editorconfig │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── public/ │ │ │ └── index.html │ │ ├── server/ │ │ │ ├── index.ts │ │ │ └── tsconfig.pkg.json │ │ ├── src/ │ │ │ ├── App.vue │ │ │ ├── assets/ │ │ │ │ └── css/ │ │ │ │ ├── border-radius.css │ │ │ │ ├── box-shadow.css │ │ │ │ ├── color.css │ │ │ │ ├── font.css │ │ │ │ ├── icon.css │ │ │ │ └── spacing.css │ │ │ ├── components/ │ │ │ │ ├── CardCommunicationChannel.test.ts │ │ │ │ ├── CardCommunicationChannel.vue │ │ │ │ ├── ForceGraph.vue │ │ │ │ ├── MenuCommunication.vue │ │ │ │ ├── SidebarCommunication.vue │ │ │ │ ├── SidebarCommunicationEventsTab.vue │ │ │ │ ├── SidebarCommunicationPropsTab.vue │ │ │ │ ├── SidebarCommunicationSlotsTab.vue │ │ │ │ ├── base/ │ │ │ │ │ ├── BaseArrowIcon.vue │ │ │ │ │ ├── BaseBadge.vue │ │ │ │ │ ├── BaseCard.vue │ │ │ │ │ ├── BaseCheckIcon.vue │ │ │ │ │ ├── BaseDelimiter.vue │ │ │ │ │ ├── BaseDropdown.vue │ │ │ │ │ ├── BaseIcon.vue │ │ │ │ │ ├── BaseIconButton.vue │ │ │ │ │ ├── BaseList.vue │ │ │ │ │ ├── BaseLoadingSpinner.vue │ │ │ │ │ ├── BaseRadioButtonGroup.vue │ │ │ │ │ └── BaseSubNav.vue │ │ │ │ ├── icons/ │ │ │ │ │ ├── IconCross.vue │ │ │ │ │ ├── IconFilter.vue │ │ │ │ │ ├── IconSearch.vue │ │ │ │ │ └── IconSelect.vue │ │ │ │ └── layout/ │ │ │ │ └── LayoutSplitView.vue │ │ │ ├── composables/ │ │ │ │ └── fetch.ts │ │ │ ├── main.ts │ │ │ ├── router/ │ │ │ │ └── index.ts │ │ │ ├── services/ │ │ │ │ └── parser.ts │ │ │ ├── shims-vue.d.ts │ │ │ ├── types/ │ │ │ │ ├── color.ts │ │ │ │ ├── force.ts │ │ │ │ └── nodeSizeAttributeType.ts │ │ │ └── views/ │ │ │ └── PageCommunication.vue │ │ └── tsconfig.json │ ├── cli/ │ │ ├── .eslintrc │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.pkg.json │ ├── parser/ │ │ ├── .eslintrc │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── utils/ │ │ │ │ ├── files.test.ts │ │ │ │ ├── files.ts │ │ │ │ ├── kababize.ts │ │ │ │ ├── kebabize.test.ts │ │ │ │ ├── vue.test.ts │ │ │ │ └── vue.ts │ │ │ └── vue/ │ │ │ ├── analyzer.ts │ │ │ ├── communication-channels.test.ts │ │ │ ├── communication-channels.ts │ │ │ └── dependencies.ts │ │ ├── test/ │ │ │ ├── parsing-process.test.ts │ │ │ └── project/ │ │ │ ├── Child.vue │ │ │ └── Parent.vue │ │ └── tsconfig.pkg.json │ └── types/ │ ├── index.d.ts │ └── package.json ├── tsconfig.build.json └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ [*.{js,jsx,ts,tsx,vue}] indent_style = space indent_size = 4 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true max_line_length = 100 ================================================ FILE: .eslintrc ================================================ { "root": true, "parser": "@typescript-eslint/parser", "plugins": [ "@typescript-eslint" ], "ignorePatterns": ["dist/**/*.js", "bin/**/*.js"], "extends": [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended" ], "rules": { "semi": ["error", "always"], "max-len": ["error", 120] } } ================================================ FILE: .github/workflows/node.js.yml ================================================ name: Push (lint + unit tests) on: [push] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [16.x] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - name: npm install, build, and test run: | npm install npm run build npm run lint npm run test env: CI: true ================================================ FILE: .gitignore ================================================ node_modules # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: .nvmrc ================================================ 18.6 ================================================ FILE: LICENSE.txt ================================================ MIT License Copyright (c) 2021-2022 Martina Scharrer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # vuensight 👀 Visualize Vue.js **component relationships** and **communication channels**, i.e. props, events and slots. This tool operates on the command line and is made for developers. The aim of vuensight is to provide visual insight into the components of a Vue.js project and to support developers before and during refactoring, e.g. by visually analyzing which prop is used in which parent component or by highlighting unused components or channels. An example visualization of vuensight itself: ![demo image of vuensight](docs/vuensight-demo.png) This tool is built on top of the two awesome packages: - [dependency-cruiser](https://github.com/sverweij/dependency-cruiser) for building the dependency tree - [vue-docgen-api](https://github.com/vue-styleguidist/vue-styleguidist/tree/dev/packages/vue-docgen-api) for parsing the Vue files ## Getting started 🚀 ### Install First, install the cli package either locally in the project you want to visualize: ``` npm i -D @vuensight/cli ``` Or globally on your machine if you intend to visualize multiple projects: ``` npm i -g @vuensight/cli ``` ### Run Then run the tool in your project folder: ``` vuensight ``` #### Options - `--dir` or `-d` (optional): Specify the directory that should be parsed relative from your current working directory, default is `src` - `--port` or `-p` (optional): Start the application in a different port, default is 4444 - `--webpack-config` or `-wpc` (optional): Specify the path to your webpack-config (from your current working directory). This is particularly important if you use aliases. - `--ts-config` or `-tsc` (optional): Specify the path to your TypeScript config file (from your current working directory). An example usage: ``` vuensight --dir resources/js --port 9999 --webpack-config ./webpack-config.json --ts-config ./tsconfig.json ``` ## Licencse [MIT](LICENSE.txt) ## Development ### Requirements - `npm version >= 7` (the project is a monorepo and uses npm workspaces which require at least npm version 7) ### Installing dependencies - `npm i` (in root directory) to install all dependencies of all packages - `npm i ` to add a global dependency for all packages - `npm i --workspace @vuensight/` to add a new dependency to a specific package ### Build packages - `npm run build` in root folder (to build all packages at the same time) - `npm run build` in each package #### or use a watcher - `npm run build:watch` in every package separately ### Link locally For testing vuensight locally in a Vue project run `npm link` in the cli package and `npm link @vuensight/cli` in the project you want to test it on. Make sure you use a correct node version and ran `npm i` in the root directory beforehand as this links the packages together internally. ### Unit tests - `npm run test` in root (to run tests for all packages) - `npm run test` in each package ### Publish - `npm publish` in each package ================================================ FILE: package.json ================================================ { "name": "vuensight", "description": "`vuensight` is a cli tool for parsing and visualizing Vue.js projects. The ultimate goal is to visualize the components communication e.g. their props, events and slots in an interactive web app. **This project is currently a work in progress!**", "scripts": { "build": "npm run build --workspaces", "lint": "npm run lint --workspaces", "test": "npm run test --workspaces" }, "repository": { "type": "git", "url": "git+https://github.com/martinascharrer/vuensight.git" }, "author": "Martina Scharrer", "license": "(MIT)", "bugs": { "url": "https://github.com/martinascharrer/vuensight/issues" }, "homepage": "https://github.com/martinascharrer/vuensight#readme", "workspaces": [ "./packages/types", "./packages/parser", "./packages/app", "./packages/cli" ], "devDependencies": { "@types/node": "^17.0.22", "@typescript-eslint/eslint-plugin": "^5.16.0", "@typescript-eslint/parser": "^5.16.0", "eslint": "^8.11.0", "jest": "^27.5.1", "jest-cli": "^27.5.1", "ts-jest": "^27.1.3", "typescript": "^4.6.2" } } ================================================ FILE: packages/app/.browserslistrc ================================================ > 1% last 2 versions not dead ================================================ FILE: packages/app/.editorconfig ================================================ [*.{js,jsx,ts,tsx,vue}] indent_style = space indent_size = 2 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true max_line_length = 120 ================================================ FILE: packages/app/.eslintrc.js ================================================ module.exports = { root: true, env: { node: true, }, extends: [ 'plugin:vue/vue3-essential', '@vue/airbnb', '@vue/typescript/recommended', ], parserOptions: { ecmaVersion: 2020, }, rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'max-len': ['error', 120], 'import/no-unresolved': 'warn', 'import/extensions': 'warn', }, overrides: [ { files: [ '**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)', ], env: { jest: true, }, }, ], }; ================================================ FILE: packages/app/.gitignore ================================================ .DS_Store node_modules # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw? /dist tsconfig.tsbuildinfo ================================================ FILE: packages/app/.npmignore ================================================ * !dist/app/**/* dist/app/**/*.js.map !dist/server/*.js !dist/server/*.d.ts !package.json !readme.md ================================================ FILE: packages/app/LICENSE.txt ================================================ MIT License Copyright (c) 2021-2022 Martina Scharrer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/app/README.md ================================================ # @vuensight/app > ⚠️ **General information about usage and setup of `vuensight` can be found [here](https://github.com/martinascharrer/vuensight)** The `app` package visualizes the parsed Vue project as force-directed graph. The visualization is built with [d3.js](https://d3js.org/) and [webCola](https://github.com/tgdwyer/WebCola). ## Project setup ``` npm install ``` ### Compiles and hot-reloads for development ``` npm run serve ``` ### Compiles and minifies for production ``` npm run build ``` ### Run your unit tests ``` npm run test:unit ``` ### Lints and fixes files ``` npm run lint ``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). ## Licencse [MIT](LICENSE.txt) ================================================ FILE: packages/app/babel.config.js ================================================ module.exports = { presets: [ '@vue/cli-plugin-babel/preset', ], }; ================================================ FILE: packages/app/jest.config.js ================================================ module.exports = { preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', transform: { '^.+\\.vue$': '@vue/vue3-jest', }, testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], moduleFileExtensions: [ 'js', 'ts', ], }; ================================================ FILE: packages/app/package.json ================================================ { "name": "@vuensight/app", "version": "0.3.3", "main": "dist/server/index.js", "description": "The front-end of @vuensight that visualizes Vue.js projects.", "repository": { "type": "git", "url": "git+https://github.com/martinascharrer/vuensight.git" }, "author": "Martina Scharrer", "license": "(MIT)", "bugs": { "url": "https://github.com/martinascharrer/vuensight/issues" }, "homepage": "https://github.com/martinascharrer/vuensight#readme", "scripts": { "serve": "vue-cli-service serve", "build": "rm -rf dist/ && echo 'Building app...' && vue-cli-service build --dest dist/app && echo 'Building server...' && cd server && tsc -p tsconfig.pkg.json", "build:watch": "vue-cli-service build --watch", "test": "vue-cli-service test:unit", "lint": "vue-cli-service lint", "prepublish": "npm run build" }, "dependencies": { "@vuensight/parser": "^0.1.4", "@vuensight/types": "^0.1.0", "@vueuse/core": "^8.3.1", "connect-history-api-fallback": "^1.6.0", "core-js": "^3.21.1", "d3": "^5.16.0", "express": "^4.17.3", "vue": "^3.2.31", "vue-router": "^4.0.14", "webcola": "^3.4.0" }, "devDependencies": { "@types/connect-history-api-fallback": "^1.3.5", "@types/d3": "^7.1.0", "@types/express": "^4.17.13", "@vue/cli-plugin-babel": "^5.0.4", "@vue/cli-plugin-eslint": "^5.0.4", "@vue/cli-plugin-router": "^5.0.4", "@vue/cli-plugin-typescript": "^5.0.4", "@vue/cli-plugin-unit-jest": "^5.0.4", "@vue/cli-service": "^5.0.4", "@vue/compiler-sfc": "^3.2.31", "@vue/eslint-config-airbnb": "^6.0.0", "@vue/eslint-config-typescript": "^10.0.0", "@vue/test-utils": "^2.0.0-rc.17", "@vue/vue3-jest": "^27.0.0", "eslint-plugin-import": "^2.25.4", "eslint-plugin-vue": "^8.5.0", "sass": "^1.49.9", "sass-loader": "^12.6.0" } } ================================================ FILE: packages/app/public/index.html ================================================ <%= htmlWebpackPlugin.options.title %>
================================================ FILE: packages/app/server/index.ts ================================================ import history from 'connect-history-api-fallback'; import express from 'express'; import { join } from 'path'; import { parse } from '@vuensight/parser'; export default async (directory: string, port?: number, webpackConfigPath?: string, tsConfigPath?: string) => { const app = express(); const localPort = port || 4444; app.get('/parse-result', async (request, response) => { const parseResult = await parse(directory, 'vue', webpackConfigPath, tsConfigPath); response.json(parseResult); }); app.use(history()); app.use(express.static(join(__dirname, '../app'))); app.listen((+localPort), () => { console.log(`👀 vuensight: http://localhost:${localPort}`); }); }; ================================================ FILE: packages/app/server/tsconfig.pkg.json ================================================ { "extends": "../../../tsconfig.build.json", "compilerOptions": { "outDir": "../dist/server" }, "include": [ "./**/*.ts" ] } ================================================ FILE: packages/app/src/App.vue ================================================ ================================================ FILE: packages/app/src/assets/css/border-radius.css ================================================ :root { --border-radius--xs: 2px; --border-radius--s: 5px; --border-radius--m: 10px; --border-radius--l: 15px; --border-radius--xl: 20px; --border-radius--2xl: 25px; } ================================================ FILE: packages/app/src/assets/css/box-shadow.css ================================================ :root { --box-shadow--xs: 0px 0px 2px rgba(0,0,0,0.2); --box-shadow--s: 0px 0px 4px rgba(0,0,0,0.23); --box-shadow--m: 0px 0px 8px rgba(0,0,0,0.3); } ================================================ FILE: packages/app/src/assets/css/color.css ================================================ :root { --mint-grey: #CBE5DE; --mint-10: #CDF6EB; --mint-30: #A0E2CF; --mint-50: #61c799; --mint-70: #369D6F; --red-grey: #E9D0C9; --red-10: #F6DCD4; --red-30: #FFB7A1; --red-50: #F57A5F; --red-70: #C54C31; --purple-grey: #c7b1c9; --purple-30: #E9BCEF; --purple-50: #C37ECD; --purple-70: #854D8D; --yellow-30: #F6D697; --yellow-50: #EDAB2C; --grey-5: #FaFaFa; --grey-10: #F2F2F2; --grey-15: #d7d7d7; --grey-20: #CECECE; --grey-30: #9a9a9a; --grey-50: #7c7c7c; --navy-50: #7A8497; --navy-90: #212c41; } ================================================ FILE: packages/app/src/assets/css/font.css ================================================ :root { font-size: 16px; --font-size--3xs: 0.35em; --font-size--2xs: 0.5em; --font-size--xs: 0.75em; --font-size--s: 0.875em; --font-size--m: 1em; --font-size--l: 1.125em; --font-size--xl: 1.25em; --font-size--2xl: 1.5em; --font-size--3xl: 2em; --font-size--4xl: 2.75em; } ================================================ FILE: packages/app/src/assets/css/icon.css ================================================ :root { --icon--xs: 0.75rem; --icon--s: 1rem; --icon--m: 1.25rem; --icon--l: 1.5rem; --icon--xl: 1.75rem; --icon--2xl: 2rem; } ================================================ FILE: packages/app/src/assets/css/spacing.css ================================================ :root { --spacing--2xs: 0.125rem; --spacing--xs: 0.25rem; --spacing--s: 0.5rem; --spacing--m: 0.75rem; --spacing--l: 1rem; --spacing--xl: 1.5rem; --spacing--2xl: 2rem; --spacing--3xl: 2.75rem; --spacing--4xl: 4rem; --spacing--5xl: 6rem; } ================================================ FILE: packages/app/src/components/CardCommunicationChannel.test.ts ================================================ // eslint-disable-next-line import/no-extraneous-dependencies import { mount, MountingOptions } from '@vue/test-utils'; import CardCommunicationChannel from '@/components/CardCommunicationChannel.vue'; describe('CardCommunicationChannel.vue', () => { it('renders the card with correct name and counter', () => { const wrapper = mount(CardCommunicationChannel, { props: { channel: { name: 'foo prop', }, dependents: [ { name: 'Bar', fullPath: 'foo/bar.vue', }, { name: 'Test', fullPath: 'foo/test.vue', }, ], }, } as MountingOptions); // eslint-disable-line @typescript-eslint/no-explicit-any expect(wrapper.find('[data-qa="name"]').text()).toBe('foo prop'); expect(wrapper.find('[data-qa="counter"]').text()).toBe('2'); }); }); ================================================ FILE: packages/app/src/components/CardCommunicationChannel.vue ================================================ ================================================ FILE: packages/app/src/components/ForceGraph.vue ================================================ ================================================ FILE: packages/app/src/components/MenuCommunication.vue ================================================ ================================================ FILE: packages/app/src/components/SidebarCommunication.vue ================================================ ================================================ FILE: packages/app/src/components/SidebarCommunicationEventsTab.vue ================================================ ================================================ FILE: packages/app/src/components/SidebarCommunicationPropsTab.vue ================================================ ================================================ FILE: packages/app/src/components/SidebarCommunicationSlotsTab.vue ================================================ ================================================ FILE: packages/app/src/components/base/BaseArrowIcon.vue ================================================ ================================================ FILE: packages/app/src/components/base/BaseBadge.vue ================================================ ================================================ FILE: packages/app/src/components/base/BaseCard.vue ================================================ ================================================ FILE: packages/app/src/components/base/BaseCheckIcon.vue ================================================ ================================================ FILE: packages/app/src/components/base/BaseDelimiter.vue ================================================ ================================================ FILE: packages/app/src/components/base/BaseDropdown.vue ================================================ ================================================ FILE: packages/app/src/components/base/BaseIcon.vue ================================================ ================================================ FILE: packages/app/src/components/base/BaseIconButton.vue ================================================ ================================================ FILE: packages/app/src/components/base/BaseList.vue ================================================ ================================================ FILE: packages/app/src/components/base/BaseLoadingSpinner.vue ================================================ ================================================ FILE: packages/app/src/components/base/BaseRadioButtonGroup.vue ================================================ ================================================ FILE: packages/app/src/components/base/BaseSubNav.vue ================================================ ================================================ FILE: packages/app/src/components/icons/IconCross.vue ================================================ ================================================ FILE: packages/app/src/components/icons/IconFilter.vue ================================================ ================================================ FILE: packages/app/src/components/icons/IconSearch.vue ================================================ ================================================ FILE: packages/app/src/components/icons/IconSelect.vue ================================================ ================================================ FILE: packages/app/src/components/layout/LayoutSplitView.vue ================================================ ================================================ FILE: packages/app/src/composables/fetch.ts ================================================ import { ref, readonly } from 'vue'; export const useFetch = (fetcher: () => Promise) => { // eslint-disable-line @typescript-eslint/no-explicit-any const data = ref(null); const isLoading = ref(false); const isError = ref(false); const get = async () => { isLoading.value = true; data.value = null; isError.value = false; try { data.value = await fetcher(); } catch (e) { isError.value = true; console.error(e); } isLoading.value = false; }; return { data: readonly(data), isLoading: readonly(isLoading), isError: readonly(isError), get, }; }; export default { useFetch, }; ================================================ FILE: packages/app/src/main.ts ================================================ import { createApp } from 'vue'; import App from './App.vue'; import router from './router'; createApp(App).use(router).mount('#app'); ================================================ FILE: packages/app/src/router/index.ts ================================================ import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; import PageCommunication from '../views/PageCommunication.vue'; import SidebarCommunicationEventsTab from '../components/SidebarCommunicationEventsTab.vue'; import SidebarCommunicationPropsTab from '../components/SidebarCommunicationPropsTab.vue'; import SidebarCommunicationSlotsTab from '../components/SidebarCommunicationSlotsTab.vue'; const routes: Array = [ { path: '/', name: 'page-communication', component: PageCommunication, children: [ { path: '', name: 'Props', component: SidebarCommunicationPropsTab, }, { path: 'events', name: 'Events', component: SidebarCommunicationEventsTab, }, { path: 'slots', name: 'Slots', component: SidebarCommunicationSlotsTab, }, ], }, ]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes, }); export default router; ================================================ FILE: packages/app/src/services/parser.ts ================================================ import { VueComponent } from '@vuensight/types'; export const get = ():Promise => fetch('/parse-result') .then((result) => result.json()); export default { get, }; ================================================ FILE: packages/app/src/shims-vue.d.ts ================================================ /* eslint-disable */ declare module '*.vue' { import type { DefineComponent } from 'vue' const component: DefineComponent<{}, {}, any> export default component } ================================================ FILE: packages/app/src/types/color.ts ================================================ export type Color = 'mint' | 'red' | 'purple' | 'light-mint' | 'light-red' | 'light-purple'; ================================================ FILE: packages/app/src/types/force.ts ================================================ import { VueComponent } from '@vuensight/types'; export type Link = { source: string, target: string, } export type ForceLayout = { nodes: VueComponent[], links: Link[], } ================================================ FILE: packages/app/src/types/nodeSizeAttributeType.ts ================================================ const nodeSizeAttributeType = { PROP: 'props', EVENT: 'events', SLOT: 'slots', CHANNELS: 'channels', DEPENDENTS: 'dependents', DEPENDENCIES: 'dependencies', NONE: 'none', }; Object.freeze(nodeSizeAttributeType); export default nodeSizeAttributeType; ================================================ FILE: packages/app/src/views/PageCommunication.vue ================================================ ================================================ FILE: packages/app/tsconfig.json ================================================ { "compilerOptions": { "target": "esnext", "module": "esnext", "strict": true, "jsx": "preserve", "importHelpers": true, "moduleResolution": "node", "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "sourceMap": true, "composite": true, "baseUrl": "./", "types": [ "webpack-env", "jest" ], "paths": { "@/*": [ "src/*" ] }, "lib": [ "esnext", "dom", "dom.iterable", "scripthost" ] }, "include": [ "src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx" ], "exclude": [ "node_modules", "server/**/*" ] } ================================================ FILE: packages/cli/.eslintrc ================================================ { "ignorePatterns": ["bin/**/*.js"] } ================================================ FILE: packages/cli/.gitignore ================================================ /dist ================================================ FILE: packages/cli/.npmignore ================================================ * !dist/* !package.json !readme.md ================================================ FILE: packages/cli/LICENSE.txt ================================================ MIT License Copyright (c) 2021-2022 Martina Scharrer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/cli/README.md ================================================ # vuensight 👀 Visualize Vue.js **component relationships** and **communication channels**, i.e. props, events and slots. This tool operates on the command line and is made for developers. The aim of vuensight is to provide visual insight into the components of a Vue.js project and to support developers before and during refactoring, e.g. by visually analyzing which prop is used in which parent component or by highlighting unused components or channels. An example visualization of vuensight itself: ![demo image of vuensight](docs/vuensight-demo.png) This tool is built on top of the two awesome packages: - [dependency-cruiser](https://github.com/sverweij/dependency-cruiser) for building the dependency tree - [vue-docgen-api](https://github.com/vue-styleguidist/vue-styleguidist/tree/dev/packages/vue-docgen-api) for parsing the Vue files ## Getting started 🚀 ### Install First, install the cli package either locally in the project you want to visualize: ``` npm i -D @vuensight/cli ``` Or globally on your machine if you plan to visualize multiple projects: ``` npm i -g @vuensight/cli ``` ### Run Then run the tool in your project folder: ``` vuensight ``` #### Options - `--dir` or `-d` (optional): Specify the directory that should be parsed relative from your current working directory, default is `src` - `--port` or `-p` (optional): Start the application in a different port, default is 4444 - `--webpack-config` or `-wpc` (optional): Specify the path to your webpack-config (from your current working directory). This is particularly important if you use aliases. - `--ts-config` or `-tsc` (optional): Specify the path to your TypeScript config file (from your current working directory). An example usage: ``` vuensight --dir resources/js --port 9999 --webpack-config ./webpack-config.json --ts-config ./tsconfig.json ``` ## Licencse [MIT](LICENSE.txt) ================================================ FILE: packages/cli/package.json ================================================ { "name": "@vuensight/cli", "version": "0.1.5", "description": "Command line interface of @vuensight.", "main": "bin/index.js", "repository": { "type": "git", "url": "git+https://github.com/martinascharrer/vuensight.git" }, "scripts": { "build": "rm -rf dist/* && tsc -p tsconfig.pkg.json", "build:watch": "tsc -w -p tsconfig.pkg.json", "lint": "eslint --ext ts", "test": "echo 'no tests yet in cli'", "prepublish": "npm run build" }, "bin": { "vuensight": "dist/index.js" }, "keywords": [ "cli", "node.js" ], "author": "Martina Scharrer", "license": "(MIT)", "bugs": { "url": "https://github.com/martinascharrer/vuensight/issues" }, "homepage": "https://github.com/martinascharrer/vuensight#readme", "dependencies": { "@vuensight/app": "^0.3.1", "commander": "^9.1.0" } } ================================================ FILE: packages/cli/src/index.ts ================================================ #!/usr/bin/env node import { program } from 'commander'; import startServer from '@vuensight/app'; program .description('Vue Component Insight CLI') .option('-d, --dir [dir]', 'specify the directory that should be analyzed (default is src)', 'src') .option('-p, --port [port]', 'start the application in a different port (default is 4444)') .option('-wpc, --webpack-config [webpackConfig]', 'path to webpack config file') .option('-tsc, --ts-config [tsConfig]', 'path to TypeScript config file') .parse(); const dir = program.opts().dir; const port = program.opts().port; const webpackConfig = program.opts().webpackConfig; const tsConfig = program.opts().tsConfig; const init = async () => { try { await startServer(dir, port, webpackConfig, tsConfig); } catch (e) { console.error('Something went wrong parsing the project', e); } }; init(); ================================================ FILE: packages/cli/tsconfig.pkg.json ================================================ { "extends": "../../tsconfig.build.json", "compilerOptions": { "outDir": "./dist/" }, "include": [ "./src/*.ts" ], "paths": { "@/*": [ "src/*" ] } } ================================================ FILE: packages/parser/.eslintrc ================================================ { "ignorePatterns": ["test/project/*"] } ================================================ FILE: packages/parser/.gitignore ================================================ /dist /coverage ================================================ FILE: packages/parser/.npmignore ================================================ * !dist/**/*.js dist/tsconfig.pkg.tsbuildinfo !package.json !readme.md ================================================ FILE: packages/parser/LICENSE.txt ================================================ MIT License Copyright (c) 2021-2022 Martina Scharrer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/parser/README.md ================================================ # @vuensight/parser > ⚠️ **General information about usage and setup of `vuensight` can be found [here](https://github.com/martinascharrer/vuensight)** The `parser` extracts information about component dependencies as well as their used props/events/slots of a given Vue.js project. ## Development Compile typescript files to `dist` folder ``` npm run build ``` Run eslint ``` npm run lint ``` Run unit tests + coverage ``` npm test ``` Run unit test watcher ``` npm run test:watch ``` ## Licencse [MIT](LICENSE.txt) ================================================ FILE: packages/parser/package.json ================================================ { "name": "@vuensight/parser", "version": "0.1.8", "main": "dist/index.js", "description": "This parser extracts information regarding the dependencies and their used props, events and slots of a given Vue.js project.", "scripts": { "build": "rm -rf dist/* && tsc -p tsconfig.pkg.json", "build:watch": "tsc -w -p tsconfig.pkg.json", "test": "jest --coverage", "test:watch": "jest --watch", "lint": "eslint --ext ts", "prepublish": "npm run build" }, "repository": { "type": "git", "url": "git+https://github.com/martinascharrer/vuensight.git" }, "keywords": [ "parser", "vue" ], "author": "Martina Scharrer", "license": "(MIT)", "bugs": { "url": "https://github.com/martinascharrer/vuensight/issues" }, "homepage": "https://github.com/martinascharrer/vuensight#readme", "jest": { "preset": "ts-jest", "testEnvironment": "node", "moduleNameMapper": { "dependency-cruiser/config-utl/extract-ts-config": "dependency-cruiser/src/config-utl/extract-ts-config", "dependency-cruiser/config-utl/extract-webpack-resolve-config": "dependency-cruiser/src/config-utl/extract-webpack-resolve-config" } }, "dependencies": { "dependency-cruiser": "^12.10.0", "fs": "^0.0.1-security", "jsdom": "^21.1.0", "vue-docgen-api": "^4.60.0" }, "devDependencies": { "@types/jsdom": "^21.1.0", "@vuensight/types": "^0.1.0" } } ================================================ FILE: packages/parser/src/index.ts ================================================ import { VueComponent } from '@vuensight/types'; import { findDependencies } from './vue/dependencies'; import { analyzeComponents, analyzeCommunicationChannelUsage } from './vue/analyzer'; export const parse = async ( directory: string, fileType = 'vue', webpackConfigPath?: string, tsConfigPath?: string ): Promise => { const modules = findDependencies(directory, fileType, webpackConfigPath, tsConfigPath); if (!modules) return new Array(); const components: VueComponent[] = await analyzeComponents(modules); return analyzeCommunicationChannelUsage(components); }; ================================================ FILE: packages/parser/src/utils/files.test.ts ================================================ import { getFileNameFromPath } from './files'; describe('files', () => { describe('getFileNameFromPath', () => { it('should extract the filename from the path', () => { expect(getFileNameFromPath('src/test/asdf/TestFile.vue')).toBe('TestFile.vue'); }); it('should extract the filename from the path', () => { expect(getFileNameFromPath('src\\test\\asdf\\TestFile.vue')).toBe('TestFile.vue'); }); }); }); ================================================ FILE: packages/parser/src/utils/files.ts ================================================ export const getFileNameFromPath = (path: string): string => { const lastDirectoryIndex = path.lastIndexOf('\\') !== -1 ? path.lastIndexOf('\\') : path.lastIndexOf('/'); return path.substring(lastDirectoryIndex + 1, path.length); }; ================================================ FILE: packages/parser/src/utils/kababize.ts ================================================ export const kebabize = (str: string):string => str.split('') .map((letter, idx) => (letter.toUpperCase() === letter ? `${idx !== 0 ? '-' : ''}${letter.toLowerCase()}` : letter)).join(''); export default { kebabize, }; ================================================ FILE: packages/parser/src/utils/kebabize.test.ts ================================================ import { kebabize } from './kababize'; describe('kebabize', () => { it('should convert the string to kebab-style', () => { expect(kebabize('FooBar')).toBe('foo-bar'); }); }); ================================================ FILE: packages/parser/src/utils/vue.test.ts ================================================ import { extractScriptContent, findTemplate, getComponentImportName, getTemplateContent } from './vue'; const TEST_FILE = ''; const TEST_FILE_WITHOUT_TEMPLATE = ''; const makeTestFileWithScript = (script: string) => ` `; describe('vue', () => { describe('findTemplate', () => { it('should find the template string in the file', () => { expect(findTemplate(TEST_FILE)).toBe(''); }); it('should not find the template string in the file when there is none', () => { expect(findTemplate(TEST_FILE_WITHOUT_TEMPLATE)).toBe(null); }); }); describe('getTemplateContent', () => { it('should return the content of the template without the template tag', () => { expect(getTemplateContent(TEST_FILE)).toBe('Hello'); }); it('should return null when there is no template', () => { expect(getTemplateContent(TEST_FILE_WITHOUT_TEMPLATE)).toBe(null); }); }); describe('extractScriptContent', () => { it('should extract the script content from the given file', () => { const script = `const test = 3`; const testFile = makeTestFileWithScript(script); expect(extractScriptContent(testFile)).toBe(script); }); }); describe('getComponentImportName', () => { it('should find the import name of a component in a js script', () => { const script = `import Button from '@/components/base/BaseButton';`; const testFile = makeTestFileWithScript(script); expect(getComponentImportName(testFile, 'BaseButton')).toBe('Button'); }); it('should not find the import name of a component if it is not imported', () => { const script = `import Test from '@/components/base/Test';`; const testFile = makeTestFileWithScript(script); expect(getComponentImportName(testFile, 'BaseButton')).toBe(null); }); }); }); ================================================ FILE: packages/parser/src/utils/vue.ts ================================================ export const findTemplate = (fileContent: string): string | null => { const templateBody = fileContent.match(/(?