vite+vue+ssg做官网 再记录一下项目创建

为什么选择插件vite-ssg + vite-plugin-pages创建官网?

1. CSR、 SSR、 SSG 和 ISR

适合高度动态的 Web 应用程序
客户端渲染 CSR. : 典型代表:单页面应用,内容都是js动态渲染
服务器端渲染 SSR : 在服务端获取数据组装页面,返回到浏览器是html,对服务器要求高,主要应用是交互多的页面需要seo的

适合高度静态的web程序
静态站点生成 SSG: 在build的的时候就已经生成好静态页面,放在服务端的也是静态页面,博客,静态官网都非常合适
增量静态再生 ISR:ISR 是 SSG 的下一个改进,它定期构建和重新验证新页面,以便内容永远不会过时

显而易见,做个官网ssg就非常合适,正好相关插件vite-ssg也有,配合多页面插件vite-plugin-pages来尝试一下

2. 创建项目步骤

第一步:三种任选其一执行

npm create vite@latest
yarn create vite
pnpm create vite

按照命令提示写项目名称,我选择vue-ts作为项目就可以生成项目了

第二步:进入项目,正常启动后准备安装
vite-ssg 需要vue-router 和 @vueuse/head
yarn add vite-plugin-pages vite-ssg vue-router @vueuse/head vite-plugin-md -D

vite-plugin-md 该插件支持使用makdown语法转换为vue
支持在vue文件中直接引入md文件组件,也可以在md文件中直接使用vue组件
需要配置

 ViteVue({
            include: [/\.vue$/, /\.md$/],
        })
第三步:修改配置内容

修改main.ts

import './style.css'
import App from './App.vue'
import { ViteSSG } from 'vite-ssg'
import routes from '~pages'

export const createApp = ViteSSG(
    App,
    { routes }
);

如果~pages有提示找不到模块或相应的类型声明,其实文档中已有说明
在vite-env.d.ts中加入声明

// vite-env.d.ts
/// <reference types="vite-plugin-pages/client" />

同理在tsconfig.json中加入也是一样的,两个地方选择一个加入就可以

"compilerOptions": {
    "types": [
      "vite-plugin-pages/client"
    ],
  },

修改build脚本

-   "build": "vite build"
+   "build": "vite-ssg build"

修改vite.config.ts

import { defineConfig } from "vite";
import ViteVue from "@vitejs/plugin-vue";
import VitePages from 'vite-plugin-pages';
import ViteMarkdown from 'vite-plugin-md';
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'

export default defineConfig({
    plugins: [
        ViteVue({
            include: [/\.vue$/, /\.md$/],
        }),

        VitePages({
            extensions: ['vue', 'md'],
            exclude: ['**/components/*.vue']
        }),

        ViteMarkdown(),
        AutoImport({
          imports: ['vue']
        }),
        Components({
            extensions: ['vue', 'md']
        }),
    ],
});

修改app.vue

<template>
  <router-view />
</template>
第四步:根据插件vite-plugin-pages的配置项来新建文件夹

主要看以下几个配置项

interface Options {
    /**
     * Paths to the directory to search for page components.
     * @default 'src/pages'
     */
    dirs: string | (string | PageOptions)[];
    /**
     * Valid file extensions for page components.
     * @default ['vue', 'js']
     */
    extensions: string[];
 /**
     * List of path globs to exclude when resolving pages.
     */
    exclude: string[];
 /**
     * Extend route records
     */
    extendRoute?: (route: any, parent: any | undefined) => any | void;
}

dirs 默认是src/pages, 这就是我们新建页面的地方。

extensions默认是['vue', 'js']src/pages中默认可以转换成页面的文件类型,这里我们将vue文件和md文件转换就可以了,配置为extensions: ['vue', 'md']

extendRoute通过这个配置可以为路由信息增加额外信息,自由发挥

exclude 可以排除某些vue文件不生成页面。比如我的页面中有组件: exclude: ['**/components/*.vue']

