背景

之前不是说了吗,公司预备升到 Vue3。后面又因为一些需求把项目结构改成了 qiankun 和 Monorepo,所以这篇文章便是一个建设模板的讲解。

老话说的好,打 BOSS 前要把 Buff 加满,不是,是工欲善其事,必先利其器。在其它小伙伴过来写代码之前需求把重复工作的模板搭好,开发效率才能更高。

所以咱们的方针便是,ESLintPrettierTypeScript之类的东西装备好,能够用指令行发动整个项目,能够用指令行新建微运用。能够让其他人零装备直接上手写代码。

开端

本项目默许包办理器运用 pnpm,IDE 运用 VSCode

pnpm create vite

Vue3,Vite,TypeScript,Monorepo,qiankun...... Buff叠满,BUG没有

TypeScript

看下依靠,只要 vue-tsc 不熟,查下文档。(留意下图红框)

Vue3,Vite,TypeScript,Monorepo,qiankun...... Buff叠满,BUG没有

看起来是一个查看 Vue SFC 文件 TS 类型的指令行东西,比方在打包或许在 merge 代码前查看 TS 类型是否正确,而在 dev 环境下运用 Volar 插件来验证。

这样看来,需求引荐装置 Volar 插件。

在根目录新建 .vscode 目录,增加

{
  "recommendations": ["johnsoncodehk.volar", "johnsoncodehk.vscode-typescript-vue-plugin"]
}

这样其他用 VSCode 翻开这个项目的人就会弹出引荐装置该插件。

插件的姓名能够在概况查看

Vue3,Vite,TypeScript,Monorepo,qiankun...... Buff叠满,BUG没有

这样开发时就有 TS 类型约束了。

Vue3,Vite,TypeScript,Monorepo,qiankun...... Buff叠满,BUG没有

并且装置这两个插件之后,也不需求本来的 *.vue 的类型声明晰。

ESLint

