«Фронтальная инфраструктура» перенесет вас в мир Вавилона

внешний интерфейс Babel
«Фронтальная инфраструктура» перенесет вас в мир Вавилона

введение

«Это первый день моего участия в ноябрьском испытании обновлений, ознакомьтесь с подробностями события:Вызов последнего обновления 2021 г.".

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

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

В статье мы говорим только о «галантерее», начиная с принципа и объединяя углубленную практику, чтобы показать вамBabelкрасота.

мы начнемBabelосновное содержание и, таким образом, постепенноBabelМир разработчиков плагинов, отныне позволяет вамBabelУдобно.

Общее содержание текста следующее:

  • BabelРуководство по ежедневному использованию

    • Начиная с основного содержания, вы освоите общиеPluginа такжеPreset.

    • в передовых инфраструктурных проектахBabelОбъяснение конфигурации.

    • BabelСвязанныйpolyfillсодержание.

  • BabelРуководство по разработке плагинов

    • взять тебя вBabelмир компиляции, опытBabelЗнание лежащих в его основе принципов.

    • Возьмите вас за руку, чтобы разработать свой собственныйBabelплагин.

🚀 Без лишних слов, давайте перейдем к делуBabelМир.

Babelежедневное использование

Сначала мы начнем с базовой конфигурацииBabelи сопутствующий контент.

общийpluginа такжеPreset

Сначала скажемPluginа такжеPresetразличия и связи.

так называемыйPresetлишь некоторыеPluginколлекция, вы можете поставитьPresetПонимание называется чем-тоPluginИнтеграция называется пакетом.

общийPreset

В статье перечислены три наиболее часто используемыхPreset,болееPrestВы можете проверить это здесь.

babel-preset-env

@babel/preset-envумный пресет, который преобразует наши высокиеJavaScriptКод переводится в более низкую версию по встроенным правиламjavascriptкод.

preset-envБольшая часть внутренней интеграцииplugin(State > 3) плагина перевода, он выполнит перевод кода в соответствии с соответствующими параметрами.

Вы можете настроить определенные параметры впосмотреть здесь.

@babel/preset-envНе будет включать какие-либо предложения по синтаксису JavaScript ниже этапа 3. Если ниже требуется совместимостьStage 3Синтаксис этапа требует дополнительного введения соответствующихPluginбыть совместимым.

Важно отметить, чтоbabel-preset-envТолько для перевода этапа грамматики, такого как перевод стрелочных функций,const/letграмматика. для некоторыхApiилиEs 6встроенный модульpolyfill,preset-envнельзя перевести. Мы опубликуем это позжеpolyfillОбъясните подробно для вас.

babel-preset-react

Обычно мы используемReactсерединаjsxЯ считаю, что всем понятна сутьjsxв конечном итоге будет скомпилирован какReact.createElement()метод.

babel-preset-reactЭто предположение будетjsxРоль перевода.

babel-preset-typescript

дляTypeScriptкод, у нас есть два способа компиляцииTypeScriptкод становитсяJavaScriptкод.

  1. использоватьtscкомандный, комбинированныйcliаргументы командной строки илиtsconfigконфигурационный файл для компиляцииtsкод.

  2. использоватьbabel,пройти черезbabel-preset-typescriptкод для компиляцииtsкод.

Общий плагин

BabelНа официальном сайте очень подробноPlugin List.

об общемPluginНа самом деле, большинство из них интегрировано вbabel-preset-env, когда вы обнаружите, что ваш проект не поддерживает последнююjsграмматике, мы можем обратиться к соответствующемуBabel Plugin ListНайдите соответствующий плагин грамматики и добавьте егоbabelконфигурация.

Есть также некоторые менее часто используемыеpackages,Например@babel/register: он будет переписанrequireкоманду, добавьте к ней хук. После этого при каждом использованииrequireнагрузка.js,.jsx,.esа также.es6Файл с суффиксным именем будет сначала перекодирован с помощью Babel.

Эти пакеты не особенно часто используются в повседневной жизни.Если есть студенты, у которых есть соответствующие требования к компиляции, они могут перейти кbabelПроверьте официальный сайт. Если официального сайта нет в готовом видеplugin/package,не переживай! Мы также научим вас практическим навыкам в будущем.babelРазработка плагинов.

Среди наиболее распространенных@babel/plugin-transform-runtimeмы будем нижеPolyfillОбъясните подробно.

интерфейсная инфраструктураBabelПодробная конфигурация

Далее поговорим о построении front-end проектовbabelсоответствующая конфигурация.

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

Здесь мы используем наиболее часто используемые в бизнесеwebpackНапример, другие инструменты сборки представляют только разные пакеты с точки зрения использования,BabelПринцип настройки тот же.

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

  • babel-loader

  • babel-core

  • babel-preset-env

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

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

