AST 與前端工程化實戰

AST : 全稱爲 Abstract Syntax Tree,意爲抽象語法樹,它是源代碼語法結構的一種抽象表示。javascript

AST 是一個很是基礎可是同時很是重要的知識點,咱們熟知的 TypeScript、babel、webpack、vue-cli 得都是依賴 AST 進行開發的。本文將經過 AST 與前端工程化的實戰向你們展現 AST 的強大以及重要性。html

直播分享視頻地址:AST 與前端工程化實戰前端

1、初識 AST

一、demo-1

第一次看見 AST 這個概念的時候仍是在《你不知道的 JavaScript》一書中看到的。咱們先看個例子vue

const a = 1
複製代碼

傳統編譯語言中,源代碼執行會先經歷三個階段java

  • 詞法分析階段:將字符組成的字符串分解成一個個代碼塊(詞法單元),例子中代碼會被解析成 const、a、=、1 四個詞法單元。node

  • 語法分析階段:將詞法單元流轉換成一個由元素逐級嵌套組成的語法結構樹,即所謂的抽象語法樹。例子中被解析出來的 const、a、=、1 這四個詞法單元組成的詞法單元流則會被轉換成以下結構樹webpack

  • 代碼生成階段:將 AST 轉換成一系列可執行的機器指令代碼,對應例子的話就是機器經過執行指令會在內存中建立一個變量 a,並將值 1 賦值給它。

二、demo-2

咱們再來拆解一個 recast 官方的例子,相對來講也會複雜一些ios

function add (a, b) {
  return a + b
}
複製代碼
  • 首先,進入到詞法分析階段,咱們會拿到 function、add、(、a、,、b、)、{、return、a、+、b、} 13 個代碼塊
  • 而後進入語法分析階段,具體如圖所示

上圖中的 FunctionDeclarationIdentifierBlockStatement 等這些代碼塊的類型的說明請點擊連接自行查看:AST 對象文檔git

2、recast

因爲文章中用到的 AST 相關的依賴包是 recast ,加上它自己是木有文檔的,只有一個很是簡短的 README.md 文件,因此這裏單獨開一篇對其常見的一些 API 作個介紹。開始以前,先給你們推薦一個在線查看 AST 結構的平臺,很是好用github

相信對 babel 稍有了解的同窗都知道,babel 有一系列包對 AST 進行了封裝,專門來處理編譯這塊的事宜。而 recast 也是基於 @babel/core@babel/parser@babel/types等包進行封裝開發的。

引入

引入 recast 有兩種方法,一種是 import 的形式,一種則是 CommonJs 的形式,分別以下

  • import 形式
import { parse, print } from 'recast'
console.log(print(parse(source)).code)

import * as recast from 'recast'
console.log(recast.print(recast.parse(source)).code)
複製代碼
  • CommonJs 形式
const { parse, print } = require('recast')
console.log(print(parse(source)).code)

const recast = require('recast')
console.log(recast.print(recast.parse(source)).code)
複製代碼

引入了 recast 以後,咱們一塊兒來看看 recast 都能作些什麼吧

一、recast.parse

咱們回到咱們例子,咱們直接對它進行 parse ,看看 parse 後的 AST 結構是如何的

// parse.js
const recast = require('recast')

const code = `function add (a, b) { return a + b }`

const ast = recast.parse(code)
// 獲取代碼塊 ast 的第一個 body,即咱們的 add 函數
const add = ast.program.body[0]
console.log(add)
複製代碼

執行 node parse.js 便可在咱們的終端查看到 add 函數的結構了

FunctionDeclaration {
  type: 'FunctionDeclaration',
  id: Identifier...,
  params: [Identifier...],
  body: BlockStatement...
}
複製代碼

固然你想看更多內容直接去 AST Explorer 平臺 將模式調成 recast 模式便可看到 ast 的全覽了,和咱們上面分析的內容基本是一致的。

二、recast.print