pnpm install eslint eslint-plugin-vue @typescript-eslint/eslint-plugin @typescript-eslint/parser -D
  • ESLint JavaScript 代码查看东西
  • eslint-plugin-vue Vue 官方的 ESLint 插件,包含 vue/* 的规矩集
  • @typescript-eslint/eslint-plugin TS 查看的 ESLint 插件,包含 @typescript-eslint/* 的规矩集
  • @typescript-eslint/parser 协助 ESLint 解析 TypeScript 语法

需求留意的 @typescript-eslint/parser@typescript-eslint/eslint-plugin 版本号必需共同

增加 .eslintrc.js.eslintignore 文件

pnpm install eslint-define-config -D

.eslintrc.js

// @ts-check
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
  root: true,
  rules: {
    // rules...
  },
});

这样 eslintrc 也有类型约束了

Vue3,Vite,TypeScript,Monorepo,qiankun...... Buff叠满,BUG没有

同样,VSCode 的 ESLint 插件也增加到引荐 JSON 里。下面不在提示了。

Prettier

VolarESLint 都有格式化功用,那为什么还要选择 Prettier 呢?

代码风格最不好办理的当地在哪里? 便是所有人都有自己的习惯,无法评论出一个让所有人都满意的计划

Prettier 的计划是 opinionated,便是给出最小的装备,要不你就用我的风格,要不你就别用。不容许随意改。(所以也不必吵了。。。)

Vue3,Vite,TypeScript,Monorepo,qiankun...... Buff叠满,BUG没有

留意代码风格语法查看的区别,代码风格是要不要加空格,分号,逗号等等。语法查看是是否声明变量,声明未运用等等。

pnpm install -D eslint-config-prettier eslint-plugin-prettier prettier
  • eslint-config-prettier 封闭和 Prettier 抵触的 ESLint 规矩
  • eslint-plugin-prettier 把 Prettier 规矩嵌入到 ESLint,下面代码的 prettier/prettier": "error" 修正 .eslintrc.js 装备
{
  "extends": [
    "some-other-config-you-use",
    "prettier"
  ],
  plugins: ["prettier"],
  rules: {
    "other-rules",
    "prettier/prettier": "error"
  }
}

在 VSCode 装置 Prettier 插件,把格式化方式改为 Prettire,测验格式化正常。

Vue3,Vite,TypeScript,Monorepo,qiankun...... Buff叠满,BUG没有

Stylelint

pnpm install --D stylelint stylelint-config-recommended-vue stylelint-config-prettier stylelint-config-recess-order postcss-html
  • Stylelint 强壮、先进的 CSS 代码查看器,能够查看 CSS 代码中的错误和风格
  • stylelint-config-recommended-vue Vue 的 Stylelint 的引荐规矩集
  • stylelint-config-prettier 封闭和 Prettier 抵触的规矩
  • stylelint-config-recess-order 对 CSS 属性进行排序
  • postcss-html 解析 HTML 或许类 HTML 的 PostCSS 语法,比方 PHP、 VueSFC 新建 .stylelintrc.json
{
  "extends": ["stylelint-config-recommended-vue", "stylelint-config-prettier", "stylelint-config-recess-order"]
}

需求留意几个问题

  1. 需求封闭 VSCode 自己的 css 验证,否则会有两个验证报错 settings.json
"css.validate": false,
"less.validate": false,
"scss.validate": false
  1. 假如 .stylelintrc 装备文件是用 .json 结束的,不要加注释。不是 JSON5,会导致拉取装备无法编译。
  2. stylelint-config-standard 默许规矩只对 css 生效,无法解析 Vue
  3. 假如运用预编译器,需求替换对应的规矩集 比方要运用scss
pnpm install stylelint-config-standard-scss -D

.stylelintrc.json 装备改为

{
  "extends": [
    "stylelint-config-standard-scss",
    "stylelint-config-recommended-vue/scss",
    "stylelint-config-prettier",
    "stylelint-config-recess-order"
  ]
}

Windi CSS

pnpm i -D vite-plugin-windicss windicss

然后,在你的 Vite 装备中增加插件:

vite.config.js

import WindiCSS from 'vite-plugin-windicss'
export default {
  plugins: [
    WindiCSS(),
  ],
}

最终,在你的 Vite 入口文件中导入virtual:windi.css

main.js

import 'virtual:windi.css'

装置 VSCode 插件,在 settings.json 里增加

  "editor.quickSuggestions": {
    "strings": true
  },

这样就会有代码提示悬停预览等等功用

Vue3,Vite,TypeScript,Monorepo,qiankun...... Buff叠满,BUG没有

windi.config.ts 中装备

import { defineConfig } from 'windicss/helpers'
export default defineConfig({
  extract: {
    include: [
      'src/**/*.{vue,jsx,tsx,svelte}',
      'shared/**/*.{vue,ts}',
    ],
  },
})

然后,在 VSCode 中 Ctrl+Shift+P , 运转指令:Windi CSS: Run & Open Analysis 能够翻开 windicss 的可视化分析。

生产依靠

演示就用 antd 了

pnpm install @ant-design/icons-vue ant-design-vue @vueuse/core pinia vue-router

业务相关代码,比方布局啊,大局主题啊不是这次的要害,就不详细写了。

monorepo

pnpm 运用 monorepo 十分简略,只需求加个文件 pnpm-workspace.yaml,然后新建一个packages 文件夹

prefer-workspace-packages: true
packages:
  - 'packages/**'

这样 packages 里便是独自的 repo,而各种装备和依靠能够共用大局的。

qiankun

为了让同享装备层级清晰,qiankun 主运用(基座)和子运用都放到 packages 中。

pnpm install -W qiankun

大局依靠需求加 -W

主运用几个需求改到的当地。

  1. 路由,需求把子运用注册路由加进来
// 获取微服务路由
const microRoutes = []
microApps.forEach(micro => {
  microRoutes.push({
    path: `${micro.activeRule}/:morePath*`,
    component: Layout
  })
})
// 创立路由实例
export const router: Router = createRouter({
  history: createWebHistory(),
  routes: microRoutes.concat([
    {
      path: "/",
      component: Layout,
      redirect: "/app1"
    }
  ])
})
  1. main.ts 入口处,增加注册信息 microApps.ts
type microApp = {
  name: string
  entry: string
  container: "#view-main"
  activeRule: string
  port: number | string
}
const apps: microApp[] = [
  {
    name: "app1",
    entry: "//localhost:10002",
    container: "#view-main",
    activeRule: "/app1",
    port: "10002"
  }
]
export default apps
import { start as startQianKun, registerMicroApps, initGlobalState, MicroAppStateActions } from "qiankun"
// 微运用的信息
import apps from "./microApps"
/**
 * 注册微运用
 * 第一个参数 - 微运用的注册信息
 * 第二个参数 - 大局生命周期钩子
 */
registerMicroApps(apps, {
  // qiankun 生命周期钩子 - 微运用加载前
  beforeLoad: (app: any) => {
    // 加载微运用前,加载进度条
    // eslint-disable-next-line no-console
    console.log("before load app.name====>>>>>", app.name)
    return Promise.resolve(app)
  },
  beforeMount: (app: any) => {
    // eslint-disable-next-line no-console
    console.log("[LifeCycle] before mount %c%s", "color: green;", app.name)
    return Promise.resolve(app)
  },
  // qiankun 生命周期钩子 - 微运用挂载后
  afterMount: (app: any) => {
    // 加载微运用前,进度条加载完结
    // eslint-disable-next-line no-console
    console.log("[LifeCycle] after mount %c%s", "color: green;", app.name)
    return Promise.resolve(app)
  }
})
/**
 * qiankun 通讯实例
 */
const initialState = {}
export const actions: MicroAppStateActions = initGlobalState(initialState)
// 注册 qiankun
startQianKun({ sandbox: { strictStyleIsolation: true } })
// qiankun 通讯
actions.onGlobalStateChange((state, prevState) => {
  // state: 改变后的状态; prevState: 改变前的状态
  // console.log(state, prevState)
})
  1. 布局处增加子运用挂载的入口
  <a-layout>
    <a-layout-sider v-model:collapsed="collapsed" :trigger="null" collapsible>
      <div class="logo" />
      <Menu></Menu>
    </a-layout-sider>
    <a-layout>
      <a-layout-header style="padding: 0; background: #fff">
        <menu-unfold-outlined v-if="collapsed" class="trigger" @click="() => (collapsed = !collapsed)" />
        <menu-fold-outlined v-else class="trigger" @click="() => (collapsed = !collapsed)" />
      </a-layout-header>
      <a-layout-content :style="{ margin: '24px 16px', padding: '24px', background: '#fff', minHeight: '280px' }">
        <!-- 子运用容器 -->
        <div id="view-main" />
        <!-- 主运用自己的路由烘托 -->
        <router-view></router-view>
      </a-layout-content>
    </a-layout>
  </a-layout>

子运用需求留意的位置

  1. package.jsonname 需求和主运用注册的 name 共同
  2. vite.config.tsserver.port 需求改为主运用注册的 entry 端口共同
  3. 子运用路由
export const router: Router = createRouter({
  history: createWebHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? `/${packageConfig.name}` : "/"),
})
  1. vite.config.ts 增加插件
import packageConfig from "./package.json"
import qiankun from "vite-plugin-qiankun"
const useDevMode = true // 假如是在主运用中加载子运用 vite, 有必要翻开这个, 否则vite加载不成功, 独自运转没影响
plugins: [
  qiankun(`${packageConfig.name}`, { useDevMode })
]
  1. main.ts 改为
