Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nuxt网站搭建 #2

Open
nh0007 opened this issue Dec 19, 2019 · 0 comments
Open

Nuxt网站搭建 #2

nh0007 opened this issue Dec 19, 2019 · 0 comments

Comments

@nh0007
Copy link
Owner

nh0007 commented Dec 19, 2019

背景

公司近来有重构主网站的打算,考虑到需要SEO(搜索引擎优化),传统的基于Vue的单页应用显然不太合适,从头搭建一个服务端渲染的应用也相当复杂,所幸Nuxt,该框架默认基于Vue,集合了Vue 服务端渲染,开箱即用,可谓是Vue SSR最为简捷妥当的选择。

SSR浅见

SSR,Server-Side Rendering简写,即服务端渲染,也称同构应用。相较于SPA项目,SSR的出现主要解决了两个问题:

  • SEO问题:SPA项目初始加载的HTML文档只是一个空的容器,并无实质性内容,很难被搜索引擎爬虫抓取搜集,因此不利于SEO。而SSR将首屏直接在服务端渲染成HTML字符串直接返回给客户端(首屏直出),解决了SEO问题。
  • 首页白屏问题:SPA项目加载首页时需要加载HTML-->加载js文件-->在js里发起ajax请求-->将获取到的数据渲染到页面上,因此首页从加载到完全渲染完毕需要经历较长时间。而SSR项目将三次请求简化为一次,直接拿到首页html结构,不需再发送ajax请求,因此会加快首页渲染,有效处理白屏问题。

需要注意的一点是,SSR项目仅仅是渲染首屏,其他页面仍然是由客户端渲染的。
关于SSR更多信息,可以参照这里,SSR方案也并非毫无争议,这篇文章提出了些许提问,可以参照阅读。

项目搭建

Nuxt项目搭建比较简单,可以参照官网,需要注意的一点是在搭建过程中选择Nuxt模式时有两种:SPA or Universal,不要选择SPA,否则就跟普通的Vue单页应用没啥差别,体现不出Nuxt服务端渲染的价值。

Nuxt搭建的项目与Vue搭建的项目的异同

  • 路由
    Nuxt搭建的项目自带Vue-Router,因此不需要额外的安装,它会根据pages目录结构自动生成路由配置,路由出口使用Nuxt标签,入口使用nuxt-link标签,其余使用与传统的Vue项目大致相同,详情可见这里

  • 数据管理
    当项目根目录下存在store目录时,Nuxt将引入Vuex模块,不需我们自己安装。Nuxt支持两种使用Vuex的模式,模块模式和经典模式,官方推荐模块模式,它会将store目录下的每个js(或者ts)文件转换为各个状态模块,而index.js(ts)作为根模块,不需要我们自己去export Vuex Store,Nuxt自己会帮助我们处理。详情可见这里

  • 入口文件
    Nuxt项目没有像传统Vue项目有一个统一的入口文件,因此全局引入第三方插件或者css文件会有所不同(传统的Vue项目可以在入口文件进行导入),两者都需要到nuxt.config.js文件进行配置,第三方插件的配置可以参照这里,全局引入css样式可以参照这里

  • 项目结构
    Nuxt搭建的项目默认帮我们对项目结构进行规划,各个目录有对应的含义,结构清晰,详情可以参见这里

项目配置

添加TypeScript支持

Nuxt脚手架目前暂不支持添加TypeScript选项,相关的提案正在进行中,等Vue3出来后,可能会有更多的支持。

添加TypeScript流程如下:

添加@nuxt/typescript-build

npm install --save-dev @nuxt/typescript-build
# OR
yarn add --dev @nuxt/typescript-build

修改nuxt.config.js,添加依赖

// nuxt.config.js
export default {
  // 中略
  buildModules: [
    '@nuxtjs/eslint-module',
    '@nuxt/typescript-build'
  ],
  // 中略
}

创建tsconfig.json文件

