一、为什么选择Nuxt.js
多数是基于webpack构建的项目,编译出来的html文件,资源文件都被打包到js中,以下图404页面代码为例。从代码中可以看出,这样的页面是不利于 搜索引擎优化(SEO, Search Engine Optimization) ,并且 内容到达时间(time-to-content) (或称之为首屏渲染时长)也有很大的优化空间。为了解决以上问题,引入了 Nuxt.js 框架。
vue官网对于Nuxt.js也是很推荐的,除此之外,Nuxt.js的开发者积极活跃,版本迭代迅速。经过一系列rc版本后,终于在1月9日发布了 v1.0.0 正式版本!
图1. 使用webpack构建的HTML(代码已格式化)
图2. 使用 Nuxt.js 构建的HTML(代码已格式化)
二、Nuxt.js 简介
Nuxt.js 是一个基于 Vue.js 的通用应用框架,它预设了利用 Vue.js 开发 服务端渲染(SSR, Server Side Render) 的应用所需要的各种配置,同时也可以一键生成静态站点。
作为框架,Nuxt.js 为 客户端/服务端 这种典型的应用架构模式提供了许多有用的特性,例如异步数据加载、中间件支持、布局支持等。区别于其他 vue SSR 框架,Nuxt.js 有以下比较明显的特性。
- 自动代码分层
- 强大的路由功能,支持异步数据(路由无需额外配置)
- HTML头部标签管理(依赖 vue-meta 实现)
- 内置 webpack 配置,无需额外配置
三、项目实战
1、项目创建
官方提供了基于 vue-cli 脚手架工具,常用的有如下三个,更多脚手架工具可以查看 nuxt-community 。本项目使用的是 express-template。
- vue init nuxt-community/starter-template <project-name>
-
- vue init nuxt-community/koa-template <project-name>
-
- vue init nuxt-community/express-template <project-name>
2、开发
1)目录结构
- ├─assets 资源目录,未编译的静态资源如less、js
- ├─components 组件目录
- ├─layouts 布局目录
- ├─mock mock数据
- ├─node_modules
- ├─pages 页面目录
- ├─index.vue
- ├─....
- ├─plugins 插件
- ├─server express服务
- ├─static 静态文件目录
- ├─store vuex store
- ├─utils 工具方法
2)配置
Nuxt.js 默认的配置涵盖了大部分使用情形,也可通过修改 nuxt.config.js
来覆盖默认配置。
- // nuxt.config.js 文件配置
- const path = require('path')
-
- module.exports = {
- // Headers of the page
- head: {
- title: '默认共用title',
- meta: [
- { charset: 'utf-8' },
- { 'http-equiv': 'pragma', content: 'no-cache' },
- { 'http-equiv': 'cache-control', content: 'no-cache' },
- { 'http-equiv': 'expires', content: '0' },
- { content: 'telephone=no', name: 'format-detection' }
- ],
- // html head 中创建 script 标签
- script: [
- { innerHTML: require('./assets/js/flexible_nuxt'), type: 'text/javascript', charset: 'utf-8'}
- ],
- // 不对<script>标签中内容做转义处理
- __dangerouslyDisableSanitizers: ['script']
- },
- // Global CSS
- css: ['~/assets/css/reset.css', '~/assets/css/main.less'],
- // Global env
- env: {
- __ENV: process.env.__ENV
- },
- build: {
- vendor: ['axios'],
- postcss: [
- require('postcss-px2rem')({
- remUnit: 75
- })
- ],
- extend (config, ctx) {
- if (ctx.isClient) {
- // 拓展 webpack 配置
- config.entry['polyfill'] = ['babel-polyfill']
- config.module.rules.push({
- enforce: 'pre',
- test: /\.(js|vue)$/,
- loader: 'eslint-loader',
- exclude: /(node_modules)/
- })
- // 添加 alias 配置
- Object.assign(config.resolve.alias, {
- 'utils': path.resolve(__dirname, 'utils')
- })
- }
- }
- },
- plugins: [{src: '~plugins/toast', ssr: false}, {src: '~plugins/dialog', ssr: false}]
- }
HTML头部标签管理:
Nuxt.js 通过 vue-meta 实现头部标签管理,在 nuxt.config.js
中的 head
配置。所有的页面都会走这个配置,如果想要修改某一页面的title,可以在 pages/**.vue 文件下,添加如下配置,这时该页面的标题就变成了“收车费”,其余页面还保持原有标题不变。
在config header配置中, __dangerouslyDisableSanitizers: ['script']
主要是为了不对<script>标签中内容做转义处理。看下面的例子?:
- head: {
- title: 'myTitle',
- meta: [
- { charset: 'utf-8' },
- { 'http-equiv': 'pragma', content: 'no-cache' },
- { 'http-equiv': 'cache-control', content: 'no-cache' },
- { 'http-equiv': 'expires', content: '0' },
- { content: 'telephone=no', name: 'format-detection' }
- ],
- script: [
- { innerHTML: 'console.log("hello")', type: 'text/javascript', charset: 'utf-8'}
- ]
- },
生成 html:
<script data-n-head="true" type="text/javascript" charset="utf-8">console.log("hello")</script>
我们发现 vue-meta 把引号做了转义处理,加入 __dangerouslyDisableSanitizers: ['script']
后,就不会再对这些字符做转义了,该字段使用需慎重!
3)路由
Nuxt.js 依据 pages 目录结构,自动生成 vue-router
模块的路由配置。
假设 pages 的目录结构如下:
那么,Nuxt.js 自动生成的路由配置如下:
嵌套路由:
创建内嵌子路由,需要添加一个 Vue 文件,同时添加一个与该文件同名的目录用来存放子视图组件。在父级 Vue 文件内增加 <nuxt-child/> 用于显示子视图内容。
4)布局
Nuxt.js布局方式如下图所示:
layouts对应目录中的layouts文件夹,默认pages下的页面走的都是 layouts/default.vue 布局方式,如下图。其中<nuxt/>可以类似vue
中slot
插槽的概念,pages/**.vue中的内容会插在<nuxt/>
内。
此外,如果想要某一页面,不走默认布局方式,可以在vue文件中配置layouts,如下。
- <script>
- export default {
- layout: 'demo_layout',
- ...
- }
- </script>
5)vuex
在根目录创建 store 目录,就会默认引用 vuex 模块,除此之外,还进行了以下的操作:1)将 vuex 模块 加到 vendors 构建配置中去;2)设置 Vue 根实例的 store 配置项。
Nuxt.js 支持两种使用 store 的方式:
- 普通方式:store/index.js 返回一个 Vuex.Store 实例
- 模块方式:store 目录下的每个 .js 文件会被转换成为状态树指定命名的子模块 (当然,index 是根模块,相当于设置了namespaced: true)
Nuxt.js提供了模块方式的简单写法:使用状态树模块化的方式,store/index.js 不需要返回 Vuex.Store 实例,直接将 state、mutations 和 actions 暴露出来即可。示例如下:
- export const state = () => ({
- accesstoken: ''
- })
-
- export const mutations = {
- setAccesstoken (state, accesstoken) {
- state.accesstoken = accesstoken
- }
- }
6)异步数据 asyncData
Nuxt.js 增加了一个 asyncData 方法,用于 在设置组件数据 之前 能够异步获取 或 处理数据。
由于 asyncData
是在组件 初始化 之前被调用的,所以不能通过 this
引用组件的实例对象,可以使用上下文对象来实现某些功能,可参考 context api
示例?:
- asyncData (params) {
- let accesstoken = params.route.query.accesstoken
- // request 基于 axios 封装的函数
- return request({
- url: '/drivers/banks',
- method: 'get',
- headers: {
- accesstoken
- }
- })
- .then(res => {
- let {
- bankInfo
- } = res.data
- return {
- banksData: bankInfo,
- accesstoken
- }
- })
- .catch(err => {
- return error({ message: 'accesstoken not found', statusCode: 404 })
- })
- }
上述代码,会在 组件初始化 之前,请求'/drivers/banks'
接口,接口返回的数据会 融合在 data 中,一并返回模版显示。在浏览器中,使用Vue DevTools可以清晰的查看到 banksData
, accesstoken
都在data中。
在调试中发现,刷新页面时,该请求是在服务端发送的,由其他页面回退到该页面时,请求是在客户端发送的。
7)fecth方法
与 asyncData
方法类似,不同的是它不会设置组件的数据,作用是设置 store
数据。
五、总结
本项目在开发中,使用的是 1.0.0-rc9 版本,我们正在积极尝试迁移到 1.0.0 正式版本。但是,1.0.0-rc9 版本,未见明显问题,比较稳定,足以投入到生产中。
本文主要介绍 Nuxt.js 的特性,后面还会和大家分享踩的坑。文中有任何表述不清或不当的地方,欢迎大家批评指正。
此外,推荐我们的公众号 前端新视野 ,一个很认真的日刊公众号,欢迎扫描下方二维码关注!