一、问题描述:使用自动化打包部署的原因
进行项目开发过程中,需要随时上传代码到云服务器,方便测试人员/自己随时查看线上效果,方便随时调试
二、部署环境搭建前提
为了提升 SEO,使用了 Nuxt 框架进行开发,默认开发者已拥有如下环境及配置:
- 云服务器
- Nginx:用于配置反向代理
- Nodejs:提供项目运行环境
- pm2:合理管理线程,文件改变自动重启项目
三、本地项目构建
通过 npx create-nuxt-app <项目名> / yarn create nuxt-app <项目名> 创建项目
一般打包后需要上传的文件/文件夹如下:
- nuxtDist 打包构建生成
- assets 资源文件夹
- static 静态资源文件夹
- server 服务端文件夹 (使用 Node 提供接口)
- nuxt.config.js 项目配置文件
- package.json 文件依赖包
- package-lock.json 文件依赖包版本锁定
手动复制上传到服务器非常麻烦,提供自动化部署思路:
- 通过 scripts 配置部署命令 npm run deploy
- 部署上传到服务器之前,先进行打包,然后将上述列出的文件拷贝到 build 文件夹中
- 服务端通过 pm2 通过上传 package.json 中的 version 变化,重新启动项目
打包之前通过 clean-webpack-plugin 需要将 build 文件夹先清空,防止无用文件上传
通过 scp2 将 build 内的文件进行上传
"clean-webpack-plugin": "^1.0.1" // 用于上传之前进行清空 build 文件夹
"scp2": "^0.5.0" // 上传 build 文件夹内容
copy-webpack-plugin 在构建打包客户端结束后回,只会打包 client 部分代码,缺少server部分,运行时报错 nuxtDist/dist/ 目录下缺少 server 部分文件
项目目录结构如下:
修改对应 package.json 中 scripts 命令如下:
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server --exec babel-node", // 启动 client 同时启动 server 服务
"build": "nuxt build", // 项目本地构建打包
"start": "cross-env NODE_ENV=production nodemon server/index.js --exec babel-node", // 打包结束后可通过 npm start 本地运行
"export": "nuxt export", // 未使用
"serve": "nuxt serve", // 未使用
"generate": "nuxt generate", // 生成静态文件
"lint:js": "eslint --ext .js,.vue --ignore-path .gitignore .",
"lint": "yarn lint:js",
"deploy": "cross-env NODE_ENV=deploy npm run build && node deploy.js" // 执行 npm run deploy 进行项目打包部署到服务器
}
nuxt.config.js 配置如下:
const colors = require('vuetify/es5/util/colors')
const CleanWebpackPlugin = require('clean-webpack-plugin')
// export default {
module.exports = {
telemetry: false, // 跳过:Are you interested in participation?
server: { // 部署到线上nginx配置
host: '0.0.0.0',
port: 3000
},
buildDir: 'nuxtDist',
/*
** Nuxt rendering mode
** See https://nuxtjs.org/api/configuration-mode
*/
mode: 'universal',
/*
** Nuxt target
** See https://nuxtjs.org/api/configuration-target
*/
target: 'server',
/*
** Headers of the page
** See https://nuxtjs.org/api/configuration-head
*/
head: {
titleTemplate: '%s - ' + process.env.npm_package_name,
title: 'Js进阶',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{
hid: 'description',
name: 'description',
content: process.env.npm_package_description || '',
},
],
link: [{ rel: 'icon', type: 'image/x-icon', href: 'logo.png' }],
},
/*
** Global CSS
*/
css: [
"~/assets/variables.scss"
],
/*
** Plugins to load before mounting the App
** https://nuxtjs.org/guide/plugins
*/
plugins: [
// '~/plugins/axios'
'~/plugins/axios-plugins'
],
/*
** Auto import components
** See https://nuxtjs.org/api/configuration-components
*/
components: true,
/*
** Nuxt.js dev-modules
*/
buildModules: [
// Doc: https://github.com/nuxt-community/eslint-module
'@nuxtjs/vuetify',
],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
'@nuxtjs/pwa',
],
/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios: {
// retry: {retries: 3}
},
/*
** vuetify module configuration
** https://github.com/nuxt-community/vuetify-module
*/
vuetify: {
customVariables: ['~/assets/variables.scss'],
theme: {
// dark: true,
dark: false,
themes: {
dark: {
primary: '#1976D2',
secondary: '#424242',
accent: '#82B1FF',
error: '#FF5252',
info: '#2196F3',
success: '#4fc08d',
warning: '#FFC107',
},
// 自定义颜色
light: {
primary: '#1976D2',
secondary: '#424242',
accent: '#82B1FF',
error: '#FF5252',
info: '#2196F3',
success: '#4fc08d',
warning: '#FFC107',
}
},
},
},
/*
** Build configuration
** See https://nuxtjs.org/api/configuration-build/
*/
build: {
filenames: { // 给所有文件添加hash
app: ({ isDev }) => isDev ? '[name].js' : '[contenthash].js',
chunk: ({ isDev }) => isDev ? '[name].js' : '[contenthash].js',
css: ({ isDev }) => isDev ? '[name].css' : '[contenthash].css',
img: ({ isDev }) => isDev ? '[path][name].[ext]' : 'img/[contenthash:7].[ext]',
font: ({ isDev }) => isDev ? '[path][name].[ext]' : 'fonts/[contenthash:7].[ext]',
video: ({ isDev }) => isDev ? '[path][name].[ext]' : 'videos/[contenthash:7].[ext]'
},
plugins: process.env.NODE_ENV !== 'deploy' ? [] : [
// 打包之前清除之前残留文件
new CleanWebpackPlugin(['build']),
]
},
router: {
middleware: 'redirect'
}
}
deploy.js 文件为文件上传部分:
const client = require('scp2')
// exists 文件夹复制到build
// copyFile 文件复制到build
// copy 复制的回调函数
const { exists, copy, copyFile } = require('./copy')
async function copyTargetFile() {
copyFile('./nuxt.config.js', './build/nuxt.config.js')
copyFile('./package.json', './build/package.json')
copyFile('./package-lock.json', './build/package-lock.json')
copyFile('./README.md', './build/README.md')
await exists('./assets', './build/assets', copy)
await exists('./server', './build/server', copy)
await exists('./static', './build/static', copy)
await exists('./nuxtDist', './build/nuxtDist', copy)
}
async function scpClient() {
await client.scp('./build', {
host: '你的服务器ip', // 服务器ip
username: 'root', // 用户名
password: '登录服务器密码', // 密码
path: '/www/my-nuxt', // 服务端路径
}, err => {
if (err) {
console.log('*********文件上传失败*******')
} else {
console.log('**********文件上传成功**********')
}
})
}
async function upload() {
console.log('**********开始生成上传文件包************')
await copyTargetFile()
console.log('***************上传文件准备完成,开始上传***************')
await scpClient()
}
upload()
copy.js 文件不贴了,node淡忘,自己尝试下吧
四、总结
- 文件上传服务器后需要手动进行依赖包下载,算是缺点之一,后续可以通过 pm2 的 ecosystem.config.js 文件进行配置自动拉取远程 git 项目的代码自动化更新代码
- pm2 ecosystem 生成 ecosystem.config.js 配置文件可进行 git 项目的自动拉取
附:ecosystem.config.js 基础配置
module.exports = {
apps : [{
name: 'nuxt-ssr',
script: 'server/index.js',
autorestart: true,
// watch: '.'
},
// {
// script: './service-worker/',
// watch: ['./service-worker']
// }
],
deploy : {
production : {
user : 'root',
host : '你的服务器ip',
ref : 'origin/master',
repo : '你的git项目地址',
path : '/root/www/my-nuxt', // 服务器上项目目录
// 'pre-deploy-local': '',
'post-deploy' : 'rm -rf node_modules && npm install && pm2 startOrReload ecosystem.config.js --env production',
// 'pre-setup': ''
}
}
};
// 执行命令
// 初始化项目
// pm2 deploy production setup
// 部署项目
// pm2 deploy production