浏览器渲染各个阶段

First Paint

浏览器从白屏第一次展现内容

FP发生在body中第一个script脚本之前的CSS解析和JS执行完成之后。换句话说就是第一脚本之前的DOM和CSSOM准备就绪之后,便会着手渲染第一脚本前的内容。

如果第一脚本前的JS和CSS加载完了,body中的脚本还未下载完成,那么浏览器就会利用构建好的局部CSSOM和DOM提前渲染第一脚本前的内容(触发FP);如果第一脚本前的JS和CSS都还没下载完成,body中的脚本就已经下载完了,那么浏览器就会在所有JS脚本都执行完之后才触发FP。

DocumentLoad

First Contentful paint

First Meaningful paint

Largest Contentful paint

相关参考

  • First Paint 和 DOMContentLoaded、load事件的触发没有绝对的关系,FP可能在他们之前,也可能在他们之后,这取决于影响他们触发的因素的各自时间(FP:第一脚本前CSSOM和DOM的构建速度;DOMContentLoaded:HTML文档自身以及HTML文档中所有JS、CSS的加载速度;load:图片、音频、视频、字体的加载速度)
  • DOMContentLoadedload事件也没有强制的先后顺序,DOMContentLoaded一般在load事件之前触发,但也可能在load事件之后触发。
  • 默认情况下,CSS外链之间是谁先加载完成谁先解析,但是JS外链之间即使先加载完成,也得按顺序执行。
  • link外链后面紧跟script外链,须先等link parse完成之后,script才会执行,即使script先下载完成。script后面紧跟link,也是一样,会等script执行完之后,link才会parse。
  • 如果script之后紧跟几个link且script比这几个link的下载时间都长,那script执行完成之后link是按顺序执行。
  • RRDL
    • R:send Request,发送资源请求
    • R:receive Response,接收到服务端响应
    • D:receive Data,开始接受服务端数据(一个资源可能执行多次)
    • L:finish Loading,完成资源下载
  • 浏览器在RRDL的时候,在D(Receive data)这个步骤可能执行多次。
  • TTFB: Time To First Byte,第一个字节返回的时间,这个是对应send Request到receive Response这段时间。
  • 浏览器会给HTML中的资源文件进行等级分类(Hightest /High /Meduim/ Low/ Lowest),一般HTML文档自身、head中的CSS都是Hightest,head中JS一般是High,而图片一般是Low,而设置了async/defer的脚本一般是Low,gif图片一般是Lowest。

reference Link

https://www.xmetal.cc/?p=1473‎(opens in a new tab)

PWA

创建manifest.webmanifest文件

清单文件可以具有任何名称,但通常是manifest.webmanifest从根目录(您网站的顶级目录)来命名 和提供的。