目前爲止,咱們只是對其進行了拆解,若是將 ast 組裝成咱們能執行的代碼呢?OK,這就須要用到 recast.print 了,咱們對上面拆解好的代碼原封不動的組裝起來

// print.js
const recast = require('recast')

const code = `function add (a, b) { return a + b }`

const ast = recast.parse(code)

console.log(recast.print(ast).code)
複製代碼

而後執行 node print.js ,能夠看到,咱們打印出了

function add (a, b) {
  return a + b
}
複製代碼

官方給的解釋就是,這就只是一個逆向處理而已,即

recast.print(recast.parse(source)).code === source
複製代碼

三、recast.prettyPrint

除了咱們上面說起的 recast.print 外,recast 還提供一個代碼美化的 API 叫 recast.prettyPrint

// prettyPrint.js
const recast = require('recast')

const code = `function add (a, b) { return a + b }`

const ast = recast.parse(code)

console.log(recast.prettyPrint(ast, { tabWidth: 2 }).code)
複製代碼

執行 node prettyPrint.js ,會發現 code 裏面的 N 多空格都能被格式化掉,輸出以下

function add(a, b) {
  return a + b;
}
複製代碼

詳細的配置請自行查看:prettyPrint

四、recast.types.builders

i. API

關於 builder 的 API ,別擔憂,我確定是不會講的,由於太多了。

想要具體瞭解每個 API 能作什麼的,能夠直接在 Parser API - Builders 中進行查看,或者直接查看 recast builders 定義

ii. 實戰階段

OK,終於進入到 recast 操做相關的核心了。咱們要想改造咱們的代碼,那麼 recast.types.builders 則是咱們最重要的工具了。這裏咱們繼續經過改造 recast 官方案例來了解 recast.types.builders 構建工具。

搞個最簡單的例子,如今咱們要作一件事,那就是將 function add (a, b) {...} 改爲 const add = function (a, b) {...}

咱們從第一章節瞭解到,若是咱們須要將其作成 const 聲明式的話,須要先一個 VariableDeclaration 以及一個 VariableDeclarator,而後咱們聲明一個 function 則有須要建立一個 FunctionDeclaration,剩下的則是填充表達式的參數和內容體了。具體操做以下

// builder1.js
const recast = require('recast')
const {
  variableDeclaration,
  variableDeclarator,
  functionExpression
} = recast.types.builders

const code = `function add (a, b) { return a + b }`

const ast = recast.parse(code)
const add = ast.program.body[0]

ast.program.body[0] = variableDeclaration('const', [
  variableDeclarator(add.id, functionExpression(
    null, // 這裏弄成匿名函數便可
    add.params,
    add.body
  ))
])

const output = recast.print(ast).code

console.log(output)
複製代碼

執行 node builder1.js ,輸出以下

const add = function(a, b) {
  return a + b
};
複製代碼

看到這,是否是以爲頗有趣。真正好玩的纔剛開始呢,接下來,基於此例子,咱們作個小的延伸。將其直接改爲 const add = (a, b) => {...} 的格式。

這裏出現了一個新的概念,那就是箭頭函數,固然,recast.type.builders 提供了 arrowFunctionExpression 來容許咱們建立一個箭頭函數。因此咱們第一步先來建立一個箭頭函數

