Nuxt(SSR)项目自动化打包部署

4,308 阅读4分钟

一、问题描述:使用自动化打包部署的原因

进行项目开发过程中,需要随时上传代码到云服务器,方便测试人员/自己随时查看线上效果,方便随时调试

二、部署环境搭建前提

为了提升 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 文件依赖包版本锁定

手动复制上传到服务器非常麻烦,提供自动化部署思路:

  1. 通过 scripts 配置部署命令 npm run deploy
  2. 部署上传到服务器之前,先进行打包,然后将上述列出的文件拷贝到 build 文件夹中
  3. 服务端通过 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淡忘,自己尝试下吧

四、总结

  1. 文件上传服务器后需要手动进行依赖包下载,算是缺点之一,后续可以通过 pm2 的 ecosystem.config.js 文件进行配置自动拉取远程 git 项目的代码自动化更新代码
  2. 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