带给你幸福感的 auto-inject-async-catch-loader
WJCHumble opened this issue · comments
前言
前段时间,写了一个 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>
可以看到 created
(async 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)联系我。