版本须知
Node 版本须在11.7.0 以上
软件架构
说明
技术栈:Nuxt + Vue + Axios + Webpack + Koa2 + PM2 + Element
使用create-nuxt-app
脚手架创建项目,项目组成部分:
- 项目采用的 Nuxt 框架开发
安装 nuxt 项目
a. 使用 npm 5.2+ 自带 npx 安装
npx create-nuxt-app <项目名>
b. 使用 yarn 安装
npm install yarn -g
yarn create nuxt-app <项目名>
c. 选择你想用的集成方案,比如 UI 框架,http 请求库,测试框架等
- 基础库采用的是 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 将会废弃该模式
- 数据请求库采用的是 Axios
引入 axios
a. 安装依赖
yarn add axios -S
二次封装 axios
构建采用的是 webpack
部署采用的是 PM2+Node 部署流程
UI 框架采用的是 ELEMENT
采用 Jest 测试框架
css 编译器采用 Scss 规范
调试 nodejs to browser 使用 consola 插件
自定义 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 变量
- 通过 process.env.baseUrl
- 通过 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 及更高版本内置支持
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上去。
最后,如果有疑问 ,可以留言 或者 加微信