前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >VueUse scripts,他们都模仿过的脚本

VueUse scripts,他们都模仿过的脚本

作者头像
码农小余
发布2022-06-16 16:50:59
1.2K0
发布2022-06-16 16:50:59
举报
文章被收录于专栏:码农小余

大家好,我是码农小余。

今天…… 女同事问小余:你平时是怎么高效地积累这么多好用的第三方包? 小余:就没事多逛 github,多点开 package.json 看看。

今天我们就“单纯”地从 VueUse scripts 入手,从中探索我们后续也许会用上的第三方包库。

scripts

先总的看一下 VueUse 的 script:

代码语言:javascript
复制
"scripts": {
    "build": "nr update && esno scripts/build.ts",
    "build:redirects": "esno scripts/redirects.ts",
    "build:rollup": "cross-env NODE_OPTIONS=\"--max-old-space-size=6144\" rollup -c",
    "build:types": "tsc --emitDeclarationOnly && nr types:fix",
    "clean": "rimraf dist types packages/*/dist",
    "dev": "nr update && nr docs",
    "docs": "vitepress dev packages --open",
    "docs:build": "nr update:full && vitepress build packages && nr build:redirects && esno scripts/post-docs.ts",
    "docs:serve": "vitepress serve packages",
    "lint": "eslint .",
    "lint:fix": "nr lint --fix",
    "publish:ci": "esno scripts/publish.ts",
    "install-fonts": "gfi install Inter && gfi install Fira Code",
    "release": "esno scripts/release.ts && git push --follow-tags",
    "size": "esno scripts/export-size.ts",
    "test": "nr test:3",
    "test:2": "vue-demi-switch 2 vue2 && vitest run --silent",
    "test:3": "vue-demi-switch 3 && vitest run",
    "test:all": "nr test:3 && nr test:2 && vue-demi-switch 3",
    "test:watch": "vitest --watch",
    "typecheck": "tsc --noEmit",
    "types:fix": "esno scripts/fix-types.ts",
    "update": "nr -C packages/metadata update && esno scripts/update.ts",
    "update:full": "nr update && nr build:types",
    "watch": "esno scripts/build.ts --watch"
}

简述每一条脚本的职责:

  • build:xx 均用于构建;
  • clean 用于删除构建产物;
  • dev、docs、docs:build、docs:serve 跟文档相关;
  • lint:xx 用于规范代码;
  • publish:ci 用于发包;
  • release 命令用于更新版本信息并打 tag;
  • size 命令用于输出每个包的体积;
  • test:xx 均与单元测试相关;
  • typecheck 命令借助 tsc 检查类型;
  • types:fix 用于修改类型声明;
  • update:xx 用于更新 README、下载量、贡献者等信息;
  • watch 支持热更的构建;

接下来我们重点了解一些常用和复杂的脚本——update、build、release、size。

update

update 脚本执行的是

代码语言:javascript
复制
nr -C packages/metadata update && esno scripts/update.ts

nr[1] 也许你之前没有接触过,它主要的作用:

  1. 根据 lock 文件自动识别包管理器;
  2. 磨平不同包管理器之间的差异,比如 nx jest 根据包管理器的不同,可能执行 npx jest、yarn dlx jest 或者 pnpm dlx jest。

前半部分 nr -C packages/metadata update 表示切换到 packages/metadata 目录然后执行 update 脚本。(补充:也可以通过 pnpm run update --filter @vueuse/metadata 完成一样的动作,项目中使用了 @antfu/ni[2] 所以通过 nr -C 包管理更加一致)。

回到 metadata 包,我们详细了解 update:

代码语言:javascript
复制
import { ecosystemFunctions } from '../../../meta/ecosystem-functions'
import { packages } from '../../../meta/packages'

// 读取整个 VueUse 的元数据
export async function readMetadata() {
  // indexes 包括 packages、categories、functions 3个属性
  const indexes: PackageIndexes = {
    packages: {},
    categories: [],
    functions: [
      ...ecosystemFunctions,
    ],
  }

  // ... 省略一堆处理代码
 for (const info of packages) {
   // ...
  }
  
  return indexes
}

