服务端渲染Nuxt.js

NuxtJs

前言:学完 NuxtJs 发现太好用了,完全不需要担心乱七八槽的配置,全部自己生成,很良心有没有♥。项目中一般 React 都是纯手配,可以看成手动挡的车,除了需要完全自己配置而且概念比较多,Vue 好一点,插件都是官方指定比较稳定容易上手,没有选择恐惧症,尤其是 cli 比较好用,可类比为半自动档的车,但是如果你用完 NuxtJs 会发现完全是自动挡。身为开发者的车手如果技术老成肯定喜欢手动挡去开赛车飙技术,新手肯定是喜欢自动挡滴了 。当你学会了 NuxtJs 框架,恭喜你点亮了全部 Vue 技能。

一、为什么需要服务端渲染

大多数单页面(SPA)应用都只是有一个div入口标签,如果项目只是内部或固定的人群使用是没问题的,但是如果涉及到新闻等类似面向普通用户的网站,就必须的考虑到 SEO,Nuxt.js 的服务端渲染就是用来解决 Vue 项目的 SEO 问题。虽然 Nuxt.js 也能创建单页面应用。

二、什么是 SSR ( 服务端渲染 )

SSR 即服务器渲染,就是 (Vue) 页面在服务器端渲染完生成 html 文件,浏览器访问时就将 html 文件直接传递给浏览器。因为数据在服务端就渲染完成,所以减少了浏览器的 HTTP 请求,看不见接口更加安全,减少了首屏加载时间。

三、安装

安装 Node 的前提下:

C:\Users\hpzhan\Desktop\test>npx create-nuxt-app app

create-nuxt-app v2.15.0
✨  Generating Nuxt.js project in app
? Project name app_test
? Project description 这是一个关于nuxt.js的项目(默认为(My beautiful Nuxt.js project))
? Author name Condor Hero
? Choose programming language JavaScript
? Choose the package manager Npm
? Choose UI framework Element
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules Axios
? Choose linting tools ESLint
? Choose test framework None
? Choose rendering mode Universal (SSR)
? Choose development tools jsconfig.json (Recommended for VS Code)
Installing packages with npm  

#安装依赖成功
�  Successfully created project app_test

  To get started:

        cd app
        npm run dev

  To build & start for production:

        cd app
        npm run build
        npm run start

进入项目文件cd app,通过 npm run dev 来启动项目,然后浏览器输入 http://localhost:3000/,就可以看到结果。

安装 Nuxt.js

四、Nuxt目录结构详讲

其实每个目录里面都有一个 README.md文件,清楚的告诉大家每个目录的作用。

|-- .nuxt                            // Nuxt自动生成,临时的用于编辑的文件,build
|-- assets                           // 用于组织未编译的静态资源入LESS、SASS 或 JavaScript
|-- components                       //Vue组件放置的地方
|-- layouts                          // 布局目录,用于组织应用的布局组件,文件名比较固定,不可更改。
|-- middleware                       // 用于存放中间件,类似路由钩子的作用
|-- pages                            // 用于存放写的页面,我们主要的工作区域
|-- plugins                          // 用于存放项目的第三方插件,例如element UI
|-- static                           // 用于存放静态资源文件,比如图片
|-- store                            // 用于组织应用的Vuex 状态管理。
|-- .editorconfig                    // 开发工具编辑器格式配置
|-- .eslintrc.js                     // ESLint的配置文件,用于检查代码格式
|-- .gitignore                       // 配置git不上传的文件
|-- nuxt.config.json                 // 用于组织Nuxt.js应用的个性化配置,已覆盖默认配置,对单页面应用类似是index.html和webpack.config.js两个文件的集成。
|-- package-lock.json                // npm自动生成,用于package依赖包固定版本的,yarn也有相同的操作
|-- package.json                     // npm包管理配置文件

统一代码风格工具——editorConfig

.editorconfig 文件,初次见到他是在用 Vue-cli 的时候,那时候没有深究怎么用的,里面的配置就是简单的看看,这次又见到了,结果用的时候完全不起作用,不起作用学习里面的语法可学不进去,而且官网 www.editorconfig.org 也是简单到看不懂。终于捯饬了一下午才发现需要安装插件,哎还以为编辑器自动支持。

