搭建nuxt项目
本文地址: ()
avatar
作者: FeRookie 类型: 原创
更新时间:2022-05-22 阅读:495

版本须知

Node 版本须在11.7.0 以上

软件架构

说明

技术栈:Nuxt + Vue + Axios + Webpack + Koa2 + PM2 + Element

使用create-nuxt-app脚手架创建项目,项目组成部分:

  1. 项目采用的 Nuxt 框架开发
  • 安装 nuxt 项目

    a. 使用 npm 5.2+ 自带 npx 安装 npx create-nuxt-app <项目名>

    b. 使用 yarn 安装 npm install yarn -g yarn create nuxt-app <项目名>

    c. 选择你想用的集成方案,比如 UI 框架,http 请求库,测试框架等

  1. 基础库采用的是 Vue,数据状态管理采用的是 Vuex
  • Nuxt + Vuex 前言

       nuxt 针对 Vuex 提供了 2 种方案

      a. 模块方式。这也是 nuxt 推荐的方式,store/index.js 按照 nuxt 使用 vuex 规则返回 state,mutations,getters,actions 而每个 modules 是根据 store 文件下 js 的命名,比如 store/todo.js 则等于 modules: { todo },如需详细了解,请查阅nuxt 中文官网

      b. classic 模式。store/index.js 返回创建 Vuex.Store 实例的方法。 在本项目中,采用的是第二种方案,因为第二种方案更贴近我们之前做的 vue 项目,所以坚持保持原样(这 也是为之后的模块化做铺垫),但是在 nuxt 3 将会废弃该模式

  1. 数据请求库采用的是 Axios
  • 引入 axios

      a. 安装依赖 yarn add axios -S

  • 二次封装 axios

  1. 构建采用的是 webpack

  2. 部署采用的是 PM2+Node 部署流程

  3. UI 框架采用的是 ELEMENT

  4. 采用 Jest 测试框架

  5. css 编译器采用 Scss 规范

  6. 调试 nodejs to browser 使用 consola 插件

  7. 自定义 UI 框架 package

  • 组件目录

  • 样式目录 theme-default 默认使用 gulp 压缩 sass

  • 入口文件 index.js

  • 使用方式

项目目录结构:


|-- .nuxt                                              // 编译后的文件
|
|-- node_modules                                       // 依赖包
|
|-- api                                                // 暴露接口文件
|
|-- assets                                             // 静态资源文件,包括images、css
|
|-- components                                         // Business-component 业务性组件
|
|-- layouts                                            // 布局目录 layouts 用于组织应用的布局组件。
|
|-- middleware                                         // 中间件,可以是路由中间件,工具类方法中间件等
|
|-- package                                            // Functional-component 功能性组件 以及 Stateless-component 交互性组件,并注册到全局使用
|
|-- pages                                              // .vue页面视图文件,文件名命名规则涉及到router规则
|   |-- custom                                         // custom 页面
|   |   |-- index.vue                                  // path: '/custom'
|   |   |-- _customid.vue                              // path: '/custom/:customid'
|   |   |-- service.js                                 // 数据接口 处理文件
|   |-- _gameid                                        // game页面 path: '/:gameid/'
|   |   |-- game.vue                                   // game子页面 path: '/:gameid/game'
|
|-- plugins                                            // 第三方插件、工具类函数等js文件
|
|-- server                                             // Nodejs 服务端入口文件以及接口规范编写
|
|-- store                                              // 状态管理器文件
|
|-- mock                                               // mock数据文件
|
|-- static                                             // 静态文件
|
|-- test                                               // 测试文件
|
|-- jest.config.js                                     // 单元测试配置文件;
|
|-- .gitignore                                         // git忽略的文件
|
|-- env.js                                             // 环境配置文件
|
|-- package.json                                       // 项目及工具的依赖配置文件
|
|-- nuxt.config.js                                     // nuxt项目配置文件,包含webpack配置等
|
|-- yarn.lock                                          // 依赖包版本控制文件
|
|-- README.md

Nuxt.js 配置系列

npx create-nuxt-app <项目名>
or
yarn create nuxt-app <项目名>

☞ 环境变量配置

module.exports = {
  env: {
    baseUrl: process.env.BASE_URL || 'http://localhost:3000'
  }
}

  通过以下两种方式来使用 baseUrl 变量

  1. 通过 process.env.baseUrl
  2. 通过 context.baseUrl,请参考context api