async function run() {
  const indexes = await readMetadata()
  await fs.writeJSON(join(DIR_PACKAGE, 'index.json'), indexes, { spaces: 2 })
}

run()

readMetadata 方法比较简单,从 meta/ecosystem-functionsmeta/packages 读取元数据,生成 indexes 结果并写入 index.json 文件,打开 index.json 瞄一眼:

代码语言:javascript
复制
{
  "packages": {
    "shared": {
      "name": "shared",
      "display": "Shared utilities",
      "dir": "packages/shared"
    },
    "core": {
      "name": "core",
      "display": "VueUse",
      "description": "Collection of essential Vue Composition Utilities",
      "dir": "packages/core"
    },
    // ...
  },
  "categories": [
    // ...
  ],
  "functions": [
    {
      "name": "computedAsync",
      "package": "core",
      "lastUpdated": 1651597361000,
      "docs": "https://vueuse.org/core/computedAsync/",
      "category": "Utilities",
      "description": "computed for async functions",
      "alias": [
        "asyncComputed"
      ]
    },
    {
      "name": "computedEager",
      "package": "shared",
      "lastUpdated": 1645956777000,
      "docs": "https://vueuse.org/shared/computedEager/",
      "category": "Utilities",
      "description": "eager computed without lazy evaluation",
      "alias": [
        "eagerComputed"
      ]
    }
    // ...
   ]
}

上述 json 就跟 VueUse 文档中的信息[3]对应上了。抛开这些,我们看看 readMeta 中那些好用的包:

  • gray-matter[4] 用于解析 front matter[5](是 markdown 文件中的第一部分,并且必须采用在三点划线之间书写的有效的 YAML);
  • simple-git[6] 用于在任何 node.js 应用程序中运行 git 命令的轻量级接口,上述的 lastUpdated 字段便是通过 git log -1 --format=%at xx 获取的结果;
  • fast-glob[7] 是 Node.js 的一个非常快速和高效的 glob 库,快得飞起、支持多种否定匹配模式、支持同步、Promise、Stream API等都是它的亮点。

写入信息之后,update 会执行 esno scripts/update.ts:

代码语言:javascript
复制
import fs from 'fs-extra'
import { metadata } from '../packages/metadata/metadata'
import { updateContributors, updateCountBadge, updateFunctionREADME, updateFunctionsMD, updateImport, updateIndexREADME, updatePackageJSON, updatePackageREADME } from './utils'

async function run() {
  await Promise.all([
    // 更新各个包的入口文件 index.ts
    updateImport(metadata),
    // 更新每个包的 README
    updatePackageREADME(metadata),
    // 更新根目录的 README
    updateIndexREADME(metadata),
    // 更新附加组件
    updateFunctionsMD(metadata),
    // 更新方法的 README
    updateFunctionREADME(metadata),
    // 更新每个包的 package.json
    updatePackageJSON(metadata),
    // 更新计数徽章
    updateCountBadge(metadata),
    // 更新贡献者
    updateContributors(),
  ])

  await fs.copy('./CONTRIBUTING.md', './packages/contributing.md')
}

run()

本文主要是借助 VueUse 去了解 monorepo 项目的脚本框架,不会深入每个操作细节,有需求再回来查看每个函数逻辑即可。

至此,就完成了整个 update 操作,我们接着看 build 过程。

build

build 脚本执行以下命令:

代码语言:javascript
复制
nr update && esno scripts/build.ts

build 依赖 update 的执行,我们进入 scripts/build.ts

代码语言:javascript
复制
if (require.main === module)
  cli()

这个判断给你 3s 思考是想表达什么?1-2-3,时间到,请看答案:

当文件直接从 Node.js 运行时,则 require.main 被设置为其 module。这意味着可以通过测试 require.main === module确定文件是否被直接运行。 对于文件 foo.js,如果通过 node foo.js 运行,则为 true,如果通过 require('./foo') 运行,则为 false。 当入口点不是 CommonJS 模块时,则 require.mainundefined,且主模块不可达。

然后我们进入 build 流程:

代码语言:javascript
复制
import { metadata } from '../packages/metadata/metadata'

