欧美三级国产三级日韩三级_亚洲熟妇丰满大屁股熟妇_欧美亚洲成人一区二区三区_国产精品久久久久久模特

【實(shí)戰(zhàn)指南】如何寫一款小程序 Prettier 插件 - 新聞資訊 - 云南小程序開發(fā)|云南軟件開發(fā)|云南網(wǎng)站建設(shè)-昆明葵宇信息科技有限公司

159-8711-8523

云南網(wǎng)建設(shè)/小程序開發(fā)/軟件開發(fā)

知識

不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們在追求其視覺表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營銷的便利,運(yùn)營的高效,讓網(wǎng)站成為營銷工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏壧峁┍憬莸闹С郑?

您當(dāng)前位置>首頁 » 新聞資訊 » 小程序相關(guān) >

【實(shí)戰(zhàn)指南】如何寫一款小程序 Prettier 插件

發(fā)表時(shí)間:2021-1-5

發(fā)布人:葵宇科技

瀏覽次數(shù):86

Prettier 是一款開箱即用的代碼格式化工具,主要特點(diǎn)是配置簡單、便于集成、支持?jǐn)U展。Prettier 原本聚焦于 Web 開發(fā)領(lǐng)域,由于表現(xiàn)優(yōu)秀,社區(qū)也利用其擴(kuò)展機(jī)制支持了 Java、PostgreSQL 等語言的格式化。

無需贅言,開發(fā)團(tuán)隊(duì)借助 Prettier 讓團(tuán)隊(duì)成員保持一致的代碼風(fēng)格(不完全等同于代碼規(guī)范)非常有必要,畢竟代碼雖然是機(jī)器運(yùn)行的,但主要是人在閱讀;而且強(qiáng)迫人接受一種他可能不喜歡的風(fēng)格,自然不如讓工具自動統(tǒng)一來的容易。

其他內(nèi)容大家可以查看官方文檔了解更多,此處不過多介紹了。

認(rèn)識插件

Prettier 主要聚焦于 Web 開發(fā)領(lǐng)域,因此 JavaScript、CSS 和 HTML 是默認(rèn)支持的,甚至 JSX 和 Vue 也是內(nèi)置支持的。

但是顯然,假如你發(fā)明了一種全新的 DSL,Prettier 是不認(rèn)的。那怎么辦?寫一款 插件!

所以,插件就是讓 Prettier 能夠支持你自己的編程語言的一種方式。本質(zhì)上,它就是一個(gè) 普通的 JavaScript Module,暴露以下 5 個(gè)模塊:

諸位明鑒,下文代碼是以 TypeScript 寫就的。

// index.ts

import { SupportLanguage, Parser, Printer, SupportOption, ParserOptions } from 'prettier';

// 支持的語言列表
export const languages: Partial<SupportLanguage>[];

// 每個(gè)語言對應(yīng)的 parser
export const parsers: Record<string, Parser>;

// 核心的格式化邏輯
export const printers: Record<string, Printer>;

// 可選,插件的自定義配置項(xiàng),此處 PluginOptions 需自行定義
export const options: Record<keyof PluginOptions, SupportOption>;

// 可選,默認(rèn)配置項(xiàng)
export const defaultOptions: Partial<ParserOptions>;
復(fù)制代碼

寫一個(gè)小程序 AXML 插件吧

阿里小程序(釘釘小程序、支付寶小程序等等)的上層 DSL 早已統(tǒng)一,但是一直都沒有 AXML 自動格式化工具。

Prettier 對 JS/TS 是內(nèi)置支持的,.acss 其實(shí)就是 CSS。

可能有人嘗試過將 .axml 設(shè)置為 XML 文件類型來做格式化,但肯定效果不理想。因?yàn)闊o法格式化 AXML 文件中的 JS 表達(dá)式。

今天我們就寫一個(gè)起碼的 AXML 的 Prettier 插件吧。

languages

我們?yōu)樾〕绦?AXML 這門語言命名為 axml,其 parser 列表是 ['axml'](a.1)。也就是說可以為它指定多個(gè) parser,但通常一個(gè)就夠了。我們就使用 axml 這個(gè) parser(其定義見下文)。

parser 是將源代碼解析為 AST 的工具。

// index.ts

import { SupportLanguage } from 'prettier';

export const languages: Partial<SupportLanguage>[] = [
  {
    name: 'axml',
    parsers: ['axml'], // (a.1)
    extensions: ['.axml'], // (a.2)
  },
];
復(fù)制代碼

parsers

在 index.ts 中新增 export const parsers

// index.ts

import { SupportLanguage, Parser } from 'prettier';
import parse from './parse';

// prettier 指定 `node` 參數(shù)為 any,因?yàn)椴煌?parser 返回的 node 類型不盡相同
function locStart(node: any): number {
  return node.startIndex;
}

function locEnd(node: any): number {
  return node.endIndex;
}

export const languages: Partial<SupportLanguage>[] = [
  {
    name: 'axml',
    parsers: ['axml'],
    extensions: ['.axml'],
  },
];

export const parsers: Record<string, Parser> = {
  // 注意此處的 key 必須要與 languages 的 parsers 對應(yīng)
  axml: {
    parse, // (b.1)
    locStart,
    locEnd,
    // 為 ast 格式命個(gè)名,后面會用到
    astFormat: 'axml-ast',
  },
};
復(fù)制代碼

parse(b.1)是一個(gè)函數(shù),在揭開它的面紗之前,我們先要確定解析 AXML 的 parser。

類 XML 的 DSL 市面上有很多 parser,我們就和小程序官方實(shí)現(xiàn)保持一致,使用 htmlparser2 來解析 AXML。所以 parse(b.1)的定義如下:

// parse.ts

import { parseDOM } from 'htmlparser2';
import { Node } from 'domhandler';

export default function parse(text: string): Node[] {
  const dom = parseDOM(text, {
    xmlMode: true,
    withStartIndices: true,
    withEndIndices: true,
  });
  return dom;
}
復(fù)制代碼

htmlparser2 解析出來的 AST 相對簡單,可以查看 這里 感受一下。

這里實(shí)際上還有一個(gè)棘手的問題,AXML 中的“無值屬性”(如:<view someAttr />)其實(shí)是模仿了 JSX 的語義,即”布爾屬性“(<view someAttr /> 等價(jià)于 <view someAttr={true} />(JSX 語法)),但在 XML 以及 htmlparser2 這個(gè) parser 中,它被解析為 <view someAttr="" />。這個(gè)需要我們特殊處理。

接下來是核心邏輯了。

printers

// index.ts

import { SupportLanguage, Parser, Printer } from 'prettier';
import parse from './parse';
import print from './print';
import embed from './embed';

// ... 省略

export const printers: Record<string, Printer> = {
  // 對應(yīng) parsers 中的 astFormat
  'axml-ast': {
    print, // (c.1)
    embed, // (c.2)
  },
};
復(fù)制代碼

print(c.1)函數(shù)負(fù)責(zé)目標(biāo)語言源代碼本身的格式化邏輯,embed(c.2)函數(shù)則用來處理目標(biāo)語言當(dāng)中內(nèi)嵌的其他語言的格式化。

對于小程序 AXML 來說,htmlparser2 解析出來的 AST 只有以下 3 種類型(node.type):

  • tag - 標(biāo)簽,<view></view> 等等
  • text - 標(biāo)簽內(nèi)的文本
  • comment - 注釋,<!-- -->,和 HTML 注釋格式一致

print

print(c.1)中:

// print.ts

import { FastPath, Doc, ParserOptions, doc } from 'prettier';

const { concat } = doc.builders;

export default function print(
  path: FastPath,
  _options: ParserOptions,
  _print: (path: FastPath) => Doc // (c.3)
): Doc {

  // 獲取 AST 中的 node
  const node = path.getValue();

  if (!node) return '';
  
  // htmlparser2 的 AST 是一個(gè)數(shù)組,因此我們需要調(diào)用 _print,它會遞歸調(diào)用我們自己定義的 print
  if (Array.isArray(node)) {
    return concat(path.map(_print));
  }
  
  // 繼續(xù)判斷 node.type,返回不同內(nèi)容,限于篇幅,省略
}
復(fù)制代碼

每一個(gè)格式化的代碼片段,Prettier 將之稱為 Doc(c.3)。

需要注意的是,AXML 中有兩個(gè)地方會存在 JS 表達(dá)式(expression),標(biāo)簽(tag)的屬性(attribute)和文本(text),它們存在于 {{}} 當(dāng)中。這些表達(dá)式也需要格式化!

要處理 {{}} 中的 JS 表達(dá)式,則需要通過 embed(c.2),在 embed 函數(shù)中可以調(diào)用其他 parser 來處理目標(biāo)文本(用法見下文)。因?yàn)槭?JS 表達(dá)式,我們調(diào)用 Prettier 內(nèi)置的 babel parser 來處理 JS 表達(dá)式就行了。

這就要求我們先解析 {{}}。{{}} 格式是非常流行的所謂 mustache 風(fēng)格,出于教學(xué)目的,我們直接用 mustache.js 來解析。

實(shí)際上簡單地用 mustache.js 會有問題,因?yàn)轭愃?{{!a && b}} 這樣的片段在 mustache.js 是有語義的({{! 表示注釋);但在 AXML 里,它僅表示 !a && b 表達(dá)式。這里我們就不展開了。 另,小程序框架是自行實(shí)現(xiàn)了一個(gè) {{}} 的解析器。

embed

Prettier 在執(zhí)行時(shí),embed(c.2)會優(yōu)先print(c.1)執(zhí)行:如果 embed 返回了非 null 的值,則結(jié)束格式化;反之,繼續(xù)執(zhí)行 print 中的邏輯。

embed(c.2)中:

// embed.ts

import { FastPath, Doc, ParserOptions, Options, doc } from 'prettier';
import { DataNode, Element } from 'domhandler';
import { parse } from 'mustache';

const {
  group,  // (d.1) Prettier 最基本的方法,會根據(jù) printWidth 等配置項(xiàng)自動換行(或不換行)
  concat, // 拼接 Doc 的方法,類似 Array.prototype.concat
  line,   // 一個(gè)換行,如果父級 group(d.1) 后不需換行,則將其轉(zhuǎn)換為一個(gè)空格
  indent, // 一個(gè)縮進(jìn),如果父級 group(d.1) 后不需換行,則忽略
  softline, // 一個(gè)換行,如果父級 group(d.1) 后不需換行,則忽略
} = doc.builders;

export default function embed(
  path: FastPath,
  print: (path: FastPath) => Doc,
  textToDoc: (text: string, options: Options) => Doc, // (d.2)
  options: ParserOptions // (d.3)
): Doc | null {
  const node = path.getValue();

  // 返回 null,則交給 print(c.1) 繼續(xù)執(zhí)行
  if (!node || !node.type) return null;

  switch (node.type) {
    // 文本類型
    case 'text':
      const text = (node as DataNode).data;
      // 1. 調(diào)用 mustache.parse 解析文本
      // 2. 調(diào)用 textToDoc(d.2) 格式化 JS 表達(dá)式(如有)
      // 3. 拼接 `{{`、格式化好的表達(dá)式、`}}`(如有)
      // 4. 調(diào)用 group(d.1) 方法包裹前面拼接好的內(nèi)容
    
    // 標(biāo)簽類型
    case 'tag':
      // 1. 如果有 children,遞歸調(diào)用
      // 2. 提取 attribute,調(diào)用 mustache.parse 解析文本
      // 3. 調(diào)用 textToDoc(d.2) 格式化 JS 表達(dá)式(如有)
      // 4. 拼接 `{{`、格式化好的表達(dá)式、`}}`(如有)
      // 5. 調(diào)用 group(d.1) 方法包裹前面拼接好的內(nèi)容
    default:
      // 返回 null,則交給 print(c.1) 繼續(xù)執(zhí)行
      return null;
  }
}
復(fù)制代碼

特別說明一下 textToDoc(d.2)方法,要解析 JS 表達(dá)式,按如下方式使用即可:

// embed.ts

// ...

const doc: Doc = textToDoc(expressionExtractedByMustache, {
  parser: 'babel',
  semi: false,
  singleQuote: true,
});

return indent(concat([softline, doc]));
復(fù)制代碼

options(d.3)參數(shù)就是我們指定的一些配置項(xiàng)了,也包含自定義的配置項(xiàng)(見下文)。

此外,關(guān)于 groupindent 等方法,建議大家 查閱文檔。

當(dāng)然還有一些需要特別注意的地方,比如 style 屬性可以直接這樣寫 style="{{height: '100%', width: '100%'}}"(實(shí)際上所有的對象型屬性都可以簡化寫成這樣),大括號里提取出來的文本并不是合法的 JS 表達(dá)式,需要我們特殊處理。此種細(xì)節(jié)都要考慮到。

options

index.ts 中的 export const options 用于指定插件所支持的自定義配置項(xiàng)。

假如我們希望小程序 AXML 插件支持一個(gè) axmlBracketSameLine 的配置項(xiàng),其作用類似 jsxBracketSameLine。

那么可以這樣定義:

// index.ts

import { SupportLanguage, Parser, Printer, SupportOption, ParserOptions } from 'prettier';

// ... 省略

interface PluginOptions {
  axmlBracketSameLine: boolean;
}


// 插件自定義的配置項(xiàng)
export const options: Record<keyof PluginOptions, SupportOption> = {
  axmlBracketSameLine: {
    name: 'axmlBracketSameLine',
    category: 'Global',
    type: 'boolean',
    default: false,
    description: 'Put the `>` of a multiline AXML element on a new line',
  },
};
復(fù)制代碼

這樣,上文的 options(d.3)參數(shù)中就可以讀到 options.axmlBracketSameLine,以此決定是否要將開標(biāo)簽的結(jié)束字符 > 放置在同一行。

defaultOptions

插件的默認(rèn)配置項(xiàng),會覆蓋 Prettier 的同名默認(rèn)配置項(xiàng),可以指定內(nèi)置配置項(xiàng)和插件自定義配置項(xiàng)。

例如:

// index.ts

import { SupportLanguage, Parser, Printer, SupportOption, ParserOptions } from 'prettier';

// ... 省略

export const defaultOptions: Partial<ParserOptions> = {
  tabWidth: 2, // 2 個(gè)空格縮進(jìn)
  printWidth: 80, // 打印寬度 80
};
復(fù)制代碼

到這里,我們的 AXML 插件就開發(fā)完成了。

使用插件

插件使用起來非常簡單,只需將我們的插件發(fā)布到 npm(或 yarn,或私有化的 npm 服務(wù)如 tnpm),且其 package 名稱以下述字符開頭,Prettier 執(zhí)行時(shí)就會自動加載插件、自動識別文件類型并調(diào)用對應(yīng)插件

  • @prettier/plugin-
  • prettier-plugin-
  • @<scope>/prettier-plugin-

假設(shè)我們將小程序 AXML 插件發(fā)布到 npm 上,并命名為 prettier-plugin-axml,那么只需要在你的項(xiàng)目中安裝:

npm i --save-dev prettier prettier-plugin-axml
復(fù)制代碼

然后執(zhí)行:

./node_modules/.bin/prettier --write "src/**/*.axml"
復(fù)制代碼

就大功告成了。

因?yàn)槲覀円呀?jīng)在 extensions 中(a.2)指定了文件后綴為 .axml,所以 prettier 會自動為此類文件匹配我們的插件,因此不用顯式指定 plugin。

總結(jié)

概括來說,要開發(fā)一個(gè) Prettier 插件,總共分三步:

  1. 用一個(gè)或多個(gè) parser 把源代碼解析為 AST;
  2. 調(diào)用 Prettier 的 API 按需加入換行、空格、縮進(jìn)等;
  3. 沒了。

是不是很簡單呢?


作者:釘釘前端團(tuán)隊(duì)
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

相關(guān)案例查看更多