babel-loader

такbabel-loaderСуть в функции, мы сопоставляем с соответствующейjsx?/tsx?файл вbabel-loader:

/**
 * 
 * @param sourceCode 源代码内容
 * @param options babel-loader相关参数
 * @returns 处理后的代码
 */
function babelLoader (sourceCode,options) {
  // ..
  return targetCode
}

оoptions,babel-loaderПоддержка напрямую черезloaderинъекция формы параметра, а такжеloaderВнутри функции, прочитав.babelrc/babel.config.js/В конфигурацию внедряются файлы babel.config.json и другие.

оbabelВ методах инициализации различных инфраструктурных проектов вы можетеПроверьте это здесь.

babel-core

мы говорили оbabel-loaderявляется только функцией, которая идентифицирует совпадающие файлы и принимает соответствующие параметры, тоbabelОсновная библиотека в процессе компиляции кода@babel/coreэта библиотека.

babel-coreдаbabelЯдро библиотеки компиляции, он может сделать наш кодЛексический анализ--Синтаксический анализ--Семантический анализпроцесс созданияASTАбстрактное синтаксическое дерево, так что операции над «этим деревом» затем компилируются как новый код.

babel-coreНа самом деле эквивалентно@babel/parseа также@babel/generatorСочетание этих двух сумок тронулоjsСкомпилированные студенты могут знатьesprimaа такжеescodegenС этими двумя библиотеками вы можете использоватьbabel-coreРоль понимания называется сочетанием этих двух библиотек.

babel-coreпройти черезtransformметод для компиляции нашего кода.

оbabel-coreНа самом деле существует множество методов компиляции вtransformметод или принятьjsПуть к файлуtransformFileметод для компиляции файла в целом.

В то же время он также поддерживает синхронные и асинхронные методы.Вы можете найти конкретные методы впосмотреть здесь.

оbabel-coreВнутренние правила компиляции и использования будут подробно обсуждаться в следующих главах плагинов.

Далее, давайте улучшим соответствующийbabel-loaderфункция:

const core = require('@babel/core')

/**
 * 
 * @param sourceCode 源代码内容
 * @param options babel-loader相关参数
 * @returns 处理后的代码
 */
function babelLoader (sourceCode,options) {
  // 通过transform方法编译传入的源代码
  core.transform(sourceCode)
  return targetCode
}

мы здесьbabel-loaderназывается вbabel-coreВ этой библиотеке был скомпилирован код.

babel-preset-env

Выше мы сказалиbabel-loaderпо сути является функцией, которая внутренне передаетbabel/coreЭтот основной пакет выполняетJavaScriptПеревод кода.

Но для перевода кодаНам нужно сказатьbabelКаковы правила конвертации, как я должен сказатьbabel: "Эй, Babel. Конвертируй мой код во что-то, называемое версией EcmaScript 5!".

В настоящее времяbabel-preset-envВот что он делает здесь:РассказыватьbabelКакие правила нужны для передачи кода.

const core = require('@babel/core');

/**
 *
 * @param sourceCode 源代码内容
 * @param options babel-loader相关参数
 * @returns 处理后的代码
 */
function babelLoader(sourceCode, options) {
  // 通过transform方法编译传入的源代码
  core.transform(sourceCode, {
    presets: ['babel-preset-env'],
    plugins: [...]
  });
  return targetCode;
}

здесьpluginа такжеprestна самом деле то же самое, так что я будуpluginпрямо в коде. Аналогично некоторые другиеpresetилиpluginТоже играет такую ​​роль.

оbabelЯ считаю, что все поняли свои соответствующие обязанности и основные принципы.Если у вас есть другие проблемы с конфигурацией, вы можете обратиться к ним.babelДокументация или ознакомьтесь с этой моей статьейReact-Webpack5-TypeScript для создания спроектированного многостраничного приложения..

BabelСвязанныйpolyfillсодержание

чтоpolyfill

оpolyfill, давайте сначала объясним, что имеется в виду подpolyfill.

Во-первых, давайте проясним эти три понятия:

  • до настоящего времениESсинтаксис, такой как: стрелочные функции,let/const.
  • до настоящего времениES Api,НапримерPromise
  • до настоящего времениESЭкземпляр/статические методы, такие какString.prototype.include

babel-prest-envбудет конвертировать только последниеesсинтаксис, не преобразует соответствующийApiи методы экземпляра, скажемES 6серединаArray.fromстатический метод.babelЭтот метод не будет переведен, если вы хотите распознать и запустить в браузерах более ранних версий.Array.fromЕсли метод соответствует нашим ожиданиям, нам необходимо ввести дополнительныеpolyfillпродолжаетсяArrayДобавьте вышеуказанный метод, чтобы реализовать это.