// tsconfig.json
{
  "compilerOptions": {
    "target": "es2018",
    "module": "esnext",
    "moduleResolution": "node",
    "lib": [
      "esnext",
      "esnext.asynciterable",
      "dom"
    ],
    "esModuleInterop": true,
    "allowJs": true,
    "sourceMap": true,
    "strict": true,
    "noEmit": true,
    "baseUrl": ".",
    "paths": {
      "~/*": [
        "./*"
      ],
      "@/*": [
        "./*"
      ]
    },
    "types": [
      "@types/node",
      "@nuxt/types"
    ]
  },
  "exclude": [
    "node_modules"
  ]
}

在目录下添加types文件夹,文件夹下添加vue-shim.d.ts文件,添加如下代码:

declare module "*.vue" {
  import Vue from 'vue'
  export default Vue
}

安装@nuxt/typescript-runtime

npm install @nuxt/typescript-runtime
# OR
yarn add @nuxt/typescript-runtime

修改package.json中script命令,将nuxt改为nuxt-ts:

"scripts": {
  "dev": "nuxt-ts",
  "build": "nuxt-ts build",
  "generate": "nuxt-ts generate",
  "start": "nuxt-ts start"
}

将nuxt.config.js改为nuxt.config.ts,将server文件夹下的index.js改为index.ts,并将index.ts文件中的require(../nuxt.config.js)改为require(../nuxt.config.ts),由于我们构建时安装了Element,所以还需把element-ui.js改为element-ui.ts,这时该文件会报错,在types文件夹下添加global.d.ts文件,添加以下代码:

declare module 'element-ui/lib/locale/lang/en' {}

在nuxt.config.ts添加typescript检查配置:

typescript: {
  typeCheck: true,
  ignoreNotFoundWarnings: true
}

接下来,为了在Vue中使用TypeScript更为便捷,添加装饰器vue-property-decorator依赖:

npm install --save-dev vue-property-decorator
# OR
yarn add --dev vue-property-decorator

同时,配置tsconfig.json,添加对装饰器的支持

{
  "compilerOptions": {
    "experimentalDecorators": true,
  }
}

因为使用了TypeScript,我们需要修改eslint配置,Nuxt添加了专门的eslint配置,先移除原有的配置:

npm uninstall @nuxtjs/eslint-config
# OR
yarn remove @nuxtjs/eslint-config

添加新的eslint配置

npm i -D @nuxtjs/eslint-config-typescript
# OR
yarn add -D @nuxtjs/eslint-config-typescript

修改package.json:

  "scripts": {
    "dev": "nuxt-ts",
    "build": "nuxt-ts build",
    "start": "nuxt-ts start",
    "generate": "nuxt-ts generate",
-    "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
+    "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore ."
  }

修改.eslintrc.js配置:

  parserOptions: {
-   parser: 'babel-eslint'
  },
  extends: [
-   '@nuxtjs',
+   '@nuxtjs/eslint-config-typescript',
    'prettier',
    'prettier/vue',
    'plugin:prettier/recommended',
    'plugin:nuxt/recommended'
  ]

接下来,我们就可以在vue中使用TypeScript和装饰器:

import { Vue, Component } from 'vue-property-decorator'
import Logo from '~/components/Logo.vue'

@Component({
  components: {
    Logo
  }
})
export default class IndexPage extends Vue {}

至此,Nuxt添加TypeScript支持完毕。更多介绍可以参照这里

添加路由鉴权

路由权限是前端一个很常见的需求,登陆和未登陆下可以访问的页面、页面的内容都会有所不同,Nutx配置路由权限可以有两种方法,以下简要介绍下:

简易方式

第一种方式比较简单,适用于路由路径比较确定、页面不多的项目,在plugins下添加auth.ts,添加对路由控制的代码,如下:

// 不需登陆权限的路径列表
const noPermissionList: string[] = ['/login']
export default ({ app, store }) => {
  app.router.beforeEach((to, from, next) => {
    if (noPermissionList.indexOf(to.path) !== -1) {
      next()
    } else {
      next({ path: '/login' })
    }
  })
}

