Вставка комментариев с помощью плагина SWC Rust

Я пытаюсь добавить комментарий к существующему коду JavaScript.

В Babel есть помощник addComment, который можно вызвать на узле AST.

Вот простое (но довольно глупое) преобразование, написанное на Babel:

console.info`Hello, ${name}!`; -> /*#__PURE__*/console.info("Hello, ", name, "!");

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

function run(code) {
  const result = babel.transform(code, {
    plugins: [
      function transform({ types: t }) {
        return {
          visitor: {
            TaggedTemplateExpression(path) {
              const { tag, quasi } = path.node;

              if (
                t.isMemberExpression(tag) &&
                t.isIdentifier(tag.object, { name: "console" }) &&
                t.isIdentifier(tag.property, { name: "log" })
              ) {
                let args = [];

                quasi.quasis.forEach((element, index) => {
                  args.push(t.stringLiteral(element.value.raw));
                  if (index < quasi.expressions.length) {
                    args.push(quasi.expressions[index]);
                  }
                });

                path.replaceWith(
                  t.callExpression(
                    t.addComment(
                      t.memberExpression(
                        t.identifier("console"),
                        t.identifier("log")
                      ),
                      "leading",
                      "#__PURE__"
                    ),
                    args
                  )
                );
              }
            },
          },
        };
      },
    ],
  });
  return result.code;
}

const code = "console.info`Hello, ${name}!`;";
console.info(run(code));
// -> /*#__PURE__*/console.info("Hello, ", name, "!");

Однако в Rust все немного сложнее, поскольку только одна переменная может владеть данными, и на нее может быть не более одной изменяемой ссылки, кроме того, в SWC реализованы некоторые трюки с производительностью.

Таким образом, в SWC вам необходимо использовать PluginCommentsProxy , который описан в текущей версии SWC 0.279.0 с помощью следующей блок-схемы:

 Below diagram shows one reference example how guest does trampoline between
 host's memory space.
┌───────────────────────────────────────┐    ┌─────────────────────────────────────────────┐
│Host (SWC/core)                        │    │Plugin (wasm)                                │
│  ┌────────────────────────────────┐   │    │                                             │
│  │COMMENTS.with()                 │   │    │  ┌──────────────────────────────────────┐   │
│  │                                │   │    │  │PluginCommentsProxy                   │   │
│  │                                │   │    │  │                                      │   │
│  │ ┌────────────────────────────┐ │   │    │  │ ┌────────────────────────────────┐   │   │
│  │ │get_leading_comments_proxy()│◀┼───┼────┼──┼─┤get_leading()                   │   │   │
│  │ │                            │ │   │    │  │ │                                │   │   │
│  │ │                            │ │   │    │  │ │ ┌──────────────────────────┐   │   │   │
│  │ │                            │─┼───┼──┬─┼──┼─┼─▶AllocatedBytesPtr(p,len)  │   │   │   │
│  │ └────────────────────────────┘ │   │  │ │  │ │ │                          │   │   │   │
│  │                                │   │  │ │  │ │ └─────────────┬────────────┘   │   │   │
│  │                                │   │  │ │  │ │               │                │   │   │
│  │                                │   │  │ │  │ │ ┌─────────────▼────────────┐   │   │   │
│  └────────────────────────────────┘   │  │ │  │ │ │Vec<Comments>             │   │   │   │
│                                       │  └─┼──┼─┼─▶                          │   │   │   │
│                                       │    │  │ │ └──────────────────────────┘   │   │   │
│                                       │    │  │ └────────────────────────────────┘   │   │
│                                       │    │  └──────────────────────────────────────┘   │
└───────────────────────────────────────┘    └─────────────────────────────────────────────┘

 1. Plugin calls `PluginCommentsProxy::get_leading()`. PluginCommentsProxy is
 a struct constructed in plugin's memory space.
 2. `get_leading()` internally calls `__get_leading_comments_proxy`, which is
 imported fn `get_leading_comments_proxy` exists in the host.
 3. Host access necessary values in its memory space (COMMENTS)
 4. Host copies value to be returned into plugin's memory space. Memory
 allocation for the value should be manually performed.
 5. Host completes imported fn, `PluginCommentsProxy::get_leading()` now can
 read, deserialize memory host wrote.
 - In case of `get_leading`, returned value is non-deterministic vec
 (`Vec<Comments>`) guest cannot preallocate with specific length. Instead,
 guest passes a fixed size struct (AllocatedBytesPtr), once host allocates
 actual vec into guest it'll write pointer to the vec into the struct.
comments.add_leading(
  node.span.lo,
  Comment {
    kind: swc_core::common::comments::CommentKind::Block,
    span: DUMMY_SP,
    text: "#__PURE__".to_string(),
  },
)

К сожалению, мне не удалось протестировать его должным образом с помощью swc_core::ecma::transforms::testing.

#[cfg(test)]

mod tests {
  use super::*;
  use std::path::PathBuf;
  use swc_core::ecma::transforms::testing::{test_fixture};
  use swc_ecma_transforms_testing::{FixtureTestConfig};

  #[testing::fixture("tests/fixture/**/input.tsx")]
  fn fixture(input: PathBuf) {
    test_fixture(
      Default::default(),
      &|tester| as_folder(TransformVisitor::new(&tester.comments)),
      &input,
      &input.with_file_name("output.tsx"),
      FixtureTestConfig::default(),
    );
  }
}

К сожалению, это не работает, потому что tester.comments относится к типу Rc<SingleThreadedComments>.

Я видел примеры использования <C>, например, трансформатора MillionJs:

fn transform_block<C>(context: &ProgramStateContext, node: &mut CallExpr, comments: C)
where
    C: Comments,
{

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

Есть ли способ лучше?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
0
59
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Автор SWC здесь.

Вы можете сделать свой трансформер более универсальным C: Comments, как в официальном проходе pure_annotations. Затем сохраните C так же, как и другие дженерики.

Новый путь

Вы можете использовать PluginCommentProxy из плагина Wasm даже во время тестирования, если запускаете тесты через swc_ecma_transforms_testing или swc_core::ecma::transforms::testing, используя такой метод, как test_fixture.


PluginCommentProxy.add_leading(n.span.lo, Comment {
    // ...fields
});

будет работать только во время тестирования.

Старый способ

Это было необходимо раньше https://github.com/swc-project/swc/pull/9150, но этот способ работает до сих пор.

struct PureAnnotations<C>
where
    C: Comments,
{
    imports: AHashMap<Id, (JsWord, JsWord)>,
    comments: Option<C>,
}

после этого вам следует сделать раздел impl универсальным поверх C.

impl<C> VisitMut for PureAnnotations<C>
where
    C: Comments,
{
    noop_visit_mut_type!();

}

Вы можете добавить подходящие методы посетителей для достижения своей цели.

Альтернативно вы можете принять &dyn Comments или Option<&dyn Comments>. Официальный проход fixer использует этот шаблон для уменьшения размера двоичного файла. В этом случае вам следует добавить '_ между impl и Fold в возвращаемом типе конструктора.

pub fn fixer(comments: Option<&dyn Comments>) -> impl '_ + Fold + VisitMut {
    as_folder(Fixer {
        comments,
        ctx: Default::default(),
        span_map: Default::default(),
        in_for_stmt_head: Default::default(),
        in_opt_chain: Default::default(),
        remove_only: false,
    })
}

Не могли бы вы рассказать, как github.com/swc-project/swc/pull/9150 это улучшилось?

jantimon 07.07.2024 10:10

@jantimon Я обновил пост. Спасибо за пинг!

kdy 07.07.2024 10:32

Другие вопросы по теме