2023 最新最细 vite+vue3+ts 多页面项目架构,建议收藏备用!

02-27 1792阅读

🌻 前言

本文教程 github地址 、码云。 如果对你有帮助,希望能点个star ⭐️⭐️⭐️ 万分感谢😊😊😊

🧱 背景

不久前我司需要重新部署一个前端项目,由我来负责这个项目的搭建。因为这个项目是需要和app混合开发的h5页面,包括以后可能会做一些运营h5,所以自然不能采用常规的SPA单页面应用架构(因为大部分页面耦合度低,全都塞一个项目里的话,即使打开个纯静态页都要跑起来整个项目,严重影响页面的加载速度).

另外,我们其实已经有混合开发h5项目,采用的gulp工作流,即每个页面都是单独的一个html文件。这种架构的优势很明显,就是体量小,结构清晰等,但是它的缺点也非常明显,例如有些轮子无法使用,组件化、模块化开发非常不便影响开发效率等。

目前此项目已在平稳运行,最近有空闲时间记录一下我搭建项目的过程,同时也希望能帮助到有这方面需求的掘友😊

💡 思路 **

综合考虑,我决定用vite + vue3 + ts + esint + prettier + stylelint + husky + lint-stage + commitlint 搭建一套多页面项目。

1. 初步定了几个目标:

  • 🍀 支持打包指定子页面,打包后的文件夹:各页面相互独立(各子页面解耦,避免相互影响)
  • 🍀 支持启动指定子页面(常规的多页面项目,启动后需要手动拼接页面地址,或者在根目录做一个重定向的页面,总之调试非常不便)
  • 🍀 支持指令化新建页面(手动创建页面太麻烦,每次都得复制一份干净的文件夹)
  • 🍀 自由选择创建ts页面 / js页面(对于一些重要的页面可以使用ts提高规范性,一些简单的页面则使用js提高开发效率)

    2. 本文将从以下几个方面逐步讲解:

    • 项目目录结构
    • 新建项目
    • 安装依赖及一些基础插件
    • vite配置项修改
    • ts配置
    • 多页面入口配置
    • 多页面打包配置
    • 指令化新建子页面(*重点)
    • 多页面架构改造(*重点)
    • 完善项目架构

      🌈 教程

      一、 项目目录结构

      ├── README.md 
      ├── .husky   //git hook钩子
      │   ├── commit-msg //规范 commit message 信息
      │   └── verify-commit-msg.mjs  //脚本:commitlint 替代方案
      ├── dist //打包输出目录
      ├── scripts //存放一些脚本
      │   ├── template         //创建子页面的js模版
      │   ├── template-ts      //创建子页面的ts模版
      │   ├── index.mjs        //创建子页面的脚本
      │   └── multiPages.json  //子页面描述说明集合文件
      ├── src 
      │   ├── arrets       //公共静态资源
      │   ├── components   //公共组件
      │   ├── store        //pinia 共享状态存储库
      │   ├── utils        //公共方法
      │   └── Projects     //多页面文件夹
      ├── types  //ts 声明文件
      ├── .env.development   //开发环境-环境变量
      ├── .env.production    //生产环境-环境变量
      ├── .eslintrc.cjs      //eslint 配置
      ├── .gitignore         //git 提交忽略文件
      ├── .prettierignore    //prettier 忽略文件
      ├── .prettierrc.js     //prettier 配置
      ├── .stylelintignore   //stylelint 忽略文件
      ├── .stylelintrc.js    //stylelint 配置
      ├── .pnpm-lock.yaml    //锁定项目于一份各个依赖稳定的版本信息
      ├── .stats.html        //chunck size 分析页面
      ├── tsconfig.json      //ts 配置
      ├── tsconfig.node.json //vite在node环境中的 ts 规则
      ├── vite.config.ts     //vite 配置
      ├── package.json
      

      二、 新建项目

      首先我们用命令行新建一个vite项目,不要使用模板创建,就创建一个基础模板就行,注意创建过程选择 vue3+typescript。创建命令如下:

      # npm 6.x
      npm create vite\@latest vue3-mpa
      # npm 7+, extra double-dash is needed:
      npm create vite\@latest  vue3-mpa
      # yarn
      yarn create vite  vue3-mpa
      # pnpm
      pnpm create vite  vue3-mpa
      

      三、 安装依赖及一些基础插件

      新建项目后记得 npm i 安装依赖。然后我们先装一些基础的插件,例如vue-router等,方便后面调试,这里可能没装全,毕竟是返工,有些东西忘记了,大家根据报错提示自行安装即可。

      //安装vue-router4
      npm install vue-router\@next -S
      //安装 sass
      npm install sass -D
      //安装 chalk(chalk是一个颜色的插件。可以通过chalk.blue(‘hello world’)来改变console打印的颜色)
      npm install chalk@^4.1.2
      //处理使用 node 模块代码飘红,例如 ‘找不到模块 “path“ 或其相对应的类型声明’
      npm install @types/node --save-dev
      

      四、 vite配置项修改

      对vite.config.ts进行调整,先做一些基础的配置,后面我们调通项目之后再丰富项目插件。

      🍀 配置下启动端口,热更新

      server: {
          host: 'localhost', // 指定服务器主机名
          port: 8880, // 指定服务器端口
          hmr: true,  // 开启热更新
          open: true, // 在服务器启动时自动在浏览器中打开应用程序
          https: false // 是否开启 https
      }
      

      🍀 配置文件路径的别名,方便书写文件引入路径。

      resolve: {
          alias: {
            '@': path.join(__dirname, './src'),
            '@Project': path.join(__dirname, './src/Project')
          }
      }
      

      五、 ts配置

      项目根目录下找到 tsconfig.json 文件,它是是用来配置 TS 编译选项的。

      这一步可以晚一点配置,但是避免后面操作过程中的一些报错,可以提前配置好,以下是我使用的配置项,基本对每一项都做了解释。

      {
      "compilerOptions": {
      "target": "esnext", //用于指定 TS 最后编译出来的 ES 版本
      "types": ["vite/client"],  //要包含的类型声明文件名列表
      "useDefineForClassFields": true,  //将 class 声明中的字段语义从 [[Set]] 变更到 [[Define]]
      "module": "esnext",  // 设置编译后代码使用的模块化系统:commonjs | UMD | AMD | ES2020 | ESNext | System
      "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
      "strict": true,  //开启所有的严格检查
      "jsx": "preserve", //在 `.tsx`文件里支持JSX: `"React"`或 `"Preserve"`
      "sourceMap": false, // 生成目标文件的sourceMap文件
      "resolveJsonModule": true, //允许导入扩展名为“.json”的模块
      "isolatedModules": true,  //确保每个文件都可以在不依赖其他导入的情况下安全地进行传输
      "esModuleInterop": true,  //支持导入 CommonJs 模块
      "lib": ["esnext", "dom", "ES2015"], //TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
      // "noLib": false, //不包含默认的库文件( lib.d.ts)
      "skipLibCheck": true, //忽略所有的声明文件( *.d.ts)的类型检查
      "allowJs": true, // 允许编译器编译JS,JSX文件
      "noEmit": true, // 不输出文件,即编译后不会生成任何js文件
      "allowSyntheticDefaultImports": true, //允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查。默认值:module === "system" 或设置了 --esModuleInterop 且 module 不为 es2015 / esnext
      "baseUrl": "./",   解析非相对模块的基地址,默认是当前目录
      "paths": {  
        "@/*": ["src/*"], //解决引入报错  找不到模块“@/xxxx” 或其相应的类型声明
        "@Project": ["src/Project/*"]
      }
      },
      "include": [
      "scripts/**/*.ts",
      "src/**/*.ts",
      "src/**/*.js",
      "src/**/*.d.ts",
      "src/**/*.tsx",
      "src/**/*.vue",
      "scripts/index.mts",
      "scripts/template-ts/router/routes.ts",
      "scripts/template-ts/router/index.ts",
      "scripts/template-ts/main.ts",
      "src/env.d.ts",
      "src/global.d.ts"
      ],
      "exclude": ["vite.config.ts"],
      "references": [{ "path": "./tsconfig.node.json" }] //每个引用的`path`属性都可以指向到包含`tsconfig.json`文件的目录,或者直接指向到配置文件本身(名字是任意的)
      }
      

      六、 多页面入口配置

      vite 和 webpack 配置多页面的方式不同。vite 使用的是 rollup 的打包方式。

      1. 基本配置

      想要将项目改造成多页面项目,我们可以自定义底层的 Rollup 打包配置,只需要指定多个 .html 文件作为入口点即可,此设置在build.rollupOptions.input 配置项下。

      首先我们现在 Project 文件夹下新建两个子页面 pageone、pagetwo ,目录结构如下:

      2023 最新最细 vite+vue3+ts 多页面项目架构,建议收藏备用!

      然后在 vite.config.ts 文件中指定这两个子页面的入口。

      build: {
      rollupOptions: {  //自定义底层的 Rollup 打包配置
        input: {
          project1: resolve(__dirname, 'src/Project/pageone/index.html'),
          project2: resolve(__dirname, 'src/Project/pagetwo/index.html')
        }
      }
      }
      

      这里需要注意的是:

      __dirname 占位符指的是 vite.config.js 文件所在的目录,即使修改了项目的根目录,它的值也不会变(后面我们将会修改项目的根目录)。

      2. 动态生成多页面入口

      因为我们要不断新建子页面,不可能每个子页面都手动去配置入口,所以可以获取到/Progect文件夹下文件名后,动态配置多页面入口。

      fs 模块是 Node.js 官方提供的、用来操作文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求,这里我们用到了fs.readdirSync 方法。

      fs.readdirSync 方法同步返回一个包含“指定目录下所有文件的名称”的数组对象。

      import fs from "fs";
      function getEntryPath () {
          const map = {} //最后生成的多页面配置项
          const PAGE_PATH = path.resolve(\__dirname, './src/Project')  //指定要查询的目录
          const entryFiles = fs.readdirSync(PAGE_PATH)   //获取到指定目录下的所有文件名
          entryFiles.forEach(filePath => {   //遍历处理每个子页面的入口
              map[filePath] = path.resolve(__dirname,
              `src/Project/${filePath}/index.html`
              )
          })
            return map
      }
      // 自定义底层的 Rollup 打包配置
      rollupOptions: {
        input: getEntryPath()
      }
      

      3. 配置重定向页面

      配置完多页面入口后,我们可以启动项目看看效果。npm run dev 启动项目:

      2023 最新最细 vite+vue3+ts 多页面项目架构,建议收藏备用!

      你会发现什么都没有,因为此时项目跟路径还是‘/’,找不到可以作为入口的index.html文件,这时我们只能手动拼接上地址/src/Project/pageone/,进入子项目的 index.html 文件

      2023 最新最细 vite+vue3+ts 多页面项目架构,建议收藏备用!

      每次启动项目的时候都手动拼接地址未免也太麻烦了,太沙壁了😅。ok,那我们在项目根目录下写一个重定向的页面,专门用来启动项目后跳转到指定的子项目,代码如下:

      注意: 到目前为止我们没有修改过项目根目录,仍然在 vite.config.ts 文件同级文件夹下。

      
      
      
      重定向
      
      
      

      子页面1

      子页面2

      2023 最新最细 vite+vue3+ts 多页面项目架构,建议收藏备用!

      七、 多页面打包配置

      build 选项指定多个入口之后,就可以进行多页面打包了。我们执行 npm run build 看看打包生成的 dist 文件夹结构。

      2023 最新最细 vite+vue3+ts 多页面项目架构,建议收藏备用!

      你会发现所有静态资源全都打包到了assets文件夹下,我们可以修改 rollupOptions 选项中的 output 选项修改输出文件夹的格式,如下:

      build: {
      rollupOptions: {
        input: 指定多页面入口,
        output: {
          assetFileNames: '[ext]/[name]-[hash].[ext]', //静态文件输出的文件夹名称
          chunkFileNames: 'js/[name]-[hash].js',  //chunk包输出的文件夹名称
          entryFileNames: 'js/[name]-[hash].js',  //入口文件输出的文件夹名称
        }
      }
      },
      

      占位符说明 >>>

      • [extname] :文件扩展名,包括前面的 . ,例如 .css;
      • [ext] :文件扩展名,不包括前面的 . ,例如 css;
      • [name] :文件名;
      • [hash] :基于文件内容生成的哈希值,可以通过[hash:10]设置特定的哈希长度;

        八、 指令化新建子页面(重点来了~)

        到这里其实已经改造出来了一个多页面项目脚手架。但是离我们都目标还相差甚远:

        • ☘️ 不能指令化创建页面
        • ☘️ 不能单独启动指定的子项目
        • ☘️ 打包后所有子项目的静态文件都混淆在一起

          那么怎么解决这些问题呢? 我们逐个剖析:

          先来解决指令化新建页面的问题,既然要创建页面,就是要和文件打交道,所以我们还是要使用到 fs 文件系统模块。先来了解它的一下几个方法,我们之后会用到(这里只是大概对方法简单说明,具体使用自行查询):

          🔥 fs.mkdirSync( path, options ) 方法用于同步创建目录,创建子页面主要就是使用这个方法。

          🔥 fs.readFile( filename, encoding, callback_function ) 方法用于异步读取指定文件中的内容。

          🔥 fs.writeFile( file, data, options, callback ) 方法用于异步读取指定文件中的内容。

          🔥 fs.existsSync( path ) 方法用于同步检测目录是否存在;

          🔥 fs.readdirSync( path, options ) 方法用于同步读取给定目录的内容。该方法返回一个数组,其中包含目录中的所有文件名或对象。

          🔥 fs.copyFileSync( src, dest, mode ) 方法用于将文件从源路径同步复制到目标路径。

          了解这些以后,我们捋一下创建文件的步骤:

          1. package.json 文件中添加以下指令,以执行创建子页面的脚本;
          "scripts": {
              ...
              "new:page": "node ./scripts/index.mjs"
          }
          
          1. 然后开发写创建子页面的脚本,第一步要先提示用户输入要创建的页面名称和描述,并验证输入的格式;
          //./scripts/index.mjs
          const log = (message) => console.log(chalk.green(`${message}`))
          const successLog = (message) => console.log(chalk.blue(`${message}`))
          const errorLog = (error) => console.log(chalk.red(`${error}`))
          log('请输入要生成的"页面名称:页面描述"、会生成在 /src/Project 目录下')
          
          1. 使用 fs.existsSync 方法验证是否已存在同名页面;
          //process.stdin属性是流程模块的内置应用程序编程接口,用于侦听用户输入,它使用on()函数来监听事件。
          process.stdin.on('data', async (chunk) => {
          // 获取输入的信息
          const content = String(chunk).trim().toString()
          const inputSearch = content.search(':')
          if (inputSearch == -1) {
              errorLog('格式错误,请重新输入')
              return
          }
          // 拆分用户输入的名称和描述
          const inputName = content.split(':')[0]
          const inputDesc = content.split(':')[1] || inputName
          const isTs = process.env.npm_config_ts
          successLog(`将在 /src/Project 目录下创建 ${inputName} 文件夹`)
          const targetPath = resolve('./src/Project', inputName)
          // 判断同名文件夹是否存在
          const pageExists = fs.existsSync(targetPath)
          if (pageExists) {
              errorLog('页面已经存在,请重新输入')
              return
          }
          })
          
          1. 在同级 script 文件夹下,新建 multiPages.json 用于记录目前已有的页面名称,每次新建页面都会写入进去;
          // 获取multiPages.json文件内容,获取当前已有的页面集合
          await fs.readFile(
          path.resolve('./scripts', 'multiPages.json'),
          'utf-8',
          (err, data) => {
            //获取老数据
            let datas = JSON.parse(data)
            //和老数据去重
            let index = datas.findIndex((ele) => {
              return ele.chunk == inputName
            })
            if (index == -1) {
              //写入新页面的
              let obj = {
                chunk: inputName,
                chunkName: inputDesc
              }
              datas.push(obj)
              setFile(datas)
            }
          }
          )
          // 写入multiPages.json
          function setFile(datas) {
          // 通过writeFile改变数据内容
          fs.writeFile(
            path.resolve('./scripts', 'multiPages.json'),
            JSON.stringify(datas),
            'utf-8',
            (err) => {
              if (err) throw err
              // 在project中建立新的目录
              fs.mkdirSync(targetPath)
              const sourcePath = resolve(
                isTs ? './scripts/template-ts' : './scripts/template'
              )
              copyFile(sourcePath, targetPath)
              process.stdin.emit('end')
            }
          )
          }
          ...
          process.stdin.on('end', () => {
          console.log('exit')
          process.exit()
          })
          
          1. 将新页面的信息写入 multiPages.json 文件后,在 Project 文件夹下复制我们提前创建好的模板页面。我这里创建了两个模版页面,分别是js和ts的模版,如果需要创建支持 TS 的子页面,创建命令为 npm run new:page --ts
          // 判断文件夹是否存在,不存在创建一个
          const isExist = (path) => {
              if (!fs.existsSync(path)) {
                  fs.mkdirSync(path)
              }
          }
          //递归复制模版文件夹内的文件
          const copyFile = (sourcePath, targetPath) => {
          const sourceFile = fs.readdirSync(sourcePath, { withFileTypes: true })
          sourceFile.forEach((file) => {
              const newSourcePath = path.resolve(sourcePath, file.name)
              const newTargetPath = path.resolve(targetPath, file.name)
              //isDirectory() 判断这个文件是否是文件夹,是就继续递归复制其内容
                  if (file.isDirectory()) {
                    isExist(newTargetPath)
                    copyFile(newSourcePath, newTargetPath)
                  } else {
                    fs.copyFileSync(newSourcePath, newTargetPath)
                  }
              })
          }
          

          九、 多页面架构改造

          到这里可以做如下思考:

          🤔 如何支持单独启动指定的子页面?

          指定子页面可以在执行 npm run dev 命令时配置自定义环境变量。格式为 npm run dev --变量名=值 ,例如我要单独启动 pageone 子页面: npm run dev --page=pageone。关于npm环境变量的使用可以查看传送门

          如何启动后直接进入这个子页面呢?这时候我们就需要修改项目的根路径了:

          注意:将根路径修改到指定子页面目录下后,就只能单独启动/打包 指定的子页面了,无法打包全部的子页面。这么做有以下优缺点:

          优点:打包后的子页面相互独立,可以直接启动指定的子页面;

          缺点:无法一次性打包全部页面,需要配置重定向页面方便调试

          所以你可以根据需要决定是否要修改项目根目录,因为我考虑到页面是逐步新增的,需要一次性打包全部页面的情况很少,而且我认为打包后的子页面相互独立非常重要,避免出现一些意料之外的问题影响多个页面,所以我果断选择了每次都单独打包页面。

          这里我把两种情况的处理都列出来:

          情况1:支持单页面打包和打包全部页面

          处理方式:

          1. 多页面入口配置\

          使用说明: npm run build打包全部页面; npm run build --page=页面名称 打包单页面;

          //vite.config.ts
          // 引入多页面配置文件
          const project = require('./scripts/multiPages.json')
          // 获取npm run dev后缀 配置的环境变量
          const npm_config_page:string =  process.env.npm_config_page || ''
          let filterProjects = []
          if (npm_config_page) {
          	//如果指定了单页面打包,过滤出这个页面的配置项
          	filterProjects = project.filter((ele) => {
          	  return ele.chunk.toLowerCase() === npm_config_page.toLowerCase()
          	})
          	console.log(`--------单独构建:${filterProjects[0]['chunkName']}--------`)
          	} else {
          	filterProjects = project
          }
          //多页面入口
          const getEnterPages = (p) => {
          	const pages = {}
          	p.forEach((ele) => {
          	const htmlUrl = path.resolve(
          	  __dirname,
          	  `src/Project/${ele.chunk}/index.html`
          	)
          	pages[ele.chunk] = htmlUrl
          	})
          	return pages
          }
          //入口配置
          build: {
          rollupOptions: {
            input: getEnterPages(filterProjects),
            ...
          
          1. 项目根路径修改为 root: './src/Project/' ,不修改其实也能实现,但是不改的话打包后dist文件夹层级太深;

          2. 修改打包的输出路径为:

          build: {
            outDir: path.resolve(__dirname, `dist`), // 指定输出路径
          ...
          

          打包后的dist目录结构:

          2023 最新最细 vite+vue3+ts 多页面项目架构,建议收藏备用!

          4. 修改 `envDir` 配置项,修改到项目根的路径,用于加载`.env` 环境变量文件
          envDir: path.resolve(__dirname)
          
          1. 在自定义的 root 根路径下(这里是 src/Project 文件夹下)创建重定向页面,方便启动后调试。

          情况2:只支持单页面打包

          1. 多页面入口配置\

          使用说明: npm run build --page=页面名称 单独打包指定页面。

          //vite.config.ts
          import chalk from 'chalk'
          // 引入多页面配置文件
          const project = require('./scripts/multiPages.json')
          // 获取npm run dev后缀 配置的环境变量
          const npm_config_page:string =  process.env.npm_config_page || ''
          // 命令行报错提示
          const errorLog = (error) => console.log(chalk.red(`${error}`))
          //获取指定的单页面入口
          const getEnterPages = () => {
              if (!npm_config_page) errorLog('-------------------请在命令行后以 `--page=页面名称` 格式指定页面名称!-------------------')
              const filterArr = project.filter(item => item.chunk.toLowerCase() == npm_config_page.toLowerCase())
              if (!filterArr.length)
              errorLog('-------------------不存在此页面,请检查页面名称!-------------------')
              
              return {
                  [npm_config_page] : path.resolve(
                    __dirname,
                    `src/Project/${npm_config_page}/index.html`
                  )
              }
          }
          //入口配置
          build: {
              rollupOptions: {
                input: getEnterPages(),
                ...
          
          1. 项目根路径修改到用户输入的单页面文件夹下
          root: path.resolve(__dirname, `./src/Project/${npm_config_page}`)
          
          1. 修改打包的输出路径为:
          build: {
            outDir: path.resolve(__dirname, `dist/${ npm_config_page }`) // 指定输出路径
          ...
          

          打包后的dist目录结构:

          2023 最新最细 vite+vue3+ts 多页面项目架构,建议收藏备用!

          1. 修改 envDir 配置项,修改到项目根的路径,用于加载.env 环境变量文件
          envDir: path.resolve(__dirname)
          

          到这里,多页面架构已经基本改造完成了🎉🎉🎉

          🤔 再做一些打包的优化:

          build: {
              sourcemap: false, // 这个生产环境一定要关闭,不然打包的产物会很大
              assetsInlineLimit: 4096, //小于此阈值的导入或引用资源将内联为 base64 编码,以避免额外的 http 请求
              emptyOutDir: true, //Vite 会在构建时清空该目录
              rollupOptions: {
                input: ...
                output: {
                  ...
                  compact: true,  //压缩代码,删除换行符等
                  manualChunks: (id: string) => {  //配置分包
                    if(id.includes("node_modules")) {
                      return id.toString().split('node_modules/')[1].split('/')[0].toString(); // 拆分多个vendors
                    }
                  }
                }
              }
          ...
          

          十、 完善项目架构

          1. pnpm 替代 npm 管理依赖包

          pnpm 是一款磁盘空间高效的软件包管理器。 如果你还在用 npm 管理依赖包,我建议你不妨试试pnpm,因为他是真的香,速度杠杠的😆

          详细介绍这里就不做展开了,官网传送门https://pnpm.io/zh/motivation

          //全局安装 pnpm
          npm install -g pnpm
          //切换淘宝源
          pnpm config set registry https://registry.npmmirror.com/
          //删除node_modules文件夹,执行 pnpm i
          

          2. 引入eslint + stylelint + prettier 规范代码风格和质量

          eslint 是代码检查工具(lint工具),我们用它来避免低级错误和统一代码的风格,以保证代码质量;

          stylelint 为css的lint工具。可格式化css代码,检查css语法错误与不合理的写法,指定css书写顺序等;

          prettier 是代码格式化工具,我们用它来统一代码格式,风格。

          🍀 首先先来安装 eslint ,依次执行以下命令:

          // 安装 eslint
          pnpm add eslint -D
          // eslint初始化
          pnpm eslint --init
          

          初始化配置项如下:

          2023 最新最细 vite+vue3+ts 多页面项目架构,建议收藏备用!

          eslint初始化之后,会生成.eslintrc.cjs配置文件。刚初始化完的时候你会发现有个 module 报错了,很简单,只要在 env 配置项下新增选项 "node": true 即可。

          2023 最新最细 vite+vue3+ts 多页面项目架构,建议收藏备用!

          // --ext 为指定lint哪些后缀的文件    --fix 开启自动修复
          "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix"
          

          此时打开.eslintrc.cjs配置文件会出现一个报错,需要再env字段中增加node: true配置以解决eslint找不到module的报错。

          注意: .eslintrc.cjs 配置文件名需以.cjs结尾,如果以 .js 结尾,需要删除package.json 文件中的字段"type": "module"。prettier 同理,因为我们要用到 chalk 插件,所以统一采用 .cjs 文件。

          具体eslint规则根据自己的习惯进行配置,这里也贴下我的粗糙配置:

          module.exports = {
          "env": {
              "browser": true,
              "es2021": true,
              "node": true
          },
          "extends": [
              "eslint:recommended",
              "plugin:vue/vue3-essential",
              "plugin:@typescript-eslint/recommended"
          ],
          "overrides": [
          ],
          "parser": "vue-eslint-parser",
          "parserOptions": {
              "ecmaVersion": "latest",
              "sourceType": "module",
              "parser": "@typescript-eslint/parser"
          },
          "plugins": [
              "vue",
              "@typescript-eslint",
              "prettier"
          ],
          "rules": {
            '@typescript-eslint/no-var-requires': 0, //解决报错:Require statement not part of import statement.
            'vue/multi-word-component-names': 'off' //关闭组件命名规则娇艳
          },
          "root": true
          }
          

          🍀 再来安装stylelint

          pnpm add stylelint stylelint-order stylelint-config-standard  stylelint-config-prettier -D
          

          依赖说明:

          • stylelint:检验工具
          • stylelint-order:css样式书写顺序插件
          • stylelint-config-standard:stylelint的推荐配置
          • stylelint-config-prettier:关闭所有不必要的或可能与 Prettier 冲突的规则

            创建 .stylelintrc.js 配置文件:

            module.exports = {
            // 注册 stylelint 的 prettier 插件
            plugins: ['stylelint-prettier'],
            // 继承一系列规则集合
            extends: [
            'stylelint-config-standard-scss',
            'stylelint-config-html/vue',
            'stylelint-config-recommended-vue/scss',
            'stylelint-config-recess-order',
            // 接入 Prettier 规则
            'stylelint-config-prettier',
            'stylelint-prettier/recommended'
            ],
            // 配置 rules
            rules: {
            // 开启 Prettier 自动格式化功能
            'prettier/prettier': true
            }
            }
            

            及忽略文件 .stylelintignore

            node_modules/*
            /dist/
            

            最后配置命令

            {
            "script":{
                "lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/"
            }
            }
            

            🍀 最后安装prettier

            pnpm add prettier -D
            

            安装好之后在项目根目录创建 .prettierrc.cjs 配置文件,这里也贴下我的配置,大家自己根据需要调整规则。

            //.prettierrc.cjs
            module.exports = {
            printWidth: 80, //一行的字符数,如果超过会进行换行,默认为80
            singleAttributePerLine: false, //每行强制换行,只能使用单个属性
            tabWidth: 2, // 一个 tab 代表几个空格数,默认为 2 个
            useTabs: false, //是否使用 tab 进行缩进,默认为false,表示用空格进行缩减
            singleQuote: true, // 字符串是否使用单引号,默认为 false,使用双引号
            semi: false, // 行尾是否使用分号,默认为true
            trailingComma: 'none', // 是否使用尾逗号
            bracketSpacing: true, // 对象大括号直接是否有空格,默认为 true,效果:{ a: 1 }
            endOfLine: 'auto',
            jsxBracketSameLine: false // 在jsx中把'>' 是否单独放一行
            }
            

            同时创建 .prettierignore 为Prettier忽略指定文件

            .DS_Store
            node_modules
            dist
            dist-ssr
            **/*.svg
            **/*.sh
            

            eslint 和 prettier 的配置难免会有冲突,目前比较成熟的方案是使用以下两个插件:

            • eslint-plugin-prettier: 基于 prettier 代码风格的 eslint 规则,即eslint使用pretter规则来格式化代码。

            • eslint-config-prettier: 禁用所有与格式相关的 eslint 规则,解决 prettier 与 eslint 规则冲突,确保将其放在 extends 队列最后,这样它将覆盖其他配置

            • pnpm add eslint-config-prettier eslint-plugin-prettier -D
              

              安装完成后在 .eslintrc.cjs 文件中extends的最后添加一个配置

              {

              extends: [

              ‘eslint:recommended’,

              ‘plugin:vue/vue3-essential’,

              ‘plugin:@typescript-eslint/recommended’,

              // 新增,必须放在最后面

              ‘plugin:prettier/recommended’

              ]

              }

              3. 引入husky + lint-staged + Commitlint 规范 git 工作流程

              在团队协作开发时,为了避免开发人员代码格式和风格不同导致的冲突,我们最好遵循husky+Commitlint+lint-staged git工作流规范,这样在本地提交代码前,能够强制让开发人员进行统一的代码规范化处理,避免造成冲突和一些潜在问题。

              husky 是常见的git hook工具,使用husky可以挂载Git钩子,当我们本地进行git commit或git push等操作前,能够执行其它一些操作,比如进行ESLint检查,如果不通过,就不允许commit或push。

              pnpm add husky -D
              # 执行以下命令会生成 .husky 文件
              npx husky install
              // 配合lint-staged使用
              npx husky add .husky/pre-commit "npx lint-staged"
              

              Lint-staged 可以在git staged阶段(即git add 后的暂存阶段)的文件上执行Linters,简单说就是当我们运行ESlint或Stylelint命令时,可以通过设置指定只检查我们通过git add添加到暂存区的文件,可以避免我们每次检查都把整个项目的代码都检查一遍,从而提高效率。

              pnpm add husky lint-staged -D
              

              在 package.json 文件中配置lint-staged

              "lint-staged": {
                  "src/**/*.{js,ts,vue}": [
                    "pnpm run lint:script",
                    "pnpm run lint:prettierrc",
                    "git add ."
                  ]
              }
              

              Commitlint 是用来规范git提交代码时 Commit Message 格式的工具。结合husky一起使用,可以在开发者进行commit前就对Commit Message进行检查,只有符合规范,才能够进行commit。

              安装Commitlint

              pnpm add @commitlint/cli @commitlint/config-conventional
              

              Commitlint配合 husky 使用:执行下方命令,会在 .husky 目录下生成 commit-msg 文件。

              npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1'
              

              然后在根目录创建 commitlint.config.js 文件进行配置。

              module.exports = {
              extends: ['@commitlint/config-conventional'],
              rules: {
              'type-case': [2, 'always', ['lower-case', 'upper-case']],
              'type-enum': [
                2,
                'always',
                [
                  'feat', //新的特性
                  'fix', //修复Bug
                  'docs',  //添加或更新文档
                  'style', //代码格式的更改
                  'refactor', //代码进行重构
                  'perf', //提升性能
                  'test', //添加或更新测试用例
                  'chore',  //更改配置文件
                  'revert',  //版本回退
                  'merge',  //分支合并
                  'build',  //打包工具的更改
                  'release',  //发布/版本标签
                  'ci'  //对CI配置和脚本的更改
                ]
              ]
              }
              }
              

              🌈 脚本替代Commitlint方案

              我这里为了在输入 commit msg 时有比较清晰的报错提示,所以决定采用脚本代替 Commitlint ,当然,你也可以采用 Commitlint 的 prompt (命令行提示工具) ,并在package.json 文件中添加 commit 命令来规范 git 提交操作(但是我认为这么整太麻烦了,降低开发效率)。

              脚本的思路无非就是定义一个 commit type 的字典集,然后比对输入的类型,不符合要求则用node插件boxen(控制台输出盒子)进行规范化提示。

              注意要将 husky 的 commit-msg 钩子修改为我们的脚本地址

              //修改husky文件夹下的 commit-msg 文件
              #!/usr/bin/env sh
              . "$(dirname -- "$0")/_/husky.sh"
              node .husky/verify-commit-msg.mjs 
              

              在mac上如果报错[hint](https://so.csdn.net/so/search?q=hint&spm=1001.2101.3001.7020): The '.husky/pre-commit' hook was ignored because it's not set as executable.,因为指定的文件是不可执行的,只需要执行以下命令即可解决:

              chmod 777 ./husky/*
              
              //安装 boxen插件
              pnpm add boxen -D
              
              import fs from 'fs'
              import chalk from 'chalk'
              //设置chalk等级 解决颜色无效问题
              chalk.level = 1
              // 引入node控制台输出盒子插件
              import boxen from 'boxen'
              // 挂载boxen
              console.boxen = (text) => {
              const options = {
              margin: { top: 1, bottom: 1 },
              padding: { left: 1, right: 1 },
              borderColor: 'yellow',
              borderStyle: 'classic',
              title: 'Vite-demo'
              }
              console.log(`\n${boxen(text, options)}`)
              }
              const TYPE_MAP = new Map([
              ['feat', { emoji: '✨', title: 'feat', description: '新的特性' }],
              ['fix', { emoji: '🐛', title: 'fix', description: '修复Bug' }],
              ['merge', { emoji: '🔀', title: 'merge', description: '分支合并' }],
              ['style', { emoji: '🎨', title: 'style', description: '代码格式的更改' }],
              ['perf', { emoji: '🚀', title: 'perf', description: '提升性能' }],
              ['test', { emoji: '✅', title: 'test', description: '添加或更新测试用例' }],
              ['revert', { emoji: '⏪️', title: 'revert', description: '版本回退' }],
              ['build', { emoji: '📦', title: 'build', description: '打包工具的更改' }],
              ['chore', { emoji: '🔧', title: 'chore', description: '更改配置文件' }],
              ['ci', { emoji: '👷', title: 'ci', description: '对CI配置和脚本的更改' }],
              ['refactor', { emoji: '💻', title: 'refactor', description: '代码进行重构' }],
              ['docs', { emoji: '📝', title: 'docs', description: '添加或更新文档' }],
              ['release', { emoji: '🔖', title: 'release', description: '发布/版本标签' }]
              ])
              // commit regexp
              const commitRE = new RegExp(
              `^(${[...TYPE_MAP.values()]
              .map(({ title, emoji }) => `${title}|${emoji} ${title}`)
              .join('|')})(\\(.+\\))?: .{1,100}`
              )
              try {
              const msgPath = process.argv.slice(2, 3)[0]
              const msg = fs.readFileSync(msgPath, 'utf-8').replace(/\n#.*/g, '').trim()
              if (/Merge.+branch \'.+\'/.test(msg)) {
              fs.writeFileSync(msgPath, `🔀 ${msg.replace('Merge', 'merge:')}`)
              process.exit(0)
              }
              if (msg.length > 100) {
              throw `commit信息内容不得超出100个字符串长度`
              }
              if (commitRE.test(msg)) {
              // 添加emoji
              for (const [key, { emoji }] of TYPE_MAP) {
                if (msg.startsWith(key)) {
                  fs.writeFileSync(msgPath, `${emoji} ${msg}`)
                  break
                }
              }
              } else {
              // show error feedback
              console.log(
                `${chalk.hex('#fbb957')('✨feat: 新的特性')}\n${chalk.hex('#41ae3c')(
                  '🐛fix: 修复Bug'
                )}\n${chalk.hex('#51c4d3')('🔀merge: 分支合并')}\n${chalk.hex('#813c85')(
                  '🎨style: 代码格式的更改'
                )}\n${chalk.hex('#ef475d')('🚀perf: 提升性能')}\n${chalk.hex('#40a070')(
                  '✅test: 提升性能添加或更新测试用例'
                )}\n${chalk.hex('#63bbd0')('⏪️revert: 版本回退')}\n${chalk.hex(
                  '#f8df70'
                )('📦build: 打包工具的更改')}\n${chalk.hex('#158bb8')(
                  '🔧chore: 更改配置文件'
                )}\n${chalk.hex('#f9d367')('👷ci: 对CI配置和脚本的更改')}\n${chalk.hex(
                  '#f86b1d'
                )('💻refactor: 代码进行重构')}\n${chalk.hex('#d2568c')(
                  '📝docs: 添加或更新文档'
                )}\n${chalk.hex('#f9d367')('🔖release: 发布/版本标签')}`
              )
              console.boxen(
                `commit信息不符合规范\n正确示例: "feat: 新增xxx功能..."\n正确示例: "feat(moduleName): 新增xxx功能..." `
              )
              // process quit
              process.exit(1)
              }
              } catch (err) {
              console.error(err)
              console.boxen('commit 提交异常')
              process.exit(1)
              }
              

              4. 配置 .env 环境变量

              因为我们修改了项目的根目录,所以在配置环境变量前,一定要记得在 vite.config.ts 中修改 envDIr 到相对于项目根的路径:

              envDir: path.resolve(__dirname)
              

              然后就可以在根目录创建 .env.development 和 .env.production 文件来分别定义开发环境和生产环境的环境变量了。

              需要注意的是: 只有以 VITE_ 为前缀的变量才会生效。例如我们设置开发环境的接口基础路径:

              VITE_BASE_URL = https://api-test.xxx.com/
              

              5. 安装一些插件

              • unplugin-vue-components 【组件自动按需导入】

                这里以 vant 为例,如下配置后,就不需要引入vant插件了,直接可以使用:

                pnpm add vant
                pnpm add unplugin-vue-components -D
                //vite.config.ts
                import Components from 'unplugin-vue-components/vite' //组件自动按需引入
                import { VantResolver } from 'unplugin-vue-components/resolvers';
                plugins: [
                Components({ resolvers: [VantResolver()] }) 
                ]
                
                • unplugin-auto-import/vite 【自动导入 Composition API】

                  配置这个插件后,就不需要引入例如 ref、reactive、useRouter 等 api 了。

                  为了避免这些 api undefined 报错,需要生成 auto-import.d.ts 说明文件,注意在项目根路路径生成。

                  pnpm add unplugin-auto-import/vite -D
                  import autoImport from 'unplugin-auto-import/vite' //自动导入 Composition API
                  //vite.config.ts
                  plugins: [
                     autoImport({
                        imports: [
                          'vue',
                          'vue-router',
                          'pinia'
                        ],
                        dts: path.resolve(__dirname,"types/auto-import.d.ts"),
                        eslintrc: {
                            // 已存在文件设置默认 false,需要更新时再打开,防止每次更新都重新生成
                            enabled: false,
                            // 生成文件地址和名称
                            filepath: path.resolve(__dirname,"./.eslintrc-auto-import.json"),
                            globalsPropValue: true,
                        }
                      })
                  ]
                  
                  • rollup-plugin-visualizer 【打包size分析工具】

                    打包后会在根目录下生成一个 stats.html 文件,用浏览器打开即可查看 chunk size 的结构。

                    pnpm add rollup-plugin-visualizer -D
                    import { visualizer } from 'rollup-plugin-visualizer' //打包size分析工具
                    //vite.config.ts
                    plugins: [
                    visualizer()
                    ]
                    
                    • vite-plugin-compression 【打包压缩工具 gzip / br 】

                      打包时使用 gzip 或 brotli 进行压缩资源,以减小打包体积:

                      pnpm add vite-plugin-compression -D
                      import compression from 'vite-plugin-compression' //gzip/br 压缩
                      //vite.config.ts
                      plugins: [
                      // gzip格式
                      compression({
                        threshold: 1024 * 500,   // 体积大于 threshold 才会被压缩,单位 b
                        ext: '.gz',   // 压缩文件格式
                        deleteOriginFile: false   // 是否删除源文件
                      }),
                      // br格式
                      // compression({
                      //   threshold: 1024 * 500,    // 体积大于 threshold 才会被压缩,单位 b
                      //   ext: '.br',
                      //   algorithm: 'brotliCompress',
                      //   deleteOriginFile: false
                      // })
                      ]
                      

                      ⏰ 写在最后

                      在搭建的过程中,一定要注意因为修改了项目根目录(root)所带来的问题,利用 path.resolve(__dirname, ...) 将路径重置到相对于项目根的位置!

                      关于如何使用这个多页面脚手架,可以看 README.md 文件。

                      github地址 、码云。 如果对你有帮助,希望能点个star ⭐️⭐️⭐️ 万分感谢😊😊😊

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]