import { createApp } from "vue"
import { createPinia } from "pinia"
import router from "./router"
import { renderWithQiankun, qiankunWindow, QiankunProps } from "vite-plugin-qiankun/dist/helper"
import { MicroAppStateActions } from "qiankun"
import Antd from "ant-design-vue"
import "ant-design-vue/dist/antd.variable.min.css"
import "virtual:windi.css" // windi Css
import App from "./App.vue"
let instance = null
type AppProps = Partial<MicroAppStateActions & QiankunProps>
function render(props: AppProps) {
  const { container } = props
  if (container) {
    // 注册主,微运用大局通讯
    props.onGlobalStateChange((state, prevState) => {
      // useTheme().theme({ state, prevState, container })
    })
  }
  instance = createApp(App)
  instance.use(router).use(createPinia).use(Antd)
  instance.mount(container ? container.querySelector("#app") : document.getElementById("app"))
  if (qiankunWindow.__POWERED_BY_QIANKUN__) {
    // console.log('我正在作为子运用运转')
  }
}
renderWithQiankun({
  /**
   * 运用每次进入都会调用 mount 办法,一般咱们在这里触发运用的烘托办法
   * @param props
   */
  mount(props: MicroAppStateActions & QiankunProps) {
    render(props)
  },
  /**
   * bootstrap 只会在微运用初始化的时分调用一次,下次微运用从头进入时会直接调用 mount 钩子
   */
  bootstrap() {
    // 一般咱们能够在这里做一些大局变量的初始化,比方不会在 unmount 阶段被销毁的运用级别的缓存等。
  },
  /**
   * 运用每次切出/卸载 会调用的办法,一般在这里咱们会卸载微运用的运用实例
   */
  unmount() {
    instance.unmount()
    instance._container.innerHTML = ""
    instance = null
  }
})
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
  render({})
}

在大局 package.json 增加发动指令

  "scripts": {
    "dev": "pnpm -r --filter ./packages --parallel run dev",
    "build": "pnpm -r --filter ./packages --parallel run build",
    "preview": "pnpm -r --filter ./packages --parallel run preview",
    "clean": "rm -rf node_modules **/*/node_modules"
  }

这样,整个项目就布置好了。

整合装备

但是,现在所有的装备仍是分散的,需求整合下。

ESLintPrettierrStylelint 用大局的一份装备就行。

packages.json 主子运用都有依靠放到大局,子运用独自用的放到自己文件内。

  • 主运用大局依靠 pnpm install xxxx -W 其他指令和原指令共同,多增加一个 -W
  • 子运用依靠装置 pnpm i xxxx --filter ${微运用pwd} 比方 pnpm uninstall uuid --filter app1 会在 app1 子运用下装置 uuid 依靠,当然也能够去子运用目录下直接装置

tsconfig.json 运用 extends 承继大局装备,其中 paths 会匹配到本运用。比方这个 @ 便是该子运用的 src,不是大局的。

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "common/*": ["../../common/*"]
    }
  },
  "exclude": ["dist", "**/*.js"]
}

vite.config.ts 运用 loadConfigFromFilemergeConfig,当然其实读取文件,手动合并也是能够的。

import { defineConfig, loadConfigFromFile, mergeConfig } from "vite"
import AutoImport from "unplugin-auto-import/vite"
import packageConfig from "./package.json"
import qiankun from "vite-plugin-qiankun"
import { resolve } from "path"
const pathResolve = (dir: string): string => {
  return resolve(__dirname, ".", dir)
}
const useDevMode = true // 假如是在主运用中加载子运用 vite, 有必要翻开这个, 否则vite加载不成功, 独自运转没影响
// https://vitejs.dev/config/
export default defineConfig(async ({ command, mode }) => {
  const base = (await loadConfigFromFile({ command, mode }, pathResolve("../../vite.config.ts"))).config
  base.plugins.shift()
  const config = {
    resolve: {
      alias: {
        "@": pathResolve("src"),
        common: pathResolve("../../common")
      }
    },
    server: { port: 10002 },
    plugins: [
      qiankun(`${packageConfig.name}`, { useDevMode }),
      AutoImport({
        // vue函数的主动导入
        imports: ["vue", "vue-router", "pinia", "@vueuse/core"],
        dts: false
      })
    ]
  }
  return mergeConfig(base, config)
})

windi.config.ts 运用 presets 承继大局装备。

import { defineConfig } from "windicss/helpers"
export default defineConfig({
  presets: [require("../../windi.config.ts").default]
})

运用指令行生成子运用

尽管创立子运用不是一个高频操作,但仍是有许多重复操作,并且,每个子运用的模板也不好统一。

所以咱们要规划一个指令行能够主动生成子运用。那么大约需求几步

  1. 依据 nameport 注册子运用
  2. 依据 namepackages 下新建项目,从 git 库房上拉一个模板下来,修正 nameport 之类的装备。
  3. pnpm install 并提示
pnpm i -W -D download-git-repo fs-extra inquirer
  • download-git-repo 从 git clone 库房到本地
  • fs-extra 一个扩展的 fs
  • inquirer 能够依据预设收集用户输入,这里用来承认子运用的 name, port

