Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

带给你幸福感的 auto-inject-async-catch-loader #12

Open
WJCHumble opened this issue Feb 20, 2021 · 0 comments
Open

带给你幸福感的 auto-inject-async-catch-loader #12

WJCHumble opened this issue Feb 20, 2021 · 0 comments

Comments

@WJCHumble
Copy link
Owner

WJCHumble commented Feb 20, 2021

前言

前段时间,写了一个 webpack-loader,auto-inject-async-catch-loader,晚上 8 点多发的 npm 包。昨天早上,上 npm 官网一看,才十几个小时下载量 282 次(着实有点意外😯)~

在之前,我写过一篇文章《现场教学,优雅地处理基于 Vue CLI 项目中的 async await 异常》介绍了如何在 Vue CLI 中给代码自动加 try catch,当时在文中也讲了这种做法欠妥。所以,上周末我花了一些时间,重新整理了一下如何使用 babel 的 tool 基于抽象语法树 AST 来实现对 Vue CLI 项目代码自动注入 try catch

一、 auto-inject-try-catch-loader 简介

auto-inject-async-catch-loader 是一个 webpack-loader,基于 babel 提供的 tool 来通过遍历抽象语法树 AST 来实现对 AwaitExpression AST 节点添加 TryStatement AST 节点,从而完成对 async function自动注入 try catch 的机制。auto-inject-async-catch-loader 的工作机制如下图所示:

而 auto-inject-async-catch-loader 这个 loader 是 fork 的 async-catch-loader。所以,这里给上作者一个大大的 Respect 👍。起初,我是想着提个 PR,没必要反复造轮子,但是出于时间问题,就 fork 了一份改了改。

相比较 async-catch-loader ,auto-inject-async-catch-loader 做了以下两点优化:

1.优化了向上查找 parent 的过程,优化后的 traverse 如下所示:

traverse(ast, {
    AwaitExpression(path) {
      // 已经包含 try 语句则直接退出
      if (
        path.findParent(path => t.isTryStatement(path.node))
      ) {
        return;
      }
      // 查找最外层的 async 语句
      const blockParent = path.findParent(path => t.isBlockStatement(path.node) && isAsyncFuncNode(path.parentPath.node))
      const tryCatchAst = t.tryStatement(
        blockParent.node,
        t.catchClause(
          t.identifier(options.identifier),
          t.blockStatement(catchNode)
        ),
        finallyNode && t.blockStatement(finallyNode)
      )
      blockParent.replaceWithMultiple([tryCatchAst])
    }
  });

2.支持使用 TypeScript + Vue CLI 开发的情况。此时 Vue 组件是 Class 语法,例如:

<template></template>
<script lang="ts">
import { Vue, Component } from "@vue-property-decorator"

@Component
export default class Login extends Vue {
  private async created(): Promise<any> {
    await Promise.resolve("user login")
  }
}
</script>

可以看到 createdasync function)是写在 Class 内部的,即其对应的抽象语法树 AST 的 type 为 ClassMethod,所以在 isAsyncFunction 判断函数中增加加了对 ClassMethod 的判断:

const isAsyncFuncNode = node =>
  t.isFunctionDeclaration(node, {
    async: true
  }) ||
  t.isArrowFunctionExpression(node, {
    async: true
  }) ||
  t.isFunctionExpression(node, {
    async: true
  }) ||
  t.isObjectMethod(node, {
    async: true
  }) ||
  t.isClassMethod(node, {
    async: true
  });

二、auto-inject-async-catch-loader 在 Vue CLI 中使用

相信很多同学对于配置 Vue CLI 的 Webpack 一头雾水,这里我就顺带地给大家的讲解一下如何在 Vue CLI 中使用 auto-inject-async-catch-loader。

首先,当然是安装 auto-inject-async-catch-loader 依赖:

npm i auto-inject-async-catch-loader -D

然后,配置 Webpack。而通常情况下,使用 Vue CLI 开发的同学会分为两类:

1.使用 JavaScript 开发

使用 JavaScirpt 开发的同学只需要通过 chainwebpack 选项在 js rule 中添加一个 loader 就行。在 vue.config.js 的 chainWepack 中加入如下配置:

chainWepack: (config) => {
  const jsRule = config.module.rule("js");
  jsRule
    .use("auto-inject-async-catch-loader")
    .loader("auto-inject-async-catch-loader")
    .end()
}

2.使用 TypeScript 开发

使用 TypeScript 开发的同学则需要重写整个 ts rule 的 loader 配置。在 vue.config.ts 的 chainWepack 选项中加入如下配置:

chainWebpack: (config) => {
  const tsRule = config.module.rule("ts");
  tsRule.uses.clear();
  tsRule
    .use("cache-loader")
      .loader("cache-loader")
      .end()
    .use("babel-loader")
      .loader("babel-loader")
      .end()
    .use("auto-inject-async-catch-loader")
      .loader("auto-inject-async-catch-loader")
      .tap(() => {
        return {
          catchCode: 'console.error(e)'
        }
      })
      .end()
    .use("ts-loader")
      .loader("ts-loader")
      .tap(() => {
        return {
                  transpileOnly: true,
                  appendTsSuffixTo: [
                    '\\.vue$'
                  ],
                  happyPackMode: false
                }
      })
      .end()
}

至于为什么需要重写整个 ts 对应的 rule,因为此时项目中代码的 loader 的加载顺序是 ts-loader 到 babel-loader 再到 cache-loader,auto-inject-async-catch-loader 必须要在 babel-loader 之前、ts-loader 之后加载:

而 Vue CLI 的 Webpack 配置是使用 webpack-chain 生成的,但是 webapck-chain 并没有提供在某个 loader 后插入 loader 的方法(只对 plugin 支持 before 和 after 方法)。所以,这里我们需要重写整个 ts rule,保证 ts-loader 到 auto-inject-async-catch-loader 到 babel-loader 再到 caceh-loader 的加载顺序:

结语

最后讲讲后期计划,写一个 babel-plugin 实现同样的功能,用 babel-plugin 实现则会精简地多,因为 babeld-plugin 本身提供了一些变量供于使用,例如可以使用 visitor 遍历抽象语法树 AST。当然,欢迎各位同学使用 auto-inject-async-catch-loader,如果有其他需求也可以给我提 Issue,或者微信(wu1957503379)联系我。

参考

https:/yeyan1996/async-catch-loader

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant