【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 命令一键生成的脚手架工具。这一过程不仅解决了大家在日常开发中反复进行基础配置的问题,也显著提升了新项目的启动效率。

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

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

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

相关推荐
恋猫de小郭8 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅14 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606115 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了15 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅15 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅15 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅16 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment16 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅16 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊16 小时前
jwt介绍
前端