const arrow = arrowFunctionExpression([], blockStatement([])
複製代碼

打印下 console.log(recast.print(arrow)),輸出以下

() => {}
複製代碼

OK,咱們已經獲取到一個空的箭頭函數了。接下來咱們須要基於上面改造的基礎進一步進行改造,其實只要將 functionExpression 替換成 arrowFunctionExpression 便可。

ast.program.body[0] = variableDeclaration('const', [
  variableDeclarator(add.id, b.arrowFunctionExpression(
    add.params,
    add.body
  ))
])
複製代碼

打印結果以下

const add = (a, b) => {
  return a + b
};
複製代碼

OK,到這裏,咱們已經知道 recast.types.builders 能爲咱們提供一系列 API,讓咱們能夠瘋狂輸出。

五、recast.run

讀取文件命令行。首先,我新建一個 read.js ,內容以下

// read.js
recast.run((ast, printSource) => {
  printSource(ast)
})
複製代碼

而後我再新建一個 demo.js,內容以下

// demo.js
function add (a, b) {
  return a + b
}
複製代碼

而後執行 node read demo.js,輸出以下

function add (a, b) {
  return a + b
}
複製代碼

咱們能看出來,咱們直接在 read.js 中讀出了 demo.js 裏面的代碼內容。那麼具體是如何實現的呢?

其實,原理很是簡單,無非就是直接經過 fs.readFile 進行文件讀取,而後將獲取到的 code 進行 parse 操做,至於咱們看到的 printSource 則提供一個默認的打印函數 process.stdout.write(output),具體代碼以下

import fs from "fs";

export function run(transformer: Transformer, options?: RunOptions) {
  return runFile(process.argv[2], transformer, options);
}

function runFile(path: any, transformer: Transformer, options?: RunOptions) {
  fs.readFile(path, "utf-8", function(err, code) {
    if (err) {
      console.error(err);
      return;
    }

    runString(code, transformer, options);
  });
}

function defaultWriteback(output: string) {
  process.stdout.write(output);
}

function runString(code: string, transformer: Transformer, options?: RunOptions) {
  const writeback = options && options.writeback || defaultWriteback;
  transformer(parse(code, options), function(node: any) {
    writeback(print(node, options).code);
  });
}
複製代碼

六、recast.visit

這是一個 AST 節點遍歷的 API,若是你想要遍歷 AST 中的一些類型,那麼你就得靠 recast.visit 了,這裏能夠遍歷的類型與 recast.types.builders 中的能構造出來的類型一致,builders 作的事是類型構建,recast.visit 作事的事則是遍歷 AST 中的類型。不過使用的時候須要注意如下幾點

  • 每一個 visit,必須加上 return false,或者 this.traverse(path),不然報錯。
if (this.needToCallTraverse !== false) {
  throw new Error(
    "Must either call this.traverse or return false in " + methodName
  );
}
複製代碼
  • 在須要遍歷的類型前面加上 visit 便可遍歷,如須要遍歷 AST 中的箭頭函數,那麼直接這麼寫便可
recast.run((ast, printSource) => {
  recast.visit(ast, {
    visitArrowFunctionExpression (path) {
      printSource(path.node)
      return false
    }
  })
})
複製代碼

七、recast.types.namedTypes

一個用來判斷 AST 對象是否爲指定類型的 API。

namedTypes 下有兩個 API,一個是 namedTypes.Node.assert:當類型不配置的時候,直接報錯退出。另一個則是 namedTypes.Node.check:判斷類型是否一致,並輸出 true 或 false。

其中 Node 爲任意 AST 對象,好比我相對箭頭函數作一個函數類型斷定,代碼以下

// namedTypes1.js
const recast = require('recast')
const t = recast.types.namedTypes

const arrowNoop = () => {}

const ast = recast.parse(arrowNoop)

recast.visit(ast, {
  visitArrowFunctionExpression ({ node }) {
    console.log(t.ArrowFunctionExpression.check(node))
    return false
  }
})
複製代碼

執行 node namedTypes1.js,能看出打印臺輸出結果爲 true。

同理,assert 用法也差很少。

const recast = require('recast')
const t = recast.types.namedTypes

const arrowNoop = () => {}

const ast = recast.parse(arrowNoop)

recast.visit(ast, {
  visitArrowFunctionExpression ({ node }) {
    t.ArrowFunctionExpression.assert(node)
    return false
  }
})
複製代碼

你想判斷更多的 AST 對象類型的,直接作替換 Node 爲其它 AST 對象類型便可。

3、前端工程化

如今,咱來聊聊前端工程化。

前段工程化能夠分紅四個塊來講,分別爲

  • 模塊化:將一個文件拆分紅多個相互依賴的文件,最後進行統一的打包和加載,這樣可以很好的保證高效的多人協做。其中包含

    1. JS 模塊化:CommonJS、AMD、CMD 以及 ES6 Module。
    2. CSS 模塊化:Sass、Less、Stylus、BEM、CSS Modules 等。其中預處理器和 BEM 都會有的一個問題就是樣式覆蓋。而 CSS Modules 則是經過 JS 來管理依賴,最大化的結合了 JS 模塊化和 CSS 生態,好比 Vue 中的 style scoped。
    3. 資源模塊化:任何資源都能以模塊的形式進行加載,目前大部分項目中的文件、CSS、圖片等都能直接經過 JS 作統一的依賴關係處理。
  • 組件化:不一樣於模塊化,模塊化是對文件、對代碼和資源拆分,而組件化則是對 UI 層面的拆分。

    1. 一般,咱們會須要對頁面進行拆分,將其拆分紅一個一個的零件,而後分別去實現這一個個零件,最後再進行組裝。
    2. 在咱們的實際業務開發中,對於組件的拆分咱們須要作不一樣程度的考量,其中主要包括細粒度和通用性這兩塊的考慮。
    3. 對於業務組件,你更多須要考量的是針對你負責業務線的一個適用度,即你設計的業務組件是否成爲你當前業務的 「通用」 組件,好比我以前分析過的權限校驗組件,它就是一個典型的業務組件。感興趣的小夥伴能夠點擊 傳送門 自行閱讀。
  • 規範化:正所謂無規矩不成方圓,一些好的規範則能很好的幫助咱們對項目進行良好的開發管理。規範化指的是咱們在工程開發初期以及開發期間制定的系列規範,其中又包含了

    1. 項目目錄結構
    2. 編碼規範:對於編碼這塊的約束,通常咱們都會採用一些強制措施,好比 ESLint、StyleLint 等。
    3. 聯調規範:這塊可參考我之前知乎的回答,先後端分離,後臺返回的數據前端無法寫,怎麼辦?
    4. 文件命名規範
    5. 樣式管理規範:目前流行的樣式管理有 BEM、Sass、Less、Stylus、CSS Modules 等方式。
    6. git flow 工做流:其中包含分支命名規範、代碼合併規範等。
    7. 按期 code review
    8. … 等等

    以上這些,我以前也寫過一篇文章作過一些點的詳細說明,TypeScript + 大型項目實戰

  • 自動化:從最先先的 grunt、gulp 等,再到目前的 webpack、parcel。這些自動化工具在自動化合並、構建、打包都能爲咱們節省不少工做。而這些前端自動化其中的一部分,前端自動化還包含了持續集成、自動化測試等方方面面。

而,處於其中任何一個塊都屬於前端工程化。

4、實戰:AST & webpack loader

而本文說起的實戰,則是經過 AST 改造書寫一個屬於咱們本身的 webpack loader,爲咱們項目中的 promise 自動注入 catch 操做,避免讓咱們手動書寫那些通用的 catch 操做。

一、AST 改造

講了這麼多,終於進入到咱們的實戰環節了。那麼咱們實戰要作一個啥玩意呢?

場景:平常的中臺項目中,常常會有一些表單提交的需求,那麼提交的時候就須要作一些限制,防止有人手抖多點了幾回致使請求重複發出去。此類場景有不少解決方案,可是我的認爲最佳的交互就是點擊以後爲提交按鈕加上 loading 狀態,而後將其 disabled 掉,請求成功以後再解除掉 loading 和 disabled 的狀態。具體提交的操做以下

this.axiosFetch(this.formData).then(res => {
  this.loading = false
  this.handleClose()
}).catch(() => {
  this.loading = false
})
複製代碼

這樣看着好像還算 OK,可是若是相似這樣的操做一多,或多或少會讓你項目總體的代碼看起來有些重複冗餘,那麼如何解決這種狀況呢?

很簡單,咱直接使用 AST 編寫一個 webpack loader,讓其自動完成一些代碼的注入,若咱們項目中存在下面的代碼的時候,會自動加上 catch 部分的處理,並將 then 語句第一段處理主動做爲 catch 的處理邏輯

this.axiosFetch(this.formData).then(res => {
  this.loading = false
  this.handleClose()
})
複製代碼

咱們先看看,沒有 catch 的這段代碼它的 AST 結構是怎樣的,如圖

其 MemberExpression 爲

this.axiosFetch(this.formData).then
複製代碼

arguments 爲

res => {
  this.loading = false
  this.handleClose()
}
複製代碼

OK,咱們再來看看有 catch 處理的代碼它的 AST 結構又是如何的,如圖

其 MemberExpression 爲

this.axiosFetch(this.formData).then(res => {
  this.loading = false
  this.handleClose()
}).catch
複製代碼

其中有兩個 ArrowFunctionExpression,分別爲

// ArrowFunctionExpression 1
res => {
  this.loading = false
  this.handleClose()
}
// ArrowFunctionExpression 2
() => {
  this.loading = false
}
複製代碼

因此,咱們須要作的事情大體分爲如下幾步

  1. 對 ArrowFunctionExpression 類型進行遍歷,得到其 BlockStatement 中的第一個 ExpressionStatement,並保存爲 firstExp
  2. 使用 builders 新建一個空的箭頭函數,並將保存好的 firstExp 賦值到該空箭頭函數的 BlockStatement 中
  3. 對 CallExpression 類型進行遍歷,將 AST 的 MemberExpression 修改爲爲有 catch 片斷的格式
  4. 將改造完成的 AST 返回

如今,按照咱們的思路,咱們一步一步來作 AST 改造

首先,咱們須要獲取到已有箭頭函數中的第一個 ExpressionStatement,獲取的時候咱們須要保證當前 ArrowFunctionExpression 類型的 parent 節點是一個 CallExpression 類型,而且保證其 property 爲 promise 的then 函數,具體操做以下

// promise-catch.js
const recast = require('recast')
const {
  identifier: id,
  memberExpression,
  callExpression,
  blockStatement,
  arrowFunctionExpression
} = recast.types.builders
const t = recast.types.namedTypes

const code = `this.axiosFetch(this.formData).then(res => { this.loading = false this.handleClose() })`
const ast = recast.parse(code)
let firstExp

recast.visit(ast, {
  visitArrowFunctionExpression ({ node, parentPath }) {
    const parentNode = parentPath.node
    if (
      t.CallExpression.check(parentNode) &&
      t.Identifier.check(parentNode.callee.property) &&
      parentNode.callee.property.name === 'then'
    ) {
      firstExp = node.body.body[0]
    }
    return false
  }
})
複製代碼

緊接着,咱們須要建立一個空的箭頭函數,並將 firstExp 賦值給它

const arrowFunc = arrowFunctionExpression([], blockStatement([firstExp]))
複製代碼

隨後,咱們則須要對 CallExpression 類型的 AST 對象進行遍歷,並作最後的 MemberExpression 改造工做

recast.visit(ast, {
  visitCallExpression (path) {
    const { node } = path

    const arrowFunc = arrowFunctionExpression([], blockStatement([firstExp]))
    const originFunc = callExpression(node.callee, node.arguments)
    const catchFunc = callExpression(id('catch'), [arrowFunc])
    const newFunc = memberExpression(originFunc, catchFunc)

    return false
  }
})
複製代碼

最後咱們在 CallExpression 遍歷的時候將其替換

path.replace(newFunc)
複製代碼

第一版的所有代碼以下

// promise-catch.js
const recast = require('recast')
const {
  identifier: id,
  memberExpression,
  callExpression,
  blockStatement,
  arrowFunctionExpression
} = recast.types.builders
const t = recast.types.namedTypes

const code = `this.axiosFetch(this.formData).then(res => { this.loading = false this.handleClose() })`
const ast = recast.parse(code)
let firstExp

recast.visit(ast, {
  visitArrowFunctionExpression ({ node, parentPath }) {
    const parentNode = parentPath.node
    if (
      t.CallExpression.check(parentNode) &&
      t.Identifier.check(parentNode.callee.property) &&
      parentNode.callee.property.name === 'then'
    ) {
      firstExp = node.body.body[0]
    }
    return false
  }
})

recast.visit(ast, {
  visitCallExpression (path) {
    const { node } = path

    const arrowFunc = arrowFunctionExpression([], blockStatement([firstExp]))
    const originFunc = callExpression(node.callee, node.arguments)
    const catchFunc = callExpression(id('catch'), [arrowFunc])
    const newFunc = memberExpression(originFunc, catchFunc)

    path.replace(newFunc)

    return false
  }
})

const output = recast.print(ast).code
console.log(output)
複製代碼

執行 node promise-catch.js ,打印臺輸出結果

this.axiosFetch(this.formData).then(res => {
  this.loading = false
  this.handleClose()
}).catch(() => {
  this.loading = false
})
複製代碼

因此能看出來,咱們已是完成了咱們想要完成的樣子了

  1. 可是咱們還得對一些狀況作處理,第一件就是須要在 CallExpression 遍歷的時候保證其 arguments 爲箭頭函數。

  2. 緊接着,咱們須要斷定咱們獲取到的 firstExp 是否存在,由於咱們的 then 處理中能夠是一個空的箭頭函數。

  3. 而後防止 promise 擁有一些自定義的 catch 操做,則須要保證其 property 爲 then。

  4. 最後爲了防止多個 CallExpression 都須要作自動注入的狀況,而後其操做又不一樣,則須要在其內部進行 ArrowFunctionExpression 遍歷操做

通過這些常見狀況的兼容後,具體代碼以下

recast.visit(ast, {
  visitCallExpression (path) {
    const { node } = path
    const arguments = node.arguments

    let firstExp

    arguments.forEach(item => {
      if (t.ArrowFunctionExpression.check(item)) {
        firstExp = item.body.body[0]

        if (
          t.ExpressionStatement.check(firstExp) &&
          t.Identifier.check(node.callee.property) &&
          node.callee.property.name === 'then'
        ) {
          const arrowFunc = arrowFunctionExpression([], blockStatement([firstExp]))
          const originFunc = callExpression(node.callee, node.arguments)
          const catchFunc = callExpression(id('catch'), [arrowFunc])
          const newFunc = memberExpression(originFunc, catchFunc)
  
          path.replace(newFunc)
        }
      }
    })

    return false
  }
})
複製代碼

而後因爲以後須要作成一個 webpack-loader,用在咱們的實際項目中。因此咱們須要對 parse 的解析器作個替換,其默認的解析器爲 recast/parsers/esprima,而通常咱們項目中都會用到 babel-loader ,因此咱們這也須要將其解析器改成 recast/parsers/babel

const ast = recast.parse(code, {
  parser: require('recast/parsers/babel')
})
複製代碼

二、webpack loader

到這裏,咱們對於代碼的 AST 改造已是完成了,可是如何將其運用到咱們的實際項目中呢?

OK,這個時候咱們就須要本身寫一個 webpack loader 了。

其實,關於如何開發一個 webpack loader,webpack 官方文檔 已經講的很清楚了,下面我爲小夥伴們作個小總結。

i. 本地進行 loader 開發

首先,你須要本地新建你開發 loader 的文件,好比,咱們這將其丟到 src/index.js 下,webpack.config.js 配置則以下

const path = require('path')

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          // ... 其餘你須要的 loader
          { loader: path.resolve(__dirname, 'src/index.js') }
        ]
      }
    ]
  }
}
複製代碼

