1.Vite #

2.配置开发环境 #

2.1 安装依赖 #

npm install vue  --save
npm install  @vitejs/plugin-vue @vue/compiler-sfc vite --save-dev

2.2 配置文件 #

vite.config.js

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
});

2.3 package.json #

package.json

{
  "name": "vite2-prepare",
  "version": "1.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview"
  },
  "dependencies": {
    "vue": "^3.0.5"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.2.4",
    "@vue/compiler-sfc": "^3.0.5",
    "vite": "^2.4.0"
  }
}

2.4 index.html #

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

2.5 src\main.js #

src\main.js

import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");

2.6 src\App.vue #

src\App.vue

<template>
  <img src="./assets/ico.jpg" />
  <HelloWorld msg="Vue3 + Vite" />
</template>

<script setup>
//https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md
import HelloWorld from './components/HelloWorld.vue'
</script>

2.7 HelloWorld.vue #

src\components\HelloWorld.vue

<template>
  <h1>{{ msg }}</h1>
</template>

3.静态资源处理 #

3.1 模板中引入 #

src\App.vue

<template>
+  <img  src="./assets/avatar.jpg" />
</template>

3.2 JS 中引入 #

<template>
  <img  src="./assets/avatar.jpg" />
+  <img  :src="imgUrl" />
  <HelloWorld msg="Hello Vue 3 + Vite" />
</template>

<script setup>
//https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md
import HelloWorld from './components/HelloWorld.vue'
+import imgUrl from './assets/avatar.jpg'
</script>

3.3 CSS 中引入 #

<template>
  <img  src="./assets/avatar.jpg" />
  <img  :src="imgUrl" />
+ <div class="avatar"></div>
  <HelloWorld msg="Hello Vue 3 + Vite" />
</template>

<script setup>
//https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md
import HelloWorld from './components/HelloWorld.vue'
import imgUrl from './assets/avatar.jpg'
</script>
+<style scoped>
+.avatar{
+  width:200px;
+  height:200px;
+  background-image: url(./assets/avatar.jpg);
+  background-size: contain;
+}
+</style>

3.4 public 目录 #

public\avatar.jpg

4.配置别名 #

4.1 vite.config.js #

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
+import {resolve} from 'path';

// https://vitejs.dev/config/
export default defineConfig({
+ resolve:{
+   alias:{
+    '@':resolve('src')
+   }
+ },
  plugins: [vue()]
})

4.2 App.vue #

src\App.vue

<template>
+ <img src="@/assets/avatar.jpg" />
  <img :src="avatarUrl" />
  <div class="avatar"></div>
  <HelloWorld msg="Hello Vue 3 + Vite" />
</template>
<script setup>
//https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md
+import HelloWorld from "@/components/HelloWorld.vue";
+import avatarUrl from "@/assets/avatar.jpg";
</script>
<style scoped>
.avatar {
  width: 200px;
  height: 200px;
+ background-image: url(@/assets/avatar.jpg);
  background-size: contain;
}
</style>

5.样式处理 #

5.1 全局样式 #

src\main.js

import { createApp } from 'vue'
import App from './App.vue'
+import './global.css'
createApp(App).mount('#app')

src\global.css

#app {
  background-color: lightgrey;
}

5.2 局部样式 #

5.2.1 scoped #

src\components\HelloWorld.vue

<template>
  <h1>{{ msg }}</h1>
+  <a>超链接</a>
</template>
+<style scoped>
+a {
+  color: #42b983;
+}
+</style>

5.2.2 CSS Modules #

5.2.2.1 内联 #

src\components\HelloWorld.vue

<template>
  <h1>{{ msg }}</h1>
+ <a :class="$style.link">超链接</a>
</template>
+<style module>
+.link {
+  color: #42b983;
+}
+</style>
5.2.2.2 外联 #
<template>
  <h1>{{ msg }}</h1>
+ <a :class="style.link">超链接</a>
</template>

<script setup>
+import style from './HelloWorld.module.css';
</script>

src\components\HelloWorld.module.css

.link {
    color: #42b983;
}

5.3 less 和 sass #

5.3.1 安装 #

npm i less sass -S

5.3.2 HelloWorld.vue #

src\components\HelloWorld.vue

