【npm】从零到一基于Vite+vue3制作自己的Vue3项目基础的npm包并发布npm

目录

  • [1. 前言](#1. 前言)

1. 前言

在日常的前端开发过程中,Vue3 + Vite 已经成为我使用频率非常高的一套技术栈,但随着项目数量的增加,一个问题逐渐变得明显:每次使用vite创建新项目时,都要重复做大量相同的基础配置

比如:

  • 配置项目目录结构
  • 引入并初始化Vue-RouterPinia
  • 配置Element-PlusAxios
  • 设置路径别名、环境变量等
  • 封装通用工具方法

这些配置本身并不复杂,但每次新建项目都要从零开始重复操作,不仅浪费时间,也容易出现遗漏或配置不一致的问题

因此,我开始思考

👉能不能把一个已经配置好的Vue3项目,直接作为模板使用

👉下次新建项目时,一条命令就能生成一个符合自己配置且开箱即用的项目结构

基于这个需求,目前我能想到的有三种方式

👉通过git clone gitee/GitHub仓库地址

👉通过degit GitHub名/仓库名 my-project

👉通过npm create xxx my-project的方式自定义Vue3项目脚手架

本篇博客将完整记录从零搭建Vue3模板项目,到封装成各种工具的全过程,三种方式均会记录

我们先创建一个Vue3项目作为后续三种方法的基础

bash 复制代码
npm create vite@latest 
cd vue3-template
npm i

然后再这个项目里一次性把常用的东西全部配置好,我这里创建的项目是js版本的,各位根据自己的需求来,我自己制作了两个版本上传了npm

bash 复制代码
npm i element-plus @element-plus/icons-vue axios pinia pinia-plugin-persistedstate less tslib vue-router

上面直接安装了一些我常用的库

✅Element-Plus

✅Element-Plus的icon

✅Axios

✅less

✅Pinia

✅Pinia持久化插件

✅tslib

✅Vue Router

下面是我当前创建项目的目录结构

接下来我们详细查看每个目录中的文件内容以及作用

下面是vite.config.js文件的配置

js 复制代码
import vue from '@vitejs/plugin-vue'
import path from 'path'
import { defineConfig } from 'vite'

// https://vite.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {// 配置@别名
      '@': path.resolve(__dirname, './src'), // 输入@代表src文件夹
      '@components': path.resolve(__dirname, './src/components'),// 输入@components代表components文件夹
      '@views': path.resolve(__dirname, './src/views') // 输入@views代表views文件夹
    }
  },
  server: {
    host: '0.0.0.0',// 指定服务器应该监听哪个 IP 地址。 如果将此设置为 0.0.0.0 或者 true 将监听所有地址,包括局域网和公网地址。
    port: 20000,// 指定开发服务器端口。注意:如果端口已经被使用,Vite 会自动尝试下一个可用的端口,所以这可能不是开发服务器最终监听的实际端口。
    strictPort: false,// 设为 true 时若端口已被占用则会直接退出,而不是尝试下一个可用端口。
    proxy: {// 为开发服务器配置自定义代理规则。
      '/api': {// 匹配以/api开头的请求
        target: 'http://localhost:8080',//真实的后端地址
        changeOrigin: true,// 修改请求头中的 Origin / Host,避免跨域报错
        rewrite: path => path.replace(/^\/api/, '')// 重写请求路径,把请求路径中的 /api 去掉
      }
    }
  }
})

下面是jsconfig.json文件内容

这个文件主要是给VS Code / WebStorm等编辑器用的

让编辑器提供一下作用,但不参与打包运行

路径别名智能提示

跳转定义(Ctrl + 点击)

代码补全、类型推断(JS 项目)

真正运行时是否生效,要看 vite.config.js 里的resolve.alias

json 复制代码
// jsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",// 路径解析的基准目录
    "paths": {// 定义 路径别名
      "@/*": [// @是别名的名称,/*通配符,表示所有子路径
        "src/*" // 表示 @ 实际指向 src 目录
      ]
    }
  },
  "exclude": [// 排除不需要 编辑器分析 的目录
    "node_modules", // 第三方依赖目录
    "dist" // 打包目录
  ]
}

下面是style.css文件,因为Element-Plus的Container 布局容器样式丢失,所以这里把原来的style.css文件的内容替换掉

css 复制代码
.el-header,
.el-footer {
  background-color: #B3C0D1;
  color: #333;
  text-align: center;
  line-height: 60px;
}