添加好后,还需要在nuxt.config.ts中进行配置,才会生效。

plugins: [
  '@/plugins/auth'
]

使用Auth Module

由于公司网站项目较大,且需要登陆权限的页面与不需要登陆权限的页面都比较多,通过数组去维护路由列表的方式随着页面逐渐增多会越来越难以应付,于是我们使用了官方Auth模块,接下来我们在本项目中添加Auth模块支持。

在使用Auth模块时,我们首先要确保项目中使用了Vuex,Nuxt内置了Vuex支持,因此不需要我们自己去安装依赖,但我们要如何激活Vuex呢,首先在项目根目录下必须存在store目录,同时,在store下创建任意名字的js或者ts文件,Nuxt会根据你命名的文件在Vuex里创建一个模块,如果该文件叫index,那么这个文件将作为根模块。

为何要先激活Vuex,因为Auth Module本质上就是Vuex的一个模块,同时,Auth Module必须跟axios一起搭配使用,首先安装模块:

yarn add @nuxtjs/auth @nuxtjs/axios
# OR
npm install @nuxtjs/auth @nuxtjs/axios

接下来,在nuxt.config.ts对其进行配置:

modules: [
  '@nuxtjs/axios',
  '@nuxtjs/auth'
],
auth: {
    strategies: {
      local: {
        endpoints: {
          login: {
            // 登陆接口url
            url: '',
            method: 'post',
            propertyName: false
          },
          // 登出接口url
          logout: {
            url: '',
            method: 'post'
          },
          // 获取用户信息接口url
          user: {
            url: '',
            method: 'get',
            propertyName: false
          }
        },
        tokenRequired: true,
        tokenType: false
      }
    },
    // 路由跳转配置
    redirect: {
      // login: '/first',
      // logout: '/',
      // callback: '/login',
      // home: '/'
    }
  }

添加配置后,我们就可以使用对应的方法进行登陆登出。
登陆时:

try {
  await this.$auth.loginWith('local', {
    data: {
      username: 'username'
      password: 'password',
    }
  })
  // do something on success
} catch (e) {    
  // do something on failure 
}

登出时:

await this.$auth.logout()

由于Auth是Vuex的一个模块,且内置了不少状态,如loggedIn:用于判断用户是否登陆;user:用户信息对象。
在组件内我们可以这样访问:

computed: {
  ...mapState('auth', ['loggedIn', 'user'])
}

或者这样:

loggedIn() {
  return this.$auth.loggedIn
},
user() {
  return this.$auth.user
}

接下来,我们来看看如何为页面添加权限或者放开权限,有以下两种方式:

  • 全局设置权限限制,适用于页面大多需要权限的情况:
    在nuxt.config.ts中添加router配置:
router: {
  middleware: ['auth']
}

这样所有的页面都需要登陆权限,如果部分不需要登陆权限即可查看,那么可以在该页面下设置:

export default {
  auth: false
}
  • 页面单独设置权限,适用于只有少数页面需要登陆权限的情况:
    在需要权限的界面组件添加
export default {
  middleware: 'auth'
}

则该页面需要登陆权限才能查看

至此,我们完成了路由鉴权设置,详细可以参照这里

修改端口

nuxt.config.ts添加端口配置,更多可以参见这里

server: {
  port: 9995, // default: 3000
  host: 'localhost' // default: localhost
}

安装CSS预处理器

如果我们仅仅想要使用CSS预处理的话,我们仅仅需要安装上对应的npm依赖包以及Webpack 加载器就行,参见这里,但考虑到预处理器有一些变量、mixin需要全局共用,我们可以添加上style-resources-module模块,这样在各个页面任意使用我们定义的东西。
首先安装模块

yarn add @nuxtjs/style-resources
# OR
npm install @nuxtjs/style-resources