На самом деле, можно кратко резюмировать,грамматическая трансформацияpreset-envПолностью дееспособен. Но некоторые встроенные модули методов, простоpreset-envГрамматическую трансформацию невозможно распознать и преобразовать, поэтому необходим ряд инструментов, похожих на «прокладки», чтобы дополнить реализацию кода низкой версии этой части контента. Это называетсяpolyfillроль,

Таргетинг наpolyfillсодержание метода,babelОн включает в себя два аспекта для решения:

  • @babel/polyfill

  • @babel/runtime

  • @babel/plugin-transform-runtime

мы выяснили, чтоpolyfillтак же какpolyfillПосле роли и значения давайте разберем эти два по одному.babelКак использовать пакет и разница.

@babel/polyfill

Сначала давайте посмотрим на первую реализациюpolyfillПуть:

@babel/polyfillпредставлять

пройти черезbabelPolyfillДобавляя свойства к глобальным объектам и напрямую изменяя встроенные объектыPrototypeДобавить реализацию метода наpolyfill.

сказать, что нам нужна поддержкаString.prototype.include, после импортаbabelPolyfillПосле этого пакета он будет глобальноStringдобавить объект-прототипincludeспособ поддержать нашуJs Api.

Мы сказали, что этот метод по существу монтирует свойства глобального объекта/встроенного объекта, поэтому этот метод неизбежно приведет к глобальному загрязнению.

заявление@babel/polyfill

существуетbabel-preset-envEстьuseBuiltInsпараметр, который определяет, какpreset-envиспользуется в@babel/polyfill.

{
    "presets": [
        ["@babel/preset-env", {
            "useBuiltIns": false
        }]
    ]
}
  • useBuiltIns--"usage""entry"false
false

когда мы используемpreset-envвходящийuseBuiltInsпараметр, значение по умолчаниюfalse. Это означает, что только последниеESсинтаксис, ничего не переводитApiи метод.

entry

при входящемentryКогда нам нужно вручную ввести его один раз в файл входа в проектcore-jsЭто будет основано на конфигурации нашего списка совместимости браузера (browserList)ПотомПолная суммаВвести несовместимоеpolyfill.

Советы: вBabel7.4。0Позже,@babel/polyfillУстаревший, он стал интеграцией двух других пакетов."core-js/stable"; "regenerator-runtime/runtime";. вы можете увидеть здесьРазнообразие, но они используются одинаково, за исключением того, что пакеты, представленные в файле входа, отличаются.

Введение в настройку списка совместимости браузера можно найти здесь..

// 项目入口文件中需要额外引入polyfill
// core-js 2.0中是使用"@babel/polyfill" core-js3.0版本中变化成为了上边两个包
import "@babel/polyfill"

// babel
{
    "presets": [
        ["@babel/preset-env", {
            "useBuiltIns": "entry"
        }]
    ]
}

Следует также отметить, что когда мы используемuseBuiltIns:entry/usage, требуется дополнительная спецификацияcore-jsэтот параметр. По умолчанию используетсяcore-js 2.0,Так называемыйcore-jsЭто реализация «шима», о котором мы упоминали выше. Он реализует ряд встроенных методов илиPromiseЖдатьApi.

core-js 2.0версия должна следоватьpreset-envУстанавливаются вместе, нет необходимости устанавливать отдельно

usage

Выше мы сказали, что конфигурацияentryчас,perset-envБудет полностью импортирован на основе нашего списка совместимости браузеров.polyfill. Так называемый полный импорт, например, используется только в нашем кодеArray.fromСюда. ноpolyfillне только ввестиArray.from, а также знакомитPromise,Array.prototype.includeи другие методы, которые не используются. Это приводит к введению слишком большого объема в упаковку.

В этот момент нашuseBuintIns:usageконфигурация.

когда мы настраиваемuseBuintIns:usage, он будет совместим с настроенным браузером, а в кодеиспользовалApiимпортироватьpolyfillДобавляйте по мере необходимости.

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

{
    "presets": [
        ["@babel/preset-env", {
            "useBuiltIns": "usage",
            "core-js": 3
        }]
    ]
}
оusageа такжеentryЕсть одно существенное отличие, о котором следует знать.

Вводим в проектPromiseНапример.

когда мы настраиваемuseBuintInts:entry, он будет импортирован только один раз в полный файл записиpolyfill. Вы можете понять это так:

// 当使用entry配置时
...
// 一系列实现polyfill的方法
global.Promise = promise

// 其他文件使用时
const a = new Promise()

и когда мы используемuseBuintIns:usageчас,preset-envТолько на основе каждого модуля для анализа того, что они используютpolyfillво вступление.

