Интенсивное чтение "Создание пользовательского синтаксиса JS с помощью Babel"

внешний интерфейс JavaScript

1. Введение

Прежде чем писать это интенсивное чтение, я хочу поговорить о том, какую ценность может принести читателям интенсивное чтение переднего плана и как оценивать эти ценности.

К 123-й статье был написан фронтенд интенсивного чтения, вам не нужно беспокоиться о том, что он вдруг перестанет обновляться, потому что у меня выработалась привычка писать одну статью каждую неделю, и читатели тоже выработали привычку читать одну статья каждую неделю. Итак, что я хочу сказать, это на самом деле более энергичный способ работы с самостоятельным медиа, который регулярно обновляется. Колонка, которая регулярно обновляется, является более динамичной и более популярной среди читателей, чем та, которая не обновляется регулярно, потому что читатели могут видеть связи между статьями и расти вместе с автором. То же самое относится и к личному обучению: полезнее выработать привычку регулярно учиться, чем несколько месяцев бегать по учебным классам, научиться регулярно учиться в жизни даже лучше, чем несколько лет учиться в известном университете. .

Интенсивное чтение перед интерфейсом хочет принести читателям не только конкретный контент и знания, знания бесконечны, и десятки тысяч статей невозможно закончить, но интенсивное чтение перед интерфейсом всегда использовало «Введение-Обзор-Интенсивное чтение-Резюме». set Режим обучения можно применять к любым внешним проблемам или размышлениям о жизни и мире Я надеюсь предоставить читателям основу для обучения, чтобы вы могли узнать, как найти хорошую статью и как ее интерпретировать.

До сих пор было выбрано много тем интерпретации исходного кода, которые отличаются от интерпретации исходного кода обучения мышлению.Я надеюсь, что вы не изучаете исходный код с целью проведения интервью, потому что это ограничит вас популярными фреймворками, такими как как реагировать и vue. Причина, по которой типы фреймворков, выбранные в интенсивном чтении интерфейса, велики, заключается в том, что я надеюсь, что вы сможете успокоиться, воспринять преимущества различных стилей фреймворков и авторов и развить элегантный темперамент программирования.

Введите тему, выбранную статью на этот раз«Создание пользовательского синтаксиса JS с помощью Babel»Это также своего рода статья для воспитания темперамента программиста.Хотя она не очень полезна для вашей реальной работы, эта статья может воспитать несколько способностей, о которых мечтают программисты: глубокое понимание Babel и глубокое понимание механизма расширения фреймворка. Понимание сложной системы или развитие рамочного мышления не происходит в одночасье, но чтение подобных статей поможет вам все ближе и ближе овладеть им.

Причина, по которой был выбран Babel, заключается в том, что Babel всегда имел дело с базовой логикой, связанной с синтаксическим деревом.Принцип компиляции является одной из основ мира программ и имеет большое значение для обучения. Таким образом, наша цель не в том, как говорится в заголовке статьи, — в создании пользовательской грамматики JS, потому что созданная вами грамматика только сделает сложную систему JS более запутанной, но позволит вам понять, как Babel анализирует стандартную грамматику JS и смотрит на новую грамматику Делая предложения, имейте возможность мыслить с уровня реализации.

Наконец, нет нужды говорить, что если вы просмотрите классический механизм расширения плагинов Babel, вы обнаружите, что механизм расширения плагинов Babel очень похож на Antrl4, и его также можно использовать в качестве эталона при разработке решений расширения бизнес-модулей.

2 Обзор

Мы собираемся использовать Babel для достиженияfunction @@новый синтаксис, используя@@Украшенные функции автоматически каррируются:

// '@@' makes the function `foo` curried
function @@ foo(a, b, c) {
  return a + b + c;
}
console.log(foo(1, 2)(3)); // 6

можно увидеть,function @@ fooописанная функцияfooслужба поддержкиfoo(1, 2)(3)Этот кричащий звонок.

Реализация делится на два этапа:

  1. Исходный код форка babel.
  2. Создайте плагин-трансформер Babel.

Не бойтесь этих шагов, "Если вы дочитаете эту статью, вы будете вавилонским богом в глазах ваших коллег" - оригинал.