<template>
  <h1>{{ msg }}</h1>
  <a :class="style.link">超链接</a>
+ <h2>less</h2>
+ <h3>sass</h3>
</template>

<script setup>
import { reactive } from 'vue'
import style from './HelloWorld.module.css';
</script>
+<style scoped lang="less">
+@color:red;
+h2{
+  color:@color;
+}
+</style>
+<style scoped lang="scss">
+$color:green;
+h3{
+  color:$color;
+}
</style>

5.4 PostCSS #

5.4.1 安装 #

npm install autoprefixer --save

5.4.2 postcss.config.js #

module.exports = {
  plugins: [require("autoprefixer")],
};

5.4.3 .browserslistrc #

>0.2%
not dead
not op_mini all

5.4.4 HelloWorld.vue #

src\components\HelloWorld.vue

<template>
  <h1>{{ msg }}</h1>
  <a :class="style.link">超链接</a>
  <h2>less</h2>
  <h3>sass</h3>
+ <div class="postcss"></div>
</template>

<script setup>
import { reactive } from 'vue'
import style from './HelloWorld.module.css';
</script>
<style scoped lang="less">
@color:red;
h2{
  color:@color;
}
</style>

<style scoped lang="scss">
$color:green;
h3{
  color:$color;
}
</style>
+<style scoped>
+.postcss{
+    height:50px;
+    width:200px;
+    background-color: orange;
+    transform: rotate(90deg);
+}
+</style>

6.typescript #

6.1 安装 #

cnpm install typescript @babel/core @babel/preset-env  @babel/preset-typescript --save-dev

6.2 .babelrc #

.babelrc

{
    "presets": [
        ["@babel/preset-env"],
        "@babel/preset-typescript"
    ]
}

6.3 tsconfig.json #

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"]
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

6.4 HelloWorld.vue #

src\components\HelloWorld.vue

<template>
  <h1>{{ msg }}</h1>
  <h2>less</h2>
  <h3>sass</h3>
  <div class="postcss"></div>
+ <button @click="handleClick">{{state.count}}</button>
</template>

<script setup lang="ts">
import { reactive,defineProps } from 'vue'
+defineProps({
+  msg:String
+})
+interface State {
+  count:number;
+}
+let state = reactive<State>({count:0});
+const handleClick = ()=>{
+  console.log(state.count);
+  state.count++;
+}
</script>
<style scoped lang="less">
@color:red;
h2{
  color:@color;
}
</style>

<style scoped lang="scss">
$color:green;
h3{
  color:$color;
}
</style>
<style scoped>
.postcss{
    height:50px;
    width:200px;
    background-color: orange;
    transform: rotate(90deg);
}
</style>

6.5 shims-vue.d.ts #

src\shims-vue.d.ts

