成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

經(jīng)過一個(gè)月的探索,我如何將 AST 操作得跟呼吸一樣自然

開發(fā) 前端
一直以來,前端同學(xué)們對(duì)于編譯原理都存在著復(fù)雜的看法,大部分人都覺得自己寫業(yè)務(wù)也用不到這么高深的理論知識(shí),況且編譯原理晦澀難懂,并不能提升自己在前端領(lǐng)域內(nèi)的專業(yè)知識(shí)。

[[435642]]

一直以來,前端同學(xué)們對(duì)于編譯原理都存在著復(fù)雜的看法,大部分人都覺得自己寫業(yè)務(wù)也用不到這么高深的理論知識(shí),況且編譯原理晦澀難懂,并不能提升自己在前端領(lǐng)域內(nèi)的專業(yè)知識(shí)。我不覺得這種想法有什么錯(cuò),況且我之前也是這么認(rèn)為的。而在前端領(lǐng)域內(nèi),和編譯原理強(qiáng)相關(guān)的框架與工具類庫(kù)主要有這么幾種:

  • 以 Babel 為代表,主要做 ECMAScript 的語(yǔ)法支持,比如 ?. 與 ?? 對(duì)應(yīng)的 babel-plugin-optional-chaining[1] 與 babel-plugin-nullish-coalescing-operator[2],這一類工具還有 ESBuild 、swc 等。類似的,還有 Scss、Less 這一類最終編譯到 CSS 的“超集”。這一類工具的特點(diǎn)是轉(zhuǎn)換前的代碼與轉(zhuǎn)換產(chǎn)物實(shí)際上是同一層級(jí)的,它們的目標(biāo)是得到標(biāo)準(zhǔn)環(huán)境能夠運(yùn)行的產(chǎn)物。
  • 以 Vue、Svelte 還有剛誕生不久的 Astro 為代表,主要做其他自定義文件到 JavaScript(或其他產(chǎn)物) 的編譯轉(zhuǎn)化,如 .vue .svelte .astro 這一類特殊的語(yǔ)法。這一類工具的特點(diǎn)是,轉(zhuǎn)換后的代碼可能會(huì)有多種產(chǎn)物,如 Vue 的 SFC 最終會(huì)構(gòu)建出 HTML、CSS、JavaScript。
  • 典型的 DSL 實(shí)現(xiàn),其沒有編譯產(chǎn)物,而是由獨(dú)一的編譯引擎消費(fèi), 如 GraphQL (.graphql)、Prisma (.prisma) 這一類工具庫(kù)(還有更熟悉一些的,如 HTML、SQL、Lex、XML 等),其不需要被編譯為 JavaScript,如 .graphql 文件直接由 GraphQL 各個(gè)語(yǔ)言自己實(shí)現(xiàn)的 Engine 來消費(fèi)。
  • 語(yǔ)言層面的轉(zhuǎn)換,TypeScript、Flow、CoffeeScript 等,以及使用者不再一定是狹義上前端開發(fā)者的語(yǔ)言,如張宏波老師的 ReScript(原 BuckleScript)、Dart 等。

無論是哪一種情況,似乎對(duì)于非科班前端的同學(xué)來說都是地獄難度,但其實(shí)社區(qū)一直有各種各樣的方案,來嘗試降低 AST 操作的成本,如 FB 的 jscodeshift[3],相對(duì)于 Babel 的 Visitor API,jscodeshift 提供了命令式 + 鏈?zhǔn)秸{(diào)用的 API,更符合前端同學(xué)的認(rèn)知模式(因?yàn)榫拖?Lodash、RxJS 這樣),看看它們是怎么用的:

示例來自于 神光[4] 老師的文章。由于本文的重點(diǎn)并不是 jscodeshift 與 gogocode,這里就直接使用現(xiàn)成的示例了。

  1. // Babel 
  2. const { declare } = require("@babel/helper-plugin-utils"); 
  3.  
  4. const noFuncAssignLint = declare((api, options, dirname) => { 
  5.   api.assertVersion(7); 
  6.  
  7.   return { 
  8.     pre(file) { 
  9.       file.set("errors", []); 
  10.     }, 
  11.     visitor: { 
  12.       AssignmentExpression(path, state) { 
  13.         const errors = state.file.get("errors"); 
  14.         const assignTarget = path.get("left").toString(); 
  15.         const binding = path.scope.getBinding(assignTarget); 
  16.         if (binding) { 
  17.           if ( 
  18.             binding.path.isFunctionDeclaration() || 
  19.             binding.path.isFunctionExpression() 
  20.           ) { 
  21.             const tmp = Error.stackTraceLimit; 
  22.             Error.stackTraceLimit = 0; 
  23.             errors.push( 
  24.               path.buildCodeFrameError("can not reassign to function", Error) 
  25.             ); 
  26.             Error.stackTraceLimit = tmp; 
  27.           } 
  28.         } 
  29.       }, 
  30.     }, 
  31.     post(file) { 
  32.       console.log(file.get("errors")); 
  33.     }, 
  34.   }; 
  35. }); 
  36.  
  37. module.exports = noFuncAssignLint; 
  38.  
  39. // jscodeshift 
  40. module.exports = function (fileInfo, api) { 
  41.   return api 
  42.     .jscodeshift(fileInfo.source) 
  43.     .findVariableDeclarators("foo"
  44.     .renameTo("bar"
  45.     .toSource(); 
  46. }; 

雖然以上并不是同一類操作的對(duì)比,但還是能看出來二者 API 風(fēng)格的差異。

以及 阿里媽媽 的 gogocode[5],它基于 Babel 封裝了一層,得到了類似 jscodeshift 的命令式 + 鏈?zhǔn)?API,同時(shí)其 API 命名也能看出來主要面對(duì)的的是編譯原理小白,jscodeshift 還有 findVariableDeclaration 這種方法,但 gogocode 就完全是 find 、replace 這種了:

  1. $(code) 
  2.     .find("var a = 1"
  3.     .attr("declarations.0.id.name""c"
  4.     .root() 
  5.     .generate(); 

看起來真的很簡(jiǎn)單,但這么做也可能會(huì)帶來一定的問題,為什么 Babel 要采用 Visitor API?類似的,還有 GraphQL Tools[6] 中,對(duì) GraphQL Schema 添加 Directive 時(shí)同樣采用的是 Visitor API,如:

  1. import { SchemaDirectiveVisitor } from "graphql-tools"
  2.  
  3. export class DeprecatedDirective extends SchemaDirectiveVisitor { 
  4.   visitSchema(schema: GraphQLSchema) {} 
  5.   visitObject(object: GraphQLObjectType) {} 
  6.   visitFieldDefinition(field: GraphQLField<anyany>) {} 
  7.   visitArgumentDefinition(argument: GraphQLArgument) {} 
  8.   visitInterface(iface: GraphQLInterfaceType) {} 
  9.   visitInputObject(object: GraphQLInputObjectType) {} 
  10.   visitInputFieldDefinition(field: GraphQLInputField) {} 
  11.   visitScalar(scalar: GraphQLScalarType) {} 
  12.   visitUnion(union: GraphQLUnionType) {} 
  13.   visitEnum(type: GraphQLEnumType) {} 
  14.   visitEnumValue(value: GraphQLEnumValue) {} 

Visitor API 是聲明式的,我們聲明對(duì)哪一部分語(yǔ)句做哪些處理,比如我要把所有符合條件 If 語(yǔ)句的判斷都加上一個(gè)新的條件,然后 Babel 在遍歷 AST 時(shí)(@babel/traverse),發(fā)現(xiàn) If 語(yǔ)句被注冊(cè)了這么一個(gè)操作,那就執(zhí)行它。而 jscodeshift、gogocode 的 Chaining API 則是命令式(Imperative)的,我們需要先獲取到 AST 節(jié)點(diǎn),然后對(duì)這個(gè)節(jié)點(diǎn)使用其提供(封裝)的 API,這就使得我們很可能遺漏掉一些邊界情況而產(chǎn)生不符預(yù)期的結(jié)果。

而 TypeScript 的 API 呢?TypeScript 的 Compiler API 是絕大部分開放的,足夠用于做一些 CodeMod、AST Checker 這一類的工具,如我們使用原生的 Compiler API ,來組裝一個(gè)函數(shù):

  1. import * as ts from "typescript"
  2.  
  3. function makeFactorialFunction() { 
  4.   const functionName = ts.factory.createIdentifier("factorial"); 
  5.   const paramName = ts.factory.createIdentifier("n"); 
  6.   const paramType = ts.factory.createKeywordTypeNode( 
  7.     ts.SyntaxKind.NumberKeyword 
  8.   ); 
  9.   const paramModifiers = ts.factory.createModifier( 
  10.     ts.SyntaxKind.ReadonlyKeyword 
  11.   ); 
  12.   const parameter = ts.factory.createParameterDeclaration( 
  13.     undefined, 
  14.     [paramModifiers], 
  15.     undefined, 
  16.     paramName, 
  17.     undefined, 
  18.     paramType 
  19.   ); 
  20.  
  21.   // n <= 1 
  22.   const condition = ts.factory.createBinaryExpression( 
  23.     paramName, 
  24.     ts.SyntaxKind.LessThanEqualsToken, 
  25.     ts.factory.createNumericLiteral(1) 
  26.   ); 
  27.  
  28.   const ifBody = ts.factory.createBlock( 
  29.     [ts.factory.createReturnStatement(ts.factory.createNumericLiteral(1))], 
  30.     true 
  31.   ); 
  32.  
  33.   const decrementedArg = ts.factory.createBinaryExpression( 
  34.     paramName, 
  35.     ts.SyntaxKind.MinusToken, 
  36.     ts.factory.createNumericLiteral(1) 
  37.   ); 
  38.  
  39.   const recurse = ts.factory.createBinaryExpression( 
  40.     paramName, 
  41.     ts.SyntaxKind.AsteriskToken, 
  42.     ts.factory.createCallExpression(functionName, undefined, [decrementedArg]) 
  43.   ); 
  44.  
  45.   const statements = [ 
  46.     ts.factory.createIfStatement(condition, ifBody), 
  47.     ts.factory.createReturnStatement(recurse), 
  48.   ]; 
  49.  
  50.   return ts.factory.createFunctionDeclaration( 
  51.     undefined, 
  52.     [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], 
  53.     undefined, 
  54.     functionName, 
  55.     undefined, 
  56.     [parameter], 
  57.     ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword), 
  58.     ts.factory.createBlock(statements, true
  59.   ); 
  60.  
  61. const resultFile = ts.createSourceFile( 
  62.   "func.ts"
  63.   ""
  64.   ts.ScriptTarget.Latest, 
  65.   false
  66.   ts.ScriptKind.TS 
  67. ); 
  68.  
  69. const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 
  70.  
  71. const result = printer.printNode( 
  72.   ts.EmitHint.Unspecified, 
  73.   makeFactorialFunction(), 
  74.   resultFile 
  75. ); 
  76.  
  77. console.log(result); 

以上的代碼將會(huì)創(chuàng)建這么一個(gè)函數(shù):

  1. export function factorial(readonly n: number): number { 
  2.   if (n <= 1) { 
  3.     return 1; 
  4.   } 
  5.   return n * factorial(n - 1); 

可以看到,TypeScript Compiler API 屬于命令式,但和 jscodeshift 不同,它的 API 不是鏈?zhǔn)降模袷墙M合式的?我們從 identifier 開始創(chuàng)建,組裝參數(shù)、if 語(yǔ)句的條件與代碼塊、函數(shù)的返回語(yǔ)句,最后通過 createFunctionDeclaration 完成組裝。簡(jiǎn)單的看一眼就知道其使用成本不低,你需要對(duì) Expression、Declaration、Statement 等相關(guān)的概念有比較清晰地了解,比如上面的 If 語(yǔ)句需要使用哪些 token 來組裝,還需要了解 TypeScript 的 AST,如 interface、類型別名、裝飾器等(你可以在 ts-ast-viewer[7] 實(shí)時(shí)的查看 TypeScript AST 結(jié)構(gòu))。

因此,在這種情況下 ts-morph[8] 誕生了(原 ts-simple-ast ),它在 TypeScript Compiler API 的基礎(chǔ)上做了一層封裝,大大降低了使用成本,如上面的例子轉(zhuǎn)換為 ts-morph 是這樣的:

  1. import { Project } from "ts-morph"
  2.  
  3. const s = new Project().createSourceFile("./func.ts"""); 
  4.  
  5. s.addFunction({ 
  6.   isExported: true
  7.   name"factorial"
  8.   returnType: "number"
  9.   parameters: [ 
  10.     { 
  11.       name"n"
  12.       isReadonly: true
  13.       type: "number"
  14.     }, 
  15.   ], 
  16.   statements: (writer) => { 
  17.     writer.write(` 
  18. if (n <=1) { 
  19.   return 1; 
  20.  
  21. return n * factorial(n - 1); 
  22.     `); 
  23.   }, 
  24. }).addStatements([]); 
  25.  
  26. s.saveSync(); 
  27.  
  28. console.log(s.getText()); 

是的,為了避免像 TypeScript Compiler API 那樣組裝的場(chǎng)景,ts-morph 沒有提供創(chuàng)建 IfStatement 這一類語(yǔ)句的 API 或者是相關(guān)能力,最方便的方式是直接調(diào)用 writeFunction 來直接寫入。

很明顯,這樣的操作是有利有弊的,我們能夠在創(chuàng)建 Function、Class、Import 這一類聲明時(shí),直接傳入其結(jié)構(gòu)即可,但對(duì)于函數(shù)(類方法)內(nèi)部的語(yǔ)句,ts-morph 目前的確只提供了這種最簡(jiǎn)單的能力,這在很多場(chǎng)景下可能確實(shí)降低了很多成本,但也注定了無法使用在過于復(fù)雜或是要求更嚴(yán)格的場(chǎng)景下。

我在寫到這里時(shí)突然想到了一個(gè)特殊的例子:Vite[9],眾所周知,Vite 會(huì)對(duì)依賴進(jìn)行一次重寫,將裸引入(Bare Import)轉(zhuǎn)換為能實(shí)際鏈接到代碼的正確導(dǎo)入,如 import consola from 'consola' 會(huì)被重寫為 import consola from '/node_modules/consola/src/index.js' (具體路徑由 main 指定,對(duì)于 esm 模塊則會(huì)由 module 指定) ,這一部分的邏輯里主要依賴了 magic-string 和 es-module-lexer 這兩個(gè)庫(kù),通過 es-module-lexer 獲取到導(dǎo)入語(yǔ)句的標(biāo)識(shí)在整個(gè)文件內(nèi)部的起始位置、結(jié)束位置,并通過 magic-string 將其替換為瀏覽器能夠解析的相對(duì)導(dǎo)入(如 importAnalysisBuild.ts[10])。這也帶來了一種新的啟發(fā):對(duì)于僅關(guān)注特定場(chǎng)景的代碼轉(zhuǎn)換,如導(dǎo)入語(yǔ)句之于 Vite,裝飾器之于 Inversify、TypeDI 這樣的場(chǎng)景,大動(dòng)干戈的使用 AST 就屬于殺雞焉用牛刀了。同樣的,在只是對(duì)粒度較粗的 AST 節(jié)點(diǎn)(如整個(gè) Class 結(jié)構(gòu))做操作時(shí),ts-morph 也有著奇效。

實(shí)際上可能還是有類似的場(chǎng)景:

  • 我只想傳入文件路徑,然后希望得到這個(gè)文件里所有的 class 名,import 語(yǔ)句的標(biāo)識(shí)(如 fs 即為 import fs from 'fs' 的標(biāo)識(shí)符,也即是 Module Specifier),哪些是具名導(dǎo)入(import { spawn } from 'child_process'),哪些是僅類型導(dǎo)入 (import type { Options } from 'prettier'),然后對(duì)應(yīng)的做一些操作,ts-morph 的復(fù)雜度還是超出了我的預(yù)期。
  • 我想學(xué)習(xí)編譯相關(guān)的知識(shí),但我不想從教科書和系統(tǒng)性的課程開始,就是想直接來理論實(shí)踐,看看 AST 操作究竟是怎么能玩出花來,這樣說不定以后學(xué)起來我更感興趣?
  • 我在維護(hù)開源項(xiàng)目,準(zhǔn)備發(fā)一個(gè) Breaking Change,我希望提供 CodeMod,幫助用戶直接升級(jí)到新版本代碼,常用的操作可能有更新導(dǎo)入語(yǔ)句、更新 JSX 組件屬性等。或者說在腳手架 + 模板的場(chǎng)景中,我有部分模板只存在細(xì)微的代碼差異,又不想維護(hù)多份文件,而是希望抽離公共部分,并通過 AST 動(dòng)態(tài)的寫入特異于模板的代碼。但是!我沒有學(xué)過編譯原理!也不想花時(shí)間把 ts-morph 的 API 都過一下...

做了這么多鋪墊,是時(shí)候迎來今天的主角了,@ts-morpher[11] 基于 ts-morph 之上又做了一層額外封裝,如果說 TypeScript Compiler API 的復(fù)雜度是 10,那么 ts-morph 的復(fù)雜度大概是 4,而 @ts-morpher 的復(fù)雜度大概只有 1 不到了。作為一個(gè)非科班、沒學(xué)過編譯原理、沒玩過 Babel 的前端仔,它是我在需要做 AST Checker、CodeMod 時(shí)產(chǎn)生的靈感。

我們知道,AST 操作通常可以很輕易的劃分為多個(gè)單元(如果你之前不知道,恭喜你現(xiàn)在知道了),比如獲取節(jié)點(diǎn)-檢查節(jié)點(diǎn)-修改節(jié)點(diǎn) 1-修改節(jié)點(diǎn) 2-保存源文件,這其中的每一個(gè)部分都是可以獨(dú)立拆分的,如果我們能像 Lodash 一樣調(diào)用一個(gè)個(gè)職責(zé)明確的方法,或者像 RxJS 那樣把一個(gè)個(gè)操作符串(pipe)起來,那么 AST 操作好像也沒那么可怕了。可能會(huì)有同學(xué)說,為什么要套娃?一層封一層?那我只能說,管它套娃不套娃呢,好用就完事了,什么 Declaration、Statement、Assignment...,我直接統(tǒng)統(tǒng)摁死,比如像這樣(更多示例請(qǐng)參考官網(wǎng)):

  1. import { Project } from "ts-morph"
  2. import path from "path"
  3. import fs from "fs-extra"
  4. import { createImportDeclaration } from "@ts-morpher/creator"
  5. import { checkImportExistByModuleSpecifier } from "@ts-morpher/checker"
  6. import { ImportType } from "@ts-morpher/types"
  7.  
  8. const sourceFilePath = path.join(__dirname, "./source.ts"); 
  9.  
  10. fs.rmSync(sourceFilePath); 
  11. fs.ensureFileSync(sourceFilePath); 
  12.  
  13. const p = new Project(); 
  14. const source = p.addSourceFileAtPath(sourceFilePath); 
  15.  
  16. createImportDeclaration(source, "fs""fs-extra", ImportType.DEFAULT_IMPORT); 
  17.  
  18. createImportDeclaration(source, "path""path", ImportType.NAMESPACE_IMPORT); 
  19.  
  20. createImportDeclaration( 
  21.   source, 
  22.   ["exec""execSync""spawn""spawnSync"], 
  23.   "child_process"
  24.   ImportType.NAMED_IMPORT 
  25. ); 
  26.  
  27. createImportDeclaration( 
  28.   source, 
  29.   // First item will be regarded as default import, and rest will be used as named imports. 
  30.   ["ts""transpileModule""CompilerOptions""factory"], 
  31.   "typescript"
  32.   ImportType.DEFAULT_WITH_NAMED_IMPORT 
  33. ); 
  34.  
  35. createImportDeclaration( 
  36.   source, 
  37.   ["SourceFile""VariableDeclarationKind"], 
  38.   "ts-morph"
  39.   ImportType.NAMED_IMPORT, 
  40.   true 
  41. ); 

這一連串的方法調(diào)用會(huì)創(chuàng)建:

  1. import fs from "fs-extra"
  2. import * as path from "path"
  3. import { exec, execSync, spawn, spawnSync } from "child_process"
  4. import ts, { transpileModule, CompilerOptions, factory } from "typescript"
  5. import type { SourceFile, VariableDeclarationKind } from "ts-morph"

再看一個(gè)稍微復(fù)雜點(diǎn)的例子:

  1. import { Project } from "ts-morph"
  2. import path from "path"
  3. import fs from "fs-extra"
  4. import { 
  5.   createBaseClass, 
  6.   createBaseClassProp, 
  7.   createBaseClassDecorator, 
  8.   createBaseInterfaceExport, 
  9.   createImportDeclaration, 
  10. from "@ts-morpher/creator"
  11. import { ImportType } from "@ts-morpher/types"
  12.  
  13. const sourceFilePath = path.join(__dirname, "./source.ts"); 
  14.  
  15. fs.rmSync(sourceFilePath); 
  16. fs.ensureFileSync(sourceFilePath); 
  17.  
  18. const p = new Project(); 
  19. const source = p.addSourceFileAtPath(sourceFilePath); 
  20.  
  21. createImportDeclaration( 
  22.   source, 
  23.   ["PrimaryGeneratedColumn""Column""BaseEntity""Entity"], 
  24.   "typeorm"
  25.   ImportType.NAMED_IMPORTS 
  26. ); 
  27.  
  28. createBaseInterfaceExport( 
  29.   source, 
  30.   "IUser"
  31.   [], 
  32.   [], 
  33.   [ 
  34.     { 
  35.       name"id"
  36.       type: "number"
  37.     }, 
  38.     { 
  39.       name"name"
  40.       type: "string"
  41.     }, 
  42.   ] 
  43. ); 
  44.  
  45. createBaseClass(source, { 
  46.   name"User"
  47.   isDefaultExport: true
  48.   extends: "BaseEntity"
  49.   implements: ["IUser"], 
  50. }); 
  51.  
  52. createBaseClassDecorator(source, "User", { 
  53.   name"Entity"
  54.   arguments: [], 
  55. }); 
  56.  
  57. createBaseClassProp(source, "User", { 
  58.   name"id"
  59.   type: "number"
  60.   decorators: [{ name"PrimaryGeneratedColumn", arguments: [] }], 
  61. }); 
  62.  
  63. createBaseClassProp(source, "User", { 
  64.   name"name"
  65.   type: "string"
  66.   decorators: [{ name"Column", arguments: [] }], 
  67. }); 

這些代碼將會(huì)創(chuàng)建:

  1. import { PrimaryGeneratedColumn, Column, BaseEntity, Entity } from "typeorm"
  2.  
  3. export interface IUser { 
  4.   id: number; 
  5.  
  6.   name: string; 
  7.  
  8. @Entity() 
  9. export default class User extends BaseEntity implements IUser { 
  10.   @PrimaryGeneratedColumn() 
  11.   id: number; 
  12.  
  13.   @Column() 
  14.   name: string; 

其實(shí)本質(zhì)上沒有什么復(fù)雜的地方,就是將 ts-morph 的鏈?zhǔn)?API 封裝好了針對(duì)于常用語(yǔ)句類型的增刪改查方法:

  • 目前支持了 Import、Export、Class,下一個(gè)支持的應(yīng)該會(huì)是 JSX(TSX)。
  • @ts-morpher 將增刪改查方法拆分到了不同的 package 下,如 @ts-morpher/helper 中的方法均用于獲取聲明或聲明 Identifier ,如你可以獲取一個(gè)文件里所有的導(dǎo)入的 Module Specifier(fs 之于 import fsMod from 'fs'),也可以獲取所有導(dǎo)入的聲明,但是你不用管這個(gè)聲明長(zhǎng)什么樣,直接扔給 @ts-morpher/checker ,調(diào)用 checkImportType,看看這是個(gè)啥類型導(dǎo)入。

為什么我要搞這個(gè)東西?因?yàn)樵谖夷壳暗捻?xiàng)目中需要做一些源碼級(jí)的約束,如我想要強(qiáng)制所有主應(yīng)用與子應(yīng)用的入口文件,都導(dǎo)入了某個(gè)新的 SDK,如 import 'foo-error-reporter' ,如果沒有導(dǎo)入的話,那我就給你整一個(gè)!由于不是所有子應(yīng)用、主應(yīng)用都能納入管控,因此就需要這么一個(gè)究極強(qiáng)制卡口來放到 CI 流水線上。如果這樣的話,那么用 ts-morph 可能差不多夠了,誒,不好意思,我就是覺得 AST 操作還可以更簡(jiǎn)單一點(diǎn),干脆自己再搞一層好了。

它也有著 100% 的單測(cè)覆蓋率和 100+ 方法,而是說它還沒有達(dá)到理想狀態(tài),比如把 AST 操作的復(fù)雜度降到 0.5 以下,這一點(diǎn)我想可以通過提供可視化的 playground,讓你點(diǎn)擊按鈕來調(diào)用方法,同時(shí)實(shí)時(shí)的預(yù)覽轉(zhuǎn)換結(jié)果,還可以在這之上組合一些常見的能力,如合并兩個(gè)文件的導(dǎo)入語(yǔ)句,批量更改 JSX 組件等等。

這也是我從零折騰 AST 一個(gè)月來的些許收獲,希望你能有所收獲。

參考資料

[1]babel-plugin-optional-chaining: https://github.com/babel/babel/blob/main/packages/babel-plugin-proposal-optional-chaining

[2]babel-plugin-nullish-coalescing-operator: https://github.com/babel/babel/blob/main/packages/babel-plugin-proposal-nullish-coalescing-operator

[3]jscodeshift: https://github.com/facebook/jscodeshift

[4]神光: https://www.zhihu.com/people/di-xu-guang-50

[5]gogocode: https://gogocode.io/

[6]GraphQL Tools: https://github.com/ardatan/graphql-tools

[7]ts-ast-viewer: https://ts-ast-viewer.com/#

[8]ts-morph: https://ts-morph.com/

[9]Vite: https://github.com/vitejs/vite

[10]importAnalysisBuild.ts: https://github.com/vitejs/vite/blob/545b1f13cec069bbae5f37c7540171128f439e7b/packages/vite/src/node/plugins/importAnalysisBuild.ts#L217

[11]@ts-morpher: https://ts-morpher-docs.vercel.app/

 

責(zé)任編輯:武曉燕 來源: 三元同學(xué)
相關(guān)推薦

2021-04-26 07:32:30

Spring Boot組件JWT

2018-01-10 12:09:12

Android開發(fā)程序員

2013-05-27 09:47:33

Java開發(fā)Java跨平臺(tái)

2021-10-28 05:39:14

Windows 10操作系統(tǒng)微軟

2009-11-23 08:52:02

Windows 7首月銷量

2019-10-08 11:07:55

Python 開發(fā)編程語(yǔ)言

2016-01-11 19:38:51

七牛

2012-08-31 16:40:24

Mac操作系統(tǒng)

2021-07-20 08:57:26

滴滴上市網(wǎng)絡(luò)安全審查

2009-02-16 09:15:49

蘋果喬布斯CEO

2012-12-20 10:18:10

Windows 8

2013-08-12 16:35:22

2020-02-14 14:36:23

DevOps落地認(rèn)知

2019-04-01 14:17:36

kotlin開發(fā)Java

2019-03-11 08:36:00

Office 應(yīng)用微軟

2015-07-30 13:28:44

創(chuàng)業(yè)者無恥

2013-03-08 09:40:00

數(shù)據(jù)百度360

2023-09-04 14:28:33

FlarumDiscourse開源

2022-11-09 11:01:11

Linux命令后臺(tái)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产一区二区三区视频 | 91久久精品国产91久久性色tv | 中文字幕日韩欧美一区二区三区 | 成人在线电影在线观看 | 日本不卡免费新一二三区 | 毛片在线视频 | 国产高清在线视频 | 欧美日韩电影一区二区 | 国产精品自产拍 | 久久九精品 | 91久久久久久久久久久久久 | 国内精品视频一区二区三区 | 日韩精品一区二区三区视频播放 | 国产在线精品免费 | 亚洲二区精品 | 色综合久 | 亚洲网站在线观看 | 国产精品久久国产精品久久 | 国产色| 亚洲毛片网站 | 一区二区三区视频播放 | 日韩在线观看视频一区 | 国产黄色在线观看 | 欧美精品久久久久久久久老牛影院 | 亚洲免费福利视频 | 狠狠躁夜夜躁人人爽天天高潮 | 久热精品在线播放 | 日本午夜在线视频 | com.国产| 黄a免费网络 | 国产激情毛片 | 精品国产欧美一区二区三区成人 | 中文字幕综合 | 欧美日韩一区二区三区四区五区 | 欧美中文在线 | 欧美精品在线一区 | av国产精品| 天天弄天天操 | 亚洲免费在线播放 | 伊人超碰 | 伊人国产精品 |