preset-envЭто поможет нам разумно внедрить там, где это необходимо, например:

// a. js 中
import "core-js/modules/es.promise";

...
// b.js中

import "core-js/modules/es.promise";
...
  • существуетusageВ этом случае, если у нас будет много модулей, несомненно будет много избыточного кода (importграмматика).

  • также используяusageпотому что он импортируется локально внутри модуляpolyfillПоэтому импортируйте его в модуль по мере необходимости иentryОн будет введен один раз при вводе кода.

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

@babel/runtime

Выше мы упоминали@babel/polyfillСуществует побочный эффект загрязнения глобальных переменных в реализацииpolyfillВремяBabelЭто также предоставляет нам другой способ достижения этой функции, т.@babel/runtime.

просто говоря,@babel/runtimeбольше похоже наРешения по запросу, например, где использоватьPromise,@babel/runtimeдобавит вверху своего файлаimport promise from 'babel-runtime/core-js/promise'.

В то же время мы упоминали выше оpreset-envизuseBuintInsэлементы конфигурации, нашиpolyfillдаpreset-envПомогите нам ввести смарт.

а такжеbabel-runtimeТогда метод внедрения будет полностью передан нам интеллектом, и мы будем внедрять то, что нам нужно.

Его использование очень простое, пока мы идем на установкуnpm install --save @babel/runtimeПосле этого при необходимости использовать соответствующийpolyfillЕго можно ввозить отдельно от места. Например:

// a.js 中需要使用Promise 我们需要手动引入对应的运行时polyfill
import Promise from 'babel-runtime/core-js/promise'

const promsies = new Promise()

в общем,babel/runtimeВы можете понимать это как библиотеку инструментов времени выполнения, на которую можно ссылаться.

противbabel/runtimeМы будем сотрудничать в большинстве случаев@babel/plugin-transfrom-runtimeВыполните использование для достижения интеллектаruntimeизpolyfillПредставлять.

@babel/plugin-transform-runtime

babel-runtimeсуществующие проблемы

babel-runtimeПрежде чем мы вручную введем некоторыеpolyfill, он внедрит в наш код что-то вроде_extend(), classCallCheck()Инструментальные функции, такие как следующие, код этих инструментальных функций будет включен в каждый скомпилированный файл, например:

class Circle {}
// babel-runtime 编译Class需要借助_classCallCheck这个工具函数
function _classCallCheck(instance, Constructor) { //... } 
var Circle = function Circle() { _classCallCheck(this, Circle); };

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

Итак, по двум упомянутым выше вопросам:

  • babel-runtimeИнтеллектуальный анализ недостижим, и нам нужно вводить его вручную.
  • babel-runtimeВо время компиляции многократно генерируется избыточный код.

Мы собираемся представить нашего главного героя@babel/plugin-transform-runtime.

@babel/plugin-transform-runtimeэффект

@babel/plugin-transform-runtimeРоль плагина как раз и заключается в решении вышеупомянутойrun-timeПлагины, поднятые для существующих проблем.

  • babel-runtimeИнтеллектуальный анализ недостижим, и нам нужно вводить его вручную.

@babel/plugin-transform-runtimeПлагин грамотно проанализирует, что нужно перевести и использовать в нашем проекте.jsкод, тем самым достигая модульности отbabel-runtimeимпортировать необходимыеpolyfillвыполнить.

  • babel-runtimeВо время компиляции многократно генерируется избыточный код.

@babel/plugin-transform-runtimeПлагин предоставляетhelpersпараметр. В частности, вы можетеПроверьте все его параметры конфигурации здесь.

этоhelpersПосле того, как параметр включен, вы можете обратиться к вышеупомянутым функциям инструмента, которые повторяются на этапе компиляции, таким какclassCallCheck, extendsРавное преобразование кода называетсяrequireутверждение. На данный момент эти служебные функции не будут повторно появляться в используемых модулях. Например:

// @babel/plugin-transform-runtime会将工具函数转化为require语句进行引入
// 而非runtime那样直接将工具模块代码注入到模块中
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck"); 
var Circle = function Circle() { _classCallCheck(this, Circle); };
настроить@babel/plugin-transform-runtime

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

Вот список его текущей конфигурации по умолчанию:

{
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
        "corejs": false,
        "helpers": true,
        "regenerator": true,
        "version": "7.0.0-beta.0"
      }
    ]
  ]
}

Суммироватьpolyfill

Мы можем видеть, что дляpolyfillНа самом деле, я потратил много времени, пытаясь понять различия и связи между ними, так что давайте немного их подытожим.