declare module '*.vue' {
  import { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

6.6 vite-env.d.ts #

src\vite-env.d.ts

/// <reference types="vite/client" />

7.配置代理 #

7.1 vite.config.js #

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path';

// https://vitejs.dev/config/
export default defineConfig({
  resolve: {
    alias: {
      '@': resolve('src')
    }
  },
+ server: {
+   proxy: {
+     '/api': {
+       target: 'http://jsonplaceholder.typicode.com',
+       changeOrigin: true,
+       rewrite: (path) => path.replace(/^\/api/, '')
+     }
+   }
+ },
  plugins: [vue()]
})

7.2 src\App.vue #

src\App.vue

<template>
  <img src="@/assets/avatar.jpg" />
  <img :src="avatarUrl" />
  <div class="avatar"></div>
  <HelloWorld msg="Hello Vue 3 + Vite" />
</template>

<script setup>
//https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md
import HelloWorld from "@/components/HelloWorld.vue";
import avatarUrl from "@/assets/avatar.jpg";
+fetch('/api/todos/1')
+  .then(response => response.json())
+  .then(json => console.log(json))
</script>
<style scoped>
.avatar {
  width: 200px;
  height: 200px;
  background-image: url(@/assets/avatar.jpg);
  background-size: contain;
}
</style>

8.mock #

npm i mockjs vite-plugin-mock -D
node ./node_modules/vite-plugin-mock/node_modules/esbuild/install.js

8.1 vite.config.js #

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path';
+import { viteMockServe } from "vite-plugin-mock";
// https://vitejs.dev/config/
export default defineConfig({
  resolve: {
    alias: {
      '@': resolve('src')
    }
  },
  server: {
    proxy: {
      '/api': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
+  plugins: [vue(),viteMockServe({})]
})

8.2 mock\test.ts #

mock\test.ts

import { MockMethod } from 'vite-plugin-mock';
export default [
    {
        url: '/api/get',
        method: 'get',
        response: ({ query }) => {
            return {
                code: 0,
                data: {
                    name: 'vben',
                },
            };
        },
    },
] as MockMethod[];

9.ESLint #

npm install eslint eslint-plugin-vue  @vue/eslint-config-typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev

9.1 src\components\HelloWorld.vue #

src\components\HelloWorld.vue

<template>
  <h1>{{ msg }}</h1>
  <h2>less</h2>
  <h3>sass</h3>
  <div class="postcss" />
  <button @click="handleClick">
    {{ state.count }}
  </button>
</template>

<script setup lang="ts">
import { reactive,defineProps } from 'vue'
defineProps({
+ msg:{
+   type:String,
+   default:''
+ }
})
interface State {
  count:number;
}
let state = reactive<State>({count:0});
const handleClick = ()=>{
  console.log(state.count);
  state.count++;
}
</script>
<style scoped lang="less">
@color:red;
h2{
  color:@color;
}
</style>

<style scoped lang="scss">
$color:green;
h3{
  color:$color;
}
</style>
<style scoped>
.postcss{
    height:50px;
    width:200px;
    background-color: orange;
    transform: rotate(90deg);
}
</style>

9.2 main.ts #

src\main.ts

import { createApp } from "vue";
import App from "./App.vue";
import "./global.css";
createApp(App).mount("#app");

9.3 .eslintrc.js #

.eslintrc.js

module.exports = {
  root: true,
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    "plugin:vue/vue3-recommended",
    "eslint:recommended",
    "@vue/typescript/recommended",
  ],
  parserOptions: {
    ecmaVersion: 2021,
  },
  rules: {
    "no-unused-vars": "off",
    "@typescript-eslint/no-unused-vars": "off",
  },
};

9.4 .eslintignore #

.eslintignore

*.css
*.jpg
*.jpeg
*.png
*.gif
*.d.ts

9.5 package.json #

package.json

{
  "name": "zhufeng-vite2-prepare",
  "version": "1.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview",
+   "lint":"eslint --ext .ts,vue src/** --no-error-on-unmatched-pattern --quiet",
+   "lint:fix":"eslint --ext .ts,vue src/** --no-error-on-unmatched-pattern --fix"
  },
  "dependencies": {
    "less": "^4.1.1",
    "sass": "^1.35.2",
    "vue": "^3.0.5"
  },
  "devDependencies": {
+   "@typescript-eslint/eslint-plugin": "^4.28.2",
+   "@typescript-eslint/parser": "^4.28.2",
    "@vitejs/plugin-vue": "^1.2.4",
    "@vue/compiler-sfc": "^3.0.5",
+   "@vue/eslint-config-typescript": "^7.0.0",
    "autoprefixer": "^10.2.6",
+   "eslint": "^7.30.0",
+   "eslint-plugin-vue": "^7.13.0",
    "mockjs": "^1.1.0",
    "vite": "^2.4.0",
    "vite-plugin-mock": "^2.9.1"
  }
}

10.Prettier #

10.1 安装 #

npm install prettier eslint-plugin-prettier  @vue/eslint-config-prettier --save-dev

10.2 package.json #

{
  "name": "zhufeng-vite2-prepare",
  "version": "1.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview",
    "lint": "eslint --ext .ts,vue src/** --no-error-on-unmatched-pattern --quiet",
    "lint:fix": "eslint --ext .ts,vue src/** --no-error-on-unmatched-pattern --fix"
  },
  "dependencies": {
    "less": "^4.1.1",
    "sass": "^1.35.2",
    "vue": "^3.0.5"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^4.28.2",
    "@typescript-eslint/parser": "^4.28.2",
    "@vitejs/plugin-vue": "^1.2.4",
    "@vue/compiler-sfc": "^3.0.5",
+   "@vue/eslint-config-prettier": "^6.0.0",
    "@vue/eslint-config-typescript": "^7.0.0",
    "autoprefixer": "^10.2.6",
    "eslint": "^7.30.0",
+   "eslint-plugin-prettier": "^3.4.0",
    "eslint-plugin-vue": "^7.13.0",
    "mockjs": "^1.1.0",
+   "prettier": "^2.3.2",
    "vite": "^2.4.0",
    "vite-plugin-mock": "^2.9.1"
  }
}

10.3 .eslintrc.js #

.eslintrc.js

module.exports = {
  root: true,
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    "plugin:vue/vue3-recommended",
    "eslint:recommended",
    "@vue/typescript/recommended",
+   "@vue/prettier",
+   "@vue/prettier/@typescript-eslint",
  ],
  parserOptions: {
    ecmaVersion: 2021,
  },
  rules: {
    "no-unused-vars": "off",
    "@typescript-eslint/no-unused-vars": "off",
+   "prettier/prettier": ["error", { endOfLine: "auto" }],
  },
};