在平常的项目开发过程中,每个开发者喜欢的编码风格是不一样的,就以缩进为例,有的人喜欢两个空格缩进,有的人喜欢四个空格的缩进,项目团队的人如果少的话,这么做是问题不大的。但是在多人合作开发项目。统一代码风格就显得十分有必要。editorConfig 有部分 ESLint 功能的感觉。

有些编辑器默认支持 editorConfig,如 webstorm;而有些编辑器则需要安装 editorConfig 插件,如 ATOM、Sublime、VS Code 等,在对应的插件市场直接搜索安装就行了。

EditorConfig for VS Code

这时候 .editorconfig 文件就能起作用了,编辑器的行为会与. editorconfig 文件中定义的一致,并且其优先级比编辑器自身的设置要高。

当打开一个文件时,EditorConfig 插件会在打开文件的目录和其每一级父目录查找.editorconfig文件,直到有一个配置文件里面有 root=true 才停止继续向上索引。

EditorConfig 的配置文件是从上往下读取的并且最近的 EditorConfig 配置文件会被最先读取,最近的配置文件中的配置项拥有优先权。如果 .editorconfig 文件没有进行某些配置,则使用编辑器默认的设置。

看看官网 https://editorconfig.org/ 有哪些配置规则:

符号 作用
* 匹配除/之外的任意字符串
** 匹配任意字符串
? 匹配任意单个字符
[name] 匹配name中的任意一个单一字符
[!name] 匹配不存在name中的任意一个单一字符
{s1,s2,s3} 匹配给定的字符串中的任意一个(用逗号分隔)
{num1..num2} 匹配num1到num2之间的任意一个整数, 这里的num1和num2可以为正整数也可以为负整
indent_style 设置缩进风格(tab是硬缩进,space为软缩进)
indent_size 用一个整数定义的列数来设置缩进的宽度,如果indent_style为tab,则此属性默认为tab_width
tab_width 用一个整数来设置tab缩进的列数。默认是indent_size
end_of_line 设置换行符,值为lf、cr和crlf
charset 设置编码,值为latin1、utf-8、utf-8-bom、utf-16be和utf-16le,不建议使用utf-8-bom
trim_trailing_whitespace 设为true表示会去除换行行首的任意空白字符。
insert_final_newline 设为true表示使文件以一个空白行结尾
root 表示是最顶层的配置文件,发现设为true时,才会停止查找.editorconfig文件

注意:所有的属性和值都是忽略大小写的. 解析时它们都是小写的
在来看官网一个例子:


# top-most EditorConfig file
root = true

# 使用Unix-style 换行符,并且每个文件以换行结束
[*]
end_of_line = lf
# CR:Carriage Return,对应ASCII中转义字符\r,表示回车
# LF:Linefeed,对应ASCII中转义字符\n,表示换行
# CRLF:Carriage Return & Linefeed,\r\n,表示回车并换行,windows的记事本换行规则
insert_final_newline = true

# 可以使用通配符匹配多个文件
# 设置默认编码为utf-8
[*.{js,py}]
charset = utf-8

# py文件 4 个空格缩进
[*.py]
indent_style = space
indent_size = 4

#  使用Tab缩进
[Makefile]
indent_style = tab

# lib目录下的js使用2个空格缩进
[lib/**.js]
indent_style = space
indent_size = 2

# 配置 package.json 或 .travis.yml文件 设置其为2个空格缩进
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

end_of_line 设置设置换行符规则,我们需要只要/r ,/n ,/r/n的区别和不同:

英文名 中文名 编辑器缩写 ASCII
carriage return 回车 CR \n
linefeed 换行 LF \r
carriage return & linefeed 回车换行 CRLF \r\n

https://www.ruanyifeng.com/blog/2006/04/post_213.html

文章中的电传打印机,这种终端不能独立使用,必须连接到一台主机,相当于显示器+键盘。这里有个Teletype Model 37 的演示视频

项目开发的时候,例如.gitignore.editorconfig等文件一旦配置完成几乎不需要改动,留着就有一点碍眼了,如何在 VSCode 目录栏中隐藏这些文件。


按图改下编辑器字体的大小按 ctrl+s 保存,这会在项目中生成 .vscode目录,里面有一个 settings.json文件,输入files.exclude会感应出出如下 JSON:

{
    "files.exclude": {
        "**/.git": true,
        "**/.svn": true,
        "**/.hg": true,
        "**/CVS": true,
        "**/.DS_Store": true
    }
}

