fengxianqi - 前端
type: channel
link: https://www.fengxianqi.com/index.php/category/web
description: 只是一个默认分类
items: 微信jsapi签名报错config:invalid url domain和config:invalid signature问题
items: 如何给项目添加eslint校验
items: 前端配合nginx代理微信头像
items: 为什么说rollup比webpack更适合打包库
items: 微前端qiankun从搭建到部署的实践
items: 前端部署和提效：从静态到node再到负载均衡
items: axios如何利用promise无痛刷新token（二）
items: axios如何利用promise无痛刷新token
items: Nuxt.js实战和配置
items: 你可能不知道的mpvue性能优化技巧

微信jsapi签名报错config:invalid url domain和config:invalid signature问题
type: item
date: 2023-07-26T12:19:12+00:00
description: 背景记录下最近遇到了jsapi签名的报错：config:invalid url domainconfig:invalid signature排查步骤1. 先根据官方文档的步骤先排查一遍这是常规的解决办法，大部分的报错都是这些原因导致的，请确认没问题再往下排查参考：企业微信：https://developer.work.weixin.qq.com/document/path/90509公众号附录5：https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html2. 疑难杂症的处理2.1 在微信中失败，在企业微信正常这个主要针对的是企业微信的自建应用（猜测是企业微信的老应用没有同步问题），可以尝试一下这个操作：可信域名删掉，保存；再重新填写，保存停用当前应用，然后再启用应用参考：https://developer.work.weixin.qq.com/community/question/detail?content_id=164827725388820618882.2 在iOS系统失败，在安卓正常问题原因：获取签名时，安卓应为实时域名，iOS应为首次进入域名解决办法：解法1：链接通过location.href这类方法打开，而不是使用vue、react的路由。解法2：判断是iOS且非企业微信时，把页面首次地址记录到window下的变量中，获取签名时取该地址作为签名地址，可参考https://blog.csdn.net/Sweet__Cat/article/details/88591688解法2,vue2处理方案代码：// router.js在页面首次进入时把url存放到`window._ios_entry_url_`变量中 // 判断是否是ios系统 export function isIos () { return /(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent) } // 判断是否是企业微信 export function isWxWork () { return /wxwork/i.test(navigator.userAgent) } router.beforeEach((to, from, next) => { // 企业微信没有该问题，因此只针对微信内做处理 if (isIos() && !isWxWork() && !window._ios_entry_url_) { // 此处不可使用location.replace window._ios_entry_url_ = window.location.href } next() }) // 在需要获取签名的地方如page.vue，传参url时区分iOS和企业微信 let webUrl = window.location.href if (isIos() && !isWxWork() && window._ios_entry_url_) { // 在ios系统，vue history模式的url不会变，会一直是首次访问的链接：https://www.fengerzh.com/jssdk-invalid-signature/ // 否则会出现签名失败的问题 webUrl = window._ios_entry_url_ } // 发起请求获取签名 getWechatConfig({ webUrl }) 参考：https://developers.weixin.qq.com/community/develop/doc/000ae2cb950808f90d8bc415551800