11.单元测试 #

11.1 安装依赖 #

cnpm i jest@next babel-jest@next @types/jest vue-jest@next ts-jest@next @vue/test-utils@next --save-dev

11.2 package.json #

{
  "name": "zhufeng-vite2-prepare",
  "version": "1.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview",
    "lint": "eslint --ext .ts,vue src/** --no-error-on-unmatched-pattern --quiet",
    "lint:fix": "eslint --ext .ts,vue src/** --no-error-on-unmatched-pattern --fix"
  },
  "dependencies": {
    "less": "^4.1.1",
    "sass": "^1.35.2",
    "vue": "^3.0.5"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^4.28.2",
    "@typescript-eslint/parser": "^4.28.2",
    "@vitejs/plugin-vue": "^1.2.4",
    "@vue/compiler-sfc": "^3.0.5",
    "@vue/eslint-config-prettier": "^6.0.0",
    "@vue/eslint-config-typescript": "^7.0.0",
    "autoprefixer": "^10.2.6",
    "eslint": "^7.30.0",
    "eslint-plugin-prettier": "^3.4.0",
    "eslint-plugin-vue": "^7.13.0",
    "mockjs": "^1.1.0",
    "prettier": "^2.3.2",
    "vite": "^2.4.0",
    "vite-plugin-mock": "^2.9.1"
  }
}

11.3 jest.config.js #

jest.config.js

module.exports = {
  testEnvironment: "jsdom",
  transform: {
    "^.+\\.vue$": "vue-jest",
    "^.+\\.jsx?$": "babel-jest",
    "^.+\\.tsx?$": "ts-jest",
  },
  moduleNameMapper: {
    "^@/(.*)$": "<rootDir>/src/$1",
  },
  testMatch: ["**/tests/**/*.spec.[jt]s"],
};

11.4 tests\test.ts #

tests\test.ts

import { mount } from "@vue/test-utils";
const MessageComponent = {
  template: "<p>{{ msg }}</p>",
  props: ["msg"],
};
test("displays message", () => {
  const wrapper = mount(MessageComponent, {
    props: {
      msg: "Hello world",
    },
  });
  expect(wrapper.text()).toContain("Hello world");
});

11.5 tsconfig.json #

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
+   "types":["vite/client","jest"],
+   "baseUrl": "./",
+   "paths": {
+     "@": ["./src"]
+   }
  },
+ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue","tests/**/*.spec.ts", "tests/test.ts"]
}

11.6 package.json #

package.json

{
   "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview",
    "lint": "eslint --ext .ts,vue src/** --no-error-on-unmatched-pattern --quiet",
    "lint:fix": "eslint --ext .ts,vue src/** --no-error-on-unmatched-pattern --fix",
+   "test": "jest  --passWithNoTests"
  }
}

12.git hook #

12.1 注释规范 #

12.1.1 type 类型 #

类型 描述
build 编译相关的修改,例如发布版本、对项目构建或者依赖的改动
chore 其他修改, 比如改变构建流程、或者增加依赖库、工具等
ci 持续集成修改
docs 文档修改
feature 新特性、新功能
fix 修改 bug
perf 优化相关,比如提升性能、体验
refactor 代码重构
revert 回滚到上一个版本
style 代码格式修改
test 测试用例修改

12.2 安装 #

cnpm i husky lint-staged @commitlint/cli @commitlint/config-conventional --save-dev

12.3 配置脚本 #

npm set-script prepare "husky install"
npm run prepare

12.4 创建 hooks #

npx husky add .husky/pre-commit "lint-staged"
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
npx husky add .husky/pre-push "npm run test"

12.5 commitlint.config.js #

commitlint.config.js

module.exports = {
  extends: ["@commitlint/config-conventional"],
  rules: {
    "type-enum": [
      2,
      "always",
      [
        "feature",
        "update",
        "fixbug",
        "refactor",
        "optimize",
        "style",
        "docs",
        "chore",
      ],
    ],
    "type-case": [0],
    "type-empty": [0],
    "scope-empty": [0],
    "scope-case": [0],
    "subject-full-stop": [0, "never"],
    "subject-case": [0, "never"],
    "header-max-length": [0, "always", 72],
  },
};

13.参考 #

13.1 setup #

<script setup> 是 Vue 3 中的一个新特性,它是一个实验性的 API,用于组合式 API 的语法糖。这个特性是为了让 Vue 的单文件组件(SFC)在使用组合式 API 时更加简洁。

在 Vue 3 之前,我们通常在 export default 中定义组件的各种选项,如 data, methods, computed 等。但是,当我们使用组合式 API(例如 ref, reactive, computed, watch 等)时,我们需要在 setup 函数中定义它们。

使用 <script setup>,我们可以直接在 <script> 标签内部定义这些响应式属性和函数,而不需要显式地定义一个 setup 函数。

示例

不使用 <script setup> 的组件:

<template>
  <div>
    {{ count }}
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const increment = () => {
      count.value++;
    };

    return {
      count,
      increment
    };
  }
}
</script>

使用 <script setup> 的相同组件:

<template>
  <div>
    {{ count }}
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const count = ref(0);
const increment = () => {
  count.value++;
};
</script>

如你所见,使用 <script setup> 使得代码更加简洁。

注意事项

  1. 使用 <script setup> 时,你不能使用 data, methods, computed 等旧的选项 API。所有的逻辑都应该使用组合式 API。
  2. 你仍然可以使用 propsemit。为了在 <script setup> 中使用它们,你需要从 vue 导入 definePropsdefineEmits
  3. <script setup> 是实验性的,所以在生产环境中使用前,请确保你了解其所有的细节和限制。

13.2 defineConfig #

defineConfig 是 Vite 提供的一个辅助函数,用于为 Vite 配置文件提供类型提示和类型检查。当你使用 TypeScript 来编写 Vite 的配置文件时,defineConfig 尤其有用。

为什么使用 defineConfig

Vite 的配置文件是一个普通的 JavaScript 或 TypeScript 文件,它默认导出一个配置对象。但是,由于这只是一个普通的对象,你可能不会得到关于该对象结构的类型提示或类型检查。

通过使用 defineConfig,你可以确保你的配置文件正确地遵循 Vite 的配置格式,并在编写配置时获得有关可用选项的自动完成和类型检查。

如何使用 defineConfig

首先,确保你已经安装了 Vite:

npm install vite --save-dev

然后,在你的 Vite 配置文件中(例如 vite.config.ts),你可以这样使用 defineConfig

import { defineConfig } from "vite";
export default defineConfig({
  // 你的配置选项
  base: "./",
  plugins: [],
  // ... 其他配置选项
});

这样,当你在配置对象中添加或修改属性时,你将获得 Vite 配置的相关类型提示。

注意事项

总之,defineConfig 是 Vite 提供的一个简单的辅助工具,用于增强配置文件的类型安全性和开发者体验。如果你正在使用 TypeScript 编写 Vite 配置,那么使用 defineConfig 是一个好的实践。

13.3 @vitejs/plugin-vue #

@vitejs/plugin-vue 是 Vite 的一个官方插件,用于支持 Vue 3 的单文件组件(SFC)。Vite 是一个由 Vue.js 的创始人 Evan You 开发的现代前端开发和构建工具,它提供了极快的冷启动、即时热模块替换和真正的按需编译。

