前言
简略的知识点搭配适合的业务场景,往往能起到意想不到的成果。这篇文章会用三个最根底人人都晓得的前端常识来阐明如何助力经营小姐姐、公司 48+ 前端开发同学的日常工作,让他们的工作效率失去极大地晋升。
看完您能够会播种
用 vue 从零开始写一个 chrome 插件
如何用 Object.defineProperty 拦挡 fetch
申请 `如何应用油猴脚本开发一个扩大程序
日常提效的一些思考
助力全公司 45+ 前端开发 – chrome 插件的始与终
通过插件一键设置 ua,模仿用户登录状态,进步开发效率。
先看后果
插件应用形式
插件应用后果
团队 48+ 小伙伴也应用起来了
背景和问题
日常 c 端业务中有很多场景都须要用户
登录后
能力失常进行,而开发阶段根本都是通过 chrome 模拟手机设施来开发,所以往往会波及到在 chrome 浏览器中模仿用户登录,其波及以下三步 (这个步骤比拟繁琐
)。
备注:放弃用户的登录态个别是通过 cookie,但也有通过 header 来做,比方咱们公司是改写 ua 来做的
获取 ua
:返回公司 UA 生成平台输出手机号生成 ua增加 ua
:将 ua 复制到 chrome devtool设置 / 批改
device应用 ua
:抉择新增加的 ua,刷新页面,从新开发调试
来看一段对话
隔壁 98 年刚毕业妹子:
又过期了,谁又把我挤下去了嘛
好的,稍等一会哈,我换个账号测测
好麻烦哎!模仿一个用户信息,要这么多步骤,好烦呀!!!
我,好奇的大叔:
“仔细”理解下,她正在做一个 h5 流动我的项目,场景简单,波及的状态很多,须要用 不同的账号 来做测试。
模仿一两个用户还好,然而此刻小姐姐测这么多场景,曾经模仿了好多个(谁都会烦啊)
公司的登录体系是单点登录,一个好不容易模仿的账号,有可能他人也在用,后果又被顶掉了,得从新生成,我 TM
看着她快气哭的小眼神,作为隔壁桌敌对的街坊,此刻我心里只想着一件事 …!帮她解决这个宜人的问题。
剖析和解决问题
通过下面的介绍您应该能够感觉到咱们开发阶段遇到须要频繁切换账号做测试时的懊恼,绝对繁琐的 ua 生成过程导致了它肯定是个
费时费力
的麻烦事。
有没有什么方法让咱们的开发效率失去晋升,别节约在这种事件上呢?一起一步步做起来
需要有哪些
提供一种便捷地模仿 ua 的形式,助力开发效率晋升。
根本诉求
:本地开发阶段,心愿有 更便捷 的形式来模仿用户登录多账号
:一个我的项目须要多个账号,不同我的项目间的账号能够共享也能够不同指定域
: 只有指定的域
下才须要模仿 ua,不能影响浏览器失常应用过期解决
: 账号过期后,能够被动生成,无需手动从新获取
如何解决
需要 1
:联合后面生成 ua 阶段,咱们能够通过某种形式让用户能间接在以后页面生成 ua,无需跳出,一键设置省略手动过程需要 2
:提供多账号治理性能,能间接选中切换 ua需要 3
:限定指定域,该 ua 才失效需要 4
:当应用到过期账号时,可一键从新生成即可
为什么是 chrome 插件
- 浏览器中发送 ajax 申请的 ua 无奈间接批改,然而 chrome 插件能够批改申请的 ua(很重要的一点)
- chrome 插件 popup 模式可间接在以后页面关上,无需跳出开发页面,缩小跳出过程
用 vue 从零开始写一个 chrome 插件
篇幅起因,这里只做示例级别的简略介绍,如果您心愿具体理解 chrome 插件的编写能够参考这里
从一个小例子开始
接下来咱们会以下页面为例,阐明用 vue 如何写进去。
基本功能
- 底部 tab 切换区域:
viewA
、viewB
、viewC
- 两头内容区域:切换 viewA、B、C 别离展现对应的页面
content 局部
借助 chrome 浏览器能够向网页插入脚本的个性,咱们会演示如何插入脚本并且在网页加载的时候弹一个hello world
popup 与 background 通信局部
popup 实现用户的次要交互,在 viewA 页面点击获取自定义的 ua 信息
批改 ajax 申请 ua 局部
会演示如果通过 chrome 插件批改申请 header
1. 理解一个 chrome 插件的形成
- manifest.json
- background script
- content script
- popup
1. manifest.json
简直所有的货色都要在这里进行申明、
权限
、资源
、页面
等等
{
"manifest_version": 2, // 清单文件的版本,这个必须写
"name": "hello vue extend", // 插件的名称,等会咱们写的插件名字就叫 hello vue extend
"description": "hello vue extend", // 插件形容
"version": "0.0.1", // 插件的版本
// 图标,写一个也行
"icons": {"48": "img/logo.png"},
// 浏览器右上角图标设置,browser_action、page_action、app 必须三选一
"browser_action": {
"default_icon": "img/logo.png",
"default_title": "hello vue extend",
"default_popup": "popup.html"
},
// 一些常驻的后盾 JS 或后盾页面
"background": {
"scripts": [
"js/hot-reload.js",
"js/background.js"
]
},
// 须要间接注入页面的 JS
"content_scripts": [{"matches": ["<all_urls>"],
"js": ["js/content.js"],
"run_at": "document_start"
}],
// devtools 页面入口,留神只能指向一个 HTML 文件
"devtools_page": "devcreate.html",
// Chrome40 以前的插件配置页写法
"options_page": "options.html",
// 权限申请
"permissions": [
"storage",
"webRequest",
"tabs",
"webRequestBlocking",
"<all_urls>"
]
}
2. background script
后盾,能够认为是一个常驻的页面,权限很高,简直能够调用所有的 API,能够与 popup、content script 等通信
3. content script
chrome 插件向页面注入脚本的一种模式(js 和 css 都能够)
4. popup
popup
是点击browser_action
或者page_action
图标时关上的一个小窗口网页,焦点来到网页就立刻敞开。
比方咱们要用 vue 做的页面。
2. 改写 vue.config.js
manifest.json 对文件援用的构造根本决定了打包后的文件门路
打包后的门路
// dist 目录用来 chrome 扩大导入
├── dist
│ ├── favicon.ico
│ ├── img
│ │ └── logo.png
│ ├── js
│ │ ├── background.js
│ │ ├── chunk-vendors.js
│ │ ├── content.js
│ │ ├── hot-reload.js
│ │ └── popup.js
│ ├── manifest.json
│ └── popup.html
源码目录
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── js
│ └── hot-reload.js
├── src
│ ├── assets
│ │ ├── 01.png
│ │ ├── disabled.png
│ │ └── logo.png
│ ├── background
│ │ └── background.js
│ ├── content
│ │ └── content.js
│ ├── manifest.json
│ ├── popup
│ │ ├── App.vue
│ │ ├── main.js
│ │ ├── router.js
│ │ └── views
│ │ ├── viewA.vue
│ │ ├── viewB.vue
│ │ └── viewC.vue
│ └── utils
│ ├── base.js
│ ├── fixCaton.js
│ └── storage.js
└── vue.config.js
批改 vue.config.js
主须要略微革新变成能够多页打包,留神输入的目录构造就能够了
const CopyWebpackPlugin = require('copy-webpack-plugin')
const path = require('path')
// 这里思考能够增加多页
const pagesObj = {}
const chromeName = ['popup']
const plugins = [
{from: path.resolve('src/manifest.json'),
to: `${path.resolve('dist')}/manifest.json`
},
{from: path.resolve('src/assets/logo.png'),
to: `${path.resolve('dist')}/img/logo.png`
},
{from: path.resolve('src/background/background.js'),
to: `${path.resolve('dist')}/js/background.js`
},
{from: path.resolve('src/content/content.js'),
to: `${path.resolve('dist')}/js/content.js`
},
]
chromeName.forEach(name => {pagesObj[name] = {
css: {
loaderOptions: {
less: {modifyVars: {},
javascriptEnabled: true
}
}
},
entry: `src/${name}/main.js`,
filename: `${name}.html`
}
})
const vueConfig = {
lintOnSave:false, // 敞开 eslint 查看
pages: pagesObj,
configureWebpack: {entry: {},
output: {filename: 'js/[name].js'
},
plugins: [new CopyWebpackPlugin(plugins)]
},
filenameHashing: false,
productionSourceMap: false
}
module.exports = vueConfig
3. 热刷新
咱们心愿批改插件源代码进行打包之后,chrome 插件对应的页面能被动更新。为什么叫热刷新而不是热更新呢?因为它其实是全局刷新页面,并不会保留状态。
这里举荐一个 github 上的解决方案 crx-hotreload
4. 实现小例子编写
文件目录构造
├── popup
│ ├── App.vue
│ ├── main.js
│ ├── router.js
│ └── views
│ ├── viewA.vue
│ ├── viewB.vue
│ └── viewC.vue
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
router.js
import Vue from 'vue'
import Router from 'vue-router'
import ViewA from './views/viewA.vue'
import ViewB from './views/viewB.vue'
import ViewC from './views/viewC.vue'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
redirect: '/view/a'
},
{
path: '/view/a',
name: 'viewA',
component: ViewA,
},
{
path: '/view/b',
name: 'viewB',
component: ViewB,
},
{
path: '/view/c',
name: 'viewC',
component: ViewC,
},
]
})
App.vue
<template>
<div id="app">
<div class="app-router">
<router-view />
</div>
<div class="app-tab">
<div class="app-tab-item" v-for="(tabName, i) in tabs" :key="i" @click="onToView(tabName)">
{{tabName}}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
tabs: [
'viewA',
'viewB',
'viewC',
]
}
},
methods: {onToView (name) {
this.$router.push({name})
}
}
}
</script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
width: 375px;
height: 200px;
padding: 15px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
flex-direction: column;
.app-router{flex: 1;}
.app-tab{
display: flex;
align-items: center;
justify-content: space-between;
.app-tab-item{
font-size: 16px;
color: coral;
cursor: pointer;
}
}
}
</style>
viewA、viewB、viewC
三个页面根本长得是一样的,只有背景色和文案内容不一样,这里我就只贴 viewA 的代码了。
须要留神的是这里会演示 popup 与 background, 通过 sendMessage 办法获取 background 后盾数据
<template>
<div class="view-a"> 我是 A 页面
<button @click="onGetCUstomUa"> 获取自定义 ua</button>
</div>
</template>
<script>
export default {
name: 'viewA',
methods: {onGetCUstomUa () {chrome.runtime.sendMessage({type: 'getCustomUserAgent'}, function(response) {alert(JSON.stringify(response))
})
}
}
}
</script>
<style lang="less">
.view-a{
background-color: cadetblue;
height: 100%;
font-size: 60px;
}
</style>
background.js
const customUa = 'hello world ua'
// 申请发送前拦挡
const onBeforeSendCallback = (details) => {for (var i = 0; i < details.requestHeaders.length; ++i) {if (details.requestHeaders[i].name === 'User-Agent') {details.requestHeaders.splice(i, 1);
break;
}
}
// 批改申请 UA 为 hello world ua
details.requestHeaders.push({
name: 'User-Agent',
value: customUa
});
return {requestHeaders: details.requestHeaders};
}
// 后面的 sendMessage 获取 getCustomUserAgent,会被这里监听
const onRuntimeMessageListener = () => {chrome.runtime.onMessage.addListener(function (msg, sender, callback) {if (msg.type === 'getCustomUserAgent') {
callback({customUa});
}
});
}
const init = () => {onRuntimeMessageListener()
onBeforeSendHeadersListener()}
init()
content.js
演示如何往网页中插入代码
function setScript({code = '', needRemove = true} = params) {let textNode = document.createTextNode(code)
let script = document.createElement('script')
script.appendChild(textNode)
script.remove()
let parentNode = document.head || document.documentElement
parentNode.appendChild(script)
needRemove && parentNode.removeChild(script)
}
setScript({code: `alert ('hello world')`,
})
对于一键设置 ua 插件
大体上和小例子差不都,只是性能绝对简单一些,会波及到
- 数据本地存储
chrome.storage.sync.get|set
、chrome.tabs.query
等 API - popup 与 background 通信、content 与 background 通信
- 拦挡申请批改 UA
- 其余的大体就是惯例的 vue 代码编写啦!
这里就不贴具体的代码实现了。
油猴脚本入门示例
因为接下来的两个小工具都是基于
油猴
脚本来实现的,所以咱们提前先理解一下它
油猴脚本是什么?
油猴脚本(Tampermonkey)是一个风行的浏览器扩大,能够运行用户编写的扩大脚本,来实现各式各样的性能,比方去广告、批改款式、下载视频等。
如何写一个油猴脚本?
1. 装置油猴
以 chrome 浏览器扩大为例,点击这里先装置
装置实现之后能够看到右上角多了这个
2. 新增示例脚本 hello world
// ==UserScript==
// @name hello world // 脚本名称
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://juejin.cn/* // 示意怎么的 url 才执行上面的代码
// @icon https://www.google.com/s2/favicons?domain=juejin.cn
// @grant none
// ==/UserScript==
(function() {
'use strict';
alert('hello world')
// Your code here...
})();
没错当关上任意一个 https://juejin.cn/*
掘金的页面时,都会弹出 hello world
,而其余的网页如https://baidu.com
则不会。
到此你就实现了一个最简略的油猴脚本,接下来咱们看一下用同样简略的代码,来解决一个理论问题吧!O(∩_∩)O
3 行代码让 SSO 主动登录
问题是什么?
1. 有一天经营小姐姐要在几个零碎之间配置点货色
一顿操作,终于把事件搞定了,情绪美美的。
然而她心想,为啥每个零碎都要我登录一次,不开心 o(~ヘ~o#)
2. 下午一沉睡来,领导让把上午的配置从新改一下(渎职的小姐姐马上开始操作)
然而让她没想到的是:上午的登录页面好像许久没有见到她一样,又和小姐姐来了一次密切接触😭
此时,她的心田曾经开始解体了
3. 然而这不是完结,当前的每一天她都是这种状态😭😭😭
痛点在哪里?
看完下面的动图,我猜你曾经在替小姐姐一起骂娘了,这做的什么玩意,太垃圾了。SSO 是对立登录,你们这搞的是什么货色。
是的,我的心田和你一样 愤愤不平
, 一样有一万个草泥马在奔流,这是哪个 sb 设计的计划,几乎 不配做人
,一天啥事也不干,尽是跳登录页,输出用户名明码点登录按钮了,长此以往,敌人间见面说的第一句话不是“你吃了吗?”,而是“你登录了吗?”。
不过吐槽完,咱们还是要想想如何通过技术手段解决这两个痛点,达到 只须要登录一次的目标
。
1. 在 A 零碎登录之后,跑到其余零碎须要从新登录。
2. 登录时效只有 2 小时,2 小时后,须要从新登录
该如何解决?
根本原因还是公司的 SSO 对立登录方案设计的有问题,所以须要推动他们批改,然而这是一个绝对长期的过程,短期内有没有什么方法能让咱们欢快的登录呢?
痛点 1: 1. 在 A 零碎登录之后,跑到其余零碎须要从新登录。
已无力回天
痛点 2: 2. 登录时效只有 2 小时,2 小时后,须要从新登录
已无力回天
咱们不好间接侵入各个系统去革新登录逻辑,革新其登录时效,然而却能够对 登录页面(示例)
做点手脚
最要害的是:
- 用户名输入框
- 明码输入框
- 点击按钮
所以能够借助油猴脚本,在 DOMContentLoaded 的时候,插入一下代码,来实现主动登录,缩小手动操作的过程,大略原理如下。
// ==UserScript==
// @name SSO 主动登录
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://*.xxx.com/login* // 这里是 SSO 登录页面地址,示意只有合乎这个规定的才注入这段代码
// @grant none
// ==/UserScript==
document.querySelector('#username').value = 'xxx' // 用户名
document.querySelector('#password').value = 'yyy' // 明码
document.querySelector('#login-submit').click() // 主动提交登录
是不是太简略了,简略到令人发指,令人痛恨,令人想吐口水!!!,没有一点技术含量
是不是太简略了,简略到令人发指,令人痛恨,令人想吐口水!!!,没有一点技术含量
是不是太简略了,简略到令人发指,令人痛恨,令人想吐口水!!!,没有一点技术含量
是的,就这 😄
,第一次帮小姐姐解决了困扰她许久的问题,早晨就请我吃了麻辣烫,还夸我 ” 技术 ” 好(此处不是开车
)
试试成果
gif 中前半部分没有开启主动登录的脚本须要手动登录,后半部开启了就能够主动登录了。
拦挡 fetch 申请,只留你想要的页面
问题是什么?
前端常见的调试形式
- chrome inspect
- vconsole
- weinre
- 等等
这些形式都有各自的优缺点,比方 chrome inspect 第一次须要翻墙能力应用,只实用于安卓; vconsole 不不便间接调试款式; weinre 只实用于调试款式等。
基于这些起因,公司很久之前搞了一个近程调试工具,能够很不便的 增删 DOM 构造、调试款式、查看申请、查看 application
批改后手机上立刻失效。
近程调试平台应用流程
他的应用流程大略是这样的
-
关上近程调试页面列表
此页面蕴含测试环境
所有人
关上的调试页面链接, 多的时候有上百个
- 点击你要调试的页面,就能够进入像 chrome 控制台一样调试了
看完流程你应该大略晓得问题在哪里了, 近程调试页面列表不仅仅蕴含我本人的页面,还包含很多其他人的,导致很难疾速找到本人想要调试的页面
该如何解决?
问题解析
有什么方法能让我疾速找到本人想要调试的页面呢?其实察看解析这个页面会发现列表是
- 通过发送一个申请获取的
- 响应中蕴含设施关键字
拦挡申请
所以聪慧的你曾经猜到了,咱们能够通过 Object.defineProperty
拦挡 fetch
申请,过滤设施让列表中只存在咱们指定的设施(毕竟平时开发时调试的设施根本是固定的,而设施完全相同的概率是很低的,所以指定了设施其实就是惟一标识了本人
)页面。
具体如何做呢?
// ==UserScript==
// @name 前端近程调试设施过滤
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://chii-fe.xxx.com/ // 指定脚本失效的页面
// @grant none
// @run-at document-start // 留神这里,脚本注入的机会是 document-start
// ==/UserScript==
;(() => {
const replaceRe = /\s*/g
// 在这里设置设施白名单
const DEVICE_WHITE_LIST = [
'Xiaomi MI 8',
'iPhone9,2',
].map((it) => it.replace(replaceRe, '').toLowerCase())
const originFetch = window.fetch
const recordListUrl = 'record-list'
const filterData = (source) => {
// 数据过滤,返回 DEVICE_WHITE_LIST 指定的设施的数据
// 具体过程省略
return data
}
// 拦挡 fetch 申请
Object.defineProperty(window, 'fetch', {
configurable: true,
enumerable: true,
get () {return function (url, options) {return originFetch(url, options).then((response) => {
// 只解决指定的 url
if (url.includes(recordListUrl)) {if (response.clone) {const cloneRes = response.clone()
return new Promise((resolve, reject) => {
resolve({text: () => {return cloneRes.json().then(json => {return filterData(JSON.stringify(json))
});
}
})
})
}
}
return response
})
}
}
})
})()
试试成果
通过下图能够看出,过滤前有 37 个页面,过滤后只剩 3 个,霎时就找到你要调试页面,再也不必从几百个页面中寻找你本人的那个啦!
日常提效的一些思考
工作中咱们时长会遇到一些妨碍咱们进步工作效率的问题,这些问题或者是因为老方案设计不合理、或者是因为流程又臭又长,又或者是现有性能不满足新的需要。等等,如果能做到这几点,不仅对本人的成长有所帮忙,对团队也会有所奉献。
主人翁心态
:发现了问题被动尝试去解决问题,不做旁观者放弃学习力
:发现问题之后,解决方案如果不在你的常识储备范畴,肯定要尝试去学习新的货色( 羞愧,没写一键设置 UA 插件之前,我本人齐全没写过 chrome 插件),走出舒服圈,会学会更多放弃热心态
:每个人遇到的问题是不一样的,被动和共事或者敌人探讨,须要时伸出你的双手执行力
:把影响效率(举例,还有其余) 的事件看成魔鬼,马上口头起来,达到魔鬼,不要一拖再拖学会推广
:兴许一开始你写的插件只是解决了本人的问题,但同样的工作环境,他人兴许也会遇到,要学会往外分享和推广
相约再见
以上就是这篇文章的全部内容啦!愿大家晚安🌛,下次再见。
参考
- 【干货】Chrome 插件 (扩大) 开发全攻略
- 油猴脚本编写教程