Lighthouse 是用于深入了解网页性能的最流行的开发工具之一,它提供了一个CLI 和 Node 模块,因此我们可以以编程方式运行它。但是,如果您在同一个网页上多次运行 LIghthouse,您会发现它的分数会有所不同,那是因为存在已知的可变性。影响 Lighthouse 可变性的因素有很多,处理差异的推荐策略之一是多次运行 Lighthouse。
在本文中,我们将使用 CLI 来实施此策略,实施将涵盖:
这是配置工具后的文件结构。
my-script
├── .eslintrc.js
├── .prettierrc.json
├── package.json
├── tsconfig.json
├── bin
└── src
├── utils.ts
└── index.ts
我们将使用 Yarn 作为这个项目的包管理器,如果您愿意,也可以使用 npm。
我们将创建一个名为 my-script 的目录:
$ mkdir my-script && cd my-script
在项目根目录中,我们使用 Yarn 创建一个 package.json:
$ yarn init
安装 TypeScript 和 NodeJS 的类型,运行:
$ yarn add --dev typescript @types/node
在我们配置 TypeScript 时,可以使用 tsc 初始化一个 tsconfig.json:
$ npx tsc --init
为了编译 TypeScript 代码并将结果输出到 /bin 目录下,我们需要在 tsconfig.json 的 compilerOptions 中指定 outDir。
// tsconfig.json
{
"compilerOptions": {
+ "outDir": "./bin"
/* rest of the default options */
}
}
然后,让我们测试一下。
在项目根目录下,运行以下命令,这将在 /src 目录下中创建 index.ts 文件:
$ mkdir src && touch src/index.ts
在 index.ts 中,我们编写一个简单的 console.log 并运行 TypeScript 编译器,以查看编译后的文件是否在 /bin 目录中。
// src/index.ts
console.log('Hello from my-script')
添加一个用 tsc 编译 TypeScript 代码的脚本。
// package.json
+ "scripts": {
+ "tsc": "tsc"
+ },
然后运行:
$ yarn tsc
你将在 /bin 目下看到一个 index.js 文件。
然后我们在项目根目录下执行 /bin 目录:
$ node bin
# Hello from my-script
首先我们需要在项目中安装 ESLint。
$ yarn add --dev eslint
EsLint 是一个非常强大的 linter,但它不支持 TypeScript,所以我们需要安装一个 TypeScript 解析器:
$ yarn add --dev @typescript-eslint/parser @typescript-eslint/eslint-plugin
我们还安装了 @typescript-eslint/eslint-plugin,这是因为我们需要它来扩展针对 TypeScript 特有功能的 ESLint 规则。
配置 ESLint,我们需要在项目根目录下创建一个 .eslintrc.js 文件:
$ touch .eslintrc.js
在 .eslintrc.js 中,我们可以进行如下配置:
// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: ['plugin:@typescript-eslint/recommended']
}
让我们进一步了解下这个配置:我们首先使用 @typescript-eslint/parser 来让 ESLint 能够理解 TypeScript 语法,然后我们应用 @typescript-eslint/eslint-plugin 插件来扩展这些规则,最后,我们启用了@typescript-eslint/eslint-plugin 中所有推荐的规则。
如果您有兴趣了解更多关于配置的信息,您可以查看官方文档 以了解更多细节。
我们现在可以在 package.json 中添加一个 lint 脚本:
// package.json
{
"scripts": {
+ "lint": "eslint '**/*.{js,ts}' --fix",
}
}
然后去运行这个脚本:
$ yarn lint
Prettier 是一个非常强大的格式化程序,它附带一套规则来格式化我们的代码。有时这些规则可能会与 ESLInt 规则冲突,让我们一起看下将如何配置它们。
首先安装 Prettier ,并在项目根目录下创建一个 .prettierrc.json 文件,来保存配置:
$ yarn add --dev --exact prettier && touch .prettierrc.json
您可以编辑 .prettierrc.json 并且添加您的自定义规则,你可以在官方文档中找到这些选项。
// .prettierrc.json
{
"trailingComma": "all",
"singleQuote": true
}
Prettier 提供了与 ESLint 的便捷集成,我们将遵循官方文档中的推荐配置 。
$ yarn add --dev eslint-config-prettier eslint-plugin-prettier
在 .eslintrc.js 中,在 extensions 数组的最后一个位置添加这个插件。
// eslintrc.js
module.exports = {
extends: [
'plugin:@typescript-eslint/recommended',
+ 'plugin:prettier/recommended'
]
}
最后添加的这个 Prettier 扩展,非常重要,它会禁用所有与格式相关的 ESLint 规则,因此冲突将回退到 Prettier。
现在我们可以在 package.json 中添加一个 prettier 脚本:
// package.json
{
"scripts": {
+ "prettier": "prettier --write ."
}
}
然后去运行这个脚本:
$ yarn prettier
我们的配置已经基本完成,唯一缺少的是一种像执行命令那样执行项目的方法。与使用 node 执行 /bin 命令不同,我们希望能够直接调用命令:
# 我们想通过它的名字来直接调用这个命令,而不是 "node bin",像这样:
$ my-script
我们怎么做呢?首先,我们需要在 src/index.ts 的顶部添加一个 Shebang):
+ #!/usr/bin/env node
console.log('hello from my-script')
Shebang 是用来通知类 Unix 操作系统这是 NodeJS 可执行文件。因此,我们可以直接调用脚本,而无需调用 node。
让我们再次编译:
$ yarn tsc
在一切开始之前,我们还需要做一件事,我们需要将可执行文件的权限分配给bin/index.js:
$ chmod u+x ./bin/index.js
让我们试一试:
# 直接执行
$ ./bin/index.js
# Hello from my-script
很好,我们快完成了,最后一件事是在命令和可执行文件之间创建符号链接。首先,我们需要在 package.json 中指定 bin 属性,并将命令指向 bin/index.js。
// package.json
{
+ "bin": {
+ "my-script": "./bin/index.js"
+ }
}
接着,我们在项目根目录中使用 Yarn 创建一个符号链接:
$ yarn link
# 你可以随时取消链接: "yarn unlink my-script"
让我们看看它是否有效:
$ my-script
# Hello from my-script
成功之后,为了使开发更方便,我们将在 package.json 添加几个脚本:
// package.json
{
"scripts": {
+ "build": "yarn tsc && yarn chmod",
+ "chmod": "chmod u+x ./bin/index.js",
}
}
现在,我们可以运行 yarn build 来编译,并自动将可执行文件的权限分配给入口文件。
是时候实现我们的核心逻辑了,我们将探索几个方便的 NPM 包来帮助我们编写CLI,并深入了解 Lighthouse 的魔力。
$ yarn add chalk@4.1.2
确保你安装的是 chalk 4,chalk 5是纯 ESM,在 TypeScript 4.6 发布之前,我们无法将其与 TypeScript 一起使用。
chalk 为 console.log 提供颜色,例如:
// src/index.ts
import chalk from 'chalk'
console.log(chalk.green('Hello from my-script'))
现在在你的项目根目录下运行 yarn build && my-script 并查看输出日志,会发现打印结果变成了绿色。
让我们用一种更有意义的方式来使用 chalk,Lighthouse 的性能分数是采用颜色标记的。我们可以编写一个实用函数,根据性能评分用颜色显示数值。
// src/utils.ts
import chalk from 'chalk'
/**
* Coloring display value based on Lighthouse score.
*
* - 0 to 0.49 (red): Poor
* - 0.5 to 0.89 (orange): Needs Improvement
* - 0.9 to 1 (green): Good
*/
export function draw(score: number, value: number) {
if (score >= 0.9 && score <= 1) {
return chalk.green(`${value} (Good)`)
}
if (score >= 0.5 && score < 0.9) {
return chalk.yellow(`${value} (Needs Improvement)`)
}
return chalk.red(`${value} (Poor)`)
}
在 src/index.ts 中使用它,并尝试使用 draw() 记录一些内容以查看结果。
// src/index.ts
import { draw } from './utils'
console.log(`Perf score is ${draw(0.64, 64)}`)
要使我们的 CLI 具有交互性,我们需要能够读取用户输入并解析它们。commander 是定义接口的一种描述性方式,我们可以以一种非常干净和纪实的方式实现界面。
我们希望用户与 CLI 交互,就是简单地传递一个 URL 让 Lighthouse 运行,我们还希望传入一个选项来指定 Lighthouse 应该在 URL 上运行多少次,如下:
# 没有选项
$ my-script https://dawchihliou.github.io/
# 使用选项
$ my-script https://dawchihliou.github.io/ --iteration=3
使用 commander 可以快速的实现我们的设计。
$ yarn add commander
让我们清除 src/index.ts 然后重新开始:
#!/usr/bin/env node
import { Command } from 'commander'
async function run() {
const program = new Command()
program
.argument('<url>', 'Lighthouse will run the analysis on the URL.')
.option(
'-i, --iteration <type>',
'How many times Lighthouse should run the analysis per URL',
'5'
)
.parse()
const [url] = program.args
const options = program.opts()
console.log(`url: ${url}, iteration: ${options.iteration}`)
}
run()
我们首先实例化了一个 Command,然后使用实例 program 去定义:
要使用参数和选项,我们首先解析命令并记录变量。
现在我们可以运行命令并观察输出日志。
$ yarn build
# 没有选项
$ my-script https://dawchihliou.github.io/
# url: https://dawchihliou.github.io/, iteration: 5
# 使用选项
$ my-script https://dawchihliou.github.io/ --iteration=3
# 或者
$ my-script https://dawchihliou.github.io/ -i 3
# url: https://dawchihliou.github.io/, iteration: 3
很酷吧?!另一个很酷的特性是,commander 会自动生成一个 help 来打印帮助信息。
$ my-script --help
我们在上一节中学习了如何解析用户输入,是时候深入了解 CLI 的核心了。
运行多个 Lighthouse 的建议是在单独的进程中运行它们,以消除干扰的风险。cross-spawn 是用于生成进程的跨平台解决方案,我们将使用它来同步生成新进程来运行 Lighthouse。
要安装 cross-spawn:
$ yarn add cross-spawn
$ yarn add --dev @types/cross-spawn
# 安装 lighthouse
$ yarn add lighthouse
让我们编辑 src/index.ts:
#!/usr/bin/env node
import { Command } from 'commander'
import spawn from 'cross-spawn'
const lighthouse = require.resolve('lighthouse/lighthouse-cli')
async function run() {
const program = new Command()
program
.argument('<url>', 'Lighthouse will run the analysis on the URL.')
.option(
'-i, --iteration <type>',
'How many times Lighthouse should run the analysis per URL',
'5'
)
.parse()
const [url] = program.args
const options = program.opts()
console.log(
` Running Lighthouse for ${url}. It will take a while, please wait...`
)
const results = []
for (let i = 0; i < options.iteration; i++) {
const { status, stdout } = spawn.sync(
process.execPath, [
lighthouse,
url,
'--output=json',
'--chromeFlags=--headless',
'--only-categories=performance',
])
if (status !== 0) {
continue
}
results.push(JSON.parse(stdout.toString()))
}
}
run()
在上面的代码中,根据用户输入,多次生成新进程。在每个过程中,使用无头Chrome 运行 Lighthouse 性能分析,并收集 JSON 数据。该 result 变量将以字符串的形式保存一组独立的性能数据,下一步是汇总数据并计算最可靠的性能分数。
如果您实现了上面的代码,您将看到一个关于 require 的 linting 错误,是因为 require.resolve 解析模块的路径而不是模块本身。在本文中,我们将允许编译 .eslintrc.js 中的 @typescript-eslint/no-var-requires 规则。
// .eslintrc.js
module.exports = {
+ rules: {
+ // allow require
+ '@typescript-eslint/no-var-requires': 0,
+ },
}
一种策略是通过计算中位数来汇总报告,Lighthouse 提供了一个内部功能computeMedianRun,让我们使用它。
#!/usr/bin/env node
import chalk from 'chalk';
import { Command } from 'commander'
import spawn from 'cross-spawn'
import {draw} from './utils'
const lighthouse = require.resolve('lighthouse/lighthouse-cli')
// For simplicity, we use require here because lighthouse doesn't provide type declaration.
const {
computeMedianRun,
} = require('lighthouse/lighthouse-core/lib/median-run.js')
async function run() {
const program = new Command()
program
.argument('<url>', 'Lighthouse will run the analysis on the URL.')
.option(
'-i, --iteration <type>',
'How many times Lighthouse should run the analysis per URL',
'5'
)
.parse()
const [url] = program.args
const options = program.opts()
console.log(
` Running Lighthouse for ${url}. It will take a while, please wait...`
)
const results = []
for (let i = 0; i < options.iteration; i++) {
const { status, stdout } = spawn.sync(
process.execPath, [
lighthouse,
url,
'--output=json',
'--chromeFlags=--headless',
'--only-categories=performance',
])
if (status !== 0) {
continue
}
results.push(JSON.parse(stdout.toString()))
}
const median = computeMedianRun(results)
console.log(`\n${chalk.green('✔')} Report is ready for ${median.finalUrl}`)
console.log(
` Median performance score: ${draw(
median.categories.performance.score,
median.categories.performance.score * 100
)}`
)
const primaryMatrices = [
'first-contentful-paint',
'interactive',
'speed-index',
'total-blocking-time',
'largest-contentful-paint',
'cumulative-layout-shift',
];
primaryMatrices.map((matrix) => {
const { title, displayValue, score } = median.audits[matrix];
console.log(` Median ${title}: ${draw(score, displayValue)}`);
});
}
run()
在底层,computeMedianRun 返回最接近第一次 Contentful Paint 的中位数和 Time to Interactive 的中位数的分数。这是因为它们表示页面初始化生命周期中的最早和最新时刻,这是一种确定中位数的更可靠的方法,而不是简单的从单个测量中找到中位数的方法。
现在再试一次命令,看看结果如何。
$ yarn build && my-script https://dawchihliou.github.io --iteration=3
我们的实现已经完成,让我们在自动化的工作流中使用 CLI,这样我们就可以在CD/CI 管道中对性能进行基准测试。
首先,让我们在 NPM 上发布这个包(假设)。
我发布了一个 NPM 包 dx-scripts,其中包含了 my-script 的生产版本,我们将用 dx-script 编写 GitHub Actions 工作流来演示我们的 CLI 应用程序。
我们需要在 packgage.json 中添加一个 files 属性,来发布 /bin 目录。
// package.json
{
+ "files": ["bin"],
}
然后简单的运行:
$ yarn publish
现在包就在 NPM 上了(假设)!
让我们讨论一下工作流,我们希望工作流:
因此,在工作流成功完成后,您将看到来自 GitHub Action Bot 的评论与您的 Lighthouse 分数。
为了专注于 CLI 的应用,我将在工作流中对功能分支预览 URL 进行硬编码。
在应用程序存储库中,安装 dx-scripts:
$ yarn add --dev dx-script
添加一个 lighthouse-dev-ci.yaml 到 GitHub 工作流目录中:
# .github/workflows/lighthouse-dev-ci.yaml
name: Lighthouse Dev CI
on: pull_request
jobs:
lighthouse:
runs-on: ubuntu-latest
env:
# You can substitute the harcoded preview url with your preview url
preview_url: https://dawchihliou.github.io/
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '16.x'
- name: Install dependencies
run: yarn
# You can add your steps here to create a preview
- name: Run Lighthouse
id: lighthouse
shell: bash
run: |
lighthouse=$(npx dx-scripts lighthouse $preview_url)
lighthouse="${lighthouse//'%'/'%25'}"
lighthouse="${lighthouse//$'\n'/'%0A'}"
lighthouse="${lighthouse//$'\r'/'%0D'}"
echo "::set-output name=lighthouse_report::$lighthouse"
- name: Notify PR
uses: wow-actions/auto-comment@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
pullRequestSynchronize: |
@{{ author }},
Here is your Lighthouse performance overview
```
${{ steps.lighthouse.outputs.lighthouse_report }}
```
在 “Run Lighthouse” 步骤中,我们运行 dx-script Lighthouse CLI,替换特殊字符以打印多行输出,并将输出设置在一个变量 lighthouse_report 中。在 “Notify PR” 步骤中,我们用 “Run Lighthouse” 步骤的输出写了一条评论,并使用 wow-actions/auto-comment 操作来发布评论。
写一个 CLI 还不错吧?让我们来看看我们已经涵盖的所有内容:
原文:https://betterprogramming.pub/writing-your-own-typescript-cli-6f9c5688ad34
近些日子,我使用了新语言编程,从JavaScript,切确地说是Elm,转成TypeScript。在本文中,我将继续深挖一些我非常喜欢的TypeScript特性。
TypeScript 和 JavaScript 是目前项目开发中较为流行的两种脚本语言,我们已经熟知 TypeScript 是 JavaScript 的一个超集,但是 TypeScript 与 JavaScript 之间又有什么样的区别呢?
Nerv_是一款由京东凹凸实验室打造的类 React 前端框架,基于虚拟 DOM 技术的 JavaScript(TypeScript) 库。它基于React标准,提供了与 React 16 一致的使用方式与 API。
交叉类型:将多个类型合并为一个类型、联合类型:表示取值可以为多种类型中的一种、混合类型:一个例子就是,一个对象可以同时做为函数和对象使用,并带有额外的属性、类型断言:可以用来手动指定一个值的类型
在做比较大的,多人合作的项目的时候,TypeScript会更加地适合,这得益于它的可读性,面向对象性以及易于重构的特点。但如果只是自己做小程序,不需要太多人参与的时候,JavaScript则会更加简单。
有两种方式安装TypeScript,如何创建第一个TypeScript文件,在TypeScript中,可以使用interface来描述一个对象有firstName和lastName两个属性,TypeScript支持JavaScript的新功能,其中很重要的一个功能就是基于类的面向对象编程
使用TypeScript已经有了一段时间,这的确是一个好东西,虽说在使用的过程中也发现了一些bug,不过都是些小问题,所以整体体验还是很不错的。有关TypeScript声明类型声明相关的目前就总结了这些比较常用的
谷歌在很早之前就张开双臂拥抱 Web 应用程序,Gmail 已经发布 14 年了。当时,JavaScript 的世界是疯狂的。Gmail 工程师不得不为 IE 糟糕的垃圾回收算法捏一把汗,他们需要手动将字符串文字从 for 循环中提取出来,以避免 GC 停顿
TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。
差不多两年前,我在一个创业团队中开始了一个全新的项目。用到的全都是类似Microservices,docker,react,redux这些时髦的东西。我在前端技术方面积累了一些类似的经验
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!