当你使用 Vite 创建一个 Vue 项目时,@vitejs/plugin-vue 通常会被默认安装,因为它允许 Vite 正确处理 .vue 文件。

如何使用

  1. 安装

    如果你正在创建一个新的 Vite + Vue 项目,你可以使用 Vite 的模板直接创建:

    npm init @vitejs/app my-vue-app --template vue
    

    如果你正在为一个现有的 Vite 项目添加 Vue 支持,你可以手动安装这个插件:

    npm install --save-dev @vitejs/plugin-vue
    
  2. 配置

    在你的 vite.config.js 文件中,你需要导入并使用这个插件:

    import { defineConfig } from "vite";
    import vue from "@vitejs/plugin-vue";
    
    export default defineConfig({
      plugins: [vue()],
    });
    

特性

注意事项

总的来说,@vitejs/plugin-vue 是 Vite 与 Vue 3 之间的桥梁,它使得在 Vite 项目中使用 Vue 变得简单且高效。

13.4 @vue/compiler-sfc #

@vue/compiler-sfc 是 Vue 3 的一个官方包,用于处理 Vue 的单文件组件(Single File Components,简称 SFC)。这个包提供了解析、编译和优化 Vue SFC 的功能。它是 Vue CLI、Vite 和其他 Vue 工具背后的核心编译器。

以下是关于 @vue/compiler-sfc 的一些关键点:

1. 主要功能

2. API

@vue/compiler-sfc 提供了一些主要的 API 函数,如 parsecompileTemplate

例如,要解析一个 .vue 文件,你可以这样做:

import { parse } from "@vue/compiler-sfc";

const source = `
<template>
  <div>Hello, Vue!</div>
</template>
`;

const { descriptor } = parse(source);
console.log(descriptor.template.content); // 输出: <div>Hello, Vue!</div>

3. 插件化

@vue/compiler-sfc 支持自定义插件,这使得开发者可以扩展或修改编译过程。

4. 与构建工具集成

构建工具,如 Webpack、Rollup 或 Vite,通常使用 @vue/compiler-sfc 来处理 .vue 文件。例如,Vue CLI 使用 vue-loader 来处理 .vue 文件,而 vue-loader 内部使用 @vue/compiler-sfc

5. <script setup>

@vue/compiler-sfc 也支持 Vue 3.2 引入的 <script setup> 语法糖,使得组合式 API 更加简洁。

总结

@vue/compiler-sfc 是 Vue 3 生态系统中的一个核心包,它处理 .vue 文件的解析和编译。如果你是一个 Vue 开发者,通常你不需要直接与这个包交互,但了解它的工作原理和功能可以帮助你更好地理解 Vue 的内部机制和构建过程。

13.5 scoped #

在 Vue 单文件组件 (SFC) 中,<style> 标签的 scoped 属性用于确保样式只应用于当前组件,而不会影响到其他组件或全局样式。这为组件提供了一种局部的、封装的样式,使得组件更加独立和可重用。

如何工作?

当你为 <style> 标签添加 scoped 属性时,Vue 的编译过程会为该组件的模板中的每个 HTML 元素添加一个唯一的属性(例如 data-v-123abc)。然后,它会在你的 CSS 选择器中添加相同的属性,从而确保这些样式只匹配那些带有相应属性的元素。

示例

考虑以下 Vue 组件:

<template>
  <div class="example">
    This is a scoped style example.
  </div>
</template>

<style scoped>
.example {
  color: blue;
}
</style>

在编译后,这个组件可能看起来像这样:

<div class="example" data-v-123abc>
  This is a scoped style example.
</div>

<style>
.example[data-v-123abc] {
  color: blue;
}
</style>

如你所见,.example 选择器现在只会匹配带有 data-v-123abc 属性的元素,这确保了样式的局部性。

注意事项

  1. 全局样式与局部样式:你可以在同一个 Vue SFC 中同时使用带有 scoped 属性的 <style> 和不带 scoped 属性的 <style>。后者会定义全局样式。

  2. 子组件样式scoped 样式不会影响子组件。如果你想为子组件定义样式,你需要使用更深的选择器或考虑使用其他策略,如 CSS 模块。

  3. 动态内容:由于 scoped 样式依赖于为模板元素添加特定的属性,因此它可能不适用于动态内容,如 v-html 指令插入的内容。

  4. 性能:虽然 scoped 样式提供了很好的封装性,但它也增加了选择器的复杂性。在大型应用中,过度使用 scoped 可能会对性能产生一些影响。

