@component-hook/create-app 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +1 -0
  2. package/package.json +10 -10
  3. package/template-react/.vscode/extensions.json +9 -0
  4. package/template-react/.vscode/settings.json +18 -0
  5. package/template-react/README.md.art +85 -0
  6. package/template-react/e2e/home.spec.ts +40 -0
  7. package/template-react/eslint.config.js +18 -0
  8. package/template-react/index.html.art +13 -0
  9. package/template-react/lint-staged.config.js +6 -0
  10. package/template-react/package.json +70 -0
  11. package/template-react/public/vite.svg +1 -0
  12. package/template-react/src/App.tsx +6 -0
  13. package/template-react/src/assets/svg-icons/react.svg +1 -0
  14. package/template-react/src/components/common/index.ts +1 -0
  15. package/template-react/src/components/common/svg-icon/index.ts +1 -0
  16. package/template-react/src/components/common/svg-icon/src/index.tsx +23 -0
  17. package/template-react/src/main.tsx +26 -0
  18. package/template-react/src/mocks/browser.ts +4 -0
  19. package/template-react/src/mocks/handlers/index.ts +1 -0
  20. package/template-react/src/mocks/server.ts +4 -0
  21. package/template-react/src/pages/home/components/hello-world/index.test.tsx +45 -0
  22. package/template-react/src/pages/home/components/hello-world/index.tsx +49 -0
  23. package/template-react/src/pages/home/index.module.css +8 -0
  24. package/template-react/src/pages/home/index.test.tsx +19 -0
  25. package/template-react/src/pages/home/index.tsx +37 -0
  26. package/template-react/src/plugins/i18n/index.ts +19 -0
  27. package/template-react/src/plugins/i18n/locales/en-US.json +3 -0
  28. package/template-react/src/plugins/i18n/locales/zh-TW.json +3 -0
  29. package/template-react/src/router/index.tsx +22 -0
  30. package/template-react/src/stores/constants.ts +1 -0
  31. package/template-react/src/stores/index.ts +1 -0
  32. package/template-react/src/stores/middleware.ts +17 -0
  33. package/template-react/src/stores/modules/config.ts +27 -0
  34. package/template-react/src/styles/base.css +68 -0
  35. package/template-react/src/styles/common/all.css +1 -0
  36. package/template-react/src/styles/common/button.css +26 -0
  37. package/template-react/src/styles/index.css +3 -0
  38. package/template-react/src/styles/tailwind.css +14 -0
  39. package/template-react/src/test-utils/index.tsx +15 -0
  40. package/template-react/src/vite-env.d.ts +9 -0
  41. package/template-react/tsconfig.app.json +32 -0
  42. package/template-react/tsconfig.vitest.json +10 -0
  43. package/template-react/vite.config.ts +40 -0
  44. package/template-react/vitest.config.ts +18 -0
  45. package/template-react/vitest.setup.ts +13 -0
  46. package/template-shared/.browserslistrc +10 -0
  47. package/template-shared/.github/actions/setup-environment/action.yml +21 -0
  48. package/template-shared/.github/renovate.json +14 -0
  49. package/template-shared/.github/workflows/ci.yml +22 -0
  50. package/template-shared/.github/workflows/deploy.yml +27 -0
  51. package/template-shared/.github/workflows/preview.yml +17 -0
  52. package/template-shared/.gitlab/setup.yml +18 -0
  53. package/template-shared/.husky/commit-msg +1 -0
  54. package/template-shared/.husky/pre-commit +1 -0
  55. package/{LICENSE → template-shared/LICENSE} +1 -1
  56. package/template-shared/_gitignore +32 -0
  57. package/template-shared/_gitlab-ci.yml +22 -0
  58. package/template-shared/commitlint.config.js +3 -0
  59. package/template-shared/playwright.config.ts +49 -0
  60. package/template-shared/postcss.config.js +15 -0
  61. package/template-shared/prettier.config.js +13 -0
  62. package/template-shared/scripts/build-info/index.ts +21 -0
  63. package/template-shared/tsconfig.json +14 -0
  64. package/template-shared/tsconfig.node.json +12 -0
  65. package/template-vue/.vscode/extensions.json +10 -0
  66. package/template-vue/.vscode/settings.json +18 -0
  67. package/template-vue/README.md.art +81 -0
  68. package/template-vue/e2e/home.spec.ts +59 -0
  69. package/template-vue/eslint.config.js +18 -0
  70. package/template-vue/index.html.art +28 -0
  71. package/template-vue/lint-staged.config.js +6 -0
  72. package/template-vue/package.json +69 -0
  73. package/template-vue/public/vite.svg +1 -0
  74. package/template-vue/src/App.vue +7 -0
  75. package/template-vue/src/assets/svg-icons/vue.svg +1 -0
  76. package/template-vue/src/components/common/index.ts +1 -0
  77. package/template-vue/src/components/common/svg-icon/index.ts +1 -0
  78. package/template-vue/src/components/common/svg-icon/src/index.vue +29 -0
  79. package/template-vue/src/locales/en-US.json +3 -0
  80. package/template-vue/src/locales/zh-TW.json +3 -0
  81. package/template-vue/src/main.ts +21 -0
  82. package/template-vue/src/mocks/browser.ts +4 -0
  83. package/template-vue/src/mocks/handlers/index.ts +1 -0
  84. package/template-vue/src/mocks/server.ts +4 -0
  85. package/template-vue/src/pages/home/components/HelloWorld.test.ts +57 -0
  86. package/template-vue/src/pages/home/components/HelloWorld.vue +54 -0
  87. package/template-vue/src/pages/home/index.test.ts +17 -0
  88. package/template-vue/src/pages/home/index.vue +46 -0
  89. package/template-vue/src/plugins/i18n.ts +11 -0
  90. package/template-vue/src/router/index.ts +23 -0
  91. package/template-vue/src/stores/constants.ts.art +1 -0
  92. package/template-vue/src/stores/index.ts +15 -0
  93. package/template-vue/src/stores/modules/config.ts +20 -0
  94. package/template-vue/src/styles/base.css +59 -0
  95. package/template-vue/src/styles/common/all.css +1 -0
  96. package/template-vue/src/styles/common/button.css +15 -0
  97. package/template-vue/src/styles/index.css +3 -0
  98. package/template-vue/src/styles/tailwind.css +1 -0
  99. package/template-vue/src/test-utils/index.ts +33 -0
  100. package/template-vue/src/vite-env.d.ts +9 -0
  101. package/template-vue/tsconfig.app.json +12 -0
  102. package/template-vue/tsconfig.vitest.json +10 -0
  103. package/template-vue/vite.config.ts +46 -0
  104. package/template-vue/vitest.config.ts +18 -0
  105. package/template-vue/vitest.setup.ts +12 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "editor.tabSize": 2,