☞ 打包分析

  package.json 中添加 analyze 命令

"analyze": "nuxt build --analyze"

  修改 nuxt.config.js

export default {
  build: {
    analyza: {
      analyzeMode: 'static'
    }
  }
}

☞ 提供全局scss变量

npm i -S @nuxtjs/style-resources
npm i -D sass-loader node-sass

  修改 nuxt.config.js

export default {
  modules: [
    '@nuxtjs/style-resources',
  ],
  styleResources: {
    scss: '~/assets/scss/variable.scss'
  }
}

☞ 按需引入element-ui

npm i -D babel-plugin-component
or
yarn add babel-plugin-component -D

  修改 nuxt.config.js

module.exports = {
  plugins: ['@/plugins/element-ui'],
  build: {
    babel: {
      plugins: [
        [
          'component',
          { libraryName: 'element-ui', styleLibraryName: 'theme-chalk' }
        ]
      ]
    }
  },
}

  修改 plugins/element-ui.js

import Vue from 'vue'
import {
  Button, Loading, Notification, Message, MessageBox
} from 'element-ui'

import lang from 'element-ui/lib/locale/lang/zh-CN'
import locale from 'element-ui/lib/locale'

locale.use(lang)

Vue.use(Loading.directive)
Vue.prototype.$loading = Loading.service
Vue.prototype.$msgbox = MessageBox
Vue.prototype.$alert = MessageBox.alert
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$prompt = MessageBox.prompt
Vue.prototype.$notify = Notification
Vue.prototype.$message = Message

Vue.use(Button);

☞ 配置hard-source-webpack-plugin

   提升构建速度

npm i -D hard-source-webpack-plugin
or
yarn add hard-source-webpack-plugin -D

  修改 nuxt.config.js

module.exports = {
  build: {
    extractCSS: true,
    extend(config, ctx) {
      if (ctx.isDev) {
        config.plugins.push(
          new HardSourceWebpackPlugin({
            cacheDirectory: '.cache/hard-source/[confighash]'
          })
        )
      }
    }
  }
}

☞ 去除多余css

npm i -D glob-all purgecss-webpack-plugin
or
yarn add glob-all purgecss-webpack-plugin -D

  若安装失败,请先用管理员身份安装以下全局依赖(耐心等待)

npm install --global windows-build-tools
or
yarn global add windows-build-tools

   windows-build-tools 安装部分插件的全局工具链,必须的,不影响项目大小

  修改 nuxt.config.js

const PurgecssPlugin = require('purgecss-webpack-plugin')
const glob = require('glob-all')
const path = require('path')
const resolve = dir => path.resolve(__dirname, dir);

module.exports = {
  build: {
    extractCSS: true,
    extend(config, ctx) {
      if (!ctx.isDev) {
        config.plugins.push(
          new PurgecssPlugin({
            paths: glob.sync([
              resolve('./pages/**/*.vue'),
              resolve('./layouts/**/*.vue'),
              resolve('./components/**/*.vue')
            ]),
            extractors: [
              {
               extractor: class Extractor {
                  static extract(content) {
                    const validSection = content.replace(
                      /<style([\s\S]*?)<\/style>+/gim,
                      ""
                    );
                    return validSection.match(/[A-Za-z0-9-_:/]+/g) || [];
                  }
                },
                extensions: ['vue']
              }
            ],
            whitelist: ['html', 'body', 'nuxt-progress']
          })
        )
      }
    }
  }
}

☞ Brotli压缩

使用 brotli 压缩,压缩方式优于 gzip,浏览器支持符合目前公司最低标准 在不支持 brotli 会选择使用 gzip

  若安装失败,请先用管理员身份安装以下全局依赖

npm install --global windows-build-tools
or
yarn global add windows-build-tools

  修改 nuxt.config.js

  根据 webpack 官网提示,使用 brotli 压缩,在 node 11.7.0 及更高版本内置支持

  brotli,zopfli,gzip 压缩数据对比

module.exports = {
  build: {
    config.plugins.push(
        new CompressionWebpackPlugin({
          filename: '[path].br[query]',
          algorithm: 'brotliCompress',
          test: productionGzipExtensions,
          compressionOptions: { level: 11 },
          threshold: 10240,
          minRatio: 0.99,
          deleteOriginalAssets: false
        })
      )
  },
}

Axios 与 Store

二次封装axios库

import axios from 'axios'
import config from './config'

const service = axios.create(config)

service.interceptors.request.use(
  request => {
    // 判断本地是否有token值,有则从新设置token,没有使用token默认配置
    /**
     * 在这里做loading ...
     * @type {string}
     */
    // 获取token
    // loadingInstance = Loading.service({ fullscreen: true });
    return request
  },
  error => {
    // 请求错误时做些事
    return Promise.reject(error)
  }
)
// 返回结果处理
service.interceptors.response.use(
  response => {
    switch (response.data.code) {
      case '403':
        break
      case '110':
        break
      case '1':
        break
      default:
        return
    }
    return response
  },
  error => {
    let response = error.response
    if (response.status == 401) {
      // 处理401错误
      console.log(response.config.url)
    } else if (response.status == 403) {
      // 处理403错误
    } else if (response.status == 412) {
      // 处理412错误
    } else if (response.status == 413) {
      // 处理413权限不足
    } else if (response.status == 500) {
      // 处理500服务器错误
      Message({
        message: 'System Error',
        type: 'error'
      })
    }
    return Promise.reject(response)
  }
)

// 自定义判断元素类型JS
function toType(obj) {
  return {}.toString
    .call(obj)
    .match(/\s([a-zA-Z]+)/)[1]
    .toLowerCase()
}

// 参数过滤函数
function filterNull(o) {
  for (var key in o) {
    if (o[key] === null) {
      delete o[key]
    }
    if (toType(o[key]) === 'string') {
      o[key] = o[key].trim()
    } else if (toType(o[key]) === 'object') {
      o[key] = filterNull(o[key])
    } else if (toType(o[key]) === 'array') {
      o[key] = filterNull(o[key])
    }
  }
  return o
}

// 获取参数
function getUrlParam(url, name) {
  var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)') //构造一个含有目标参数的正则表达式对象
  console.log(url.substr(1))
  var r = url.substr(1).match(reg) //匹配目标参数
  if (r != null) return unescape(r[2])
  return null //返回参数值
}

//isFormdata判断是否为Formdata
function apiAxios(method, url, params, isFormdata, headers, timeout) {
  if (params) {
    params = filterNull(params)
  }
  return new Promise((resolve, reject) => {
    axios({
      method,
      url:
        method === 'GET'
          ? url.indexOf('?') > -1
            ? url + '&time=' + new Date().getTime()
            : url + '?time=' + new Date().getTime()
          : url,
      data:
        method === 'POST' ||
        method === 'PUT' ||
        method === 'DELETE' ||
        method === 'PATCH'
          ? isFormdata
            ? qs.stringify(params)
            : params
          : null,
      params:
        method === 'GET' || method === 'DELETE'
          ? isFormdata
            ? params
            : params
          : null,
      baseURL: process.env.baseUrl,
      timeout: timeout && timeout.timeout ? timeout.timeout : 18000,
      headers: Object.assign({ platform: 'DingDangMa' }, headers)
    })
      .then(response => {
        resolve(response.data)
      })
      .catch(err => {
        // consola.info(err.response.data)
        // consola.info(err.response.data.status)
        reject(err.response.data)
      })
  })
}

/**
 * 暴露出 get post put delete请求方法
 * @param url
 * @param params
 * @param data
 * @returns {Promise}
 */
export default {
  get: function(url, params = {}, isFormdata = false, headers, timeout) {
    return apiAxios('GET', url, params, isFormdata, headers, timeout)
  },
  post: function(url, data = {}, isFormdata = false, headers = {}, timeout) {
    return apiAxios('POST', url, data, isFormdata, headers, timeout)
  },
  put: function(url, data = {}, isFormdata = false, timeout) {
    return apiAxios('PUT', url, data, isFormdata, {}, timeout)
  },
  delete: function(url, data = {}, isFormdata = false, headers = {}, timeout) {
    return apiAxios('DELETE', url, data, isFormdata, headers, timeout)
  },
  patch: function(url, data = {}, isFormdata = false, headers = {}, timeout) {
    return apiAxios('PATCH', url, data, isFormdata, headers, timeout)
  }
}

以上代码可以直接使用,复制后看一下 就明白了。

在nuxt中使用Store,譬如本文开头所说,我采用的是贴近于我们不使用nuxt框架的store模式,就是使用modules

而每个modules都在各个视图文件内,可以区分开,方便管理。如图:

完整的一套starter模板,还在持续搭建中... 最后会将此发布到我的github上去。 最后,如果有疑问 ,可以留言 或者 加微信