Nuxt3 常用插件集成方案
ElementPlus 集成
按需加载
目前来看按需加载仅仅针对组件,对于样式并不能实现按需加载。
安装 Element
组件库和图标插件。
pnpm install element-plus @element-plus/icons-vue
新建 assets/index.scss
并输入内容;注意: 使用 .scss
文件,请先安装对应的 loader
;pnpm install sass sass-loader -D
@use "element-plus/dist/index.css";
然后在 nuxt.config.ts
中增加如下内容。
export default defineNuxtConfig({
// 定义需要全局加载的文件。
css: ['~/assets/index.scss'],
build: {
// 生产环境使用 babel 转译依赖。
transpile: process.env.prod ? ['element-plus']: []
}
});
最后在 app.vue
(这只是示例,实际你可以在任何地方去使用) 中去使用它。
<template>
<div id="app">
<ElConfigProvider size="small" :z-index="3000" :local="zhCn">
<ElButton type="primary" size="small">按钮</ElButton>
<ElIcon :size="20">
<Edit />
</ElIcon>
</ElConfigProvider>
</div>
</template>
<script setup lang="ts">
import { ElConfigProvider, ElButton, ElIcon } from 'element-plus';
import { ElIcon } from '@element-plus/icons-vue';
import zhCn from 'element-plus/dist/locale/zh-cn.mjs';
</script>
在引用国际化时可能会有些错误,找不到模块“element-plus/dist/locale/zh-cn.mjs”或其相应的类型声明 个人目前的解决方案是在项目的根目录新建 index.d.ts
然后输入以下内容。如果有更好的方法,欢迎分享。
declare module 'element-plus/dist/locale/zh-cn.mjs'
declare module 'element-plus/dist/locale/en.mjs'
全量加载
新建 plugins/element-plus.ts
在此文件中新增如下配置,注册全局组件。
与按需加载唯一的区别可能就是每次使用时不再需要 import { * } from 'element-plus';
。但实际上按需加载也可以在这里面做,具体方案可以参考下面的 vant
以及 antd
的集成方案,也是我认为比较合理的方式。
import ElementPlus from 'element-plus';
import * as ElementPlusIcons from '@element-plus/icons-vue';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(ElementPlus);
for (const [key, component] of Object.entries(ElementPlusIcons)) {
nuxtApp.vueApp.component(key, component);
}
});
自动导入
安装插件 unplugin-vue-components
Vue
组件的按需自动导入
pnpm install unplugin-vue-components -D
然后在 nuxt.config.ts
中增加如下配置。遗憾的是 ElementPlus
的 icon
并不支持这样的操作,或者说是我没有找到对应的方案。
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
export default defineNuxtConfig({
vite: {
plugins: [
Components({
resolvers: [ElementPlusResolver()]
})
],
ssr: {
noExternal: ['element-plus'],
}
}
});
vant 集成
按需加载
实际上 vant
的按需加载完全可以按照 element-plus
那样照葫芦画瓢来做,但这里示范官网比较推荐的方式来演示。
需要注意的是:vant
同样不支持样式的按需引入;ok 先安装组件库。
pnpm add vant
再 plugins/vant.ts
中去引入全局的样式并且注册组件。
import { Row, Col, Icon, Image, Cell, Button } from 'vant';
import 'vant/lib/index.css';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp
.use(Row)
.use(Col)
.use(Image)
.use(Icon)
.use(Cell)
.use(Button);
});
最后由于 nuxt
框架本身的限制,还需要在 nuxt.config.ts
添加一些配置。问题溯源
export default defineNuxtConfig({
experimental: {
externalVue: true
}
});
在 plugins/vant.ts
中注册后的组件则不需要在像 ElementPlus
一样手动导入在使用,可以直接在项目中使用。
<template>
<VanButton type="primary" size="small">按钮</VanButton>
</template>
全量加载
全量的加载也仅仅只是在导入组件时的语法差异上有所体现。
import Vant from 'vant';
import 'vant/lib/index.css';
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.use(Vant);
});
viewport 移动端适配
安装 postcss-px-to-viewport
插件用来转换单位
pnpm install postcss-px-to-viewport -D
随后在 nuxt.config.ts
中增加如下配置后重启项目即可生效。
export default defineNuxtConfig({
postcss: {
plugins: {
'postcss-px-to-viewport': {
viewportWidth: 375
}
}
}
});
插件的配置信息显然不止这么多,查看更多配置请移步 文档
启动项目后在终端中可能会出现 WARN
⚠️ postcss-px-to-viewport: postcss.plugin was deprecated. Migration guide: https://evilmartians.com/chronicles/postcss-8-plugin-migration
postcss-px-to-viewport
这个插件配不上 v8
版本的 postcss
,换个插件即可。
pnpm install postcss-px-to-viewport-8-plugin
在使用的层面与之前完全一致包括它的配置项。
rem 移动端适配
rem
布局需要依赖两个插件配合 postcss-pxtorem
负责将像素转换为 rem
。lib-flexible
则是用来设置基准值。
pnpm install postcss postcss-pxtorem amfe-flexible -D
在 nuxt.config.ts
中设置插件内容
export default defineNuxtConfig({
postcss: {
plugins: {
'postcss-pxtorem': {
rootValue: 37.5,
propList: ['*']
}
}
}
});
最后在 plugins/vant.ts
中引入 lib-flexible
设置基准值。注意该库的执行时机
import Vant from 'vant';
import 'vant/lib/index.css';
// 保证下面的代码在客户端中执行。
if (process.client) {
import('amfe-flexible');
}
// ....
插件的配置信息显然不止这么多,查看更多配置请移步 文档
对 lib-flexible
有兴趣可以查看 文档
如果你的设计图不是 375 而是其他的大小可以参考 vant
官方配置
个人见解:从
lib-flexible
的文档中来看,目前来说这已经不算是一个完美的方案了,比较推荐实用viewport
方式做移动端开发。
自动引入
安装自动引入插件 unplugin-vue-components
。
pnpm isntall unplugin-vue-components -D
nuxt.config.ts
中增加如下配置。
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';
export default defineNuxtConfig({
vite: {
plugins: [
Components({
resolvers: [VantResolver({ importStyle: 'css' })]
})
],
ssr: {
noExternal: ['vant']
}
},
experimental: {
externalVue: true
}
});
此时如果你使用 rem
的适配方案就会出现样式的问题。还记得在 plugins/vant.ts
中导入的 lib-flexible
文件吗?改为自动导入后,实在没有合适的时机去导入这个文件,你当然可以使用 cdn
的方式去配置,比如下面这样,但我不是很推荐。
export default defineNuxtConfig({
app: {
head: {
script: [
{ src: 'http://amfe-flexible' }
]
}
}
});
也许此时可以弃用 rem
的适配方案,使用 viewport
方案就不会遇到以上问题,这也是目前比较推荐的方式。
当然自动引入也会存在一些不方便的地方,比如 Toast
Dialog
Notify
ImagePreview
这些组件的样式需要你自行去导入。文档
antd 集成
按需加载
安装组件库和图标插件。
pnpm install ant-design-vue @ant-design/icons-vue
在 plugins/antd.ts
中注册组件。注意:antd
的 icon
并不是那么的纯粹,所以不能像 ElementPlus
那样批量的注册
import { Button, Col, Row } from 'ant-design-vue';
import { MessageOutlined , MessageFilled } from '@ant-design/icons-vue';
import 'ant-design-vue/dist/antd.css';
const iconMap = { MessageOutlined, MessageFilled };
export default defineNuxtPlugin((nuxtApp) => {
// 注册组件
nuxtApp.vueApp
.use(Button)
.use(Col)
.use(Row);
// 注册 Icon 组件
for (let key in iconMap) {
nuxtApp.vueApp.component(key, (iconMap as any)[key]);
}
});
或者你也可以像 ElementPlus
那样在 .vue
文件中那样导入并且直接使用它,不过并不是很推荐那种方式。
随后在 index.vue
中使用组件。
<template>
<div class="index-page">
<MessageOutlined />
<AButton type="primary" @click="showMessage">按钮</AButton>
</div>
</template>
<script setup lang='ts'>
// 类似于 message 这种方法还是需要手动的导入。
import { message } from 'ant-design-vue'
const showMessage = () => {
message.success({ content: 'success' });
}
</script>
全量加载
只需要修改导入的方式即可完成全量的加载。
import antd from 'ant-design-vue';
// ....
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(antd);
// ....
});
自动导入
安装插件 unplugin-vue-components
Vue
组件的按需自动导入
pnpm install unplugin-vue-components -D
然后在 nuxt.config.ts
中增加如下配置,注意的是像 message
这种组件的样式以及需要自行导入。
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
export default defineNuxtConfig({
// message 组件的样式文件
css: ['ant-design-vue/es/message/style/css'],
vite: {
plugins: [
Components({
resolvers: [AntDesignVueResolver({resolveIcons: true})]
})
],
ssr: {
noExternal: ['moment', 'compute-scroll-into-view', 'ant-design-vue','@ant-design/icons-vue'],
}
}
});
tailwindcss 集成
tailwind
的集成倒是非常简单,按照 文档 跟下来就可以了,不过既然是汇总还是简单记录下。
下载相关依赖包。
pnpm install -D @nuxtjs/tailwindcss tailwindcss@latest postcss@latest autoprefixer@latest
在 nuxt.config.ts
的 modules
中注册插件。
export default defineNuxtConfig({
modules: ['@nuxtjs/tailwindcss']
});
生成 tailwind.config.js
,并增加 purge
选项,在生产环境可以对未使用样式的文件进行 tree shaking
。
# 生成配置文件
npx tailwindcss init
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./components/**/*.{vue,js}',
'./layouts/**/*.vue',
'./pages/**/*.vue',
'./plugins/**/*.{js,ts}',
'./nuxt.config.{js,ts}',
],
theme: {
extend: {},
},
plugins: [],
}
最后在 assets/css/tailwind.css
中增加以下内容(没有该文件则手动创建)。
@tailwind base;
@tailwind components;
@tailwind utilities;
其实完成上面的一系列操作后,tailwind
在项目中已经可以使用了。但是 css
文件内可能会出现 Unknown at rule @tailwindcss(unknownAtRules)
这样的错误,其实原因也很简单就是 css
不认识该语法。
根目录下新增 .vscode/settings.json
并增加以下内容解决以上问题。这属实是 🤣 物理外挂了;
{
"css.lint.unknownAtRules": "ignore"
}
Pinia 集成
nuxt
推荐使用内部提供的 useState
方法来做 store
,这确实是一个好的办法,但对于一些企业级的应用,pinia
应该更加的适合,当然 pinia
也是 nuxt
官方比较推荐的仓库管理工具。
如果你想了解更多官方推荐插件 Nuxt Module。
nuxt
集成 pinia
需要两个包支持。
pnpm install @pinia/nuxt pinia
然后在 nuxt.config.ts
的 modules
中注册插件。更多的配置参考
export default defineNuxtConfig({
modules: ['@pinia/nuxt']
});
至此插件就已经可以使用了,但与普通的 vue
项目中使用方式略显不同,减少了 createPinia
的步骤。
创建 /store
目录并在 /store/counter.ts
中写入示例代码。
import { defineStore } from 'pinia';
export const useCounterStore = defineStore({
id: 'counter-store',
state: () => ({
count: 0
}),
getters: {
doubleCount: state => state.count * 2
},
actions: {
countPlusOne() {
this.count++
}
}
})
在文件中去使用导入并使用它。
<template>
<div class="index-page">
count: {{ store.count }}
<br />
doubleCount: {{ store.doubleCount }}
<AButton type="primary" @click="store.countPlusOne">PlusOne</AButton>
</div>
</template>
<script setup lang='ts'>
import { useCounterStore } from '~/store/count';
const store = useCounterStore();
</script>
持久化 store
集成
pinia
的持久化依赖于 pinia-plugin-persistedstate
。
pnpm install pinia-plugin-persistedstate
注册该插件的方式也有两种。分别为 客户端 和 ssr
两种方式。在开始之前可以先将 counterStore
中增加如下配置。更多配置参考
export const useCounterStore = defineStore({
id: 'counter-store',
state: () => ({/** ... */}),
getters: {/** ... */},
actions: {/** ... */},
// 确认该仓库使用持久化
persist: true
})
客户端持久化:plugins
下创建 persistedstate.client.ts
并输入以下内容。
import { createPersistedState } from 'pinia-plugin-persistedstate';
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.$pinia.use(createPersistedState());
});
如果按照示例代码去测试是否成功,可能就碰到下面的问题了。
对应的解决方案就是需要在使用到
store
数据的模板中用 ClientOnly
组件包裹一下,类似于这样。
<ClinetOnly>
<!-- .... -->
</ClientOnly>
ssr
持久化:plugins
下创建 persistedstate.ts
并输入以下内容。了解更多
import { createNuxtPersistedState } from 'pinia-plugin-persistedstate/nuxt';
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.$pinia.use(createNuxtPersistedState(
useCookie, {
cookieOptions: {
maxAge: 3600,
sameSite: 'strict',
}
}
));
});
异步数据获取方式
axios 暂不支持 👋🏻(并不代表不能用)。就目前(当前这个时间节点来看)无论是
@nuxtjs/axios
还是nuxt3
自身都推荐使用$fetch api
来获取服务端数据。但无论用哪种方式前端都绕不开一个话题。
解决跨域及官方推荐异步获取方式
跨域问题。网上找了一大堆的示例(说到这我忍不住吐槽一句做笔记之前能不能自测一下,全是教你怎么用代理,你自己试了吗?)代理这条路是行不通滴。这个问题的前世今生就看这个 issue
,这个也很有参考价值。
- 你用了
vite.server.proxy
的代理再使用useFetch
就会出现404
问题。 - 你不设置代理使用
useFetch
就会出现CORS
问题。
看官网示例中 useFetch
去请求 server/api
下的接口,这种肯定不会有跨域的情况出现,比如这样。
// server/api/calendar.ts
export default defineEventHandler(async () => {
const res = await fetch('http://v.juhe.cn/calendar/day?date=xxx&key=xxx');
const data = await res.json();
return data;
});
// pages/xxx.vue
const getServerData = async () => {
const { data } = await useFetch('api/calendar');
console.log(data.value);
}
但是如果你采用了这种方案就意味着你的代码量会增加很多,而且还有很大的优化空间。从 nuxt rc13
开始推荐使用 devProxy
,可以解决开发环境下的跨域问题。
export default defineNuxtConfig({
// https://github.com/nuxt/framework/issues/8303 具体参考
// 辅助参考以下 issues
// https://github.com/nuxt/framework/discussions/1223
// https://github.com/unjs/nitro/issues/113
// https://github.com/nuxt/framework/issues/6650
nitro: {
// 更多选项 https://github.com/http-party/node-http-proxy#options
devProxy: {
'/api': {
target: 'http://apis.juhe.cn',
changeOrigin: true
}
}
}
});
在使用时则可以这样编写请求代码。
const { data, refresh } = useFetch(
'/api/simpleWeather/query',
{
params: { city: '上海', key: 'xxx' }
}
);
关于 url
开头中的 /api/
暂时不确定能不能通过配置在生产环境去掉(后续做项目时可以试下),目前能够预想到的操作就是通过 DotENV
根据不同的环境来读取对应的 baseURL
进行异步请求。
现在需要做的其实就是将 useFetch
进行二次封装即可应用到生产环境去使用它。比如这样
type RequestOptions = {
method?: string;
body?: Record<string, unknown>;
pick?: string[];
params?: Record<string, unknown>;
};
function getBaseURL() {
const config = useRuntimeConfig();
return process.dev ? config.API_URL : '';
}
export const useApi = async <Result = unknown>(endpoint: string, opts?: RequestOptions) => {
const baseURL = getBaseURL();
const headers = useRequestHeaders(['cookie']);
return useFetch<string, Result>(endpoint, {
method: opts?.method,
body: opts?.body,
baseURL,
headers,
params: opts?.params
});
};
axios 获取数据方式
很无奈 axios
并没有像 nuxt2
一样拥有官方推荐的包。但是仍然不妨碍你去使用它,只是会相对麻烦一些。
<template>
<div class="home">
<VanButton type="primary" @click="getAxiosData">axios</VanButton>
</div>
</template>
<script setup lang="ts">
import axios from 'axios';
const key = 'xxx';
const getAxiosData = async () => {
const { data } = await axios.get('/api/simpleWeather/query', { params: { key, city: '上海' } });
console.log(data);
};
</script>
如果你真的喜欢使用 axios
用来作为你获取异步数据的主要工具,你应该封装一下。参考
目前来看我心中理想的方式就是这样,在 utils
中封装之后在 composables
中将它变成 hook
最后应用到页面中。(当然思想可能不成熟,欢迎指正)
国际化 i18n
集成
目前社区主要有两种集成的方案 ① i18n 官网集成 ② nuxt 推荐模块集成 其中各有千秋。但前者缺少一些特性的支持,目前来看我还是更希望使用后者。
入门示例
安装指定的 npm
包
pnpm add @nuxtjs/i18n@next --save
nuxt.config.ts
中添加如下配置
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
vueI18n: {
// 使用 compositionApi
legacy: false,
// 默认的语言
locale: 'zhCN',
// 国际化信息
messages: {
zhCN: {
welcome: '欢迎'
},
enUS: {
welcome: 'welcome'
}
}
}
},
});
结合前面安装的组件库,在模板中去使用它
<template>
<div class="home">
<VanSwitch v-model="locale" active-value="zhCN" inactive-value="enUS"></VanSwitch>
<div style="width: 100%; height: 200px; border: 1px dashed red; box-sizing: border-box; margin-top: 10px">
{{ $t('welcome') }}
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
const { locale, t } = useI18n();
watch(
() => locale.value,
(val: string) => {
console.log(t('welcome'));
}
);
</script>
使用此种方式表面看没有任何问题,但如果你需要做国际化的 SEO
可能会有些困难。如果你确实有这个需求,可以去了解一些更高级的内容,比如 URL
路由。
国际化本地化
上面的方式集成后,虽然能够切换语言,但当查看网页源码时并没有除默认语言之外其他语言的内容。i18n
可以使用路由的功能使国际化更好的支持 SEO
;此时其他语言就会以 举例: /en
的下展示
nuxt.config.ts
中新增如下配置
export default defineNuxtConfig({
locales: [
'zn-CN', 'en-US'
],
defaultLocale: 'zh-CN',
});
切换语言的方式随之也要进行改变。
<template>
<div>
<NuxtLink :to="switchLocalePath('zh-CN')">中文</NuxtLink>
<NuxtLink :to="switchLocalePath('en-US')">英文</NuxtLink>
<div>{{ $t('welcome') }}</div>
</div>
</template>
<script setup lang="ts">
const switchLocalePath = useSwitchLocalePath();
</script>
再次提示:如果你需要深度学习可以去 官网
代码及git规范
git
提交规范的问题,推荐使用 commitizen
和 husky
去做这件事情,如果你没有任何头绪可以参考 基于Vue3 + TS + Vite代码提交及代码风格规范
eslint
集成
eslint
推荐使用官网提供方式。nuxt eslint 配置 或者你也可以持续关注这个 issue
它可能给你带来不一样的集成方式。
安装相关 npm
包
pnpm add eslint @nuxtjs/eslint-config-typescript -D
# 不能下载到 devDependencies 中,不然 eslint 无法工作
pnpm add typescript
项目根目录下新增 .eslintrc
文件配置规则。
{
"env": {
"node": true,
"browser": true
},
"extends": [
"@nuxtjs/eslint-config-typescript"
],
// 新的规则可以配置在这里。
"rules": {}
}
并修改 vscode
的配置,就可以实现保存自动 format code
目前来看 nuxt
是不推荐使用 prettier
对代码进行美化,同样不喜欢的还有另一位大佬 antfu
,具体原因推荐查看 antfu博客:为什么我不用Prettier
"prettier.enable": false,
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll": false,
"source.fixAll.eslint": true
},
或者我们也可以集成其他预设,@antfu/eslint-config 还是比较友好的,毕竟跟随大佬脚步。修改 .eslintrc
{
"env": {...},
"extends": [
"@nuxtjs/eslint-config-typescript",
"@antfu"
]
...
}
stylelint
集成
以 scss
为例,假如你使用 less
也可以作为参考,不过推荐看 vben
项目作为参考。
安装相关包
pnpm install stylelint postcss-html postcss-scss stylelint-config-recommended-vue stylelint-config-standard-scss stylelint-config-recess-order -D
stylelint-config-standard-scss
:scss
版本的基础配置;stylelint-config-recommended-vue
:有关stylelint-config-recommended-vue
的配置;stylelint-config-recess-order
:css
属性排序插件;stylelint postcss-html postcss-scss
:基准包及依赖包。
在项目根目录新建 stylelint.config.js
输入以下内容;更多配置参考stylelint
中文
module.exports = {
root: true,
extends: [
'stylelint-config-standard-scss',
'stylelint-config-recommended-vue',
'stylelint-config-recess-order',
],
customSyntax: 'postcss-html',
overrides: [
{
files: ['**/*.{css,scss,vue}'],
customSyntax: 'postcss-scss',
},
],
rules: {
'at-rule-no-unknown': null,
'scss/at-rule-no-unknown': [true,
{
ignoreAtRules: [
'tailwind',
'apply',
'variants',
'responsive',
'screen',
'function',
'if',
'each',
'include',
'mixin',
],
},
],
'selector-pseudo-class-no-unknown': [true,
{ ignorePseudoClasses: ['v-deep'] },
],
},
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
}
安装 vscode
插件 stylelint
。然后在项目中 .vscode/settings.json
中增加如下配置。配合上面 eslint
的相关配置即可实现 **/*.{css,scss,vue}
文件中样式的保存格式化。
{
"css.lint.unknownAtRules": "ignore",
"scss.lint.unknownAtRules": "ignore",
"stylelint.validate": ["css","scss",],
}
当然你也可以把配置直接放在配置文件中,但是为了团队的协同,个人还是推荐放到项目的根目录下,但后面又引发的一个问题,我不确定把 .vscode/
放到仓库中是否是比较正确的选择。
转载自:https://juejin.cn/post/7189535112340439101