3
+ "editor.formatOnSave": true,
4
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
5
+
6
+ "[json]": {
7
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
8
+ },
9
+
10
+ "debug.javascript.autoAttachFilter": "smart",
11
+
12
+ "typescript.tsdk": "node_modules/typescript/lib",
13
+
14
+ "workbench.editor.splitInGroupLayout": "vertical",
15
+ "terminal.integrated.shellIntegration.enabled": true,
16
+
17
+ "cSpell.words": ["dotenv", "pinia", "persistedstate", "unplugin", "Sonarjs", "typecheck", "pxtorem", "norem"]
18
+ }
@@ -0,0 +1,81 @@
1
+ # {{projectName}}
2
+
3
+ This project is built with [vue3](https://vuejs.org/) + [vite](https://vite.dev/), using [pnpm](https://pnpm.io/) for package management and [pinia](https://pinia.vuejs.org/) for state management.
4
+
5
+ ## Features
6
+
7
+ - **Development Experience**: Written in [typeScript](https://www.typescriptlang.org/), styled with [tailwindcss](https://tailwindcss.com/), and structured with [vue-router](https://router.vuejs.org/), and [vue-i18n](https://vue-i18n.intlify.dev/). Supports SVGs ([vite-plugin-svg-icons](https://github.com/vbenjs/vite-plugin-svg-icons)).
8
+ - **API & Mocking**: Handles requests with [axios](https://axios-http.com/docs/intro) and mocks APIs using [msw](https://mswjs.io/).
9
+ - **Code Quality**: Enforces standards with [eslint](https://eslint.org/) and [prettier](https://prettier.io/), with [husky](https://www.npmjs.com/package/husky), [commitlint](https://commitlint.js.org/#/), and [lint-staged](https://github.com/okonet/lint-staged) for commit validation.
10
+ - **Testing**: Uses [vitest](https://vitest.dev/) + [testing-library](https://testing-library.com/) for unit testing and [playwright](https://playwright.dev/) for E2E testing.
11
+ - **Optimization**: Compresses CSS with [cssnano](https://cssnano.github.io/cssnano/), converts units with [postcss-pxtorem](https://www.npmjs.com/package/postcss-pxtorem), convert modern CSS into something most browsers can understand with [postcss-preset-env](https://www.npmjs.com/package/postcss-preset-env), and analyzes assets with [rollup-plugin-visualizer](https://github.com/btd/rollup-plugin-visualizer).
12
+ {{ciContent}}
13
+
14
+ ## Code Features / Opinions
15
+
16
+ - Project root aliased as `@` to `<project_root>/src`
17
+ - Store persistedstate use [pinia-plugin-persistedstate](https://www.npmjs.com/package/pinia-plugin-persistedstate)
18
+ - ESlint plugin use [@component-hook/eslint-plugin](https://www.npmjs.com/package/@component-hook/eslint-plugin?activeTab=readme)
19
+ - if encounter npx: command not found, can execute `ln -s $(which npx) /usr/local/bin/npx` in zsh
20
+ - Predefined and fully typed global variables:
21
+ - `VITE_APP_VERSION` is read from `package.json` version at build time
22
+ - `VITE_APP_LAST_COMMIT_HASH` is last commit hash at build time
23
+ - `VITE_APP_BUILD_EPOCH` is populated as `Date.now()` at build time
24
+ - `VITE_APP_MOCK` is use mock environment
25
+
26
+ ## Project Files
27
+
28
+ ```text
29
+ e2e/*
30
+ src/
31
+ ├── assets/*
32
+ │ └── svg-icons
33
+ │ └── vue.svg
34
+ ├── components/
35
+ │ └── common
36
+ │ ├── svg-icon/
37
+ │ │ ├── src
38
+ │ │ │ └── index.vue
39
+ │ │ └── index.ts
40
+ │ └── index.ts
41
+ ├── constants/*
42
+ ├── hooks/*
43
+ ├── locales/
44
+ │ ├── en-US.json
45
+ │ └── zh-CN.json
46
+ ├── mocks/
47
+ │ ├── handlers/*
48
+ │ │ ├── modules/*
49
+ │ │ └── index.ts
50
+ │ │── browser.ts
51
+ │ └── server.ts
52
+ ├── pages/
53
+ │ └── home/
54
+ │ ├── components
55
+ │ │ ├── HelloWorld.test.ts
56
+ │ │ └── HelloWorld.vue
57
+ │ ├── index.test.ts
58
+ │ └── index.vue
59
+ ├── plugins/
60
+ │ └── i18n.ts
61
+ ├── router/
62
+ │ └── index.ts
63
+ ├── stores/
64
+ │ ├── modules
65
+ │ │ └── config.ts
66
+ │ └── index.ts
67
+ ├── styles/
68
+ │ ├── common/
69
+ | │ ├── all.css
70
+ | │ └── button.css
71
+ │ ├── base.css
72
+ │ ├── index.css
73
+ │ └── tailwind.css
74
+ ├── test-utils/
75
+ │ └── index.ts
76
+ ├── types/*
77
+ ├── utils/*
78
+ ├── App.vue
79
+ ├── vite-env.d.ts
80
+ └── main.ts
81
+ ```
@@ -0,0 +1,59 @@
1
+ import { expect, test } from '@playwright/test';
2
+
3
+ test.beforeEach(async ({ page }) => {
4
+ await page.goto('/');
5
+ });
6
+
7
+ test('click Vite logo open Vite official website blank tab', async ({ page }) => {
8
+ const link = page.getByRole('link', { name: /vite logo/i });
9
+
10
+ await expect(link).toBeVisible();
11
+ const [popup] = await Promise.all([page.waitForEvent('popup'), link.evaluate((node: HTMLElement) => node.click())]);
12
+
13
+ await popup.waitForLoadState();
14
+ await expect(popup).toHaveTitle('Vite | Next Generation Frontend Tooling');
15
+ });
16
+
17
+ test('click Vue logo open Vue official website blank tab', async ({ page }) => {
18
+ const link = page.getByLabel(/vue link/i);
19
+
20
+ await expect(link).toBeVisible();
21
+ const [popup] = await Promise.all([page.waitForEvent('popup'), link.evaluate((node: HTMLElement) => node.click())]);
22
+
23
+ await popup.waitForLoadState();
24
+ await expect(popup).toHaveTitle('Vue.js - The Progressive JavaScript Framework | Vue.js');
25
+ });
26
+
27
+ test('homepage count button interact', async ({ page }) => {
28
+ await page.getByRole('button', { name: 'count is 0' }).click();
29
+ await expect(page.getByRole('button', { name: 'count is 1' })).toBeVisible();
30
+ await page.getByRole('button', { name: 'count is 1' }).click();
31
+ await expect(page.getByRole('button', { name: 'count is 2' })).toBeVisible();
32
+ });
33
+
34
+ test('locale change interact', async ({ page }) => {
35
+ await page.getByRole('button', { name: 'English' }).click();
36
+ await expect(page.getByRole('button', { name: '中文' })).toBeVisible();
37
+ await page.getByRole('button', { name: '中文' }).click();
38
+ await expect(page.getByRole('button', { name: 'English' })).toBeVisible();
39
+ });
40
+
41
+ test('click create-vue link open Vue quick start blank tab', async ({ page }) => {
42
+ const link = page.getByRole('link', { name: 'create-vue' });
43
+
44
+ await expect(link).toBeVisible();
45
+ const [popup] = await Promise.all([page.waitForEvent('popup'), link.evaluate((node: HTMLElement) => node.click())]);
46
+
47
+ await popup.waitForLoadState();
48
+ await expect(popup).toHaveTitle('Quick Start | Vue.js');
49
+ });
50
+
51
+ test('click Volar link open language-tools github blank tab', async ({ page }) => {
52
+ const link = page.getByRole('link', { name: 'Volar' });
53
+
54
+ await expect(link).toBeVisible();
55
+ const [popup] = await Promise.all([page.waitForEvent('popup'), link.evaluate((node: HTMLElement) => node.click())]);
56
+
57
+ await popup.waitForLoadState();
58
+ await expect(popup).toHaveTitle(/vuejs\/language-tools/);
59
+ });
@@ -0,0 +1,18 @@
1
+ import componentHookPlugin from '@component-hook/eslint-plugin';
2
+
3
+ export default [
4
+ ...componentHookPlugin.configs.basic,
5
+ ...componentHookPlugin.configs.vue,
6
+ componentHookPlugin.configs.prettier,
7
+ ...componentHookPlugin.configs.sonarjs,
8
+ componentHookPlugin.configs.security,
9
+ ...componentHookPlugin.configs.markdown,
10
+ {
11
+ files: ['**/*.test.[jt]s?(x)'],
12
+ ...componentHookPlugin.configs['testing-library/vue'],
13
+ },
14
+ {
15
+ files: ['**/e2e/**/*.spec.[jt]s?(x)'],
16
+ ...componentHookPlugin.configs.playwright,
17
+ },
18
+ ];
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link
6
+ rel="icon"
7
+ type="image/svg+xml"
8
+ href="/vite.svg"
9
+ />
10
+ <meta
11
+ name="viewport"
12
+ content="width=device-width, initial-scale=1.0"
13
+ />
14
+ <title>{{projectName}}</title>
15
+ </head>
16
+ <body>
17
+ <div id="app"></div>
18
+ <script
19
+ type="module"
20
+ src="/src/main.ts"
21
+ ></script>
22
+ <noscript>
23
+ <h1>Opinionated, production ready template for Vite and Vue 3</h1>
24
+
25
+ <p>This site requires JavaScript to work.</p>
26
+ </noscript>
27
+ </body>
28
+ </html>
@@ -0,0 +1,6 @@
1
+ /** @type {import('lint-staged').Configuration} */
2
+
3
+ export default {
4
+ '*.{ts,vue}': () => 'pnpm typecheck',
5
+ '*.{ts,js,vue,json,yml}': () => 'pnpm lint',
6
+ };
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "vue-starter",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "dev:mock": "cross-env MOCK=true vite",
9
+ "build": "pnpm typecheck && vite build",
10
+ "lint": "eslint --fix .",
11
+ "test:unit": "vitest --coverage",
12
+ "test:e2e": "playwright test --headed",
13
+ "test:ui-e2e": "playwright test --ui",
14
+ "test:ci-e2e": "playwright test",
15
+ "typecheck": "vue-tsc --noEmit",
16
+ "prepare": "husky",
17
+ "preview": "vite preview"
18
+ },
19
+ "dependencies": {
20
+ "axios": "^1.20.0",
21
+ "pinia": "^4.0.3",
22
+ "pinia-plugin-persistedstate": "^4.7.1",
23
+ "vue": "^3.5.43",
24
+ "vue-i18n": "^11.4.12",
25
+ "vue-router": "^5.3.1"
26
+ },
27
+ "devDependencies": {
28
+ "@component-hook/eslint-plugin": "^1.5.1",
29
+ "@intlify/unplugin-vue-i18n": "^11.2.5",
30
+ "@playwright/test": "^1.63.0",
31
+ "@tailwindcss/vite": "^4.3.3",
32
+ "@testing-library/jest-dom": "^7.0.1",
33
+ "@testing-library/user-event": "^14.6.7",
34
+ "@testing-library/vue": "^8.1.0",
35
+ "@tsconfig/node24": "^24.0.5",
36
+ "@types/node": "^24.13.5",
37
+ "@vitejs/plugin-vue": "^6.0.9",
38
+ "@vitest/coverage-v8": "^5.0.1",
39
+ "@vue/tsconfig": "^0.9.1",
40
+ "cross-env": "^10.1.0",
41
+ "cssnano": "^9.0.5",
42
+ "eslint": "^9.39.5",
43
+ "fast-glob": "^3.3.3",
44
+ "jsdom": "^30.1.0",
45
+ "msw": "^2.15.0",
46
+ "postcss": "^8.5.28",
47
+ "postcss-preset-env": "^11.5.3",
48
+ "postcss-pxtorem": "^6.1.0",
49
+ "prettier": "^3.9.8",
50
+ "prettier-plugin-tailwindcss": "^0.8.1",
51
+ "rollup-plugin-visualizer": "^7.1.1",
52
+ "tailwindcss": "^4.3.3",
53
+ "typescript": "^6.0.3",
54
+ "vite": "^8.3.0",
55
+ "vite-plugin-svg-icons": "^2.0.1",
56
+ "vitest": "^5.0.1",
57
+ "vue-tsc": "^3.3.11"
58
+ },
59
+ "optionalDependencies": {
60
+ "@commitlint/cli": "^21.2.2",
61
+ "@commitlint/config-conventional": "^21.2.2",
62
+ "husky": "^9.1.7",
63
+ "lint-staged": "^17.5.1"
64
+ },
65
+ "packageManager": "pnpm@12.4.2",
66
+ "msw": {
67
+ "workerDirectory": "public"
68
+ }
69
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
@@ -0,0 +1,7 @@
1
+ <script setup lang="ts"></script>
2
+
3
+ <template>
4
+ <router-view />
5
+ </template>
6
+
7
+ <style scoped></style>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
@@ -0,0 +1 @@
1
+ export * from './svg-icon';
@@ -0,0 +1 @@
1
+ export { default as SvgIcon } from './src/index.vue';
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+
4
+ interface Props {
5
+ name: string;
6
+ prefix?: string;
7
+ color?: string;
8
+ }
9
+
10
+ defineOptions({ name: 'SvgIcon' });
11
+ const props = withDefaults(defineProps<Props>(), {
12
+ prefix: 'icon',
13
+ color: '#333',
14
+ });
15
+
16
+ const symbolId = computed(() => `#${props.prefix}-${props.name}`);
17
+ </script>
18
+
19
+ <template>
20
+ <svg
21
+ aria-hidden="true"
22
+ :title="symbolId"
23
+ >
24
+ <use
25
+ :href="symbolId"
26
+ :fill="color"
27
+ />
28
+ </svg>
29
+ </template>
@@ -0,0 +1,3 @@
1
+ {
2
+ "language": "English"
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "language": "中文"
3
+ }
@@ -0,0 +1,21 @@
1
+ import { createApp } from 'vue';
2
+ import i18NPlugin from '@/plugins/i18n';
3
+ import router from '@/router';
4
+ import App from './App.vue';
5
+ import { createPinia } from './stores';
6
+ import 'virtual:svg-icons-register';
7
+ import '@/styles/index.css';
8
+
9
+ if (import.meta.env.VITE_APP_MOCK === 'service-worker') {
10
+ const { worker } = await import('@/mocks/browser');
11
+
12
+ worker.start();
13
+ }
14
+
15
+ const app = createApp(App);
16
+ const pinia = createPinia();
17
+
18
+ app.use(pinia);
19
+ app.use(router);
20
+ app.use(i18NPlugin);
21
+ app.mount('#app');
@@ -0,0 +1,4 @@
1
+ import { setupWorker } from 'msw/browser';
2
+ import { handlers } from '@/mocks/handlers';
3
+
4
+ export const worker = setupWorker(...handlers);
@@ -0,0 +1 @@
1
+ export const handlers = [];
@@ -0,0 +1,4 @@
1
+ import { setupServer } from 'msw/node';
2
+ import { handlers } from '@/mocks/handlers';
3
+
4
+ export const mswServer = setupServer(...handlers);
@@ -0,0 +1,57 @@
1
+ import userEvent from '@testing-library/user-event';
2
+ import { screen } from '@testing-library/vue';
3
+ import HelloWorld from '@/pages/home/components/HelloWorld.vue';
4
+ import i18n from '@/plugins/i18n';
5
+ import { useConfigStore } from '@/stores';
6
+ import { renderComponent } from '@/test-utils';
7
+
8
+ describe('home page HelloWorld component', () => {
9
+ const { t } = i18n.global;
10
+
11
+ it('renders the correct content', () => {
12
+ const msg = 'Hello World!';
13
+ const { version } = useConfigStore();
14
+
15
+ renderComponent(HelloWorld, { props: { msg } });
16
+ expect(screen.getByRole('heading', { name: msg })).toBeInTheDocument();
17
+ expect(screen.getByRole('button', { name: /count is 0/i })).toBeInTheDocument();
18
+ expect(screen.getByText(/check out , the official vue \+ vite starter/i)).toBeInTheDocument();
19
+ expect(screen.getByRole('link', { name: /create-vue/i })).toBeInTheDocument();
20
+ expect(screen.getByText(/install in your ide for a better dx/i)).toBeInTheDocument();
21
+ expect(screen.getByRole('link', { name: /volar/i })).toBeInTheDocument();
22
+ expect(screen.getByText(/click on the vite and vue logos to learn more/i)).toBeInTheDocument();
23
+ expect(screen.getByText(version)).toBeInTheDocument();
24
+ expect(screen.getByRole('button', { name: t('language') })).toBeInTheDocument();
25
+ });
26
+
27
+ it('count button interact', async () => {
28
+ renderComponent(HelloWorld, { props: { msg: '' } });
29
+ const countButton = screen.getByRole('button', { name: /count is 0/i });
30
+
31
+ await userEvent.click(countButton);
32
+ expect(countButton).toHaveTextContent(/count is 1/i);
33
+ await userEvent.click(countButton);
34
+ await userEvent.click(countButton);
35
+ expect(countButton).toHaveTextContent(/count is 3/i);
36
+ });
37
+
38
+ it('link correct attribute', () => {
39
+ renderComponent(HelloWorld, { props: { msg: '' } });
40
+ const createVueLink = screen.getByRole('link', { name: /create-vue/i });
41
+ const volarLink = screen.getByRole('link', { name: /volar/i });
42
+
43
+ expect(createVueLink).toHaveAttribute('href', 'https://vuejs.org/guide/quick-start.html#local');
44
+ expect(createVueLink).toHaveAttribute('target', '_blank');
45
+ expect(volarLink).toHaveAttribute('href', 'https://github.com/vuejs/language-tools');
46
+ expect(volarLink).toHaveAttribute('target', '_blank');
47
+ });
48
+
49
+ it('locale change interact', async () => {
50
+ renderComponent(HelloWorld, { props: { msg: '' } });
51
+ const localeButton = screen.getByRole('button', { name: t('language') });
52
+
53
+ expect(localeButton).toHaveTextContent('English');
54
+ await userEvent.click(localeButton);
55
+ expect(localeButton).toHaveTextContent('中文');
56
+ });
57
+ });
@@ -0,0 +1,54 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue';
3
+ import { useI18n } from 'vue-i18n';
4
+ import { useConfigStore } from '@/stores';
5
+
6
+ defineProps<{ msg: string }>();
7
+
8
+ const { locale } = useI18n();
9
+ const { version } = useConfigStore();
10
+ const count = ref(0);
11
+
12
+ function changeLocale() {
13
+ locale.value = locale.value === 'en-US' ? 'zh-TW' : 'en-US';
14
+ }
15
+ </script>
16
+
17
+ <template>
18
+ <h1>{{ msg }}</h1>
19
+
20
+ <div class="p-8">
21
+ <button @click="count++">count is {{ count }}</button>
22
+ </div>
23
+
24
+ <p>
25
+ Check out
26
+ <a
27
+ href="https://vuejs.org/guide/quick-start.html#local"
28
+ target="_blank"
29
+ >create-vue</a
30
+ >, the official Vue + Vite starter
31
+ </p>
32
+ <p>
33
+ Install
34
+ <a
35
+ href="https://github.com/vuejs/language-tools"
36
+ target="_blank"
37
+ >Volar</a
38
+ >
39
+ in your IDE for a better DX
40
+ </p>
41
+ <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
42
+ <p class="read-the-docs">
43
+ {{ version }}
44
+ </p>
45
+ <button @click="changeLocale">
46
+ {{ $t('language') }}
47
+ </button>
48
+ </template>
49
+
50
+ <style lang="css" scoped>
51
+ .read-the-docs {
52
+ color: #888;
53
+ }
54
+ </style>
@@ -0,0 +1,17 @@
1
+ import { screen } from '@testing-library/vue';
2
+ import Home from '@/pages/home/index.vue';
3
+ import { renderComponent } from '@/test-utils';
4
+
5
+ describe('home page', () => {
6
+ it('renders the correct content', () => {
7
+ renderComponent(Home);
8
+ const viteLink = screen.getByRole('link', { name: /vite logo/i });
9
+ const vueLink = screen.getByLabelText(/vue link/i);
10
+
11
+ expect(viteLink).toHaveAttribute('href', 'https://vitejs.dev');
12
+ expect(viteLink).toHaveAttribute('target', '_blank');
13
+
14
+ expect(vueLink).toHaveAttribute('href', 'https://vuejs.org/');
15
+ expect(vueLink).toHaveAttribute('target', '_blank');
16
+ });
17
+ });
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ import { SvgIcon } from '@/components/common';
3
+ import HelloWorld from './components/HelloWorld.vue';
4
+ </script>
5
+
6
+ <template>
7
+ <div>
8
+ <a
9
+ href="https://vitejs.dev"
10
+ target="_blank"
11
+ >
12
+ <img
13
+ src="/vite.svg"
14
+ class="logo"
15
+ alt="Vite logo"
16
+ />
17
+ </a>
18
+ <a
19
+ href="https://vuejs.org/"
20
+ target="_blank"
21
+ aria-label="Vue link"
22
+ >
23
+ <svg-icon
24
+ name="vue"
25
+ class="logo hover:drop-shadow-[0_0_2em_#42b883aa]"
26
+ />
27
+ </a>
28
+ </div>
29
+
30
+ <hello-world msg="Vite + Vue" />
31
+ </template>
32
+
33
+ <style lang="css" scoped>
34
+ .logo {
35
+ width: 96px;
36
+ height: 96px;
37
+ margin: 0 auto;
38
+ padding: 24px;
39
+ will-change: filter;
40
+ transition: filter 300ms;
41
+
42
+ &:hover {
43
+ filter: drop-shadow(0 0 2em #646cffaa);
44
+ }
45
+ }
46
+ </style>
@@ -0,0 +1,11 @@
1
+ import messages from '@intlify/unplugin-vue-i18n/messages';
2
+ import { createI18n } from 'vue-i18n';
3
+
4
+ const i18n = createI18n({
5
+ legacy: import.meta.env.MODE === 'test',
6
+ locale: navigator.language,
7
+ fallbackLocale: 'en-US',
8
+ messages,
9
+ });
10
+
11
+ export default i18n;
@@ -0,0 +1,23 @@
1
+ import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
2
+ import HomePage from '@/pages/home/index.vue';
3
+
4
+ export const routes: Array<RouteRecordRaw> = [
5
+ {
6
+ path: '/:catchAll(.*)',
7
+ redirect: '/',
8
+ },
9
+ {
10
+ path: '/',
11
+ component: HomePage,
12
+ meta: {
13
+ title: 'Vue3 Coding Standards',
14
+ },
15
+ },
16
+ ];
17
+
18
+ const router = createRouter({
19
+ history: createWebHistory(),
20
+ routes,
21
+ });
22
+
23
+ export default router;
@@ -0,0 +1 @@
1
+ export const STORAGE_PREFIX = '{{projectName}}';
@@ -0,0 +1,15 @@
1
+ import { createPinia as create } from 'pinia';
2
+ import { createPersistedState } from 'pinia-plugin-persistedstate';
3
+ import { STORAGE_PREFIX } from './constants';
4
+ export { useConfigStore } from './modules/config';
5
+
6
+ export function createPinia() {
7
+ const pinia = create();
8
+ const persistedState = createPersistedState({
9
+ key: storeKey => `${STORAGE_PREFIX}-${storeKey}`,
10
+ });
11
+
12
+ pinia.use(persistedState);
13
+
14
+ return pinia;
15
+ }
@@ -0,0 +1,20 @@
1
+ import { defineStore } from 'pinia';
2
+
3
+ const versionString = import.meta.env.DEV
4
+ ? `${import.meta.env.VITE_APP_VERSION}-dev`
5
+ : import.meta.env.VITE_APP_VERSION;
6
+
7
+ const defaultState = {
8
+ isDev: import.meta.env.DEV,
9
+ version: versionString,
10
+ };
11
+
12
+ export const useConfigStore = defineStore('config', {
13
+ state: () => defaultState,
14
+ getters: {},
15
+ actions: {},
16
+ persist: {
17
+ storage: localStorage,
18
+ pick: [],
19
+ },
20
+ });