接下来安装预处理依赖,本项目我们使用的是scss,因此安装:

yarn add sass-loader sass
# OR
npm install sass-loader sass

修改nuxt.config.ts文件

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

更多配置可以看这里

按需加载element-ui

在项目实际使用过程中,我们常常不需要用到插件的所有东西,因此我们可以通过按需加载进行优化,在Nuxt项目中进行按需加载与Vue项目大同小异,以前是操作流程:
首先安装相应模块

yarn add babel-plugin-component -dev
# OR
npm install babel-plugin-component --save-dev

修改nuxt.config.ts配置

build: {
  ...other
  // 按需引入element-ui
  babel: {
    plugins: [
      [ "component", 
        {
          "libraryName": "element-ui",
          "styleLibraryName": "theme-chalk"
        }
      ] 
    ] 
  },
}

修改plugins/element-ui.ts

import Vue from 'vue'
// 全局引用
// import Element from 'element-ui'
// Vue.use(Element, { locale })
// 按需引用
import {
  Button,
  Input,
} from 'element-ui'
import locale from 'element-ui/lib/locale/lang/en'

Vue.use(Button, { locale })
Vue.use(Input, { locale })

至此,完成了Nuxt项目对Element UI的按需引用

添加低版本浏览器升级提示

由于运营系统并不支持IE10以下浏览器,所以在判断浏览器版本后,在低版本浏览器添加升级提示会更加友善,以下是添加过程中的踩坑记录:
首先我们想到的是添加中间件,中间件能在进入页面之前执行,从而在其中判断浏览器的版本情况,在低版本浏览器下进行升级提示,过程如下:
在middleware目录下添加checkIE.ts文件,添加如下内容

export default function(context) {
  const userAgent = process.server
    ? context.req.headers['user-agent']
    : navigator.userAgent
  const isIE = userAgent.includes('compatible') && userAgent.includes('MSIE') // 判断是否IE<11浏览器
  const isIE11 = userAgent.includes('Trident') && userAgent.includes('rv:11.0')

  if (isIE && !isIE11) {
    alert('浏览器版本过低,请升级浏览器')
    return context.redirect(
      'https://support.dmeng.net/upgrade-your-browser.html'
    )
  }
}

然后在nuxt.config.ts添加配置:

router: {
  middleware: ['auth', 'checkIE']
}

添加配置后运行项目后发现在IE浏览器下context.req.headers['user-agent']的值为:"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
通过这个值并没办法判断是否是IE浏览器以及IE版本,而navigator.userAgent的值为:"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; InfoPath.3)",则可以进行判断,但是如果直接直接使用这个值就会显示navigator未定义从而报错,因为服务端是没有navigator这个变量的,网上说添加process.client判断是否在浏览器端在取这个值就不会报错,关键是如果中间件貌似默认就是在服务端运行的,因此就一直不会取到navigator.userAgent值,只会取到我们判断不出是否IE浏览器的context.req.headers['user-agent']的值。

所以就只能换一种思路了,将判断的逻辑添加到html中,那么就需要修改html模板了,Nuxt可以在根目录下添加app.html覆盖默认模板,详情可参见这里[https://zh.nuxtjs.org/guide/views],
修改后的html模板如下:

<!DOCTYPE html>
<!--[if lt IE 10]>
  <script>
    alert('浏览器版本过低,请升级浏览器,建议使用谷歌浏览器');
    window.location = 'https://support.dmeng.net/upgrade-your-browser.html'
  </script>
<![endif]-->
<!--[if (gt IE 10)|!(IE)]><!-->
  <html {{ HTML_ATTRS }}>
  <head {{ HEAD_ATTRS }}>
    {{ HEAD }}
  </head>
  <body {{ BODY_ATTRS }}>
    {{ APP }}
  </body>
<!--<![endif]-->
</html>

从而,就可以在低版本浏览器弹出提示,从而跳转到升级浏览器页面啦~

参考文章链接

路由鉴权
TypeScript支持

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant