vue项目中使用workspaces

vue 和 yarn workspaces 结合搭建vue项目

背景

在一个项目中, 一般分用户端和管理端,又细分PC、H5,各个端很多东西是公用的, 比如配置文件,依赖库,工具库,而我们一般的做法就是搭建很多个项目,配置就拷贝,但这样很容易数据出错,拷贝漏掉了一些,还有就是把一些公用的组件封装好, 通过npm发布,但是这样还麻烦,在开发环境中会出现频繁更新,每次更新都要发布版本,还有到各个项目中升级版本

使用 yarn workspaces

yarn workspaces就是使用工作空间,专业的术语就叫 **monorepo(monolithic repository)**就是一种项目管理方式, 可以使用 yarn lerna pnpm来实现, 我们公司目前使用的是yarn, 目前很多项目都是使用这种项目管理方式, 像vue、react, babel这些的源码都是使用这种方式来管理的

react https://github1s.com/facebook/react

创建工作空间

要创建一个工作空间,我们只需在package.json里面添

  "private": true,
  "workspaces": [
    "packages/*"
  ],

要private为true时workspace才会被启用,workspaces属性的值为一个字符串数组,每一项指代一个workspace路径,支持全局匹配,这里的路径指向指的是package.json所在文件夹文件夹名。

我们工作空间的目录就是下面这个样子了

|--node_modules
|--packages
  |--workspace-home  # 工作空间workspace-home
     |--src 
     |--package.json #独有的依赖,我们就可以单独安装在这个里面
  |--workspace-admin
  	 |--src   #我们这里可以引用share模块的, 由于定义的名字叫 @project/share, 我们引入的时候就像这样 import { xxxx } from '@project/share'
     |--package.json 
  |--share
     |--package.json #在package.json里面要定义name属性,比如这个叫 @project/share
|--package.json  #公用的依赖,我们就可以安装在这个里面

注意, 工作空间中(packages)package.json我们要添加name属性,这就是工作空间的名字,而不是文件夹名

大概了解了这些知识,我们就可以在vue项目中使用这种方式,用户PC端的放在一个工作空间中,后端管理PC端放在另一个工作空间中,我们就可以把公用的配置文件,组件,工具库放到一个工作空间中,在另个两个工作空间中引用

工作空间命令

#能够共享的包就安装到根
#工作空间独立的就单独安装到工作区
#添加到根
yarn add cross-env -D -W
#删除根
yarn remove cross-env  -W

#如果想单独添加或者移除某个子项目的依赖,可以使用如下命令:
yarn workspace <workspace_name > add <pkg_name> --dev
yarn workspace <workspace_name > remove <pkg_name>

#注意: workspace_name workspace_name 包名,package.json 中设置的 name,不是文件夹名

#比如
yarn workspace @project/home add  swiper
yarn workspace @project/admin add  swiper

yarn workspace @project/home add react-custom-scrollbars

在vue项目中搭建

比如现在我们要搭建一个项目,总共有两个端,管理端和用户端,项目名叫 vue-workspace

创建项目

使用 vue create vue-workspace 创建一个项目,选择项目配置,搭建好的项目大致如下在这里插入图片描述

改造项目

新建一个 packages的目录,这个目录下方新建三个文件夹admin, home, share

把项目根目录的srcpublic,babel.config.js,分别拷贝到 packages/aminpackages/home文件下,分别在两个文件夹中创建package.json

目录结构

/vue-workspace
  |--packages
     |--admin 后台:管理端
     	|--public
        |--src
        |--babel.config.js
        |--package.json
     |--home 门户:用户端
        |--public
     	|--src
        |--babel.config.js
        |--package.json
     |--share 放公用的代码
     	|--index.js
        |--package.json
  |--package.json

把根目录的 @vue/cli-service 依赖和scripts拷贝过来,定义工作空间的名字

为什么要拷贝 @vue/cli-service,这样我们就能直接在 packages/admin文件夹下执行npm run dev这些命令,而不会出现vue-cli-service不存在的报错

amin/package.json 注意我多添加了一个dev的命令, 习惯npm run dev运行

{
  "name": "@project/admin",
  "version": "0.1.0",
  "scripts": {
    "serve": "vue-cli-service serve",
    "dev": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "devDependencies": {
    "@vue/cli-service": "~4.5.0"
  }
}

home/package.json 注意我多添加了一个dev的命令, 习惯npm run dev运行

{
  "name": "@project/home",
  "version": "0.1.0",
  "scripts": {
    "serve": "vue-cli-service serve",
    "dev": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "devDependencies": {
    "@vue/cli-service": "~4.5.0"
  }
}

share/package.json

{
  "name": "@project/share",
  "version": "0.1.0",
  "main": "index.js",
  "scripts": {
    "build": "echo 没有打包",
    "lint": "vue-cli-service lint"
  },
  "devDependencies": {
    "@vue/cli-service": "~4.5.0"
  }
}

修改项目根目录下的package.json, 添加workspaces,修改 scripts

 "private": true,
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    "lint": "yarn workspaces run lint",
    "build": "yarn workspaces run build",
    "dev:admin": "yarn --cwd packages/admin dev",
    "build:admin": "yarn --cwd packages/admin build",
    "dev:home": "yarn --cwd packages/home dev",
    "build:home": "yarn --cwd packages/home build"
  },

yarn --cwd 可以指定工作目录 yarn --cwd packages/admin dev 就是相当于在 packages/admin目录下执行 npm run dev

到这一步, 我们在根目录下执行安装依赖,这样工作空间@project/admin@project/home的依赖能安装上

yarn install

这样 yarn dev:admin就运行管理端, yarn dev:home运行用户端

大致目录结构如下, 这样在管理端admin中,就可以通过,就能很方便的修改了

// 引入公用样式
import "@project/share/styles/index.scss"

// 引入组件、工具库等
import { formatDate } from "@project/share"

在这里插入图片描述

项目链接 https://github.com/zf1832729975/vue-workspace

问题

如遇到公用的share没有解析正确,或者需要自己解析时,要配置vue.config.js

// vue.config.js
const path = require("path")

function resolve(dir) {
  return path.join(__dirname, dir)
}

/**
 * @type {import('@vue/cli-service').ProjectOptions}
 */
module.exports = {
  publicPath: process.env.NODE_ENV === "development" ? "/" : "./", // 部署应用包时的基本 URL
  assetsDir: "static",
  devServer: {},
  chainWebpack: (config) => {
    // 解析 share--如遇到 share 不能被解析时可以用
    // config.module.rule("js").include.add(resolve("../share")).add(resolve("src"))
    // config.module.rule("vue").include.add(resolve("../share")).add(resolve("src"))
  },
}

请求封装分享

import axios from "axios"
import { Message, Loading } from "element-ui"
import { getToken, blobSaveFile } from "@/utils"
import store from "@/store"
import router from "@/router"

import qs from "qs"

// 创建axios实例
// @ts-ignore
const service = axios.create({
  withCredentials: true,
  // baseURL: "/api-backstand",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
  },
  timeout: 30000, // 请求超时时间
})