существуетbabelреализовано вpolyfillЕсть в основном два пути:

  • один через@babel/polyfillСотрудничатьpreset-envИспользование таким способом может загрязнить глобальную область видимости.

  • один через@babel/runtimeСотрудничать@babel/plugin-transform-runtimeПри использовании этот способ не загрязняет область применения.

  • Глобальный импорт загрязняет глобальную область, но в отличие от локального импорта. Это добавит много дополнительных операторов импорта и увеличит размер пакета.

существуетuseBuintIns:usageНа самом деле и@babel/plugin-transform-runtimeВ случае аналогичного эффекта

Обычно мой личный выбор заключается в том, чтобы придерживаться первого использования, не загрязняя глобальную среду при разработке библиотеки классов.@babel/plugin-transform-runtimeпока в развитии бизнеса@babel/polyfill.

babel-runtime 是为了减少重复代码而生的。 babel生成的代码,可能会用到一些_extend(), classCallCheck() 之类的工具函数,默认情况下,这些工具函数的代码会包含在编译后的文件中。如果存在多个文件,那每个文件都有可能含有一份重复的代码。

babel-runtime插件能够将这些工具函数的代码转换成require语句,指向为对babel-runtime的引用,如 require('babel-runtime/helpers/classCallCheck'). 这样, classCallCheck的代码就不需要在每个文件中都存在了。

BabelРазработка плагина

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

Вам может быть интересноBabelЧто могут плагины? Проще говоря, поbabelПлагины могут помочь вам глубже погрузиться в знания о принципах компиляции внешнего интерфейса на базовом уровне.

Конечно, если это не только для улучшения личных способностей, если вы разрабатываете собственную библиотеку компонентов, вы хотите добиться чего-то вродеelement-plusВведение по запросу в , или, возможно, дляlintУ вас есть свои особые правила. В противном случае для некоторыхjsПоддержка специальной записи в.

В общем, знание принципа компиляции может сделать что угодно!

взять тебя вbabelмир компиляции

Для знания компиляции основное внимание в статье уделяется не этому. Но, пожалуйста, будьте уверены, я буду использовать самый популярный способ, чтобы вы началиbabelРазработка плагинов.

webpack,lint,babelИ так много инструментов и основной библиотеки черезабстрактное синтаксическое дерево(Абстрактное синтаксическое дерево, AST) это понятие для реализации обработки кода.

AST

Так называемое абстрактное синтаксическое деревоJavaScript ParserПреобразуйте код в абстрактное синтаксическое дерево, которое определяет структуру кода. Затем проанализируйте, измените и оптимизируйте код, манипулируя добавлением, удалением, модификацией и проверкой этого дерева.

Для преобразования кода в разныеASTты сможешьздесьastexplorerЛюбой текущий основной парсерASTтрансформировать.

Здесь мы сначала перечислим некоторые справочные веб-сайты:

  • astexplorer: это онлайн-переводчик кода, который может преобразовать любой код вAST.

  • babel-handbook:babelРуководство по разработке плагинов на китайском языке.

  • the-super-tiny-compiler-cn:Одинgithubс открытым исходным кодом маленькийlistpпередача стиляjsCompiler, студентам, интересующимся принципом компиляции, настоятельно рекомендуется ознакомиться с его кодом.

babelРуководство по основам разработки плагинов

Когда нам нужно разработать собственныйbabelПри подключении мы обычно используемbabelНекоторые библиотеки для кодированияparserтак же какtransform ast,generator code, не требует от нас ручного выполнения процесса лексического/синтаксического анализа кода.

В разработке плагинов обычно участвуют следующие библиотеки:

  • @babel/core: Мы сказали вышеbabel/coreдаbabelосновная библиотека, ядроapiвсе здесь. Как то, что мы упоминали вышеtransform,parseметод.

  • @babel/parser:babelпарсер.

  • @babel/types: Этот модуль содержит методы для ручного построения AST и проверки типа узлов AST (например, через соответствующийapiсоздать соответствующий узел).

  • @babel/traverse: Этот модуль используется дляAstобход, который поддерживает состояние всего дерева (обратите внимание, чтоtraverseдляastглубокий обход).

  • @babel/generator: Этот модуль используется для генерации кода черезASTСоздайте новый код для возврата.

babelрабочий процесс

В ежедневных фронтенд-проектах большую часть времени мы используемbabelпровестиjsпреобразование кода.

Его рабочий процесс можно грубо свести к следующим трем аспектам:

  • Parseфаза (анализ): на этой фазе нашjsКод (строка) лексически анализируется для создания серииtokens, а затем грамматический анализ будетtokensкомбинация, называемаяASTАбстрактное синтаксическое дерево. (Напримерbabel-parserЕго роль - этот шаг)

  • Transform(преобразование) этап: этот этапbabelПересекая это дерево, для старогоASTДобавляйте, удаляйте, изменяйте и проверяйте, а такжеjsПреобразования синтаксических узлов называются синтаксическими узлами, совместимыми с браузером. (babel/traverseИменно на этом шаге нужно пройти по дереву)

  • Generator(создать) этап: этот этапbabelбудет новыйASTПреобразования также выполняют глубокий обход для создания нового кода. (@babel/generator)