async function build() {
  consola.info('Clean up')
  exec('pnpm run clean', { stdio: 'inherit' })

  consola.info('Generate Imports')
  // 从上述 update 过程生成的 index.json 中获取,所以脚本顺序上是依赖 nr update 的
  await updateImport(metadata)

  consola.info('Rollup')
  exec(`pnpm run build:rollup${watch ? ' -- --watch' : ''}`, { stdio: 'inherit' })

  consola.info('Fix types')
  exec('pnpm run types:fix', { stdio: 'inherit' })

  await buildMetaFiles()
}

build 逻辑也很清晰:

  1. 执行 pnpm run clean 清除上一次构建结果,也就是删除 types、dist 等目录;等等,这里为什么不用 nr 而用了 pnpm run?建议给 antfu[8] 提个 PR~
  2. 执行 await updateImport(metadata) 生成每个包的入口文件(index.ts);
  3. 接着执行 pnpm run build:rollup 通过 rollup 完成构建动作,构建配置待会再看;
  4. 然后执行 pnpm run types:fix 完成 @vue/composition-api、vue 到 vue-demi 的类型修复;
  5. 最后更新 metadata 包中的 dist 信息,包括 LICENSE、index.json、package.json。

整个流程清楚之后,我们来深入学习 build:rollup:

代码语言:javascript
复制
/* eslint-disable no-global-assign */
// 使用 esbuild 即时转换 JSX、TypeScript 和 esnext 功能
require('esbuild-register')
module.exports = require('./scripts/rollup.config.ts')

查阅 ./scripts/rollup.config.ts

代码语言:javascript
复制
const esbuildPlugin = esbuild()
const esbuildMinifer = (options: ESBuildOptions) => {
  const { renderChunk } = esbuild(options)

  return {
    name: 'esbuild-minifer',
    renderChunk,
  }
}

// 96-108 行
{
  file: `packages/${name}/dist/${fn}.iife.min.js`,
  format: 'iife',
  name: iifeName,
  extend: true,
  globals: iifeGlobals,
  plugins: [
    injectVueDemi,
    esbuildMinifer({
      minify: true,
    }),
  ],
}

// 112-125
configs.push({
  input,
  output,
  plugins: [
    target
      ? esbuild({ target })
      : esbuildPlugin,
    json(),
  ],
  external: [
    ...externals,
    ...(external || []),
  ],
})

rollup 的配置只截取了部分配置,重点学习 rollup 构建中使用 esbuild 能力的过程。在插件部分,iife 输出格式上使用了 esbuild[9]minify [10] 配置,对于指定 target 的构建需求上,使用了 esbuild 的 target[11] 配置。

构建完了,接下来就看看发版(release)、发布(build)流程

release

release 流程执行的是:

代码语言:javascript
复制
esno scripts/release.ts && git push --follow-tags

查阅 scripts/release.ts :

代码语言:javascript
复制
import { execSync } from 'child_process'
import { readJSONSync } from 'fs-extra'

// 读取 package.json 中的 version 字段
const { version: oldVersion } = readJSONSync('package.json')

// 自动化发布过程
execSync('npx bumpp', { stdio: 'inherit' })

// 再次读取 version 字段
const { version } = readJSONSync('package.json')
// 比较新旧版本、如果一致就退出进程
if (oldVersion === version) {
  console.log('canceled')
  process.exit()
}
// 类型声明构建
execSync('npm run build:types', { stdio: 'inherit' })
// 执行更新
execSync('npm run update', { stdio: 'inherit' })
// add
execSync('git add .', { stdio: 'inherit' })
// commit
execSync(`git commit -m "chore: release v${version}"`, { stdio: 'inherit' })
// 打tag
execSync(`git tag -a v${version} -m "v${version}"`, { stdio: 'inherit' })

release 流程非常清晰,上述代码中有一个包引起我的注意力:bumpp[12] 基于 `version-bump-prompt`[13] 添加了以下特性:

  • 重命名为 bumpp ,可以直接使用 npx bumpp
  • 提供 ESM 和 CJS 构建包;
  • 添加一个新参数 --execute 以在提交前执行命令;

publish

最后来看 publish 过程:

代码语言:javascript
复制
esno scripts/publish.ts

流程如下:

代码语言:javascript
复制
import { packages } from '../meta/packages'

// 执行包的构建
execSync('npm run build', { stdio: 'inherit' })

let command = 'npm publish --access public'

// 如果 version 包含 beta 就在发布命令上添加 tag
if (version.includes('beta'))
  command += ' --tag beta'

// 依次发布每一个包
for (const { name } of packages) {
  execSync(command, { stdio: 'inherit', cwd: path.join('packages', name, 'dist') })
  consola.success(`Published @vueuse/${name}`)
}

publish 先执行包的构建,然后定义发布命令,如果 version 中包含 beta 字段,就在发布参数上打上 tag,可以在终端执行 npm info @vueuse/core 查看 beta 包:

最后循环包数据 packages 依次执行 npm publish --access public 进行发包。整个流程可以通过 vueuse 的 publish action[14] 进一步查看。

size

VueUse 有一个完全 tree shaking 的特性,并展示了每个函数构建后的体积。export-size[15] 页面可以查看到每个函数的大小:

这是怎么实现的呢?答案是在 scripts 中可以看到一条 esno scripts/export-size.ts 的脚本:

代码语言:javascript
复制
import { markdownTable } from 'markdown-table'
import { getExportsSize } from 'export-size'
import filesize from 'filesize'
import fs from 'fs-extra'
import { version } from '../package.json'
import { packages } from '../meta/packages'

async function run() {
  // ...

  for (const pkg of [...packages.slice(1), packages[0]]) {
    const { exports, packageJSON } = await getExportsSize({
      pkg: `./packages/${pkg.name}/dist`,
      output: false,
      bundler: 'rollup',
      external: ['vue-demi', ...(pkg.external || [])],
      includes: ['@vueuse/shared'],
    })

    // ...

    md += markdownTable([
      ['Function', 'min+gzipped'],
      ...exports.map((i) => {
        return [`\`${i.name}\``, filesize(i.minzipped)]
      }),
    ])

    md += '\n\n'
  }
  
  await fs.writeFile('packages/export-size.md', md, 'utf-8')
}

遍历 packages 列表,调用 getExportsSize 依次生成每个包下函数的信息:

然后通过以下两个包的处理

  • filesize[16] 用于显示可读的文件体积,并且支持国际化、四舍五入等配置;
  • markdownTable[17] 用于生成 markdown 格式的表格字符串;

最终将 md 字符串写入 export-size.md。

总结

本文从 VueUse 项目的 scripts 作为切入点,通过阅读 update、build、release、publish、size 等脚本的源码,不仅接触了大量好用的包,而且后续如果要自定义构建、更新版本、发包等流程,也能轻松应对~

参考资料

[1]

nr: https://www.npmjs.com/package/@antfu/ni

[2]

@antfu/ni: https://www.npmjs.com/package/@antfu/ni

[3]

VueUse 文档中的信息: https://vueuse.org/functions.html

[4]

gray-matter: https://www.npmjs.com/package/gray-matter

[5]

front matter: https://vuepress.vuejs.org/zh/guide/frontmatter.html#%E5%85%B6%E4%BB%96%E6%A0%BC%E5%BC%8F%E7%9A%84-front-matter

[6]

simple-git: https://www.npmjs.com/package/simple-git

[7]

fast-glob: https://www.npmjs.com/package/fast-glob

[8]

antfu: https://github.com/antfu

[9]

esbuild: https://esbuild.github.io/

[10]

minify : https://esbuild.github.io/api/#minify

[11]

target: https://esbuild.github.io/api/#target

[12]

bumpp: https://www.npmjs.com/package/bumpp

[13]

version-bump-prompt: https://github.com/JS-DevTools/version-bump-prompt

[14]

publish action: https://github.com/vueuse/vueuse/runs/6670930037?check_suite_focus=true

[15]

export-size: https://vueuse.org/export-size.html

[16]

filesize: https://www.npmjs.com/package/filesize

[17]

markdownTable: https://www.npmjs.com/package/markdown-table

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农小余 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • scripts
  • update
  • build
  • release
  • publish
  • size
  • 总结
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档