{
  "short_name": "Weather",
  "name": "Weather: Do I need an umbrella?",
  "description": "Weather forecast information",
  "icons": [
    {
      "src": "/images/icons-192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/images/icons-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": "/?source=pwa",
  "background_color": "#3367D6",
  "display": "standalone",
  "scope": "/",
  "theme_color": "#3367D6",
  "shortcuts": [
    {
      "name": "How's weather today?",
      "short_name": "Today",
      "description": "View weather information for today",
      "url": "/today?source=pwa",
      "icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
    },
    {
      "name": "How's weather tomorrow?",
      "short_name": "Tomorrow",
      "description": "View weather information for tomorrow",
      "url": "/tomorrow?source=pwa",
      "icons": [{ "src": "/images/tomorrow.png", "sizes": "192x192" }]
    }
  ]
}

short_name或name

您必须至少提供short_name或name属性。如果同时提供了两者,short_name则在用户的主屏幕,启动器或其他空间可能受限的地方使用。name在安装应用程序时使用。

icons

对于Chrome,您必须至少提供192×192像素的图标和512×512像素的图标

要使用 可屏蔽图标(有时在Android上称为自适应图标),您还需要添加”purpose”: “any maskable”到 icon属性

start_url

并告诉在那里,当它启动你的应用程序应该启动浏览器

display

  • fullscreen 在没有任何浏览器UI的情况下打开Web应用程序,并占用了整个可用显示区域
  • standalone 打开Web应用程序,使其看起来和感觉都像一个独立的本机应用程序
  • minimal-ui 此模式类似于standalone,但是为用户提供了用于控制导航
  • browser

scope

注意: 如果用户单击应用程序中位于之外 scope的链接,则该链接将在现有PWA窗口中打开并呈现。如果要在浏览器选项卡中打开链接,则必须添加target=”_blank” 到 a 标签中。在Android设备上,与的链接target="_blank"将在Chrome自定义标签中打开

将网络应用清单添加到您的页面

标签添加到Progressive Web App的所有页面

<link rel="manifest" href="/manifest.webmanifest">

Sentry 错误监控

git https://github.com/getsentry/sentry

文档 https://docs.sentry.io/ 社区

  • 集成gitlab 一键创建issue
  • 配置邮件通知
  • 配置规则,添加邮件发送条件
  • 配置版本号,为开发和线上配置不同的邮件发送规则
  • sourcemap,直接查看报错js代码片段
//最简单的方式是主动触发:
try {
    doSomething(a[0])
} catch(e) {
    Raven.captureException(e)
}

//window.onerror捕捉异常
window.onerror = function (e) {
    Raven.captureException(e)
}

//在vue里可以使用Vue.config.errorHandler 钩子来捕捉
Vue.config.errorHandler = (err, vm, info) => {
  Raven.captureException(err)
}

//对于接口报错,可以在全局拦截里实现
request.interceptors.response.use(null, error => {
      axiosHelper.error(error)
      Raven.captureException(error)
      return Promise.reject(error)
    })

使用过程中可能遇到的问题

1.采集到的信息不全,没有我们想要的用户信息,如用户guid和phone

解决方案:文档里有提供方法setUserContext(),顾名思义该方法是设置全局上下文,因此我们可以在拿到用户信息后执行一次该方法。

request.interceptors.response.use(null, error => {
      axiosHelper.error(error)
      Raven.setUserContext({
        phone: token.Phone || '',
        guid: token.CustomerGuid || ''
      })
      Raven.captureException(error)
      return Promise.reject(error)
    })

2.当异常信息过多时,在监控后台没有有效的筛选条件,导致我要看指定的异常信息什么困难
解决方案:查询文档发现有新增标签的方法,标签可以帮助我们来筛选异常信息。

Raven.setTagsContext({
   phone: token.Phone || '未登录'
})

3.由于线上的代码都是压缩过的,所以报错时很难定位到具体的哪一行代码出错。

解决方案: 上传sourcemap,sentry会自动匹配源码 https://docs.sentry.io/

搭建自己的sentry服务

由于官方提供的免费服务有一定次数的限制,达到一定限制后想要再使用就需要收费了,但是sentry是开源项目所以我们可以在本地搭建自己的服务,官方页提供了具体的操作步骤。Sentry的搭建

EDM 规则

table 布局父级table 定义 宽度

  • body 定义font-family padding 0 margin 0 width 100%
  • 所有table标签加属性border=”0″ cellspacing=”0″ cellpadding=”0″
  • 父级table 定义具体宽度,max-width
  • 子 孙 table with=100%, 如果不需要 父级宽度用 with=auto
  • td padding 调间距
  • 属性 dir=”rtl” 内容 方向从右向左 ; ltr 左向右
  • 文本框居中用 上下 padding
  • td 可以 用 align=”center” 让内部元素居中
<table border="0" class="m_-3118914792098885372phoenix-email-container" cellspacing="0" cellpadding="0" width="512" bgcolor="#FFFFFF" style="background-color:#ffffff;margin:0 auto;max-width:512px;width:inherit">

Cross-platform single sign-in Google 单点登陆

加载登陆按钮时, 会立即检查授权 情况, 如果成功 the Google servers return an access token and pass a new authorization result object to the callback. 如果该按钮无法进行即时模式授权,则用户必须单击登录按钮以触发访问流程

To enable cross-platform single sign-on:

  • The Android and web app must be registered in the same Google API Console project.
  • The requested scopes on each platform must match the scopes from other platforms.

关联 app 和 web site

https://developers.google.com/identity/smartlock-passwords/android/associate-apps-and-sites

OAuth 2

https://developers.google.com/identity/protocols/oauth2#libraries

  • 先请求google 接口 跳转到Google 认证框,会带上自己的redirect 链接
  • 在google 同意框 里 确认后, 会重定向 自己的页面 而且url 会带有 访问 token 的code
  • 在页面url里拿到code 再次请求 Google 接口 获取 token
{
  "access_token": "HbkBrQ5TAJFYUeLWsjhgWZ1Qt-ov9F0_B0S92aDhCMTQ", 
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjZmY2Y0MTMyMjQ3NjUxNTZiNDg3NjhhNDJmYWMwNjQ5NmEzMGZmNWEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI0MDc0MDg3MTgx", 
  "expires_in": 3599, 
  "token_type": "Bearer", 
  "scope": "https://www.googleapis.com/auth/userinfo.profile", 
  "refresh_token": "1//KCgYIARAAGAQSNwF-L9IEcbqfjUdRQuK1y01gl2m4"
}
  • 然后拿着access_token 去请求 Google 接口用户数据

https://developers.google.com/accounts/images/webflow.png

Using OAuth 2.0 for Web Server Applications

https://developers.google.com/identity/protocols/oauth2/web-server

Service accounts

Google API(例如Prediction API和Google Cloud Storage)可以代表您的应用程序运行,而无需访问用户信息。在这些情况下,您的应用程序需要向API证明自己的身份,但无需用户同意。同样,在企业方案中,您的应用程序可以请求委派对某些资源的访问

您从Google API控制台获得的服务帐户的凭据, include a generated email address that is unique, a client ID, and at least one public/private key pair. You use the client ID and one private key to create a signed JWT and construct an access-token request in the appropriate format.  然后,您的应用程序将令牌请求发送到Google OAuth 2.0授权服务器,该服务器会返回access token。该应用程序使用access token访问Google API。当令牌过期时,应用程序将重复该过程。

Using OAuth 2.0 for Server to Server Applications

https://developers.google.com/identity/protocols/oauth2/service-account

vue组件从开发到发布

1. 大纲

想要搭建一个组件库,我们必须先要有一个大概的思路。

  1. 规划目录结构
  2. 配置项目以支持目录结构
  3. 编写组件
  4. 编写示例
  5. 配置使用库模式打包编译
  6. 发布到npm

2. 规划目录结构

1.创建项目

在指定目录中使用命令创建一个默认的项目,或者根据自己需要自己选择。

vue create myproject

2. 调整目录

.
...
|-- examples      // 原 src 目录,改成 examples 用作示例展示
|-- packages      // 新增 packages 用于编写存放组件
...

3. 配置项目以支持新的目录结构

  • src目录更名为examples导致项目无法运行
  • 新增packages目录,该目录未加入webpack编译

1 重新配置入口,修改配置中的 page 选项

新版 Vue CLI 支持使用 vue.config.js 中的pages选项构建一个多页面的应用。

module.exports = {
  // 修改 src 目录 为 examples 目录
  pages: {
    index: {
      entry: 'examples/main.js',
      template: 'public/index.html',
      filename: 'index.html'
    }
  }
}

支持对packages目录的处理,修改配置中的chainWebpack选项

module.exports = {
  // 修改 src 为 examples
  pages: {
    index: {
      entry: 'examples/main.js',
      template: 'public/index.html',
      filename: 'index.html'
    }
  },
  // 扩展 webpack 配置,使 packages 加入编译
  chainWebpack: config => {
    config.module
      .rule('js')
      .include
        .add('/packages')
        .end()
      .use('babel')
        .loader('babel-loader')
        .tap(options => {
          // 修改它的选项...
          return options
        })
  }
}

4 编写组件

创建一个新组件

  • 在packages目录下,所有的单个组件都以文件夹的形式存储,所有这里创建一个目录 color-picker/
  • 在color-picker/目录下创建src/目录存储组件源码
  • 在/color-picker/目录下创建index.js文件对外提供对组件的引用。

修改 /packages/color-picker/index.js 文件,对外提供引用。

# /packages/color-picker/index.js
// 导入组件,组件必须声明 name
import colorPicker from './src/color-picker.vue'

// 为组件提供 install 安装方法,供按需引入
colorPicker.install = function (Vue) {
  Vue.component(colorPicker.name, colorPicker)
}

// 默认导出组件
export default colorPicker

2. 整合所有的组件,对外导出,即一个完整的组件库

修改 package.json 文件,对整个组件库进行导出。

// 导入颜色选择器组件
import colorPicker from './color-picker'

// 存储组件列表
const components = [
  colorPicker
]

// 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,则所有的组件都将被注册
const install = function (Vue) {
  // 判断是否安装
  if (install.installed) return
  // 遍历注册全局组件
  components.map(component => Vue.component(component.name, component))
}

// 判断是否是直接引入文件
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}

export default {
  // 导出的对象必须具有 install,才能被 Vue.use() 方法安装
  install,
  // 以下是具体的组件列表
  colorPicker
}

5 编写示例

1 在示例中导入组件库

import Vue from 'vue'
import App from './App.vue'

// 导入组件库
import ColorPicker from './../packages/index'
// 注册组件库
Vue.use(ColorPicker)

Vue.config.productionTip = false

new Vue({
  render: h => h(App)
}).$mount('#app')

2 在示例中使用组件库中的组件

在上一步用使用Vue.use()全局注册后,即可在任意页面直接使用了,而不需另外引入。当然也可以按需引入。

<template>
	<colorPicker v-model="color" v-on:change="headleChangeColor"></colorPicker>
</template>

<script>
export default {
	data () {
		return {
			color: '#ff0000'
		}
	},
	methods: {
		headleChangeColor () {
			console.log('颜色改变')
		}
	}
}
</script>

发布到 npm,方便直接在项目中引用

到此为止我们一个完整的组件库已经开发完成了,接下来就是发布到npm以供后期使用。

1 package.json 中新增一条编译为库的命令

在库模式中,Vue是外置的,这意味着即使在代码中引入了 Vue,打包后的文件也是不包含Vue的。

https://cli.vuejs.org/zh/guide/build-targets.html#%E5%BA%93

以下我们在 scripts 中新增一条命令 npm run lib

  • –target: 构建目标,默认为应用模式。这里修改为 lib 启用库模式。
  • –dest: 输出目录,默认dist。这里我们改成 lib
  • [entry]: 最后一个参数为入口文件,默认为 src/App.vue。这里我们指定编译 package/ 组件库目录。
"scripts": {
	// ...
	"lib": "vue-cli-service build --target lib --name vcolorpicker --dest lib packages/index.js"
}

配置 package.json 文件中发布到 npm 的字段

  • name: 包名,该名字是唯一的。可在 npm 官网搜索名字,如果存在则需换个名字。
  • version: 版本号,每次发布至 npm 需要修改版本号,不能和历史版本号相同。
  • description: 描述。
  • main: 入口文件,该字段需指向我们最终编译后的包文件。 “lib/vcolorpicker.umd.min.js”,
  • keyword:关键字,以空格分离希望用户最终搜索的词。
  • author:作者
  • private:是否私有,需要修改为 false 才能发布到 npm
  • license: 开源协议

3 添加.npmignore文件,设置忽略发布文件

我们发布到 npm 中,只有编译后的 lib 目录、package.json、README.md才是需要被发布的。所以我们需要设置忽略目录和文件。

# 忽略目录
examples/
packages/
public/

# 忽略指定文件
vue.config.js
babel.config.js
*.map

4 登录到 npm

$ npm config set registry http://registry.npmjs.org 
$ npm login
$ npm publish

5 发布成功 使用

$ npm install vcolorpicker -S

# 在 main.js 引入并注册
import vcolorpicker from 'vcolorpicker'
Vue.use(vcolorpicker)

jwt oauth2

jwt

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

JWT 的数据结构

  • Header(头部):Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

{ “alg”: “HS256”, “typ”: “JWT” } — alg默认签名算法是 HMAC SHA256

  • Payload(负载)

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

除了官方字段,你还可以在这个部分定义私有字段

  • Signature(签名)

Signature 部分是对前两部分的签名,防止数据篡改。首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名

 HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。JWT 作为一个令牌(token),有些场合可能会放到 URL,Base64 有三个字符+/=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-/替换成_ 。这就是 Base64URL 算法。

客户端收到服务器返回的 JWT, 可以放到Cookie,或header Authorization字段里

JWT 特点

(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。

(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。

(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。

(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

————————————————————————-

oauth2 :是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息

名词

(1) Third-party application:第三方应用程序 即网站A

(2)HTTP service:HTTP服务提供商,本文中简称”服务提供商”,即上例的qq。

(3)Resource Owner:资源所有者,即登录用户。

(4)User Agent:用户代理,本文中就是指浏览器。

(5)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。

(6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。

6.授权码模式

授权码模式(authorization code)是功能最完整、流程最严密的授权模式。

ios safari ,app webview 在chrome devtool 里调试

https://github.com/google/ios-webkit-debug-proxy

可以跳过上面链接 直接用这个https://github.com/RemoteDebug/remotedebug-ios-webkit-adapter

安装完成后 启动

remotedebug_ios_webkit_adapter --port=9000


前面加 DEBUG=remotedebug 可以显示日志

打开 chrome://inspect 在discover network target  添加 localhost:9000