tip:文档中提到一个匹配所有404路由的方式:新建一个[...all].vue文件即可。
src/pages/[...all].vue -> /* (/non-existent-page)

第五步: 最后我们看一下目录结构和生成的dist文件夹
企业微信截图_81083726-a904-42a8-9da6-b39c5eb4705f.png

可以发现

  • 1.项目会默认打开src/pages/index.vue
  • 2.test文件夹中的components中的child不会生成页面,配置忽略了
    1. md文件也可以生成页面

3. 继续完善项目的其他配置

可以选择性观察本篇vite + vue3 多页面实战优化续集:eslint+lint-staged+husky+stylelint

官网有新的一行命令生成eslint: npm init @eslint/config可以替代 npx eslint --init,
同样的一步步往下选择

上述文章中安装eslint非常顺利,这次配置eslint出现了不少错误提示.可以发现与上次安装插件相比的eslint-config-standard 变成了eslint-config-standard-with-typescript

    "@typescript-eslint/eslint-plugin": "^5.44.0",
    "eslint": "^8.28.0",
    "eslint-config-standard-with-typescript": "^23.0.0",
    "eslint-plugin-import": "^2.26.0",
    "eslint-plugin-n": "^15.5.1",
    "eslint-plugin-promise": "^6.1.1",
    "eslint-plugin-vue": "^9.8.0",

有变化就要踩坑,从插件名称上看是主要针对ts的写法校验,这里记录一下这次出现的问题:

  • 问题一:Error: Error while loading rule '@typescript-eslint/dot-notation': You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.

starkoverflow有提到要在.eslintrc 中加入project

parserOptions: {
   project: ['./tsconfig.json'],
   extraFileExtensions: ['.vue']
 }
  • 问题二:The extension for the file (.vue) is non-standard. You should add parserOptions.extraFileExtensions to your config。 如上述配置加入extraFileExtensions: ['.vue']
  • 问题三:This rule requires the strictNullChecks compiler option to be turned on to function correctly @typescript-eslint/strict-boolean-expressions

可以在.eslintrc 的rules中设置为警告,并在tsconfig.json中开启"strictNullChecks": true,修改tsconfig.json记得重启项目生效 starkoverflow

// .eslintrc
rules: {
"@typescript-eslint/strict-boolean-expressions": "warn",
}
// tsconfig.json
  "compilerOptions": {
    "strictNullChecks": true
}

以前的null严格检查为了向下兼容都是默认关闭的,现在新建项目建议都开启null检查,可以避免很多空值导致的bug
如果你不想用它,也可以直接设为关闭:‘off’

关于问题二三我在eslint官网也看到了相关配置

{
    "root": true,
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": { "project": ["./tsconfig.json"] },
    "plugins": [
        "@typescript-eslint"
    ],
    "rules": {
        "@typescript-eslint/strict-boolean-expressions": [
            2,
            {
                "allowString" : false,
                "allowNumber" : false
            }
        ]
    },
    "ignorePatterns": ["src/**/*.test.ts", "src/frontend/generated/*"]
}
  • 问题四:Do not use a triple slash reference for vite/client, use import style instead @typescript-eslint/triple-slash-reference

不鼓励使用三斜线,在vite-env.d.ts中有vite/client类型引入。关掉这个规则吧

// .eslintrc
rules: {
'@typescript-eslint/triple-slash-reference': 'off'
}
  • 问题五:在.eslintrc.cjs中的module.exports飘红:However, that TSConfig does not include this file. Either:
  • Change ESLint's list of included files to not include this file
  • Change that TSConfig to include this file
  • Create a new TSConfig that includes this file and include it in your parserOptions.project
    提示使用了parserOptions.project.但是tsconfig.json不包含本文件

那就在tsconfig.json的include数组中加入.eslintrc.cjs.重启生效

"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue",".eslintrc.cjs"],
  • 问题六:
    image.png

加入parser解决,这里的@typescript-eslint/parser就不需要单独安装了。
eslint-config-standard-with-typescript文档中说了
This package has @typescript-eslint/parser in dependencies.

parserOptions: {
    parser: '@typescript-eslint/parser',
  }
  • 问题七: .vue文件可以格式化,但是.ts,.less文件不能正常格式化的

原因:我的vscode在设置中的用户开启了format on save,这是针对我的编辑器。
此时我还没在项目中新建针对工作区.vscode/settings.json文件。所以此时保存自动格式化的能力完全来源于用户中的format on save

解决:

  1. 为了多人开发保持项目的统一,我应该关闭我自己的用户中的format on save,关闭后任何文件保存就不会自动格式化了
  2. 新建.vscode/settings.json文件,加入以下配置就会修改每个人的工作区配置,达到项目配置的统一了
"editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
    "source.fixAll.tslint": true,
    "source.fixAll.stylelint": true
  }
  • 问题八: . 当保存文件时,你会发现单引号或者最后面的分号,保存的时候会消失又出现,并不能正确按照eslint的规则格式化.

直接修改工作区配置,在.vscode/settings.json中加入

关闭编辑器的格式化,按照eslint的规则来
{
  "editor.formatOnSave": false,
}

当然你也可以选择性加入使用prettier,我这里稍微写一下(以下prettier内容可以跳过)

这里使用prettier 集成到 eslint 的校验中
第一步新建json文件

echo {}> .prettierrc.json
// .prettierrc.json
{
    "semi": false,
    "singleQuote": true,
    "trailingComma": "none"
  }
  • eslint-config-prettier关闭可能与 prettier 冲突的规则
  • eslint-plugin-prettier 使用 prettier 代替 eslint 格式化

第二步安装


npm i prettier  eslint-config-prettier eslint-plugin-prettier -D

第三步:在 .eslintrc.cjs 三个地方 加入prettier.

module.exports = {
  extends: [
    'plugin:vue/vue3-essential',
    'standard-with-typescript',
    'prettier'
  ],
  plugins: [
    'vue',
    '@typescript-eslint',
    'prettier'
  ],
  rules: {
    'prettier/prettier': 'error',
  }
}

  • 问题八: 项目使用了unplugin-auto-import自动导入,但是eslint会检测Cannot find name ‘ref’

解决:

  • 先在vite.config.tsunplugin-auto-import/vite配置项中加入eslintrc.enabled 开启为true
  • npm run dev 运行项目,此时项目根目录会自动生成.eslintrc-auto-import.json文件
  • eslintrc.cjs 的extends加入该文件
  • 重启项目生效。生效后记得删掉eslintrc.enabled,避免重复生成
// eslintrc.cjs
extends: [
    'plugin:vue/vue3-essential',
    'standard-with-typescript',
    'prettier',
    './.eslintrc-auto-import.json'
  ]
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
     AutoImport({
      imports: ['vue', 'vue-router'],
      eslintrc: {
        enabled: true
     }
    }),
})
  • 问题九: .stylelintrc.js require() of ES modules is not supported. require() of /Users/ruios/web/wisbayar-website/.stylelintrc.js from /Users/ruios/web/wisbayar-website/node_modules/cosmiconfig/dist/loaders.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules. Instead rename .stylelintrc.js to end in .cjs, change the requiring code to use import(), or remove "type": "module"

vscode提示报错,less文件也没有正常校验。其实上面说的很明确了,本次新建的项目package.json中默认有"type": "module",这种情况下,我们平常新建的.stylelintrc.js或者.eslintrc.js 里面都是使用的module.exports,和定义"type": "module"完全使用es模块的import方式是不兼容的。

解决:将.stylelintrc.js改为.stylelintrc.cjs,用后缀名使其识别为commonjs方式导出,修改后less文件不规范的地方也被正常识别红色波浪了

2023年1月12新增: 在项目使用过程中发现新的配置问题

  • 问题十:在vite-ssg的github项目issue中问的最多的就是打包的时候报错:"window is not defined","document is not defined" .

打包是在服务器,是没有BOM浏览器相关的东西的。所以在使用window的时候,我们分两种情况来使用

  1. 初始化的时候,可以在ViteSSG第三个参数函数中的isClient来判断是否是浏览器中。npm vite-ssg还有详细的案例告诉我们vuex和initialState来保持数据持久的方式
export const createApp = ViteSSG(App, { routes }, ({ app, router, routes, isClient, initialState }) => {
//   app.provide('$wow', WOW)
  if (isClient) {
    window.onload = fontSize()
  }
})
  1. 当写在hook中需要使用window的时候,可以放在onBeforeMount,onMounted生命周期中
export default function () {
  const isMobile = ref(false)

  const resizeHandler = () => {
    if (!document.hidden) {
      const rect = document.body.getBoundingClientRect()
      isMobile.value = rect.width - 1 < 750
    }
  }
  onMounted(() => {
    resizeHandler()
    window.addEventListener('resize', resizeHandler)
  })
  onBeforeUnmount(() => {
    window.removeEventListener('resize', resizeHandler)
  })
  return { isMobile }
}


  1. 还提到了一个第三方插件报window错误的话,异步引入
export const install = ({ isClient, app }) => {
  if (isClient)
    import('vue3-scroll-spy').then(({ registerScrollSpy }) => {
      registerScrollSpy(app)
    })
}
// 你要说想在单个页面引入,那就在onMounted中
  onMounted(() => {
      import('vue3-scroll-spy').then(({ registerScrollSpy }) => {
      registerScrollSpy(app)
    })
  })
  • 问题十一:当我想在这个项目中使用animate.css 加wowjs实现滚动动画的时候。出现了些意外

先安装

npm i animate.css wowjs

main.ts引入

import 'animate.css/animate.min.css'. // 这是
import { WOW } from 'wowjs'

 new WOW({
  boxClass: 'wow',    //需要执行动画元素的Class
  animateClass: 'animated',    //animation.css动画的Class
  offset: 0,    //距离可视区域多少开始执行动画
  mobile: true,    //是否在移动设备执行动画
  live: true    //异步加载的内容是否有效
  }).init()

上面都是默认配置,直接使用默认配置
 new WOW().init()

!!注意
这里单独安装了animate.css,它的类名我们可以从官网复制。它和wow里面自己依赖的样式名不一样,缺少了前缀animate__

使用

<div class="wow animate__animated animate__bounce"></div>

滚动动画没生效,浏览器下面有错误WOW.js cannot detect dom mutations, please call .sync() after loading new content.
MutationObserver is not supported by your browser.
百度得知需要支持异步的内容,改成这样。提示是没有了,但是滚动动画还是无效。

new WOW({live: false}).init()

接下来,尝试在isClient中执行,或者import后执行,均无效无报错。
我在单个页面中单独引入wowjs,并在onMounted中初始化。来了个新的报错:Cannot set properties of undefined (setting 'getPropertyValue')

原因:查看源码得知,只是个很老的库了,直接导出的wow单例,里面用到的外部this,在vue3中肯定是没有的,自然在挂在一些方法的时候就报错了。

解决

  1. 到github找个wow压缩包放入public文件中,在index.html中引入吧。生效
 <script type="text/javascript" src="/wow.min.js"></script>
    <script type="text/javascript">
      new WOW().init()
    </script>
  1. 发现还有个wow.js,安装它。看源码了解到它写了UMD导出,可以使用。
npm i wow.js animate.css

使用。样式还是跟上面一样,使用animate.css中的样式

// main.ts
import 'animate.css/animate.min.css'
import WOW from 'wow.js'

new WOW().init()
  1. 其实wow.js这个库作者已经废弃,推荐使用aos。这个就更好了,再次向下滚动后到视野会再次触发动画

下面单独说一下aos 版本,npm介绍上还是第一版本
aos 有两个版本,第一版aos 有自己的少量动画,配置较少,且不能结合animate.css
使用第二版 aos@next,详细介绍到github仓库去看

安装
npm i aos@next animate.css

使用要用到它的配置项。这里我们还是使用animate.css,效果更多。就不需要引入aos的样式了

// main.ts
import 'animate.css/animate.min.css'
import AOS from 'aos'

AOS.init({
// 这个默认是关闭的,它会把`data-aos`上要写的动画样式名,自动加到class上,这样才会有第三方动画效果
  useClassNames: true,  
// 这个就是关闭掉初始化的样式名,默认值,也就是默认样式名是'aos-init'。这里选用第三方样式,就不要被它内置的干扰了,去掉它
  initClassName: null,
// 这里的默认值是'aos-animate',我们替换为animate.css的样式名
  animatedClassName: 'animate__animated'
})

在公共样式表做样式覆盖


[data-aos] {
  visibility: hidden;
}
[data-aos].animate__animated {
  visibility: visible;
}

最后使用起来

<div data-aos="animate__fadeInLeft"></div>

最后配置好的项目,我放到这里gitee,直接取用
最新vite + ssg 项目配置地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容