.el-aside {
  background-color: #D3DCE6;
  color: #333;
  text-align: center;
  line-height: 200px;
}

.el-main {
  background-color: #E9EEF3;
  color: #333;
  text-align: center;
  line-height: 160px;
}

body>.el-container {
  margin-bottom: 40px;
}

.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {
  line-height: 260px;
}

.el-container:nth-child(7) .el-aside {
  line-height: 320px;
}

下面是main.js的内容

js 复制代码
import router from '@/router/index' // router配置
import '@/style.css' // 引入style.css样式文件
import * as ElementPlusIconsVue from '@element-plus/icons-vue' // 引入element-plus的icon
import ElementPlus from 'element-plus' // 引入element-plus
import 'element-plus/dist/index.css' // 引入element-plus的样式
import { createPinia } from 'pinia' // 引入pinia的createPinia 方法
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' // 引入pinia的持久化插件
import { createApp } from 'vue' // 引入vue的createApp方法
import App from './App.vue' // 引入程序入口

const app = createApp(App) // 创建Vue实例
// 全局注册element-plus的icon
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

// 挂载插件(链式调用)
app
  .use(createPinia().use(piniaPluginPersistedstate)) // 创建Pinia实例,并使用Pinia持久化插件,注册到vue中
  .use(ElementPlus) // 注册ElementPlus
  .use(router) // 注册VueRouter
  .mount('#app') // 挂载到DOM上

下面是utils/request.js文件的内容

这个文件主要是针对Axios的二次封装,之前的文章中也有提过

js 复制代码
import { useToken } from '@/stores/token.js' // 引入token的pinia仓库
import axios from "axios" // 引入axios
import { ElMessage } from 'element-plus' // 引入element-plus的消息组件

// 创建axios实例
const request = axios.create({
  baseURL: '/api', // 设置所有请求基础路径,再结合 vite.config.js 里的 proxy:转换成真实发送后端的请求地址
  timeout: 10000, // 请求超时时间
  headers: { // 设置默认请求头
    'Content-Type': 'application/json' // 设置请求默认以json方式发送数据
  }
})

// 添加请求拦截器
request.interceptors.request.use(function (config) {
  const { token } = useToken() // 获取pinia中保存的token
  if (token) {
    config.headers['Authorization'] = `Bearer ${token}` // 如果token存在,则在请求头中添加Authorization,这里的Bearer 在后端会处理,具体和后端沟通,这种是常见的后端规范,发送请求到后端后,后端的拦截器在进行验证时会去掉这个的
  }
  return config // 返回config,否则会中断请求
}, function (error) {
  // 处理请求拦截器错误并给出消息提示
  console.error('请求拦截器错误:', error)
  ElMessage.error('请求发送失败')
  return Promise.reject(error)
})

// 添加响应拦截器
request.interceptors.response.use(function (response) {
  // 请求成功返回(HTTP 状态 2xx)但不代表业务成功
  // 通常后端业务成功,返回的json中的code为200,否则后端给出了报错信息
  if (response.data.code && response.data.code !== 200) {
    ElMessage.error(response.data.msg || '请求失败') // 消息提示失败
    return Promise.reject(new Error(response.data.msg || '请求失败')) // 返回失败状态
  }
  // 将请求返回的json数据的data对象提取出来返回,这里的data就是后端返回的数据
  return response.data
}, function (error) {
  // 响应拦截器错误分支
  console.error('响应拦截器错误:', error)
  if (error.response) { // 判断是否有响应对象
    const status = error.response.status // 获取 HTTP 状态码
    switch (status) { // 按状态码分类处理,这里的处理并不完全规范,比如状态码不规范,或下方状态码419在弹窗后需要返回到首页,这里没有写,有需要自行添加
      case 401:
        ElMessage({
          type: 'warning',
          message: '未登录或登录已过期,请重新登录'
        })
        break
      case 403:
        ElMessage({
          type: 'warning',
          message: '权限不足'
        })
        break
      case 404:
        ElMessage({
          type: 'warning',
          message: '请求地址不存在'
        })
        break
      case 419:
        ElMessage({
          type: 'warning',
          message: '登录过期,请重新登录'
        })
        break
      case 500:
        ElMessage({
          type: 'warning',
          message: '服务器错误,请稍后再试'
        })
        break
      default:
        ElMessage({
          type: 'warning',
          message: error.response.data?.msg || '请求错误'
        })
    }
  } else { // 没有 error.response,网络断开,后端未启动,请求超时
    ElMessage({
      type: 'warning',
      message: '网络错误或服务器未响应'
    })
  }
  // 统一向外抛出错误
  return Promise.reject(error)
})