Мы используем картинку, чтобы описать этот процесс:

image.png

babelсерединаASTпроцесс обхода

  • ASTЭто так называемый обход в глубину.Студенты, которые не знают, что такое обход в глубину, могут сами проверить соответствующую информацию~

  • babelсерединаASTОбход узлов основан на шаблоне посетителя (Visitor), разные посетители будут выполнять разные действия и получать разные результаты.

  • visitorМетод, названный в честь каждого узла, монтируется наASTПри обходе соответствующее имя метода будет запущено для выполнения соответствующего метода для работы.

Рука с вашим развитиемbabelплагин

Здесь мы используем простойES6Функция стрелки преобразуется вES5Способ начать, чтобы привести всех в реальностьbabelРазработка плагина.

Я полагаю, что у некоторых студентов могут быть сомнения,babelУже присутствует в соответствующем@babel/plugin-transform-arrow-functionsПреобразование стрелочной функции, зачем нам ее реализовывать.

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

Давайте начнем~

Сначала давайте посмотрим на результат, который нам нужно достичь:

Цель

// input
const arrowFunc = () => {
	console.log(this)
}

// output
var _this = this
funciton arrowFunc() {
    console.log(_this)
}

babelоригинальный метод преобразования

/**
 * babel插件
 * 主要还是@babel/core中的transform、parse 对于ast的处理
 * 以及babel/types 中各种转化规则
 *
 * Ast是一种深度优先遍历
 * 内部使用访问者(visitor)模式
 *
 * babel主要也是做的AST的转化
 *
 * 1. 词法分析 tokens : var a  = 1 ["var","a","=","1"]
 * 2. 语法分析 将tokens按照固定规则生成AST语法树
 * 3. 语法树转化 在旧的语法树基础上进行增删改查 生成新的语法书
 * 4. 生成代码 根据新的Tree生成新的代码
 */

// babel核心转化库 包含core -》 AST -》 code的转化实现
/* 
  babel/core 其实就可以相当于 esprima+Estraverse+Escodegen
  它会将原本的sourceCode转化为AST语法树
  遍历老的语法树
  遍历老的语法树时候 会检查传入的插件/或者第三个参数中传入的`visitor`
  修改对应匹配的节点 
  生成新的语法树
  之后生成新的代码地址
*/
const babel = require('@babel/core');

// babel/types 工具库 该模块包含手动构建TS的方法,并检查AST节点的类型。(根据不同节点类型进行转化实现)
const babelTypes = require('@babel/types');

// 转化箭头函数的插件
const arrowFunction = require('@babel/plugin-transform-arrow-functions');


const sourceCode = `const arrowFunc = () => {
	console.log(this)
}`;

const targetCode = babel.transform(sourceCode, {
  plugins: [arrowFunction],
});

console.log(targetCode.code)

Здесь мы используемbabel/core,этоtransformметод преобразует наш код вASTвойти одновременноpluginsобработка становится новойASTи, наконец, сгенерируйте соответствующий код.

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

реализовать это самостоятельно@babel/plugin-transform-arrow-functionsплагин

Здесь мы пытаемся реализовать такую ​​функцию самостоятельно.

Во-первых, давайте сначала напишем базовую структуру:

const babel = require('@babel/core');

// babel/types 工具库 该模块包含手动构建TS的方法,并检查AST节点的类型。(根据不同节点类型进行转化实现)
const babelTypes = require('@babel/types');

// 我们自己实现的转化插件
const { arrowFunctionPlugin } = require('./plugin-transform-arrow-functions');


const sourceCode = `const arrowFunc = () => {
	console.log(this)
}`;

const targetCode = babel.transform(sourceCode, {
  plugins: [arrowFunctionPlugin],
});
// 打印编译后代码
console.log(targetCode.code)
// plugin-transform-arrow-functions.js
const arrowFunctionPlugin = () => {
    // ...
}
module.exports = {
    arrowFunctionPlugin
}

Здесь мы построилиplugin-transform-arrow-functionsфайл для реализации нашего собственного плагина:

Мы сказали вышеbabelПлагин — это, по сути, объект со свойством в нем.visitor. этоvisitorУ объекта есть много методов, и каждый метод назван на основе имени узла.

когдаbabel/coreсерединаtransformметодASTвойдет при обходеvisitormatch в объекте, если тип соответствующего узла совпадаетvisitorЗатем свойства на нем будут выполнять соответствующий метод.