需要隐藏的文件类似添加就 OK 了。这时别忘了先在 .gitignore文件里面忽略.vscode 目录。

package.js文件

{
    "name": "condor",
    "version": "1.0.0",
    "description": "My marvelous Nuxt.js project",
    "author": "Condor Hero",
    "private": true,
    "scripts": {
        "dev": "nuxt",
        "build": "nuxt build",
        "start": "nuxt start",
        "generate": "nuxt generate"
    },
    "dependencies": {
        "nuxt": "^2.0.0"
    },
    "devDependencies": {},
    "config":{
        "nuxt":{
          "host":"127.0.0.1",
          "port":"8888"
        }
    }
}

主要是 scripts 字段里面的内容:

指令 描述
nuxt 开启一个监听3000端口的服务器,同时提供热加载功能
nuxt build 构建整个应用,压缩合并JS和CSS文件(用于生产环境)
nuxt start 开启一个生产模式的服务器(必须先运行nuxt build命令)
nuxt generate 构建整个应用,并为每一个路由生成一个静态页面(用于静态服务器)

其中通过 config 字段能解决 ip 占用问题,自定义端口和 IP。

nuxt.config.js

nuxt.config.js 如果对照单页面应用的话,就是综合了 index.html 和 webpack.config.js 两个文件。只捡一些重要的讲:

head 配置竟然还专门有一个网站。
https://vue-meta.nuxtjs.org/

很明显 nuxt.js 内置了这个插件,如果想自己安装也是可以的

 npm i vue-meta
plugins 配置要结合 plugins 目录使用,主要是为 Vue 插件提供服务的

此处以 element-ui 为例:

npm install -D element-ui

文件根目录 plugins/ 目录下创建相应的插件文件,我创建一个名为 element-ui.js 的文件,内容如下:

import Vue from 'vue';
import ElementUI from 'element-ui';

Vue.use(ElementUI);

在nuxt.config.js中,添加配置:

css:[
    'element-ui/lib/theme-chalk/index.css'
],
plugins:[
    '~/plugins/element-ui'
]

这时已经配置完成,可直接在 page 目录里面直接使用了。常见的插件还有 axios ,如果你不使用官方提供的话。可自行在此处配置,不过一般推荐使用 NuxtJs 官方推荐的 @nuxtjs/axios 插件。

使用 modules 来配置 @nuxtjs/axios

第一步安装:

npm i -D @nuxtjs/axios

第二步在 nuxt.config.js 文件的 modules 字段配置:

modules: [
    '@nuxtjs/axios'
]

这时可通过可在组件内直接使用 this.$axios 访问 axios 的实例。如果你需要通过注册拦截器或者改变全局设置来定制化axios, 你需要在 plugins 字段配置,同时在 plugins 目录下创建一个文件,我创建的文件名为 axios.js ,然后在添加到 nuxt.config.js 文件 plugins 字段里面:

plugins: [
    '~/plugins/element-ui',
    '@/plugins/axios'
]

axios.js 的文件内容为:

export default function (ctx) {
    const { $axios, redirect } = ctx;


    /* $axios发送ajax两种方式1、$axios.$get 2、$axios.get */
    console.log($axios.defaults.headers);
    console.log($axios.defaults.baseURL);
    console.log($axios.defaults.timeout);
    console.log($axios.setToken);


    /* 源码提供三个特别有用的函数 */
    /* setBaseURL(baseURL) {
        this.defaults.baseURL = baseURL
    };
    setHeader(name, value, scopes = 'common') {
        for (let scope of Array.isArray(scopes) ? scopes : [scopes]) {
            if (!value) {
                delete this.defaults.headers[scope][name];
                return
            }
            this.defaults.headers[scope][name] = value
        }
    };
    setToken(token, type, scopes = 'common') {
        const value = !token ? null : (type ? type + ' ' : '') + token
        this.setHeader('Authorization', value, scopes)
    }; */



    // 拦截器主要支持下面几个
    /* onRequest(config)
    onResponse(response)
    onError(err)
    onRequestError(err)
    onResponseError(err) */


    $axios.onRequest(config => {
        console.log('Making request to ' + config.url)
    });

    $axios.onError(error => {
        const code = parseInt(error.response && error.response.status)
        if (code === 400) {
            redirect('/400')
        };
    });
};

参考:

  • http://www.axios-js.com/zh-cn/docs/nuxtjs-axios.html
  • https://axios.nuxtjs.org/