创立提示用户输入

  const apps = await readAppsFile()
  // console.log(apps)
  const meta = await inquirer.prompt([
    {
      type: "input",
      message: "请输入你要新建微运用姓名(英文或许数字):",
      name: "appName",
      validate(answer) {
        console.log(answer)
        const done = this.async()
        const validateRes = RegxMap.IS_APP_NAME.test(answer)
        if (!validateRes) {
          done("请按要求输入正确的微运用姓名!")
          return
        }
        if (apps.find(item => item.name === answer)) {
          done("已存在同名微运用,请承认后替换姓名再重试。")
          return
        }
        done(null, true)
      }
    },
    {
      type: "input",
      message: "请输入你要新建的微运用的端口(数字,引荐 10000 - 10010):",
      name: "port",
      validate(answer) {
        const done = this.async()
        const validateRes = RegxMap.IS_PORT.test(answer)
        if (!validateRes) {
          done("只能输入数字!")
          return
        }
        // eslint-disable-next-line eqeqeq
        if (apps.find(item => item.port == answer)) {
          done("已存在相同微运用端口,请承认后替换再重试。")
          return
        }
        done(null, true)
      }
    }
  ])
  return meta

用户输入成功之后,从 git 下载模板,正则匹配对应的要害字,替换为用户输入字段

module.exports = async meta => {
 await cloneRepo(meta)
 await replaceName(meta)
 const newApps = await writeAppsFile(meta)
 await writeMicroApps(newApps)
 const workerProcess = exec(`pnpm install`)
 workerProcess.stdout.on("data", data => {
   console.log("stdout: " + data)
 })
 workerProcess.stderr.on("data", data => {
   console.log("stderr: " + data)
 })
 console.log(`微服务创立成功,请在 pnpm install 完结之后,前往 packages/${meta.appName} 目录进行开发`)
}

下面是修正注册子运用信息的代码,其他修正大同小异

// 修正 modules/microApps.ts
const writeMicroApps = async newApps => {
  const reg = /const\sapps:\smicroApp\[\]\s=\s\[(.|\s)*export\sdefault\sapps/
  const filePath = `../../common/modules/microApps.ts`
  const microAppsFile = fs.readFileSync(resolve(__dirname, filePath), "utf-8")
  const microAppsContent = microAppsFile.replace(reg, `const apps: microApp[] = ${newApps} \r\n export default apps`)
  fs.writeFile(resolve(__dirname, filePath), microAppsContent, err => {
    if (err) console.log(err)
  })
}

我这里是用一个 JSON 记录了所有子运用信息,当然也能够用正则去匹配 fs 读取的注册信息

增加指令,测验一下。

"gen": "node ./scripts/genNewMicroApp/index.js"

Vue3,Vite,TypeScript,Monorepo,qiankun...... Buff叠满,BUG没有

Vue3,Vite,TypeScript,Monorepo,qiankun...... Buff叠满,BUG没有

因为 Menu 的逻辑没有写,手动改下路由看下。

Vue3,Vite,TypeScript,Monorepo,qiankun...... Buff叠满,BUG没有

其它问题

代码库房: github.com/zhl1232/qia…
模板库房:github.com/zhl1232/qia…

  1. 一些业务逻辑没有写,比方 Menu 逻辑,大局主题之类的
  2. 现在子运用模板只要 Vue
  3. 只要开发环境,没有布置装备
  4. 库房行尾是用的 linux 装备,windows 环境自行更改 Prettier 下的 endOfLine 等等吧,假如有需求的话,后面再补吧。

Vue3,Vite,TypeScript,Monorepo,qiankun...... Buff叠满,BUG没有

引荐

VSCode 开发的话,能够试下这个插件 GitHub Copilot

便是最近很火的那个 AI 写代码的。挺有意思的,一方面,简略的代码能够靠提示就行,另一方面,假如思路卡住了,他或许还真能给你一些提示。

Code Spell Checker
一个单词拼写查看。语法,风格都有查看的,但实际上单词拼错了也会影响项目质量。不信你装上看一下,基本每个页面都有拼错的单词。。。