export default request // 导出实例

下面是router/index.js文件内容

js 复制代码
import { useToken } from '@/stores/token'// 引入token的pinia仓库
import { createRouter, createWebHashHistory } from "vue-router" // 引入vue-router的核心API

const router = createRouter({ // 创建router实例
  history: createWebHashHistory(), // 路由模式:Hash 模式
  routes: [] // 路由表,具体配置查看vue-router官网
})

// 全局前置路由守卫
// to:即将进入的路由
// from:当前离开的路由
// next:是否放行
router.beforeEach((to, from, next) => {
  const { token } = useToken() // 获取当前 token
  if ((to.name !== 'login') && token == '') { // 登录校验逻辑,访问的不是登录页,且没有token则拦截
    next({ name: 'login' }) // 拦截并跳转到登录页
  } else {
    next() // 放行
  }
})

export default router // 导出router

下面是stores/token.js文件

这个是pinia仓库

js 复制代码
import { defineStore } from "pinia" // Pinia 中 定义仓库(store) 的核心 API
import { ref } from "vue"

export const useToken = defineStore('token', () => { // 定义token仓库叫useToken, token是唯一ID,使用的是setup写法
  let token = ref('') // 定义 token 状态

  // 更新 token 的方法
  function updateToken (option) {
    token.value = option
  }
  
  // 清空 token 的方法
  function removeToken () {
    token.value = ''
  }

  // 暴露给外部使用的内容
  return {
    token,
    updateToken,
    removeToken
  }
}, {
  persist: {
    enabled: true, // 启用 pinia 持久化插件配置
    strategies: [{
      key: 'token', // 本地存储的 key
      storage: localStorage // 使用 localStorage存储
    }]
  }
})

然后我们运行一下npm run dev命令去启动一下项目,确保项目 目前是正常运行,没有报错,如果有报错,那就先解决报错在进行下面的操作,如果正常运行,我们需要将npm i 后生成的node_modules文件夹,或者可能运行了npm run build命令生成的dist文件夹进行删除,以及自动生成的package-lock.json文件进行删除,注意不是package.json,不要误删

至此,一个Vite + Vue3项目的基础模板就做好了,下面我们开始进行创建模板仓库

1.用 Vite 官方模板 + Git 仓库模板

这个方法很简单,也最常用

核心思想就是,将上面创建好的基础模板放到Gitee或者GitHub远程代码仓库上

这里再次提示一次,确保项目正常运行后,我们需要将npm i 后生成的node_modules文件夹,或者可能运行了npm run build命令生成的dist文件夹进行删除,以及自动生成的package-lock.json文件进行删除,注意不是package.json,不要误删

bash 复制代码
git init # 初始化本地仓库
git add . # 将代码提交到暂存区
git commit -m "init vue3 vite template" # 将代码提交到本地代码仓库
git remote add origin 你的代码仓库地址
git push -u origin "master" # 将代码提交到master分支

后续有修改或更新这个基础项目直接使用下面命令

bash 复制代码
git add . # 将代码提交到暂存区
git commit -m "init vue3 vite template" # 将代码提交到本地代码仓库
git pull # 从远程仓库拉取代码并修改合并
git push # 推送到远程仓库

下次创建项目直接使用git clone命令将这个仓库下载下来

2.使用degit(轻量化、无git历史)

这个方法的核心思想和上面使用git仓库来差不多,只不过是使用的degit进行下载代码

这里再次提示一次,确保项目正常运行后,我们需要将npm i 后生成的node_modules文件夹,或者可能运行了npm run build命令生成的dist文件夹进行删除,以及自动生成的package-lock.json文件进行删除,注意不是package.json,不要误删

我们先全局安装degit包,前提条件是需要安装node环境

不确定本地有没有node环境的可以在cmd命令窗口运行node -v查看版本

运行下面的代码,安装degit

bash 复制代码
npm i -g degit # -g参数表示全局安装degit

然后将这个代码模板添加到GitHub仓库
一定是GitHub,不能是gitee!!!
一定是GitHub,不能是gitee!!!
一定是GitHub,不能是gitee!!!