完整的 nuxt.config.js


export default {
    // 'spa': 没有服务器端渲染(只有客户端路由导航等)
    // 'universal': 同构应用程序(服务器端呈现+客户端路由导航等)
    mode: 'universal',
    /*
    ** Headers of the page
    */
    head: {
        // 页面标题
        title: process.env.npm_package_name || '',
        // 页面最终展示的标题,%s就是title的占位符
        titleTemplate:"%s - Hero",
        // html和body标签增加属性
        htmlAttrs: {
            lang: 'zh-CN',
            amp: true
        },
        bodyAttrs: {
            class: ['dark-mode', 'mobile']
        },
        // 元数据
        meta: [
            { charset: 'utf-8' },
            { name: 'viewport', content: 'width=device-width, initial-scale=1' },
            // 支持模板☞
            { names: 'template', content: 'Condor' , template:chunk=>`${chunk} - Hero`},
            // 注意:为了避免子组件中的meta标签不能正确覆盖父组件中相同的标签而产生重复的现象,
            // 建议利用 hid 键为meta标签配一个唯一的标识编号。不加hid,子和父相同时,不能覆盖父
            { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
            // 单组件中可调用head方法,自定义此页面的head信息
            /* head () {
                return {
                    title: this.title,
                    meta: [
                        { hid: 'description', name: 'description', content: 'My custom description' }
                    ]
                }
            } */
        ],
        // 元素
        link: [
            { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
        ],
        // style元素,自动创建style标签
        style: [
            { cssText: '.foo { background-color: red }', type: 'text/css' }
        ],
        // script标签
        script: [
            { src: '/test.js', async: true, defer: true }
        ],
    },
    /*
    ** Customize the progress-bar color
    ** 页面最顶端加载进度条
    ** https://zh.nuxtjs.org/api/configuration-loading/#__layout
    */
    loading: { color: 'blue' , height: '5px'},
    /*
    ** Global CSS
    ** 引入全局的 CSS 文件,之后每个页面都会被引入。
    */
    css: [
        '@/assets/css/reset.css', //引入assets下的reset.css全局标签重置样式
        'element-ui/lib/theme-chalk/index.css'
    ],
    /*
    ** Plugins to load before mounting the App
    ** plugins 配置要结合 plugins 目录使用
    ** @和~都表示从根目录开始索引
    */
    plugins: [
        '~/plugins/element-ui',
        '@/plugins/axios'
    ],
    /*
    ** Nuxt.js dev-modules
    ** 构建模块
    */
    buildModules: [
        // Doc: https://github.com/nuxt-community/eslint-module
        // Simple usage
        '@nuxtjs/eslint-module',

        // With options
        // ['@nuxtjs/eslint-module', { /* module options */ }]
    ],
    // Using top level options
    eslint: {
        /* module options */
    },
    /*
    ** Nuxt.js modules
    ** modules是Nuxt.js扩展,里面放置的是NuxtJs推荐的插件
    ** 一般命名类似为'@nuxtjs/axios',
    */
    modules: [
        '@nuxtjs/axios'
    ],
    /*
    ** Build configuration
    ** 这个配置项用来配置 Nuxt.js 项目的构建规则,即 Webpack 的构建配置
    */
    build: {
        /*
        ** You can extend webpack config here
        */
        extend(config, ctx) {
        }
    }
}

五、Nuxt 的默认模版(app.html)和默认布局(default.vue)

在开发应用时,经常会用到一些公用的元素,比如网页的标题是一样的,每个页面都是一模一样的标题。这时候我们有两种方法:

  • 第一种方法是作一个公用的组件出来,公用组件更加灵活,但是每次都需要自己手动引入。
  • 第二种方法是修改默认模版。但是模板每个页面都会引入。

Nuxt 为我们提供了超简单的默认模版订制方法,需要在应用根目录下创建一个 app.html 的文件。在保持 nuxt.config.js 文件里创建的 index.html 不变的情况下,新增内容。记住设置重启下。



  
    {{ HEAD }}
  
  
    

每个页面都有我

{{ APP }}

{{xxx}}里面是读取 index.html 的默认设置。

和默认模板类似的功能还有默认布局,但是从名字上你就可以看出来,默认布局主要针对于页面的统一布局使用。它在位置根目录下的 layouts/default.vue。需要注意的是在默认布局里不要加入头部信息,只是关于