@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.
- package/README.md +1 -0
- package/package.json +10 -10
- package/template-react/.vscode/extensions.json +9 -0
- package/template-react/.vscode/settings.json +18 -0
- package/template-react/README.md.art +85 -0
- package/template-react/e2e/home.spec.ts +40 -0
- package/template-react/eslint.config.js +18 -0
- package/template-react/index.html.art +13 -0
- package/template-react/lint-staged.config.js +6 -0
- package/template-react/package.json +70 -0
- package/template-react/public/vite.svg +1 -0
- package/template-react/src/App.tsx +6 -0
- package/template-react/src/assets/svg-icons/react.svg +1 -0
- package/template-react/src/components/common/index.ts +1 -0
- package/template-react/src/components/common/svg-icon/index.ts +1 -0
- package/template-react/src/components/common/svg-icon/src/index.tsx +23 -0
- package/template-react/src/main.tsx +26 -0
- package/template-react/src/mocks/browser.ts +4 -0
- package/template-react/src/mocks/handlers/index.ts +1 -0
- package/template-react/src/mocks/server.ts +4 -0
- package/template-react/src/pages/home/components/hello-world/index.test.tsx +45 -0
- package/template-react/src/pages/home/components/hello-world/index.tsx +49 -0
- package/template-react/src/pages/home/index.module.css +8 -0
- package/template-react/src/pages/home/index.test.tsx +19 -0
- package/template-react/src/pages/home/index.tsx +37 -0
- package/template-react/src/plugins/i18n/index.ts +19 -0
- package/template-react/src/plugins/i18n/locales/en-US.json +3 -0
- package/template-react/src/plugins/i18n/locales/zh-TW.json +3 -0
- package/template-react/src/router/index.tsx +22 -0
- package/template-react/src/stores/constants.ts +1 -0
- package/template-react/src/stores/index.ts +1 -0
- package/template-react/src/stores/middleware.ts +17 -0
- package/template-react/src/stores/modules/config.ts +27 -0
- package/template-react/src/styles/base.css +68 -0
- package/template-react/src/styles/common/all.css +1 -0
- package/template-react/src/styles/common/button.css +26 -0
- package/template-react/src/styles/index.css +3 -0
- package/template-react/src/styles/tailwind.css +14 -0
- package/template-react/src/test-utils/index.tsx +15 -0
- package/template-react/src/vite-env.d.ts +9 -0
- package/template-react/tsconfig.app.json +32 -0
- package/template-react/tsconfig.vitest.json +10 -0
- package/template-react/vite.config.ts +40 -0
- package/template-react/vitest.config.ts +18 -0
- package/template-react/vitest.setup.ts +13 -0
- package/template-shared/.browserslistrc +10 -0
- package/template-shared/.github/actions/setup-environment/action.yml +21 -0
- package/template-shared/.github/renovate.json +14 -0
- package/template-shared/.github/workflows/ci.yml +22 -0
- package/template-shared/.github/workflows/deploy.yml +27 -0
- package/template-shared/.github/workflows/preview.yml +17 -0
- package/template-shared/.gitlab/setup.yml +18 -0
- package/template-shared/.husky/commit-msg +1 -0
- package/template-shared/.husky/pre-commit +1 -0
- package/{LICENSE → template-shared/LICENSE} +1 -1
- package/template-shared/_gitignore +32 -0
- package/template-shared/_gitlab-ci.yml +22 -0
- package/template-shared/commitlint.config.js +3 -0
- package/template-shared/playwright.config.ts +49 -0
- package/template-shared/postcss.config.js +15 -0
- package/template-shared/prettier.config.js +13 -0
- package/template-shared/scripts/build-info/index.ts +21 -0
- package/template-shared/tsconfig.json +14 -0
- package/template-shared/tsconfig.node.json +12 -0
- package/template-vue/.vscode/extensions.json +10 -0
- package/template-vue/.vscode/settings.json +18 -0
- package/template-vue/README.md.art +81 -0
- package/template-vue/e2e/home.spec.ts +59 -0
- package/template-vue/eslint.config.js +18 -0
- package/template-vue/index.html.art +28 -0
- package/template-vue/lint-staged.config.js +6 -0
- package/template-vue/package.json +69 -0
- package/template-vue/public/vite.svg +1 -0
- package/template-vue/src/App.vue +7 -0
- package/template-vue/src/assets/svg-icons/vue.svg +1 -0
- package/template-vue/src/components/common/index.ts +1 -0
- package/template-vue/src/components/common/svg-icon/index.ts +1 -0
- package/template-vue/src/components/common/svg-icon/src/index.vue +29 -0
- package/template-vue/src/locales/en-US.json +3 -0
- package/template-vue/src/locales/zh-TW.json +3 -0
- package/template-vue/src/main.ts +21 -0
- package/template-vue/src/mocks/browser.ts +4 -0
- package/template-vue/src/mocks/handlers/index.ts +1 -0
- package/template-vue/src/mocks/server.ts +4 -0
- package/template-vue/src/pages/home/components/HelloWorld.test.ts +57 -0
- package/template-vue/src/pages/home/components/HelloWorld.vue +54 -0
- package/template-vue/src/pages/home/index.test.ts +17 -0
- package/template-vue/src/pages/home/index.vue +46 -0
- package/template-vue/src/plugins/i18n.ts +11 -0
- package/template-vue/src/router/index.ts +23 -0
- package/template-vue/src/stores/constants.ts.art +1 -0
- package/template-vue/src/stores/index.ts +15 -0
- package/template-vue/src/stores/modules/config.ts +20 -0
- package/template-vue/src/styles/base.css +59 -0
- package/template-vue/src/styles/common/all.css +1 -0
- package/template-vue/src/styles/common/button.css +15 -0
- package/template-vue/src/styles/index.css +3 -0
- package/template-vue/src/styles/tailwind.css +1 -0
- package/template-vue/src/test-utils/index.ts +33 -0
- package/template-vue/src/vite-env.d.ts +9 -0
- package/template-vue/tsconfig.app.json +12 -0
- package/template-vue/tsconfig.vitest.json +10 -0
- package/template-vue/vite.config.ts +46 -0
- package/template-vue/vitest.config.ts +18 -0
- 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,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 @@
|
|
|
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,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 @@
|
|
|
1
|
+
export const 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
|
+
});
|