bash 复制代码
git init # 初始化本地仓库
git add . # 将代码提交到暂存区
git commit -m "init vue3 vite template" # 将代码提交到本地代码仓库
git remote add origin 你的代码仓库地址
git push -u origin "master" # 将代码提交到master分支

将代码提交到GitHub仓库后,我们进行拉取模板的操作

degit 默认不会拷贝 git 记录

bash 复制代码
degit 你的GitHub用户名/你的代码仓库名称 新项目名称 # 这是最基础的命令
degit 你的GitHub用户名/你的代码仓库名称#master 新项目名称 --force # 这里#master的意思是从代码仓库的master分支拉取代码 -- force参数是覆盖目录
bash 复制代码
cd 目录名 # 进入项目目录
npm i # 安装依赖
npm run dev # 运行项目

3.自定义npm create xxx脚手架

这个方法是将你配置好的项目模板做成一个npm包上传到npm管理器,使用npm create 包名创建项目

这里再次提示一次,确保项目正常运行后,我们需要将npm i 后生成的node_modules文件夹,或者可能运行了npm run build命令生成的dist文件夹进行删除,以及自动生成的package-lock.json文件进行删除,注意不是package.json,不要误删

我们在新的文件夹中运行一下命令

bash 复制代码
npm init -y

此时文件夹中会生成一个package.json文件

下面是生成的package.json文件内容