Например этот кусок кода:

const arrowFunctionPlugin = {
    visitor: {
      ArrowFunctionExpression(nodePath) {
          // do something
      }
    },
}

когда проводитсяASTПри обходе, если встречается тип узлаArrowFunctionExpressionвойдетvisitorв объектеArrowFunctionExpressionТаким образом, метод выполняет соответствующую логику для работы с текущим деревом.

вот дваtipНужно немного объяснить вам.

  • Как узнать тип каждого узла? НапримерArrowFunctionExpressionявляется типом стрелочной функции.

первый,babel/typesВсе типы узлов описаны в . Мы можем проверить поbabel/typesПроверьте соответствующий тип узла.

Конечно, есть и другой, более удобный способ, о котором мы упоминали выше.astexplorer, вы можете проверить соответствующую генерацию кода здесьASTТем самым получив соответствующий узел.

  • чтоnodePathПараметр, что это делает?

Каждый метод здесь имеетnodePathпараметры, так называемыеnodePathПараметр вы можете понимать как путь к узлу. Он содержит всю информацию о развилке этого узла в этом дереве и соответствующемapi.Обратите внимание, что путь может быть выделен здесь, ты сможешьПроверьте это здесьсмысл и всеAPI.

После того, как мы написали базовую структуру, приступим к реализации внутренней логики плагина.

Мы явно хотим поговорить о коде для компиляции, это неизбежно.ASTМодификация узлов. По сути, мы все еще проходимASTМодификация операции узлаASTЭто дает результат кода, который мы хотим.

  • Прежде всего, мы можем пройтиastexplorerВведите наш исходный код и ожидаемый скомпилированный код, чтобы получить соответствующийASTструктура.

  • После этого мы сравниваем структуру двух деревьев, чтобы можно было сравнить оригинал.ASTИзмените на основе, чтобы получить наш окончательныйAST.

  • Шагов больше быть не должно.babel transformМетод будет пересмотрен в соответствии с нашей редакциейASTСоздайте соответствующий исходный код.

Студентам настоятельно рекомендуется входить самостоятельноastexplorerВведите код для перевода и сравните его с переведенным кодом.

Скриншот части функции стрелки узла, который необходимо скомпилировать:

image.png

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

image.png

Здесь мы находим контрастinputа такжеoutput:

  • outputФункциональный узел генерал-лейтенанта СтрелкаArrowFunctionExpressionзаменяетсяFunctionDeclaration.
  • outputдля стрелочных функций вbody, объявление вызывающего выраженияExpressionStatementкогда входящийargumentsотThisExpressionзаменяетсяIdentifier.
  • в то же времяoutputВ ту же область видимости, что и стрелочная функция, добавлено объявление дополнительной переменной.const _this = this.

Это просто, нам просто нужноarrowFunctionPluginРеализация этих трех функций может удовлетворить требования, давайте попробуем это вместе.

const babelTypes = require('@babel/types');

function ArrowFunctionExpression(path) {
  const node = path.node;
  hoistFunctionEnvironment(path);
  node.type = 'FunctionDeclaration';
}

/**
 *
 *
 * @param {*} nodePath 当前节点路径
 */
function hoistFunctionEnvironment(nodePath) {
  // 往上查找 直到找到最近顶部非箭头函数的this p.isFunction() && !p.isArrowFunctionExpression()
  // 或者找到跟节点 p.isProgram()
  const thisEnvFn = nodePath.findParent((p) => {
    return (p.isFunction() && !p.isArrowFunctionExpression()) || p.isProgram();
  });
  // 接下来查找当前作用域中那些地方用到了this的节点路径
  const thisPaths = getScopeInfoInformation(thisEnvFn);
  const thisBindingsName = generateBindName(thisEnvFn);
  // thisEnvFn中添加一个变量 变量名为 thisBindingsName 变量值为 this
  // 相当于 const _this = this
  thisEnvFn.scope.push({
    // 调用babelTypes中生成对应节点
    // 详细你可以在这里查阅到 https://babeljs.io/docs/en/babel-types
    id: babelTypes.Identifier(thisBindingsName),
    init: babelTypes.thisExpression(),
  });
  thisPaths.forEach((thisPath) => {
    // 将this替换称为_this
    const replaceNode = babelTypes.Identifier(thisBindingsName);
    thisPath.replaceWith(replaceNode);
  });
}

/**
 *
 * 查找当前作用域内this使用的地方
 * @param {*} nodePath 节点路径
 */
function getScopeInfoInformation(nodePath) {
  const thisPaths = [];
  // 调用nodePath中的traverse方法进行便利
  // 你可以在这里查阅到  https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md
  nodePath.traverse({
    // 深度遍历节点路径 找到内部this语句
    ThisExpression(thisPath) {
      thisPaths.push(thisPath);
    },
  });
  return thisPaths;
}