如何给项目添加eslint校验
type: item
date: 2021-06-24T06:26:00+00:00
description: 前提给一个纯原生的项目添加eslint，如：remember-scroll。目标做到项目运行时自动提示语法错误。做到 git commit 时校验语法错误。步骤1. 安装和配置eslint安装eslintnpm install eslint --save-dev初始化.eslintrc.jsnpx eslint --init可能还需配置一些global变量和exclude node_modules，博主这里最终的.eslint.js如下：module.exports = { "env": { "browser": true, "es2021": true }, "extends": "eslint:recommended", "parserOptions": { "ecmaVersion": 12, "sourceType": "module" }, "rules": { }, globals: { "process": true, "__dirname": true, "module": true, } }; 在根目录添加.eslintignore:node_modules dist2.git commit添加校验使用lint-staged 添加git commit钩子npx mrm@2 lint-staged这个命令会根据package.json里面的依赖工具，自动安装和配置好 husky和 lint-staged，跑完后就能在git commit。该命令还会更改我们的package.json文件，注意其中有一行是： "lint-staged": { "*.js": "eslint --cache --fix" }这就是在git commit时执行的命令： eslint --cache --fix。--cache表示每次只校验有修改的文件，该参数会在根目录生成一个.eslintcache文件，我们需要在.gitignore中忽略这个文件。如果想每次都全量校验所有文件，可以把该参数去掉，去掉后就不会生成.eslintcache文件了。--fix 表示自动修复。3.项目运行时自动提示语法错误由于我项目使用的是rollup来打包，因此需要在rollup.config.js里面去新增一个eslint相关的插件来做到，如果是webpack项目请自行搜索对应的插件或配置方法。安装@rollup/plugin-eslintnpm install @rollup/plugin-eslint --save-devrollup.config.js配置import eslint from '@rollup/plugin-eslint' export default { // ... plugins: [ eslint({ throwOnError: true, throwOnWarning: true, include: ['src/**'], exclude: ['node_modules/**'] }), // ... ] }至此，已经实现了我们的前面提到的两个目标啦，可以测试一下。

前端配合nginx代理微信头像
type: item
date: 2021-06-05T13:32:00+00:00
description: 有个企业微信的应用，最近想要在页面里面展示当前用户的微信头像。用户的头像是在授权登录后，可以拿到userId，然后服务端再通过获取用户个人信息的接口拿到用户的个人信息，包括头像，存到数据库里。为了不增加额外的存储成本，所以不会将用户的头像再上传到我们自己的存储服务，使用时是直接使用微信头像对应的url从微信服务器中取，得到的微信头像url如下：http://wework.qpic.cn/bizmail/ia7iaXMXfXKuOiapqsvvaibY332FrQpdWPGrbbfGhIeObj2dmXZPJfFLOA/0问题如果我们的前端页面想要直接通过 插入到页面中，会出现跨域问题:Access to image at 'http://wework.qpic.cn/bizmail/ia7iaXMXfXKuOiapqsvvaibY332FrQpdWPGrbbfGhIeObj2dmXZPJfFLOA/0' from origin 'http://${your-domain}' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.解决思路上面说了，为了省成本，我们不会在自己的服务器上再存一份头像的图片，所以拷贝到自己服务器的方案就不考虑了。主要两个方案：后端写个接口帮忙获取图片，获取到的图片转为base64返回给前端。前端拼接特殊url前缀，通过nginx代理转发到微信图片服务器。两个方案其实都差不多，我们来看下第二种方案的具体思路。前端发起/wechat_image前缀的请求，url上携带原微信头像的路径，如:/wechat_image?url=http://wework.qpic.cn/bizmail/ia7iaXMXfXKuOiapqsvvaibY332FrQpdWPGrbbfGhIeObj2dmXZPJfFLOA/0&host=wework.qpic.cnnginx配置拦截 location /wechat_image的代理。实现步骤1. 处理url从接口拿到avatar的url：http://wework.qpic.cn/bizmail/ia7iaXMXfXKuOiapqsvvaibY332FrQpdWPGrbbfGhIeObj2dmXZPJfFLOA/0我们可以使用new URL来解析一个url，这里以vue代码为例： 2. nginx拦截配置考虑到微信头像的域名可能有多个，所以我们的代理需要根据url动态改变。但nginx有个安全限制，就是如果proxy_pass后面是一个参数变量，它会报一个no resolver defined to resolve的错误。因此需要在http节点配置一下：resolver 8.8.8.8 ipv6=off;http { # 解决no resolver defined to resolve错误 resolver 8.8.8.8 ipv6=off; server { listen 80; location /wechat_image { proxy_set_header Host "$arg_host"; proxy_pass $arg_url; } # 其他的配置... } }PS: 在nginx中，可以通过$arg_name来获取url后的query参数。可以看到使用了两个参数$arg_url和$arg_host，它们就是上一步在url上拼接的参数，在这个例子里面，他们分别是http://wework.qpic.cn/bizmail/ia7iaXMXfXKuOiapqsvvaibY332FrQpdWPGrbbfGhIeObj2dmXZPJfFLOA/100 和 wework.qpic.cn。本来host参数也是可以在url上截取的，但在nginx中不好处理，所以索性传多一个host参数了。实践证明， proxy_set_header Host "$arg_host";是必不可少的，不然的话微信图片服务器不会返回真正的图片。

为什么说rollup比webpack更适合打包库
type: item
date: 2020-12-28T23:29:00+00:00
description: 前言大概一年前写了个小小的js插件 remember-scroll，并且分享了一篇文章：用Class写一个记住用户离开位置的js插件,是一个纯js库，功能是在用户再次进入页面时能自动定位到上一次浏览的位置，使用webpack+babel打包，里面的webpack和babel的配置至今看来也算是很典型的。前端打包工具有很多——webpack,gulp,rollup等等，网上有很多文章分析它们分别更适合哪些场景，webpack更适合打包组件库、应用程序之类的应用，而rollup更适合打包纯js的类库。因此笔者一直有想法尝试将 remember-scroll 的打包工具由webpack更换为rollup，从实际应用的角度来对比一下两者的区别。从零配置rollup安装rollup和一些插件npm i rollup rollup-plugin-uglify rollup-plugin-filesize @rollup/plugin-node-resolve @rollup/plugin-commonjs -Drollup-plugin-uglify 用于压缩混淆打包后的js。rollup-plugin-filesize打包后在控制台显示文件大小。@rollup/plugin-node-resolve让 rollup 能够识别node_modules的第三方模块。@rollup/plugin-commonjs将 CommonJS 的模块转换为 ES2015 供 rollup 处理。添加babelnpm i @rollup/plugin-babel @babel/core @babel/plugin-transform-runtime @babel/preset-env core-js@2 -D@rollup/plugin-babel rollup 的babel插件。@babel/core babel核心。@babel/plugin-transform-runtime 用于避免污染全局函数（不是必须要用到，但作为类库最好要加上）。@babel/preset-env 自动根据目标浏览器注入相关的polyfill。core-js polyfill的类库，这里使用的是2.x版本（使用3会增加包的大小）。根目录下的babel.config.js如下：const presets = [ [ '@babel/env', { useBuiltIns: 'usage', corejs: { version: 2 } }, ], ] const plugins = [ '@babel/plugin-transform-runtime' ] module.exports = { presets, plugins }根目录下新建rollup.config.js,全部配置如下：import filesize from 'rollup-plugin-filesize' import babel from '@rollup/plugin-babel' import resolve from '@rollup/plugin-node-resolve' import { uglify } from 'rollup-plugin-uglify' import commonjs from '@rollup/plugin-commonjs' const isProd = process.env.NODE_ENV === 'production' export default { input: 'src/index.js', output: { file: isProd ? 'dist/remember-scroll.min.js' : 'dist/remember-scroll.js', format: 'umd', exports: 'default', name: 'RememberScroll', }, plugins: [ resolve(), commonjs(), filesize(), babel({ babelHelpers: 'runtime', exclude: ['node_modules/**'] }), (isProd && uglify()) ] }package.json打包命令如下： "build": "rollup -c --environment NODE_ENV:production && rollup -c", "dev": "rollup -c --watch",总之，一切配置都是与之前webpack版本的一样，都使用了babel。npm run build就可以将资源打包到dist了，接下来我们对比一下webpack和rollup两个…

微前端qiankun从搭建到部署的实践
type: item
date: 2020-09-22T16:46:00+00:00
description: 最近负责的新项目用到了qiankun，写篇文章分享下实战中遇到的一些问题和思考。示例代码： https://github.com/fengxianqi/qiankun-example。在线demo：http://qiankun.fengxianqi.com/单独访问在线子应用：subapp/sub-vuesubapp/sub-react为什么要用qiankun项目有个功能需求是需要内嵌公司内部的一个现有工具，该工具是独立部署的且是用React写的，而我们的项目主要技术选型是vue，因此需要考虑嵌入页面的方案。主要有两条路：iframe方案qiankun微前端方案两种方案都能满足我们的需求且是可行的。不得不说，iframe方案虽然普通但很实用且成本也低，iframe方案能覆盖大部分的微前端业务需求，而qiankun对技术要求更高一些。技术同学对自身的成长也是有强烈需求的，因此在两者都能满足业务需求时，我们更希望能应用一些较新的技术，折腾一些未知的东西，因此我们决定选用qiankun。项目架构后台系统一般都是上下或左右的布局。下图粉红色是基座，只负责头部导航，绿色是挂载的整个子应用，点击头部导航可切换子应用。参考官方的examples代码，项目根目录下有基座main和其他子应用sub-vue，sub-react，搭建后的初始目录结构如下： ├── common //公共模块 ├── main // 基座 ├── sub-react // react子应用 └── sub-vue // vue子应用基座是用vue搭建，子应用有react和vue。基座配置基座main采用是的Vue-Cli3搭建的，它只负责导航的渲染和登录态的下发，为子应用提供一个挂载的容器div，基座应该保持简洁（qiankun官方demo甚至直接使用原生html搭建），不应该做涉及业务的操作。qiankun这个库只需要在基座引入，在main.js中注册子应用,为了方便管理，我们将子应用的配置都放在：main/src/micro-app.js下。const microApps = [ { name: 'sub-vue', entry: '//localhost:7777/', activeRule: '/sub-vue', container: '#subapp-viewport', // 子应用挂载的div props: { routerBase: '/sub-vue' // 下发路由给子应用，子应用根据该值去定义qiankun环境下的路由 } }, { name: 'sub-react', entry: '//localhost:7788/', activeRule: '/sub-react', container: '#subapp-viewport', // 子应用挂载的div props: { routerBase: '/sub-react' } } ] export default microApps 然后在src/main.js中引入import Vue from 'vue'; import App from './App.vue'; import { registerMicroApps, start } from 'qiankun'; import microApps from './micro-app'; Vue.config.productionTip = false; new Vue({ render: h => h(App), }).$mount('#app'); registerMicroApps(microApps, { beforeLoad: app => { console.log('before load app.name====>>>>>', app.name) }, beforeMount: [ app => { console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name); }, ], afterMount: [ app => { console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name); } ], afterUnmount: [ app => { console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name); }, ], }); start();在App.vue中，需要声明micro-app.js配置的子应用挂载div（注意id一定要一致），以及基座布局相关的，大概这样： 头部导…

前端部署和提效：从静态到node再到负载均衡
type: item
date: 2020-01-20T07:58:00+00:00
description: 前言相信很多前端同学对 vue 或 react 的开发很熟悉了，也知道如何去打包生成一个生产环境的包，但对于生产环境的部署可能有些同学了解比较少。小公司可能都是后端帮忙部署了，大公司会有专门的运维同学部署，对于生产环境的部署工作有些同学接触的不多，所以这次来分享和总结下前端项目部署相关的实战经验：从静态站点的部署，到 node 项目的部署，再到负载均衡的部署，顺便也会分享一下提高部署效率的脚本和方法。准备工作一台或多台服务器或虚拟机。一份 vue 或 react 项目的打包后的文件。一份 node 项目的源码。静态站点的部署静态站点的部署指的是前端的 html/css/js 资源的部署，如 vue 或 react 打包后生成的 html，css 和 js 文件，我们将这些文件上传到服务器后，通过 Nginx 将这些资源暴露到公网上。上传文件到服务器就是将文件人工将打包后的文件拷贝到服务器上，这个很简单，但如果每次都是人工拷贝，部署效率未免会低了一些，所以建议使用一些脚本或工具。在大公司，一般对服务器权限控制得很严格，可能需要各种跳板机或动态密码等，而大公司一般都有专门的运维人员或者 CI/CD 工具。小公司可能相对自由一些，可以允许个人直接 ssh 连接服务器，此时可以配合使用rsync或scp命令（Linux 或 Mac 系统）来一键上传文件到服务器上，给部署提效。在这里分享一下以前使用过的部署脚本,在前端项目根目录新建一个名为deploy.sh的文件：#!/bin/bash function deploy() { # 测试服务器 test_host="root@test_server_ip" # 生产服务器 prod_host="root@prod_server_ip" project_path="/srv/YourProject" if [ "$1" == "prod" ]; then target="$prod_host:$project_path" else target="$test_host:$project_path" fi rsync -azcuP ./dist/ --exclude node_modules --exclude coverage --exclude .env --exclude .nyc_output --exclude .git "$target" echo "deploy to $target" } deploy $@以上脚本的意思是将/dist目录下的所有文件，上传到对应服务器的/srv/YourProject目录下。测试环境的部署是直接在根目录运行./deploy.sh，该命令会将/dist目录直接上传到root@test_server_ip服务器上；生产环境的部署是在后面加一个参数./deploy.sh prod，这样可以实现多环境部署。更进一步的做法是将运行脚本的命令直接写进package.json中，如： "scripts": { "build": "vue-cli-service build --mode staging", "deploy": "npm run build && ./deploy.sh", "deploy:prod": "npm run build && ./deploy.sh prod" },这样，通过npm run deploy命令就可以实现直接打包并部署到测试环境了。如果你的公司目前还在用人工拷贝或 FTP 工具这种低效的部署方式，不妨试一下用上面的脚本来提效哦。PS：由于 rsync 命令只在 Linux 或 Mac 才有，所以只有开发环境是 Linux 或 Mac 的用户才可以运行哦，Windows 用户是没法跑这个命令的。编写网站的 conf上传文件到服务器后，就可以着手配置 nginx 了。一般 nginx 的配置都会放在/etc/nginx/conf.d目录下，我们在该目录新建一个test.conf作为该项目的配置：server { listen 80; server_name your-domain.com; # 域名 location / { root /srv/YourProject; # 网站源码根目录 index index.html; } location /api { proxy_pass http://localhost:8080; # 反向代理后端接口地址 } }一般来说，静态站点只需配置以上几个就可以了，server_name表示域名，需要先解析到服务器的公网 ip；root表示服务器中代码所在的位置，index指明了默认的处理文件是index.html；location /api是反向代理后端服务(这里假设了后端服务部署…

axios如何利用promise无痛刷新token（二）
type: item
date: 2019-11-13T03:07:00+00:00
description: 前言前段时间写了篇文章《axios如何利用promise无痛刷新token》，陆陆续续收到一些反馈。发现不少同学会想要从在请求前拦截的思路入手，甚至收到了几个邮件来询问博主遇到的问题，所以索性再写一篇文章来说说另一个思路的实现和注意的地方。过程会稍微啰嗦，不想看实现过程的同学可以直接拉到最后面看最终代码。PS：在本文就略过一些前提条件了，请新同学阅读本文前先看一下前一篇文章《axios如何利用promise无痛刷新token》。前提条件前端登录后，后端返回token和token有效时间段tokenExprieIn，当token过期时间到了，前端需要主动用旧token去获取一个新的token，做到用户无感知地去刷新token。PS: tokenExprieIn是一个单位为秒的时间段，不建议使用绝对时间，绝对时间可能会由于本地和服务器时区不一样导致出现问题。实现思路方法一在请求发起前拦截每个请求，判断token的有效时间是否已经过期，若已过期，则将请求挂起，先刷新token后再继续请求。方法二不在请求前拦截，而是拦截返回后的数据。先发起请求，接口返回过期后，先刷新token，再进行一次重试。前文已经实现了方法二，本文会从头实现一下方法一。实现基本骨架在请求前进行拦截，我们主要会使用axios.interceptors.request.use()这个方法。照例先封装个request.js的基本骨架：import axios from 'axios' // 从localStorage中获取token，token存的是object信息，有tokenExpireTime和token两个字段 function getToken () { let tokenObj = {} try { tokenObj = storage.get('token') tokenObj = tokenObj ? JSON.parse(tokenObj) : {} } catch { console.error('get token from localStorage error') } return tokenObj } // 给实例添加一个setToken方法，用于登录后方便将最新token动态添加到header，同时将token保存在localStorage中 instance.setToken = (obj) => { instance.defaults.headers['X-Token'] = obj.token window.localStorage.setItem('token', JSON.stringify(obj)) // 注意这里需要变成字符串后才能放到localStorage中 } // 创建一个axios实例 const instance = axios.create({ baseURL: '/api', timeout: 300000, headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' } }) // 请求发起前拦截 instance.interceptors.request.use((config) => { const tokenObj = getToken() // 为每个请求添加token请求头 config.headers['X-Token'] = tokenObj.token // **接下来主要拦截的实现就在这里** return config }, (error) => { // Do something with request error return Promise.reject(error) }) // 请求返回后拦截 instance.interceptors.response.use(response => { const { code } = response.data if (code === 1234) { // token过期了，直接跳转到登录页 window.location.href = '/' } return response }, error => { console.log('catch', error) return Promise.reject(error) }) export default instance与前文略微不同的是，由于方法二不需要用到过期时间，所以前文localStorage中只存了token一个字符串，而方法一这里需要用到过期时间了，所以得存多一个数据，因此localStorage中存的是Object类型的数据，从localStorage中取值出来需要…

axios如何利用promise无痛刷新token
type: item
date: 2019-08-28T03:56:00+00:00
description: 需求最近遇到个需求：前端登录后，后端返回token和token有效时间，当token过期时要求用旧token去获取新的token，前端需要做到无痛刷新token，即请求刷新token时要做到用户无感知。需求解析当用户发起一个请求时，判断token是否已过期，若已过期则先调refreshToken接口，拿到新的token后再继续执行之前的请求。这个问题的难点在于：当同时发起多个请求，而刷新token的接口还没返回，此时其他请求该如何处理？接下来会循序渐进地分享一下整个过程。实现思路由于后端返回了token的有效时间，可以有两种方法：方法一：在请求发起前拦截每个请求，判断token的有效时间是否已经过期，若已过期，则将请求挂起，先刷新token后再继续请求。方法二：不在请求前拦截，而是拦截返回后的数据。先发起请求，接口返回过期后，先刷新token，再进行一次重试。两种方法对比方法一优点： 在请求前拦截，能节省请求，省流量。缺点： 需要后端额外提供一个token过期时间的字段；使用了本地时间判断，若本地时间被篡改，特别是本地时间比服务器时间慢时，拦截会失败。PS：token有效时间建议是时间段，类似缓存的MaxAge，而不要是绝对时间。当服务器和本地时间不一致时，绝对时间会有问题。方法二优点：不需额外的token过期字段，不需判断时间。缺点： 会消耗多一次请求，耗流量。综上，方法一和二优缺点是互补的，方法一有校验失败的风险（本地时间被篡改时，当然一般没有用户闲的蛋疼去改本地时间的啦），方法二更简单粗暴，等知道服务器已经过期了再重试一次，只是会耗多一个请求。在这里博主选择了 方法二。实现这里会使用axios来实现，方法一是请求前拦截，所以会使用axios.interceptors.request.use()这个方法；而方法二是请求后拦截，所以会使用axios.interceptors.response.use()方法。封装axios基本骨架首先说明一下，项目中的token是存在localStorage中的。request.js基本骨架:import axios from 'axios' // 从localStorage中获取token function getLocalToken () { const token = window.localStorage.getItem('token') return token } // 给实例添加一个setToken方法，用于登录后将最新token动态添加到header，同时将token保存在localStorage中 instance.setToken = (token) => { instance.defaults.headers['X-Token'] = token window.localStorage.setItem('token', token) } // 创建一个axios实例 const instance = axios.create({ baseURL: '/api', timeout: 300000, headers: { 'Content-Type': 'application/json', 'X-Token': getLocalToken() // headers塞token } }) // 拦截返回的数据 instance.interceptors.response.use(response => { // 接下来会在这里进行token过期的逻辑处理 return response }, error => { return Promise.reject(error) }) export default instance这个是项目中一般的axios实例的封装，创建实例时，将本地已有的token放进header，然后export出去供调用。接下来就是如何拦截返回的数据啦。instance.interceptors.response.use拦截实现后端接口一般会有一个约定好的数据结构，如：{code: 1234, message: 'token过期', data: {}}如我这里，后端约定当code === 1234时表示token过期了，此时就要求刷新token。instance.interceptors.response.use(response => { const { code } = response.data if (code === 1234) { // 说明token过期了,刷新token return refreshToken().then(res => { // 刷新token成功，将最新的token更新到header中，同时保存在localStorage…

Nuxt.js实战和配置
type: item
date: 2019-08-04T16:38:00+00:00
description: 前段时间刚好公司有项目使用了Nuxt.js来搭建，而刚好在公司内部做了个分享，稍微再整理一下发出来。本文比较适合初用Nuxt.js的同学，主要讲下搭建过程中做的一些配置。建议初次使用Nuxt.js的同学先过一遍官方文档，再回头看下我这篇文章。一、为什么要用Nuxt.js原因其实不用多说，就是利用Nuxt.js的服务端渲染能力来解决Vue项目的SEO问题。二、Nuxt.js和纯Vue项目的简单对比1. build后目标产物不同vue: distnuxt: .nuxt2. 网页渲染流程vue: 客户端渲染，先下载js后，通过ajax来渲染页面；nuxt： 服务端渲染，可以做到服务端拼接好html后直接返回，首屏可以做到无需发起ajax请求；3. 部署流程vue： 只需部署dist目录到服务器，没有服务端，需要用nginx等做Web服务器；nuxt： 需要部署几乎所有文件到服务器（除node_modules，.git），自带服务端，需要pm2管理（部署时需要reload pm2），若要求用域名，则需要nginx做代理。4. 项目入口vue: /src/main.js，在main.js可以做一些全局注册的初始化工作；nuxt: 没有main.js入口文件，项目初始化的操作需要通过nuxt.config.js进行配置指定。三、从零搭建一个Nuxt.js项目并配置新建一个项目直接使用脚手架进行安装：npx create-nuxt-app <项目名>大概选上面这些选项。值得一说的是，关于Choose custom server framework（选择服务端框架），可以根据你的业务情况选择一个服务端框架，常见的就是Express、Koa，默认是None，即Nuxt默认服务器，我这里选了Express。选择默认的Nuxt服务器，不会生成server文件夹，所有服务端渲染的操作都是Nuxt帮你完成，无需关心服务端的细节，开发体验更接近Vue项目，缺点是无法做一些服务端定制化的操作。选择其他的服务端框架，比如Express，会生成server文件夹，帮你搭建一个基本的Node服务端环境，可以在里面做一些node端的操作。比如我公司业务需要（解析protobuf）使用了Express，对真正的服务端api做一层转发，在node端解析protobuf后，返回json数据给客户端。还有Choose Nuxt.js modules（选择nuxt.js的模块），可以选axios和PWA,如果选了axios，则会帮你在nuxt实例下注册$axios，让你可以在.vue文件中直接this.$axios发起请求。开启eslint检查在nuxt.config.js的build属性下添加: build: { extend (config, ctx) { // Run ESLint on save if (ctx.isDev && ctx.isClient) { config.module.rules.push({ enforce: 'pre', test: /\.(js|vue)$/, loader: 'eslint-loader', exclude: /(node_modules)/ }) } } }这样开发时保存文件就可以检查语法了。nuxt默认使用的规则是@nuxtjs（底层来自eslint-config-standard），规则配置在/.eslintrc.js:module.exports = { root: true, env: { browser: true, node: true }, parserOptions: { parser: 'babel-eslint' }, extends: [ '@nuxtjs', // 该规则对应这个依赖： @nuxtjs/eslint-config 'plugin:nuxt/recommended' ], // add your custom rules here rules: { 'nuxt/no-cjs-in-config': 'off' } } 如果不习惯用standard规则的团队可以将@nuxtjs改成其他的。使用dotenv和@nuxtjs/dotenv统一管理环境变量在node端，我们喜欢使用dotenv来管理项目中的环境变量，把所有环境变量都放在根目录下的.env中。安装：npm i dotenv使用：在根目录下新建一个.env文件，并写上需要管理的环境变量，比如服务端地址APIHOST:APIHOST=http://your_server.com/api在/server/index.js中使用（该文件是选Express服务端框架自动生成的）：require('dotenv').config() //…

你可能不知道的mpvue性能优化技巧
type: item
date: 2019-04-16T03:29:00+00:00
description: 最近一直在折腾mpvue写的微信小程序的性能优化，分享下实战的过程。先上个优化前后的图：可以看到打包后的代码量从813KB减少到387KB,Audits体验评分从B到A，效果还是比较明显的。其实这个指标说明不了什么，而且轻易就可以做到，更重要的是优化小程序运行过程中的卡顿感，请耐心往下看。常规优化常规的Web端优化方法在小程序中也是适用的，而且不可忽视。一、压缩图片这一步最简单，但是容易被忽视。在tiny上在线压缩，然后下载替换即可。我这项目的压缩率高达72%，可以说打包后的代码从813KB降到387KB大部分都是归功于压缩图片了。二、移除无用的库我之前在项目中使用了Vant Weapp，在static目录下引入了整个库，但实际上我只使用了button,field,dialog等几个组件，实在是没必要。所以干脆移除掉了，微信小程序自身提供的button，wx.showModal等一些组件基本可以满足需求，自己手写一下样式也不用花什么时间。在这里建议大家，在微信小程序中，尽量避免使用过多的依赖库。不要贪图方便而引入一些比较大的库，小程序不同于Web，限制比较多，能自己写一下就尽量自己写一下吧。小程序的优化咱们首先得看一下官方优化建议，大多是围绕这个建议去做。一、开启Vue.config._mpTrace = true这个是mpvue性能优化的一个黑科技啊，可能大多数同学都不知道这个，我在官方文档都没有搜到到这个配置，我真的是服了。我能找到这个配置也是Google机缘巧合下看到的，出处：mpvue重要更新，页面更新机制进行全面升级具体做法是在/src/main.js添加Vue.config._mpTrace = true，如：Vue.config._mpTrace = true Vue.config.productionTip = false App.mpType = 'app'添加了Vue.config._mpTrace属性，这样就可以看到console里会打印每500ms更新的数据量。如图：如果数据更新量很大，会明显感觉小程序运行卡顿，反之就流畅。因此我们可以根据这个指标，逐步找出性能瓶颈并解决掉。二、精简data1. 过滤api返回的冗余数据后端的api可能是需要同时为iOS，Android，H5等提供服务的，往往会有些冗余的数据小程序是用不到的。比如api返回的一个文章列表数据有很多字段：this.articleList = [ { articleId: 1, desc: 'xxxxxx', author: 'fengxianqi', time: 'xxx', comments: [ { userId: 2, conent: 'xxx' } ] }, { articleId: 2 // ... }, // ... ]假设我们在小程序中只需要用到列表中的部分字段，如果不对数据做处理，将整个articleList都setData进去，是不明智的。小程序官方文档： 单次设置的数据不能超过1024kB，请尽量避免一次设置过多的数据。可以看出，内存是很宝贵的，当articleList数据量非常大超过1M时，某些机型就会爆掉（我在iOS中遇到过很多次）。因此，需要将接口返回的数据剔除掉不需要的，再setData，回到我们上面的articleList例子，假设我们只需要用articleId和author这两个字段，可以这样：import { getArticleList } from '@/api/article' export default { data () { return { articleList: [] } } methods: { getList () { getArticleList().then(res => { let rawList = res.list this.articleList = this.simplifyArticleList(rawList) }) }, simplifyArticleList (list) { return list.map(item => { return { articleId: item.articleId, author: item.author // 需要哪些字段就加上哪些字段 } }) } } }这里我们将返回的数据通过simplifyArticleList 来精简数据，此时过滤后的articleList中的数据类似：[ {articleId: 1, author: 'fengxianqi'}, {articleId: 2, author: 'others'} // ... ]当然，如果你的需求中是所有数据都要用到（或者大部分数据），就没必要做一层精简了，收益不大。毕…