src/index.js 內容以下

const recast = require('recast')
const {
  identifier: id,
  memberExpression,
  callExpression,
  blockStatement,
  arrowFunctionExpression
} = recast.types.builders
const t = recast.types.namedTypes

module.exports = function (source) {
  const ast = recast.parse(source, {
    parser: require('recast/parsers/babel')
  })

  recast.visit(ast, {
    visitCallExpression (path) {
      const { node } = path
      const arguments = node.arguments

      let firstExp

      arguments.forEach(item => {
        if (t.ArrowFunctionExpression.check(item)) {
          firstExp = item.body.body[0]

          if (
            t.ExpressionStatement.check(firstExp) &&
            t.Identifier.check(node.callee.property) &&
            node.callee.property.name === 'then'
          ) {
            const arrowFunc = arrowFunctionExpression([], blockStatement([firstExp]))
            const originFunc = callExpression(node.callee, node.arguments)
            const catchFunc = callExpression(id('catch'), [arrowFunc])
            const newFunc = memberExpression(originFunc, catchFunc)
    
            path.replace(newFunc)
          }
        }
      })

      return false
    }
  })

  return recast.print(ast).code
}
複製代碼

而後,搞定收工。

ii. npm 發包

這裏我在之前的文章中說起過,這裏不談了。若是還沒搞過 npm 發包的小夥伴,能夠點擊下面連接自行查看