总的来说,<style scoped> 提供了一种简单的方法来封装 Vue 组件的样式,但在使用它时,你应该了解其工作原理和限制。

13.6 CSS Modules #

CSS Modules 是一种将样式封装到组件中的方法,它可以确保样式的局部作用域,避免全局样式的污染。在 Vue 中,你可以很容易地使用 CSS Modules。

如何在 Vue 中使用 CSS Modules

  1. 在单文件组件中声明

    在你的 Vue 单文件组件中,你可以通过为 <style> 标签添加 module 属性来使用 CSS Modules:

    
    
    
    

    注意如何使用 $style.myClass 来引用样式。这是因为当你使用 CSS Modules 时,所有的类名都会被转换为局部作用域的,并且可以通过组件的 $style 对象来访问。

  2. 自定义类名转换

    默认情况下,CSS Modules 会将类名转换为 camelCase。这意味着 .my-class 会变成 $style.myClass。如果你想使用其他格式,你可以这样配置:

    
    

    然后在模板中这样使用:

    
    
  3. 与全局样式共存

    你可以在同一个组件中同时使用 CSS Modules 和全局样式。只需为需要全局作用域的样式添加 global 属性:

    
    
    
    

优点

  1. 局部作用域:确保样式不会意外地影响其他组件。
  2. 避免命名冲突:由于类名是局部的,你不必担心在不同的组件中使用相同的类名。
  3. 清晰的结构:明确地知道哪些样式是为特定组件定义的。

缺点

  1. 学习曲线:需要适应新的语法和概念。
  2. 不是所有项目都需要:对于小型项目或那些不太关心样式隔离的项目,这可能是过度的。

总的来说,CSS Modules 是 Vue 提供的一个强大的工具,它可以帮助你更好地管理和封装组件的样式。如果你正在寻找一种确保样式的局部作用域的方法,那么它绝对值得一试。

13.7 hash #

哈希值是一个固定长度的数字或字符序列,它是通过哈希函数从输入数据(通常是一个字符串或文件)计算得出的。哈希函数的设计目的是确保即使输入数据的微小变化也会导致输出的哈希值发生显著变化。哈希值有许多应用,包括数据检索、密码学、数据完整性验证等。

以下是关于哈希值的一些关键点:

  1. 固定长度:无论输入数据的大小或长度如何,哈希函数输出的哈希值都是固定长度的。

  2. 快速计算:对于给定的输入,哈希函数应该能够快速地计算出哈希值。

  3. 不可逆:从哈希值本身,不应该能够(至少在实际中)重新构造出原始的输入数据。

  4. 冲突最小化:虽然理论上不同的输入可能会产生相同的哈希值(称为“冲突”),但好的哈希函数会使这种情况的发生概率极低。

  5. 敏感性:即使是输入数据的微小变化,也应该导致输出哈希值的显著变化。

哈希值的应用:

  1. 数据结构:哈希表(或哈希映射)是一种使用哈希值来快速存储和检索数据的数据结构。

  2. 数据完整性:哈希值常用于验证数据的完整性。例如,下载文件时,网站可能会提供文件的哈希值,以便用户可以验证下载的文件是否完整且未被篡改。

  3. 密码学:在密码学中,哈希函数用于多种目的,如密码存储(存储哈希值而不是明文密码)和数字签名。

  4. 缓存:Web 浏览器和其他系统使用哈希值来确定数据是否已经在缓存中,从而避免不必要的数据重新获取或计算。

  5. 负载均衡:在分布式系统中,哈希值可以用于确定将请求发送到哪个服务器,确保请求均匀地分布在多个服务器上。

常见的哈希函数:

需要注意的是,随着技术的进步,一些早期的哈希函数(如 MD5 和 SHA-1)现在被认为是不安全的,因为它们容易受到攻击。在安全关键的应用中,建议使用更强大和经过时间验证的哈希函数,如 SHA-256 或 SHA-3。