service.interceptors.request.use(
  (config) => {
    const token = getToken()
    if (token) {
      config.headers.Authorization = "Bearer " + token
    }
    return config
  },
  (error) => Promise.reject(error)
)

service.interceptors.response.use(function responseHandler(response) {
  const remote = response.data
  // 文件下载
  if (response.config.responseType === "blob") {
    if (response.data.type === "application/json") {
      const fileReader = new FileReader()
      fileReader.readAsText(response.data)

      return new Promise((resolve, reject) => {
        fileReader.onload = function () {
          const resData = JSON.parse(this.result)
          Message.error("下载失败:" + resData.msg)
          return reject(resData)
        }
      })
    }
    // 获取文件名、如果生产环境获取不到,要让后端设置Access-Control-Expose-Headers https://www.jianshu.com/p/84d2f8b32842
    const disposition = response.headers["content-disposition"]
    if (disposition) response.fileName = decodeURIComponent(disposition.slice("attachment;filename=".length))
    return response
  }

  // /api-backstand 的要判断 code 是否为 0
  if (response.config.url.includes("/api-backstand") && remote.code != 0) {
    return errorHandler({ response })
  }

  return remote
}, errorHandler)

export function errorHandler(error) {
  let msg
  if (error.response) {
    if (!(msg = error.response.data.msg)) {
      switch (error.response.status) {
        case 400:
          msg = "请求错误(400)"
          break
        case 401:
          msg = "未授权,请重新登录(401)"
          break
        case 403:
          msg = "拒绝访问(403)"
          break
        case 404:
          msg = "请求路径未找到(404)"
          break
        case 408:
          msg = "请求超时(408)"
          break
        case 500:
          msg = "服务器错误(500)"
          break
        case 501:
          msg = "服务未实现(501)"
          break
        case 502:
          msg = "网关错误(502)"
          break
        case 503:
          msg = "服务不可用(503)"
          break
        case 504:
          msg = "网络超时(504)"
          break
        case 505:
          msg = "HTTP版本不受支持(505)"
          break
        default:
          msg = `连接出错(${error.response.status})!`
      }
    }
    if (error.response.status === 401) {
      store.commit("CLEAR_USER_INFO")
      router.push("/login")
    }
  } else {
    msg = "请求无响应"
  }

  Message.error(msg)

  error.message = msg
  return Promise.reject(error)
}

const http = {
  request(axiosConfig) {
    return service(axiosConfig)
  },
  get(url, params, config) {
    return service({
      ...config,
      url,
      method: "GET",
      params,
      paramsSerializer: (params) => {
        return qs.stringify(params, { indices: false })
      },
    })
  },
  /** post application/x-www-form-urlencoded */
  post(url, data, config) {
    return service({
      url,
      data,
      method: "POST",
      ...config,
      transformRequest: [
        (data) => {
          return qs.stringify(data, { indices: false })
        },
      ],
    })
  },
  /** post application/json */
  postJson(url, data, config) {
    return service({
      method: "POST",
      url: url,
      data: data,
      ...config,
      headers: {
        "Content-Type": "application/json;charset=UTF-8",
      },
    })
  },
  /** post multipart/form-data */
  postForm(url, data) {
    return service({
      method: "POST",
      url,
      data,
      headers: {
        "Content-Type": "multipart/form-data",
      },
    })
  },
  /** 下载文件 responseType: "blob"  */
  downloadFile(config) {
    // @ts-ignore
    return service({
      // 1s
      timeout: 60 * 1000,
      ...config,
      responseType: "blob",
    })
  },
  /** 下载文件并保存 */
  downloadFileSava(config, defaultFileName) {
    const loadingInstance = Loading.service({
      fullscreen: true,
      text: "下载中",
      background: "rgba(0, 0, 0, 0.08)",
    })

    return http.downloadFile(config).then(
      (res) => {
        loadingInstance.close()
        const { data } = res
        const fileName = res.fileName || defaultFileName || ""
        blobSaveFile(data, fileName)
        return res
      },
      (err) => {
        loadingInstance.close()
        return err
      }
    )
  },
}

export default http

完整代码: https://github.com/zf1832729975/vue-workspace

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值