揭祕組件庫一二事(發佈 npm 包片斷)

OK,到這一步,個人 promise-catch-loader 也是已經開發完畢。接下來,只要在項目中使用便可

npm i promise-catch-loader -D
複製代碼

因爲個人項目是基於 vue-cli3.x 構建的,因此我須要在個人 vue.config.js 中這樣配置

// js 版本
module.exports = {
  // ...
  chainWebpack: config => {
    config.module
      .rule('js')
      .test(/\.js$/)
      .use('babel-loader').loader('babel-loader').end()
      .use('promise-catch-loader').loader('promise-catch-loader').end()
  }
}
// ts 版本
module.exports = {
  // ...
  chainWebpack: config => {
    config.module
      .rule('ts')
      .test(/\.ts$/)
      .use('cache-loader').loader('cache-loader').end()
      .use('babel-loader').loader('babel-loader').end()
      .use('ts-loader').loader('ts-loader').end()
      .use('promise-catch-loader').loader('promise-catch-loader').end()
  }
}
複製代碼

而後我項目裏面擁有如下 promise 操做

<script lang="ts"> import { Component, Vue } from 'vue-property-decorator' import { Action } from 'vuex-class' @Component export default class HelloWorld extends Vue { loading: boolean = false city: string = '上海' @Action('getTodayWeather') getTodayWeather: Function getCityWeather (city: string) { this.loading = true this.getTodayWeather({ city: city }).then((res: Ajax.AjaxResponse) => { this.loading = false const { low, high, type } = res.data.forecast[0] this.$message.success(`${city}今日:${type} ${low} - ${high}`) }) } } </script>
複製代碼

而後在瀏覽器中查看 source 能看到以下結果

關於代碼,我已經託管到 GitHub 上了,promise-catch-loader

總結

到這步,咱們的實戰環節也已是結束了。固然,文章只是個初導篇,更多的類型還得小夥伴本身去探究。

AST 它的用處還很是的多,好比咱們熟知的 Vue,它的 SFC(.vue) 文件的解析也是基於 AST 去進行自動解析的,即 vue-loader,它保證咱們能正常的使用 Vue 進行業務開發。再好比咱們經常使用的 webpack 構建工具,也是基於 AST 爲咱們提供了合併、打包、構建優化等很是實用的功能的。

總之,掌握好 AST,你真的能夠作不少事情。

最後,但願文章的內容可以幫助小夥伴瞭解到:什麼是 AST?如何藉助 AST 讓咱們的工做更加效率?AST 又能爲前端工程化作些什麼?

若是以爲文章不錯,那麼但願你能動動你的小手,幫忙點個贊,謝謝了 ~

前端交流羣:731175396

前端公衆號

相關文章
相關標籤/搜索