json 复制代码
{
  "name": "create-vue-template-js",// 这个name一定要是以create-开头,因为后面运行的命令是npm create xxx,这个xxx就是vue-template-js可以替换成你自己想要的名称,且必须和下面bin对象的属性名称一摸一样
  "version": "1.0.0", // npm包版本号
  "description": "vite vue3 js template", // npm官网描述
  "main": "index.js", // 主入口是index.js文件,可有可无,真正起作用的是下面bin的配置
  "author": "YwaiX", // 作者
  "license": "MIT", // 开源协议,MIT 是最常用、最宽松的协议
  "type": "module", // 表示 Node 使用 ES Module
  "bin": { // 核心
    "create-vue-template-js": "bin/index.js" // create-vue-template-js全局命名,bin/index.js执行的脚本,也就是说运行npm create vue-template-js,实际上运行的是node bin/index.js,但同时必须保证#!/usr/bin/env node在 bin/index.js 第一行
  },
  "scripts": { // npm 脚本,对脚手架 不是必须,可以直接删掉,不影响使用
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [// npm 搜索关键词,方便别人搜到你的脚手架
    "vite",
    "vue",
    "js"
  ]
}

下面是目前的目录结构

bin文件夹存放的index.js是上面package.json中需要运行的文件

template文件夹中存放的是我们创建好的vue3基础模板,再次提醒,删除node_modules,package-lock.json,dist文件夹或文件后复制到本文件夹中

这里把Vue3基础模板复制到template文件夹后我们需要把模板里面的package.json文件做个修改

这里复制进来是Vue3模板项目的那个名字,这里改成__PROJECT_NAME__

下面看一下bin\index.js文件的内容

js 复制代码
#!/usr/bin/env node
import { execSync } from 'child_process'
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

const projectName = process.argv[2]

if (!projectName) {
  console.log('❌ 请输入项目名称')
  console.log('👉 npm create vue-template-js my-project')
  process.exit(1)
}

const targetDir = path.resolve(process.cwd(), projectName)
const templateDir = path.resolve(__dirname, '../template')

if (fs.existsSync(targetDir)) {
  console.log('❌ 目录已存在')
  process.exit(1)
}

console.log('🚀 创建项目:', projectName)

// 1. 拷贝模板
fs.cpSync(templateDir, targetDir, { recursive: true })

// 2. 替换 package.json 里的项目名
const pkgPath = path.join(targetDir, 'package.json')
const pkg = fs.readFileSync(pkgPath, 'utf-8')
fs.writeFileSync(
  pkgPath,
  pkg.replace('__PROJECT_NAME__', projectName)
)

// 3. 安装依赖
console.log('📦 安装依赖中...')
execSync('npm install', {
  cwd: targetDir,
  stdio: 'inherit'
})

console.log(`
✅ 项目创建成功!
👉 cd ${projectName}
👉 npm run dev
`)

直接把这个代码复制进去就好了,这个代码主要做的事情就是,把我们这个vue3模板复制到本地,然后替换package.json中的name为你的npm create 包名 项目名中的项目名,然后自动执行npm i命令安装依赖

如果你是直接在VS Code中打开的文件夹,执行npm create 命令,就只需要等待命令执行完毕后cd进目录,执行npm run dev启动项目

到这里,我们已经做到npm包了,接下来我们测试一下这个包有没有成功

在vue-template-js目录下执行npm link命令进行链接

然后随便打开文件夹执行 create-vue-template-js my-test命令去创建项目,此时创建的项目是从本地直接复制来的,并不是从npm 官网下载下来的
create-vue-template-js是我们在外层package.json中写的

bash 复制代码
"bin": {
    "create-vue-template-js": "bin/index.js"
  },

my-test是我们创建新项目的名称

如果能够成功创建测试项目,我们去npm官网,注册登录账号

这里提示一下会使用科学上网的小伙伴,这个官网虽然是国外IP,但是,如果用科学上网访问,登录后可能无法点击用户头像,进行下一步操作,建议把科学上网关闭进行操作

下面点击用户头像---->点击Access Tokens

点击Generate New Token

如果之前没有生成过token或者完全没有注册登录过npm官网的,页面进来是没有token的

然后按照流程输入密码登录

下面看图

接上图

然后会自动跳回刚刚的页面,这里的token只会出现一次,刷新后就没有了,需要马上保存起来

然后将npm的镜像切换为默认镜像
镜像必须切换为npm默认镜像,不然执行不了操作

怎么快速切换镜像,查看我的其他文章,其他文章有教学

bash 复制代码
nrm ls #查看所有镜像
nrm use npm # 切换npm官方镜像

切换到npm官方镜像后,使用一下命令,进行登录并验证

bash 复制代码
npm config set //registry.npmjs.org/:_authToken=刚刚生成的Token # 登录
npm whoami # 验证是否登录成功,如果登录成功会显示你的username

然后执行下面的命令,进行发布,下载验证

bash 复制代码
npm publish --access public #发布
npm create vue-template-js my-project # 我的npm包名是vue-template-js,我的项目名是my-project

如果新项目创建成功就说明已经制作好了npm包
npm publish --access public命令一定要在vue-template-js文件夹执行,我这里是vue-template-js文件夹,你们按照自己实际情况
npm create vue-template-js my-project命令可以在任何文件夹执行

如果不想在命令上写创建的项目名,可以自行修改bin\index.js文件的内容去切换操作

最后给自己打个广告,如果觉得自己写npm包比较麻烦的,可以直接使用下面两个命令来直接下载js或ts版本的基础项目

bash 复制代码
npm create vite-vue-js # 获取js版本基础项目
npm create vite-vue-ts # 获取ts版本基础项目

4.总结

通过本文的实践与整理,完成了一套基于 Vite 的 Vue3 项目自定义模板,并使用三种方式将其封装为可通过git命令,degit工具, npm create xxx 命令一键生成的脚手架工具。这一过程不仅解决了大家在日常开发中反复进行基础配置的问题,也显著提升了新项目的启动效率。

在实际使用中,这种方式带来的收益是非常明显的:

  • 减少重复劳动:常用依赖、目录结构、基础配置一次完成,后续项目直接复用
  • 统一项目规范:保证不同项目在技术选型和代码风格上的一致性
  • 提升开发效率:新项目创建后即可专注于业务开发,而不是环境搭建
  • 便于持续演进:模板可以随着个人或团队技术栈的升级不断迭代优化

以上就是本次介绍的内容,如果有疑问可以评论区留言或主页联系方式解答

相关推荐
专注VB编程开发20年2 小时前
vb.net宿主程序通过统一接口直接调用,命名空间要一致
服务器·前端·.net
2503_928411562 小时前
12.18 中后台项目-权限管理
前端·javascript·数据库
elangyipi1232 小时前
pnpm 深度解析:下一代包管理工具的原理与实践
npm·node.js
Y‍waiX‍‍‮‪‎⁠‌‫‎‌‫‬2 小时前
NRM-NPM的镜像源管理工具使用方法
前端·npm·node.js
未来之窗软件服务7 小时前
一体化系统(九)智慧社区综合报表——东方仙盟练气期
大数据·前端·仙盟创梦ide·东方仙盟·东方仙盟一体化
陈天伟教授10 小时前
人工智能训练师认证教程(2)Python os入门教程
前端·数据库·python
信看11 小时前
NMEA-GNSS-RTK 定位html小工具
前端·javascript·html
Tony Bai11 小时前
【API 设计之道】04 字段掩码模式:让前端决定后端返回什么
前端