我的技术和生活

  • 使用 webpack-rpc-inline-plugin 打包内联函数体

    使用 webpack-rpc-inline-plugin 打包内联函数体

    使用 Puppeteer 的时候,我们常常要使用 page.evaluate() 或者 page.evaluateHandle() 函数到目标页面的环境里执行一段代码。

    如果是简单的代码,比如返回页面标题、改变某个节点的属性,那么直接写在 .evaluate() 里面就行了;但实际生产中,尤其是前厂的 Showman 产品里,要执行的函数往往非常复杂,经常需要组合多个函数:

    page.evaluate(() => {
      function func1() {}
      function func2() {}
      // ...
      function funcN() {}
    
      func1();
      func2();
      // ...
      funcN();
    });

    这种场景下,我们必须用上面这种写法,而不是下面这种我们更熟悉的写法:

    import func1 from './func1';
    import func2 from './func2';
    // ...
    import funcN from './funcN';
    
    page.evaluate(() => {
      func1();
      func2();
      // ...
      funcN();
    });

    因为被执行的函数会被转换成字符串,传输到目标环境,然后重新实例化成函数,再执行。所以上面这种写法,引擎会在全局环境下查找需要的函数,而那些函数都没传递过去,就会找不到。

    如果开发时按照方案一组织代码,会遇到几个问题:

    1. 子函数放在主函数体内部,不方便独立开发、调试、测试
    2. 每个主函数内部都需要写死子函数,不方便共享复用

    所以我就想从工具链入手,写一个专用工具,可以继续用方案二的形式组织代码,但是编译打包之后,就恢复到方案一的状态。

    我选择了 webpack 插件,原因有二:

    1. 我比较熟悉 webpack
    2. 这种情况不能用 loader

    最后我选择在 compilation.afterProcessAssets 钩子处理 JS。此时 JS 已经打包了所有资源,并且经过 terser 压缩。所以我会先将 bundle 解开(直接用 string.substring),然后 return webpack 对象,从中找到目标函数替换。

    具体的代码在 GitHub 仓库里,我就不详细解释了(困了),感兴趣的同学可以看看。

    欢迎需要在 rpc 环境下执行 JS 的同学使用,欢迎反馈需求和问题。

  • babel@6 升级到 babel@7,兼容性代码膨胀的原因

    babel@6 升级到 babel@7,兼容性代码膨胀的原因

    最近尝试把厂里项目的依赖从 babel@6 升级到 babel@7,发现打包之后体积大了很多。于是打开 webpack-bundle-analyzer,果然大部分代码都是 corejs 引入的,项目本身的逻辑只占少部分。

    从报告来看,虽然目标浏览器的版本均高于 Promise 的启动版本(比如 Chrome 32),但 es.promise 仍然会被打包进来。于是以 es.promise 为突破口开始分析,找到答案:因为 JavaScript 引擎 V8 直至 v6.6 版本时,在 Promise 实现方面都存在严重 bug,所以 babel@7 保守地选择 Chrome 66/67 作为临界点。

    想来其它体积多半也是这么加上来的,就不再一个一个排查了。 ​​​

    这就很难处理。不升级吧,新特性兼容不了;升级吧,包体积变大,公司上层又未必同意。

    下一步试试 esbuild 吧,或者回头手动打包一套 polyfill。目前设想的方案是:

    1. 用 babel 之类的工具提取出所有特性
    2. 根据 caniuse 生成必须的特性列表
    3. 像上面说的 Promise,因为 bug 所以必须兼容,我们就不考虑了,可以反过来加一条 eslint 规则规避
    4. 最终生成新的 polyfill 打包进来
    (更多…)
  • 使用 caniuse-lite 检查目标浏览器的特性支持情况

    使用 caniuse-lite 检查目标浏览器的特性支持情况

    起因

    之前得知 loading="lazy" 新特性,正巧在学习如何使用 html-webpack-plugin,于是就写了个 lazyload-webpack-plugin,可以给页面里所有 <img><iframe> 加上 loading="lazy" 属性,以启动原生 lazyload。

    不过当初写得很简单,只会不分青红皂白加属性,甚至可能会覆盖已有的 loading="eager" 属性,引发 bug。所以这几天就想找时间升级一下:

    1. 不再覆盖 loading 属性
    2. 根据 browserslist 得到的目标浏览器器列表采取不同策略
      1. 支持 loading="lazy" 就延续之前的做法
      2. 不支持的话,用 data-src 替换 src,然后在页面里根据浏览器特性处理

    caniuse-lite

    没想到这个需求还挺难满足,找了一圈竟然没有成型的教程,只好自己摸索一下,还好并不复杂。

    以下代码实现了根据环境配置检查目标浏览器是否支持 loading="lazy" 的功能。我用在新版本的 lazyload-webpack-plugin 中,现在可以实现前面说的功能了。

    // caniuse-lite 是官方提供的 caniuse 仓库封装,方便我们查询特性支持
    const lite = require('caniuse-lite');
    const browserslist = require('browserslist');
    // `features` 是特性支持列表,`feature()` 可以将其转换成好用的 json 格式
    const {features, feature: unpackFeature} = lite;
    
    // 这一步,用特性名 'loading-lazy-attr' 获取支持列表
    const feature = unpackFeature(features['loading-lazy-attr']);
    // 直接声明 browserslist 实例,它会自动查找本地 `.browserslistrc` 或环境变量 `BROWSERSLIST` 来生成浏览器列表
    const browsers = browserslist();
    const {stats} = feature;
    // 遍历浏览器列表,根据名称、版本验证对 `loading="lazy"` 的支持情况
    const isSupport = browsers.every(browser => {
      const [name, version] = browser.split(' ');
      const browserData = stats[name];
      const isSupport = browserData && browserData[version] === 'y';
      if (!isSupport) {
        console.log(`[lazyload-webpack-plugin] target browser '${browser}' DOES NOT supported \`loading="lazy"\`.`);
      }
      return isSupport;
    });
    
    module.exports = isSupport;

    问题

    现在的情况是,如果知道特性名称(如“loading-lazy-attr”),可以判断目标浏览器是否支持;但是如果不知道准确名称,就没法判断。如果我想在项目当中使用,比如检查当前代码仓库用到哪些特性不被目标浏览器兼容,并生成 polyfill 套件,就很难操作。

    有待进一步学习。

    自荐

    欢迎有制作静态页面需求的同学使用 lazyload-webpack-plugin<img><iframe> 添加 loading="lazy"。关于使用 webpack 制作多页面站点的经验分享,可以阅读我的这篇文章《使用 Webpack 开发多页面站点》。

    有任何问题、意见、建议,欢迎通过各种方式提给我。

    (更多…)
  • 正确使用 @babel/preset-env 与 @babel/plugin-transform-runtime

    正确使用 @babel/preset-env 与 @babel/plugin-transform-runtime

    这几天又在折腾项目脚手架,看到之前的配置里用到 transform-runtime,于是就想研究下。找了半天,发现现有的文章、讨论都没有准确、合理的说明,所以写篇博客介绍一下。

    定位

    @babel/preset-env

    @babel/preset-env 是一大堆插件的集合,包含了当前浏览器环境下,所有语言特性的插件,可以根据 browserslist 的结果,选择合适的插件将新语言特性转译成旧浏览器可以支持的表达方式。

    官方推荐使用预制件(preset),可以大大节省配置时间,提高开发效率。

    @babel/plugin-transform-runtime

    babel 转译时,往往需要用到一些工具函数,比如 _extend。转译以文件为单位,意味着每个文件里都可能有重复的 babel 工具函数。现代化项目常有大量的文件,就可能产生重复和浪费。

    @babel/plugin-transform-runtime 可以帮我们把直接写入文件里工具函数变成对 core-js 的引用,可以减少转译后的文件大小。

    使用场景

    从上面的定位可以看出,@babel/plugin-transform-runtime@babel/preset-env 并不重合,两者的功能没有交集。

    对于任何需要考虑兼容性的项目,我们都应该使用 babel 进行转译,并使用 @babel/preset-env 降低配置的复杂度,大部分通用的配置如下:

    module.exports = {
      'presets': [
        [
          '@babel/preset-env',
          {
            'corejs': 3, // 使用 core-js@3 版本,core-js@2 从 ES2017 之后就没再更新了,不推荐使用
            'useBuiltIns': 'usage', // 只转译用到的新语言元素
            'bugfix': true, // v7.9 之后引入的新选项,尽量减小转译后的代码体积,v8 之后会变成默认选项
          },
        ],
      ],
    };

    小型项目可以到此为止,中大型项目、文件数量比较多的,可以引入 @babel/plugin-transform-transform。此时我们需要先安装两个依赖——注意,这俩依赖都要装:

    # 用来开发、debug 和编译
    npm i @babel/plugin-transform-runtime -D
    
    # 用来在生产环境中使用
    npm i @babel/runtime
    # 如果要使用特定的 core-js 版本,也可以安装特定的 runtime
    npm install --save @babel/runtime-corejs2
    npm install --save @babel/runtime-corejs3

    接下来,修改上面的 babel.config.js,加入插件配置:

    module.exports = {
      // 重复上面的配置
      "plugins": [
        // 其它插件
        [
          "@babel/plugin-transform-runtime",
          // 一般情况下我们使用默认配置即可
        ],
      ],
    };

    完成。

    库项目

    库类项目也推荐使用 @babel/plugin-transform-runtime,因为库项目通常会面临另一个问题。如果我们直接导入 core-js 作 polyfill 的话,像 PromiseSetMap 这样的全局对象就会被覆盖。对于一般的应用而言,问题不大;但如果是库,你无法预期其它开发者会在什么情况下使用你的库,很可能他的目标平台都支持这些新语法元素,不希望转译污染。

    此时,使用 @babel/plugin-transform-runtime 可以让 babel 在转译时使用沙箱包装 polyfill,避免污染全局。

    参考文档

  • SonarQube + SonarJS 体验笔记

    试用了一下 SonarSource 开发的代码质量静态分析工具,记录一些过程和体验。

    SonarQube

    SonarQube 是 SonarSource 开发的代码质量静态分析工具,有四个不同的收费级别,分别是:

    1. 社区版(免费)
    2. 开发者版(个人版)
    3. 企业版
    4. 数据中心版

    很自然我会选择社区版来体验。社区版支持 15 种语言,包括我们前端要用到的 CSS、HTML、JS、TS,以及衍生出来的 React、Vue。基本上就够我们用了。社区版支持自主部署,后面三个付费版基本上对主要功能(代码质量静态分析)没有变化,只是增加语言类型、代码行数和并发,属于企业级需求,可以说对商业相当友好,起步完全免费。

    安装和使用

    官方提供安装文档:Install the Server | SonarQube Docs

    安装后,可以使用 SonarScanner 对代码仓库进行扫描,并将结果上传到 SonarCube 实例上,以供查看。

    分析结果很全面,包含可靠性、安全性、可维护性、覆盖率、重复性、复杂度等等。用他们自己的话说,SonarJS “可能”是最好的静态分析工具,此言非虚。

    SonarJS

    从 SonarQube 的安装文档可以看出,一套完整 Qube 产品由三个部分组成:

    1. 数据库,用来存储数据
    2. Web 服务器,用来提供我们查阅的界面
    3. 各种 scanner,用来扫描项目

    SonarJS 就是其中之一,可以单独使用。不过我没仔细研究。

    eslint-plugin-sonarjs

    我们还可以把 sonarjs 整合到现有开发流程当中,通过添加 eslint-plugin-sonarjs 插件。

    (小小吐个槽,作为质量管理工具,eslint-plugin-sonarjs 上来就往我的项目中添加了 33 个威胁,其中 11 个中等威胁, 22 个高等威胁……)

    使用方式比较简单:

    1. npm i eslint-plugin-sonarjs -D 安装插件
    2. 修改配置文件,添加 { plugins: ['sonarjs'] },以及规则集 { extends: ['plugin:sonarjs/recommended'] } 即可
    3. 如果需要启动全部规则,则需要使用 @typescript-eslint/parser。具体的还是看官方文档吧。

    不过这个 eslint 插件里的规则似乎并不够多,粗粗一数也就 30 左右,远远少于 SonarJS 号称 240+的规则,可能还是受限于 ESLint 本身吧。可能的话还是要通过 scanner 来做检查。

    总结

    作为一款免费工具,SonarQube 很是给了我一些惊喜。我准备自己搭一个服务器端,然后把 eslint-plugin-sonarjs 加到我所有个人项目中。

  • 再见,OpenResty Inc.

    再见,OpenResty Inc.

    被前司(现在是前前司了)裁员之后,我受罗辑思维鼓动,想尝试知识付费,于是折腾了大半年。发现这样下去养不活自己和家庭之后,我又开始找工作,刚好看到春哥在微博上招人,因为事先对 OpenResty 有所耳闻,知道这是个很厉害的产品,春哥也是个很厉害的程序员,就去应聘。于是幸运的得到这个机会,加入了 OpenResty Inc.。

    时光荏苒,一晃又是四年多,到了说再见的时间。

    在 OpenResty 的工作整体来说是紧张而快乐的。春哥应该是很多程序员最向往的样子,至少对我来说是如此:

    1. 有成功的作品
    2. 有厉害的技术
    3. 有很高的业界声望
    4. 有良好的个人家庭生活

    所以能为春哥工作对我来说是一件幸事,因为可以近距离观察这样一位成功的程序员,能从他身上学习,指导自己接下来的发展。我也的确从他那里学到很多东西。

    不过相应的,在 OpenResty 工作也面临很大压力。一方面,OpenResty 是创业公司,我们要从零开始搭建很多产品,应对很多挑战;另一方面,春哥又会对产品的实施细节进行多方位多角度的监督和审视。所以,虽然过去几年都是远程工作,我面临的工作压力却一点也不小,工作时间也一点都不短。

    这样紧张而快乐的生活在本月末画上了句号。接下来,我会为金山文档效力。希望接下来的新旅程能为我带来新的成长,希望我可以给新东家带来超出他们预期的价值吧。

    聊聊远程

    接下来聊聊远程。很多同学听说我放弃远程工作,转投一家集中办公的公司,表示不理解,所以今天借此机会聊两句。

    远程工作爽么?爽,但也就那么回事。正像我在前面一篇博客中说的:没有工作会让你很爽。如果你工作的很爽,要么你有问题,要么工作有问题。

    有人为财务自由划定了若干标准,比如菜场自由、超市自由、便利店自由、电子产品自由、车自由、房子自由等。这个做法其实很科学,因为很多事情本就不是非黑即白的,是否两头中间存在大量中间地带。

    我也仿效 ta 来制定一些新的“自由”标准吧。远程工作可以带来很多平价自由,比如:

    • “穿衣自由”:我在过去四年里,85% 的时间只穿一条内裤
    • “食物自由”:韭菜盒子、臭豆腐、大蒜想吃就吃
    • “厕所自由”:几千块的卫立洗只有我一个人用
    • “空调自由”、“电影自由”、“青轴自由”、“听歌自由”等等等等

    这些自由你说没价值吧,肯定不对;但是如果有人觉得应该抛弃其它要素来追求这些自由,那就完全跑偏了。因为很明显,还有很多更有价值、价格也更高的自由,比如“孩子上学自由”、“买电脑自由”、“买房自由”等。

    所以,当一份工作,能够提供其它更有价值的加分项,只是缺少远程工作能带来的平价自由的话,我们当然应该好好考虑这个机会。尤其对于我这种年纪越来越大,机会越少越少,机会成本越来越高的中年男子来说,更是如此。

    “远程工作工资会低么?”

    多半会低。我们的工资是老板决定的,也是市场决定的,里面包含着地理位置溢价。比如,在北京,一个月至少要大几千块才能保证生活质量,那么一个靠谱的程序员就不可能接受万把块的工资;而比如我,假如在郑州老家的话,有房有车有老妈,一个月节省 1w 块的开销,收入一万就能抵两万。

    所以同样技术水平、工作态度的两个人,一个在北京一个在郑州,两个人对工资的要求完全不同。集中办公时,他们俩不产生交集,无所谓;远程办公时,他们俩直接竞争,老板很可能更倾向于后者。

    所以,远程工作的工资会明显低于一线,持平或略低于二线。除非候选人有他人没有的竞争优势。

    “我没有在大城市工作过,想在老家找个远程工作,可以么?”

    我不敢把话说死,说一定不行;但我必须说,这非常难。

    对于程序员来说,自己的努力固然重要,公司带来的产品压力与用户压力同样重要。比如我的博客,一天几百访问,随便弄个月租$10 的小 VPS 就能跑起来。WordPress + 本地 MySQL,简单配置下缓存就好。如果你只搞过这个级别的服务,突然要你分库、读写分离、上缓存,你就没法搞。

    前端也一样。我因为只考虑放客是开发者,所以只关心最新 PC/macOS Chrome 能不能看。但是真正的生产环境,哪家公司不得兼容最近三年所有系统和浏览器版本?到时候一堆兼容性和性能问题就折腾死人了。

    没有大城市大公司的工作经验,往往就欠缺解决这些问题的技能和经验。于是跟那些大城市退回老家的人比起来,也就缺乏竞争力了。现在真正意义上的远程岗位并没有很多,大多数其实是接单和基础外包,所以我要说,很难。

    总结

    时光如流水,突如其来一个拐弯,我也步入下一个阶段。简单与前面告个别,希望下一个五年,能不负时光,继往开来。

  • Linux 命令行笔记

    遍历目录查找内容

    grep -rnw '/path/to/somewhere/' -e 'pattern'
    • -r 或 -R 表示遍历
    • -n 显示行号
    • -w 全文匹配
    • -l (小写 L) 只显示文件名
    • -e 匹配的表达式

    参考:How do I find all files containing specific text on Linux? – Stack Overflow

    复制多个目录

    cp -r d1 d2 d-* dist/

    查找磁盘占用

    ubuntu@VM-16-15-ubuntu:~$ sudo du -h --max-depth=1 /var/lib/docker/volumes | sort -h
    8.0K	/var/lib/docker/volumes/7e6264b4649948452587e6b562dae6a5881d17ba0254eefb6a98e32dae35f593
    8.0K	/var/lib/docker/volumes/e3a990377c6c214dd7b8c45807c8f63319988b2a29c4e58d13c0445da3e2e7d5
    8.0K	/var/lib/docker/volumes/sentry-self-hosted_sentry-kafka-log
    8.0K	/var/lib/docker/volumes/sentry-self-hosted_sentry-secrets
    12K	/var/lib/docker/volumes/373a3159029b89230c64a7dc74633a48f29fa5a10dd7fee57a16318d659e76a7
    12K	/var/lib/docker/volumes/sentry-self-hosted_sentry-smtp-log
    28K	/var/lib/docker/volumes/sentry-self-hosted_sentry-nginx-cache
    36K	/var/lib/docker/volumes/sentry-symbolicator
    92K	/var/lib/docker/volumes/sentry-self-hosted_sentry-smtp
    144K	/var/lib/docker/volumes/sentry-self-hosted_sentry-zookeeper-log
    368K	/var/lib/docker/volumes/sentry-zookeeper
    712K	/var/lib/docker/volumes/sentry-data
    1.8M	/var/lib/docker/volumes/sentry-redis
    814M	/var/lib/docker/volumes/sentry-self-hosted_sentry-clickhouse-log
    1.6G	/var/lib/docker/volumes/sentry-clickhouse
    14G	/var/lib/docker/volumes/sentry-postgres
    16G	/var/lib/docker/volumes/sentry-kafka
    31G	/var/lib/docker/volumes
    
  • WSL2 使用 ssh-server 笔记

    WSL2 使用 ssh-server 笔记

    升级到 WSL2 之后,我面临几个问题:

    • webpack-dev-server 无法监控 /mnt/c/xxxx 这样的路径下的文件变化,使得 HMR 失效。只能借由 ~/path/to/project/ 启动开发环境。
    • 启动服务后,无法通过 127.0.0.1:port/ 访问,只能通过 localhost:port 访问。

    今天想使用 ssh-server 连上去,结果失败,经查,原来 WSL2 的底层逻辑变了:

    If this is WSL2 (which it appears to be) this is for all intents by-design. WSL2 has its own Real Linux™ network stack inside a VM, and a virtual Ethernet device. Contrast WSL1 which took the approach of attempting to present the Windows network stack inside Linux.

    WSL2 就是这么设计的。它在虚拟机里有自己真实的 Linux 网络栈,以及虚拟的以太网设备。

    WSL2: ifconfig not showing all network connections · Issue #4915 · microsoft/WSL (github.com)

    所以要用不同的方式来做,记录在这篇笔记里。

    方案一:使用 ssh-server

    1. 在 Windows 里安装 ssh-server

    1. 设置 > 应用 > 应用和功能 > 可选功能 > 添加功能
    2. 搜索并安装 ssh-sever
    3. 启动 ssh-server,需使用管理员身份启动 powershell
    # 启动 ssh 服务
    Start-Service sshd
    
    # 可选,设置 sshd 为开机自动启动
    Set-Service -Name sshd -StartupType 'Automatic'
    
    # 往防火墙里添加规则
    if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | Select-Object Name, Enabled)) {
        Write-Output "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..."
        New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
    } else {
        Write-Output "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists."
    }

    2. ssh 连接

    在另外一台机器上输入 ssh username@ip-or-host 即可完成连接。

    不过需要注意的是,比如我的账户名为 Meathill Zhai,这个名称不能直接使用。我的邮箱是 realmeathill[at]hotmail.com,系统会自动截取一段,我在命令行里的初始位置是:C:\Users\realm,于是登录的时候只能用 ssh realm@ip,然后输入 realmeathill[at]hotmail.com 的密码,方能完成登录。

    3. 改为连接 WSL2

    默认情况下,使用 ssh 连入的命令行是 cmd.exe,可以通过以下命令修改为 Powershell:

    New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force
    

    如果把里面的 WindowsPowerShell\v1.0\powershell.exe 修改为 bash.exe,那么就会连入 WSL2 了。

    4. 改回 powershell

    直接连入WSL2 也不是不行,不过这样的话,一些涉及 Windows 系统的操作无法进行,exit 也是直接退出 ssh 连接。于是我觉得默认连入 powershell 比较好,想用 WSL 就手动 bash 进入 WSL 即可。

    待解决问题

    • powershell ssh-server 不支持 authorized_keys 登录

    参考文章

  • Chrome Extension Manifest V3 升级笔记

    Chrome Extension Manifest V3 升级笔记

    关于 MV3 的知识,可以看这篇前文:

    《Chrome 扩展大升级 Manifest V3:变化》

    近期尝试把一个浏览器扩展升级到 MV3,然后发现了很多问题,写篇博客记录一下。

    与文档不符

    • 很多号称返回 Promise 实例的方法实际上不会返回结果,或者在没有 callback 的时候会报错

    文档未说明

    • service worker 里无法使用 chrome.i18n.getMessage 或者 chrome.i18n.getUILanguage(【v100 已修复】)。于是目前右键菜单都无法 i18n 了。
    • chrome.contextMenus.create 无法向 browser_action 添加菜单项(MV3 不再支持)

    未实现 Promise 或者有 bug

    • chrome.storage.local.get(keys) Promise 结果固定返回 undefined,不可用,需用回 callback (【v97 已修复】)
    • 【未测试】chrome.permissions.request 未 Promise 化,无返回值
    • 【未测试】必须传入 callback,不然报错
      • chrome.permissions.contains
      • chrome.contextMenus.remove

    文档写了,但我没看见(>_<!)

    • 在 service worker 里使用 chrome.contextMenus.create 时,必须传入 id,且不能使用 onclick 函数。只能侦听 chrome.contextMenus.onClicked.addListener,然后做进一步判断。

    MV2 存在的功能,但无法迁移至 MV3

    (有些可能是暂时的)

    因:不允许使用 remote JS

    MV3 不支持使用外部 JS,所以 Google Analytics 和 Facebook 分享按钮之类的 SDK 都无法使用。也即是说,现在扩展不能使用 GA,也不能嵌入 Facebook Like,也不能使用其它需要引用外部 SDK 才能实现的功能。

    因:Service worker 取代 background page/JS

    无法获取系统 light/dark mode

    Service worker 没有 window 对象,也就没有那些 window 对象上才有的方法,比如 matchMedia()。所以我们就无法在 service worker 里检查系统的 dark mode,实现动态修改 icon 的功能。

    无法将 SVG 解析成 ImageData

    因为 createImageBitmap 无法解析 SVG blob,所以原则上,所有 SVG 的操作均无法执行。

    外传信息比较麻烦

    无法使用 alert()prompt(),也无法手动弹出 popup。所以如果我们想从 service worker 里向外传出内容,只有三个选择:

    1. 使用通知 notification API
    2. 直接打开页面
    3. 使用 chrome.scripting.executeScript() 在目标页执行脚本

    (1)(3)需要不同的需要权限,(2)的体验不好。怎么用就自己选择吧。

  • 代友招聘:广州番禺广告公司招 PHP 工程师

    代友招聘:广州番禺广告公司招 PHP 工程师

    做 ocpx 和 rta 方面的。dsp 平台 adx 方便的接口对接。

    (上面这一行我完全看不懂……)

    岗位职责:

    1. 负责公司产品后端系统维护,新功能开发,广告投放API对接;
    2. 参与需求讨论,对产品原型、功能逻辑设计等提出可靠建议;
    3. 使用yii2框架实现产品的标准化。

    岗位要求:

    1. 计算机相关专业大专或本科学历,有2年以上PHP开发经验,有大型网站或者数据系统开发经验者优先;
    2. 熟悉前端语言:HTML5/CSS3,JavaScript,Ajax,jQuery等;
    3. 熟悉 MVC 框架,熟悉主流 PHP 框架 yii2,有独立开发项目经验者优先;
    4. 精通 MySQL,熟悉 Linux、Apache/nginx、Redis 的管理和维护。
    5. 有开发小程序后端、广告 Marketing API 项目经验者优先;
    6. 熟悉版本控制器 git/svn 的使用;
    7. 有优秀的沟通与表达能力,突出的学习能力,较强的动手能力与逻辑分析能力;
    8. 具备良好的团队合作精神,高度的责任感,善于沟通,为人踏实,抗压能力强,拥有严谨的工作态度,能虚心学习。
    9. 具备良好的代码编程习惯,熟悉面向对象编程,及较强的文档编写能力。

    工作地点在番禺,朋友的公司,很靠谱。感兴趣的同学直接联系我吧。