/**
 * 判断之前是否存在 _this 这里简单处理下
 * 直接返回固定的值
 * @param {*} path 节点路径
 * @returns
 */
function generateBindName(path, name = '_this', n = '') {
  if (path.scope.hasBinding(name)) {
    generateBindName(path, '_this' + n, parseInt(n) + 1);
  }
  return name;
}

module.exports = {
  hoistFunctionEnvironment,
  arrowFunctionPlugin: {
    visitor: {
      ArrowFunctionExpression,
    },
  },
};

Далее давайте воспользуемся плагином, который мы написали в коде, чтобыrunМомент.

image.png

Подведем итоги процесса разработки плагина.

Хотя выше это простой плагинDemoНапример, а вот воробей маленький и полный. полныйbabelПроцесс плагина примерно такой, подытожим немного оbabelПроцесс разработки плагина.

  • Из исходного кода и транспилированного кодаASTСравните узлы, найдите соответствующие разные узлы и максимально повторно используйте предыдущие узлы.

  • Есть измененные/добавленные/удаленные узлы, черезnodePathсерединаApiвызвать соответствующий методASTобработка.

Макроскопически, процесс разработки плагинов в основном разделен на эти два шага, а остальноеast«Бизнес-логика» в разделе Среднего преобразования.

babelВ части разработки плагинов могут участвовать некоторые люди, которые раньше не сталкивались сAPI, здесь я решил напрямую использовать код для объяснения разработки плагина и не объяснял это подробноAPI. Если вы не понимаете некоторые части, вы можете оставить мне сообщение в области комментариев, соответствующееAPIЯ лично рекомендую вам делать большеруководство по разработке плагина babel-handbookЗапрос, понимание здесь будет более глубоким.

Плагин в тексте просто маленькийDemoуровне, цель состоит в том, чтобы привести всех вbabelШлюз для разработки плагинов. Вы можете найти код в статье наздесьПроверить. этоrepoсодержит не толькоdemo, также включает в себя несколько более сложных плагинов для обучения и имитации, а также плагин загрузки по требованию, который реализует библиотеку компонентов, упомянутую в начале статьи (Загружаю плагины по запросу. Я все еще пишу, простите мою лень...).

11.6 Обновление

Здесь мы добавляемbabel-registerпростые очки знаний.

Давайте посмотрим на официальный сайт дляbabel-registerописание:

One of the ways you can use Babel is through the require hook. The require hook will bind itself to node's require and automatically compile files on the fly. This is equivalent to CoffeeScript's coffee-script/register.

Короткий ответ - представитьbabel/registerназадbabelФайлы с определенным суффиксом будут обработаны.

All subsequent files required by node with the extensions .es6, .es, .jsx, .mjs, and .js will be transformed by Babel.

Сначала я вообще-то не совсем понял, в чем его функция.Для файлов с вышеуказанными суффиксами дайтеBabelДля конвертации мы используем различные инструменты компиляции переднего плана, такие какwebpackсерединаbabel-loaderа такжеrollupсередина@rollup/plugin-babelПомогите нам сделать что-то?

здесь@babel/regiserФункция не более чем одним махом.

пока яstackoverflowувидеть это вотвечать.

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

пока вnodejsсерединаbabelне его ядроAPIчасть, если мы хотимnodejsиспользуется вbabelЧтобы перевести наши файлы, мы можем передатьbabel/regiseterТаргетинг наrequireФайл, представленный оператором, передаетсяbabelидтиtransform. чтобы достичьnodejsПереводитjsдокумент.

Важно отметить, чтоbabel/registerэто своевременная компиляция.

// index.js
require('@babel/register')
const data = require('./register');

console.log(`${data}`, 'data');

// register.js
const arrowFunction = () => {
  console.log('Hello,My name is wang.haoyu');
};

module.exports = arrowFunction

// babelrc
{
  "presets": ["@babel/preset-env"]
}

когда мы используемnodeбегатьindex.jsВремя:

image.png

Вы обнаружите, что наш результат печати стал нормальной функцией.

На этом этапе давайте закомментируем соответствующий@babel/register

// require('@babel/register')
const data = require('./register');

console.log(`${data}`, 'data');

image.png

Мы обнаружили, что в этот момент наша стрелочная функция неpreset-envэффективный.

11.16 Обновление

Дополнение оuseBuiltIns:usageа такжеplugin-runtimeОтличия и лучшие практики.

напиши в конце

На этом этапе я хотел бы поблагодарить всех, кто видел здесь маленького партнера.

в статьеbabelОбъяснение — это только вершина айсберга, надеюсь, эта статья станет исследованием для всех.Babelотправная точка.