Сначала разветвите исходный код babel на локальный компьютер, выполните следующую команду для инициализации и компиляции babel:

$ make bootstrap
$ make build

вавилонское использованиеMakefileВыполните команду компиляции и используйте управление монорепозиторием.На этот раз нам нужно позаботиться оpackage/babel-parserэтот модуль.

Лексический

В первую очередь необходимо разобраться с лексическими знаниями.Для более подробной информации вы можете прочитать исходный текст или серию статей перед интенсивным чтением:Интенсивное чтение «Лексического анализа».

Для разбора грамматики сначала проводится лексический анализ. Любой ввод синтаксиса представляет собой строку, напримерfunction @@ foo(a, b, c), лексический анализ заключается в разбиении этого символа длины 24 на смысловые фрагменты слова:function @@ foo ( a ..

из-за@@— это созданная нами грамматика, поэтому наша первая задача — заставить babel lexer распознать ее.

Нижеpackage/babel-parserФайловая структура:

- src/
  - tokenizer/
  - parser/
  - plugins/
    - jsx/
    - typescript/
    - flow/
    - ...
- test/

Как видите, он делится на лексический разборtokenizer,Разборparser, а также плагины, поддерживающие особый синтаксис, и тестовые примерыtest.

Рекомендуемое использованиеРазработка через тестирование (TDD) — подход к разработке через тестирование, заключается в том, чтобы сначала написать тест-кейсы, а затем разрабатывать в соответствии с тест-кейсами. Этот метод разработки очень распространен в бэкенде или базовой среде, такой как babel, потому что логика, разработанная в методе TDD, может обеспечить 100% покрытие тестовых случаев, и это также хороший аспект мышления программирования, чтобы посмотреть на тест дела в первую очередь.

// packages/babel-parser/test/curry-function.js

import { parse } from '../lib';

function getParser(code) {
  return () => parse(code, { sourceType: 'module' });
}

describe('curry function syntax', function() {
  it('should parse', function() {
    expect(getParser(`function @@ foo() {}`)()).toMatchSnapshot();
  });
});

Вы можете протестировать этот код непосредственно с помощью шутки:

BABEL_ENV=test node_modules/.bin/jest -u packages/babel-parser/test/c

Результатом будет следующая ошибка:

SyntaxError: Unexpected token (1:9)

at Parser.raise (packages/babel-parser/src/parser/location.js:39:63)
at Parser.raise [as unexpected] (packages/babel-parser/src/parser/util.js:133:16)
at Parser.unexpected [as parseIdentifierName] (packages/babel-parser/src/parser/expression.js:2090:18)
at Parser.parseIdentifierName [as parseIdentifier] (packages/babel-parser/src/parser/expression.js:2052:23)
at Parser.parseIdentifier (packages/babel-pars

9-й символ@, что указывает на то, что программа еще не поддерживает функцию перед@Разбор. Мы также можем найти позицию ошибки в стеке ошибок и распечатать текущий токен и следующий токен:

// packages/babel-parser/src/parser/expression.js

parseIdentifierName(pos: number, liberal?: boolean): string {
  if (this.match(tt.name)) {
    // ...
  } else {
    console.log(this.state.type); // current token
    console.log(this.lookahead().type); // next token
    throw this.unexpected();
  }
}

this.state.typeпредставляет текущий токен,this.lookahead().typeУказывает следующий токен.lookaheadэто правильное слово для лексического анализа, что означает взгляд назад. После печати мы обнаружим, что два выхода@ Token:

TokenType {
  label: '@',
  // ...
}

Далее нам нужно позволить babel lexer распознать@@этот Токен. Сначала вам нужно зарегистрировать этот Токен:

// packages/babel-parser/src/tokenizer/types.js

export const types: { [name: string]: TokenType } = {
  // ...
  at: new TokenType('@'),
  atat: new TokenType('@@'),
};

После регистрации нам нужно добавить суждение при обходе Токена «Если текущий персонаж@и следующий символ тоже@, то целое составляет@@Token и курсор переместится на два пробела назад":

// packages/babel-parser/src/tokenizer/index.js

getTokenFromCode(code: number): void {
  switch (code) {
    // ...
    case charCodes.atSign:
      // if the next character is a `@`
      if (this.input.charCodeAt(this.state.pos + 1) === charCodes.atSign) {
        // create `tt.atat` instead
        this.finishOp(tt.atat, 2);
      } else {
        this.finishOp(tt.at, 1);
      }
      return;
    // ...
  }
}

Снова запустив тестовый файл, вывод будет таким:

// current token
TokenType {
  label: '@@',
  // ...
}

// next token
TokenType {
  label: 'name',
  // ...
}

На данный момент он был правильно проанализирован@@Токен поднят.

грамматика

Лексический уже может быть@@решаетatatТокен, следующим шагом будет использование этого токена, чтобы сгенерированная структура AST содержала информацию о функции каррирования, и использование подключаемого модуля babel для реализации функции каррирования во время синтаксического анализа.

Сначала мы можемBabel AST explorerУвидев структуру синтаксического анализа AST, давайте проверим ее с помощью функции-генератора, потому что эта структура функции похожа на каррированную функцию:

Как видите, вавилон проходитgenerator asyncчтобы определить, является ли функция генератором или асинхронной функцией. Аналогичным образом добавьтеcurryСвойство может достичь первого шага:

Для достижения вышеуказанного эффекта достаточно в лексическом анализеparser/statementдокументparseFunctionДобавитьatatЕго можно разобрать:

// packages/babel-parser/src/parser/statement.js

export default class StatementParser extends ExpressionParser {
  // ...
  parseFunction<T: N.NormalFunction>(
    node: T,
    statement?: number = FUNC_NO_FLAGS,
    isAsync?: boolean = false
  ): T {
    // ...
    node.generator = this.eat(tt.star);
    node.curry = this.eat(tt.atat);
  }
}

eatТо есть есть, по сути, это можно понимать как проглатывание Жетона, что имеет два эффекта: 1. Добавлена ​​функцияcurryАтрибут 2. Проглотил@@Идентификация, обеспечивающая распознавание всех токенов, является необходимым условием правильного анализа AST.

Для получения дополнительной информации о парсинге рекурсивного спуска вы можете обратиться кИнтенсивное чтение "Рукописный компилятор SQL - анализ синтаксиса"или прочитать исходный текст.

Мы снова выполняем тестовую функцию и обнаруживаем, что тест проходит и все идет так, как ожидалось.

Плагин Бабель

Теперь у нас есть маркерыcurryAST, то, наконец, для реализации каррирования необходим плагин для парсинга babel.

Во-первых, эффект, которого мы достигаем, модифицируя исходный код babel, можно преобразовать в собственный плагин парсера babel:

// babel-plugin-transformation-curry-function.js

import customParser from './custom-parser';

export default function ourBabelPlugin() {
  return {
    parserOverride(code, opts) {
      return customParser.parse(code, opts);
    },
  };
}

Таким образом можно добиться того же эффекта, что и при изменении исходного кода babel, что также является широко используемым механизмом подключаемых модулей для фреймворков.

Во-вторых, нам нужно понять, как реализовать карри. Карри может быть достигнут путем упаковки функции Carrying:

function currying(fn) {
  const numParamsRequired = fn.length;
  function curryFactory(params) {
    return function (...args) {
      const newParams = params.concat(args);
      if (newParams.length >= numParamsRequired) {
        return fn(...newParams);
      }
      return curryFactory(newParams);
    }
  }
  return curryFactory([]);
}

// from
function @@ foo(a, b, c) {
  return a + b + c;
}

// to
const foo = currying(function foo(a, b, c) {
  return a + b + c;
})

Каррированная функция строит рекурсию, связанную с количеством параметров, возвращает новую функцию, когда параметров недостаточно, и сохраняет ранее переданные параметры и, наконец, вызывает функцию один раз, когда параметры заполнены.

Что нам нужно сделать, так это@@ fooрешаетcurrying()Новая функция, обернутая функцией.

Вот знакомая часть плагина Babel:

// babel-plugin-transformation-curry-function.js

export default function ourBabelPlugin() {
  return {
    // ...
    visitor: {
      FunctionDeclaration(path) {
        if (path.get('curry').node) {
          // const foo = curry(function () { ... });
          path.node.curry = false;
          path.replaceWith(
            t.variableDeclaration('const', [
              t.variableDeclarator(
                t.identifier(path.get('id.name').node),
                t.callExpression(t.identifier('currying'), [
                  t.toExpression(path.node),
                ])
              ),
            ])
          );
        }
      },
    },
  };
}

FunctionDeclarationЭто хук посещения AST, этот хук срабатывает, когда функция выполняется, мы передаемpath.get('curry')получатьКарри функцияи использоватьreplaceWithПостройте эту функцию какcurryingНовая функция, обернутая функцией.

Остался последний вопрос:curryingКуда поместить исходный код функции.

Первый способ, создайте что-то вродеbabel-plugin-transformation-curry-functionЭтот плагин при разборке бабелаcurryingФункция прописана в глобальном, что является схемой глобального мышления.

Второе — это модульное решение, создающее индивидуальную@babel/helpers,ЗарегистрируйтесьcurryingЯ БЫ:

// packages/babel-helpers/src/helpers.js
helpers.currying = helper("7.6.0")`
  export default function currying(fn) {
    const numParamsRequired = fn.length;
    function curryFactory(params) {
      return function (...args) {
        const newParams = params.concat(args);
        if (newParams.length >= numParamsRequired) {
          return fn(...newParams);
        }
        return curryFactory(newParams);
      }
    }
    return curryFactory([]);
  }
`;

использовать функцию посещенияaddHelperспособ получитьcurrying:

path.replaceWith(
  t.variableDeclaration('const', [
    t.variableDeclarator(
      t.identifier(path.get('id.name').node),
      t.callExpression(this.addHelper("currying"), [
        t.toExpression(path.node),
      ])
    ),
  ])
);

Таким образом, после преобразования babel хелпер будет автоматически импортирован, а на экспортированный хелпер будет сделана ссылка.currying.

Наконец, в конце исходного текста остается некоторое расширенное содержание для чтения.нажмите на оригинал.

3 Интенсивное чтение

Я полагаю, что после прочтения этой статьи вы не только получили более глубокое представление о плагинах Babel, но и узнали, как добавить новый синтаксис, который является черной магией js.

Позвольте мне помочь вам обобщить некоторые модели программирования и знания из статьи о Babel, а также использовать Babel для создания примеров пользовательского синтаксиса, чтобы углубить ваше понимание.

TDD

Разработка через тестирование — это модель разработки через тестирование.

Как видно из примера в статье, для создания новой грамматики можно сначала написать эту грамматику в тестовом примере, а решить задачу шаг за шагом через стек ошибок, выполнив команду test. Такой способ разработки может повысить охват тестами, сделать цель более целенаправленной и легче обеспечить качество кода.

Программирование Lenovo

Ассоциативное программирование не относится к какой-либо модели программирования, но с точки зрения введения автор рассматривает «создание нового синтаксиса js для babel» как процесс детективного исследования.Посредством стека ошибок и чтения кода он реализуется шаг за шагом через разумная ассоциация Конечная цель.

В разделе AST мы также использовалиBabel AST explorerИнструмент просматривает структуру AST, находит похожие структуры AST, связывая функции генератора, и находит прорывы в расширении AST.

По мере решения проблемы метод ассоциации также отличается: если вы можете делать выводы из одного случая и делать разумные ассоциации для разных сценариев, вы можете считаться обладающим мягкими качествами технического эксперта.

Лексический и грамматический анализ

Лексический и грамматический анализ относится к знанию принципов компиляции.Понимание лексического разделения и рекурсивного спуска может помочь вам глубже погрузиться в технологию.

Будь то использование плагинов Babel или добавление пользовательского синтаксиса JS в Babel, требуется знание основных принципов компиляции. Знание принципов компиляции также может помочь вам в разработке онлайн-редакторов, умных подсказок по синтаксису и многом другом.

Механизм плагина

Ниже приведен метод расширения пользовательского плагина парсера Babel:

export default function ourBabelPlugin() {
  return {
    parserOverride(code, opts) {
      return customParser.parse(code, opts);
    },
  };
}

Это просто некое расширение плагина, есть декларативное и императивное, оно написано на JS, а еще оно написано на JSON. Babel выбрал расширение за счет объектов, что больше подходит для унифицированной обработки структур AST.

Чтобы сделать фреймворк, нужно сначала определить спецификацию интерфейса, например, парсер, сначала реализовать набор официальных парсингов по спецификации интерфейса, а при стыковке подключиться по интерфейсу, который естественно можно заменить пользовательским плагином- входы

Статьи, на которые вы можете ссылаться:Интенсивное чтение «Подключаемое мышление»

карри

Каррирование — это знание, которое часто изучается на собеседованиях.Мы можем научиться двум вещам: пониманию рекурсии и пониманию того, как превратить функции в каррирование.

Чтобы расширить это, мы также можем подумать об оптимизации хвостовой рекурсии JS. Как быстро написать функцию, поддерживающую хвостовую рекурсию?

const fn = tailCallOptimize(() => {
  if ( /* xxx */ ) {
    fn()
  }
})

путем инкапсуляцииtailCallOptimizeфункцию, вы можете легко построить функцию, которая поддерживает хвостовую рекурсию.Эта функция может быть записана следующим образом:

export function tailCallOptimize<T>(f: T): T {
  let value: any;
  let active = false;
  const accumulated: any[] = [];
  return function accumulator(this: any) {
    accumulated.push(arguments);
    if (!active) {
      active = true;
      while (accumulated.length) {
        value = (f as any).apply(this, accumulated.shift());
      }
      active = false;
      return value;
    }
  };
}

Заинтересованные читатели могут объяснить принцип работы этой функции в комментариях.

AST visit

Решение, часто используемое для обхода дерева AST, состоит в том, чтобы сделать посетителя обходчика, поэтому babel часто используется для расширения в процессе обхода:

return {
  // ...
  visitor: {
    FunctionDeclaration(path) {
      if (path.get('curry').node) {
        // const foo = curry(function () { ... });
        path.node.curry = false;
        path.replaceWith(
          t.variableDeclaration('const', [
            t.variableDeclarator(
              t.identifier(path.get('id.name').node),
              t.callExpression(t.identifier('currying'), [
                t.toExpression(path.node),
              ])
            ),
          ])
        );
      }
    },
  },
};

visitorКаждое имя ключа ниже является точкой расширения в процессе обхода.Например, в приведенном выше примере мы можем расширить и переписать позицию определения функции.

Встроенная функция регистрации

Babel предоставляет два встроенных метода регистрации функций: один похож на polyfill, который глобально регистрирует переменные уровня окна, а другой является модульным методом.

В дополнение к этому, что можно узнать, это то, что Бабел проходитthis.addHelper("currying")Этот метод расширения плагина автоматически импортирует соответствующий модуль из помощника после компиляции, при условии, что@babel/helperТребуется регистрацияcurryingэтот помощник.

Babel скрывает процесс компиляции и пишет плагины более семантическим способом с помощью некоторых сильно инкапсулированных вызовов функций, так что написанный код легко понять.

4 Резюме

Хотя в статье «Создание пользовательского синтаксиса JS с помощью Babel» рассказывается о знаниях, связанных с babel, из нее можно извлечь много общих знаний, поэтому я до сих пор понимаю babel.

С определенной функциональной точки зрения прохождение всего процесса фреймворка является эффективным методом продвинутого обучения.Если вы видели подобные статьи, вы можете порекомендовать их.

Адрес обсуждения:Интенсивное чтение «Создание пользовательского синтаксиса JS с помощью Babel» · Выпуск № 210 · dt-fe/weekly

Если вы хотите принять участие в обсуждении, пожалуйста,кликните сюда, с новыми темами каждую неделю, выходящими по выходным или понедельникам. Интерфейс интенсивного чтения — поможет вам отфильтровать надежный контент.

Сфокусируйся наАккаунт WeChat для интенсивного чтения в интерфейсе

Заявление об авторских правах: Бесплатная перепечатка - некоммерческая - не производная - сохранить авторство (Лицензия Creative Commons 3.0)