Как началась история

Каждая история начинается с ошибки ...

В то время как я наслаждался тем, что mangle-props дает нам почти 40% -ную степень сжатия, используя способ, о котором я упоминал в своем последнем сообщении в блоге, все идет хорошо.

Однажды мы решили переместить наши новые компоненты пользовательского интерфейса в React.js, и я понял, что мне нужно скомпилировать синтаксис JSX, прежде чем загружать их в компрессор Terser. Terser не справится с минимизацией синтаксиса JSX, это невозможно. Даже если вы используете плагин babel-minify + syntax-jsx, он имеет ошибку.

Я устал применять этот процесс сборки к нашим веб-приложениям. Странно делиться процессом сборки как инструментом или пакетом, потому что это слишком необычно для людей.

Babel (специальные синтаксисы, например, JSX) → Compress (terser-js) → Babel (остальные плагины)

Вы хотите, чтобы вышеперечисленное стало вашим инструментом для сборки / сборки? Думаю, нет.

Решения

В основном у нас есть несколько вариантов для переноса с ES6 + на ES5:

  • Компилятор закрытия
  • Желудь
  • Вавилон
  • Компилятор машинописного текста (tsc)
  • Бубле
  • … (Есть и другие, но, возможно, не такие популярные)

Давайте проверим их по одному

  1. Компилятор закрытия

тяжелые Java Depdencies. поддержка mangle-props, хорошо, но я уже стал короче. Нам нужен простой способ, без огромных зависимостей, так что до свидания, сначала закрываем компилятор ...

  1. Желудь

Это слишком сырой парсер. нам нужно написать логику преобразования с acorn-walk, babel уже работает с преобразованием, верно? А бабель узнал из желудя, поддержал jsx, неплохо звучит? Далее переходим в babel.

Давай действительно попробуем их

Babel, Typescript и Buble звучат просто, у них есть плагины для бандлера (загрузчик накопительных пакетов / веб-пакетов) или их можно настраивать (ts и buble). Нас легко взломать.

Вавилон

Вкратце расскажем, в чем проблема Babel. Пример ниже довольно прост:

Синтаксис ES6 1: синтаксис класса ES6 2: средство доступа / мутатор свойства

class A {
  method() {
    console.log('method');
  }
  get x() { return 1 }
}
const B = {
  get x() { return 2 }
}

Мы ожидали, что транспилятор максимально просто преобразует их в чистый код ES5. Следующий фрагмент является результатом работы babel transpiler с предустановкой ES2015 в свободном режиме.

var A =
  /*#__PURE__*/
  (function() {
    function A() {}
    var _proto = A.prototype;
    _proto.method = function method() {
      console.log("method");
    };
    _createClass(A, [
      {
        key: "x",
        get: function get() {
          return 1;
        }
      }
    ]);
    return A;
  })();
var B = {
  get x() {
    return 2;
  }
};

Проблема 1: невозможно транспилировать получатели литерала объекта. Проблема 2: getter цитируется babel в _createClass helper, что означает, что terser-js (включен mangle-props) в этом случае будет иметь ошибку.

Для проблемы 1: мы могли бы решить ее с помощью плагина @ babel / plugin-transform-property-mutators, что несложно.

Для проблемы 2: как взломать _createClass помощник? Можем ли мы взломать его до того, как класс трансформируется? Может быть!

Мысли здесь: если бы мы могли переместить логику установки / получения из функции _createClass, определив их другими способами, такими как (Object.defineProperty), terser-js внесет эти выражения в белый список и не будет искажать их имена. Звучит отлично!

Можем ли мы сделать то же самое с любым плагином? Нет. Хорошо, позволь мне написать один.

Я потратил некоторое время на написание плагина babel для решения проблемы 2, наконец, я сделал babel-plugin-transform-class-property-mutators

С этими плагинами ввод вроде этого

class A {
  get x() {
    return this._x || 1
  }
}

будет выводиться так

Object.defineProperties(A, {
  x: {
    get: function () {
      return A._x || 1;
    },
    configurable: true,
    enumerable: true
  }
})

Видеть? getter / setter теперь определены в выражении Object.defineProperties, и это ES5. Выглядит неплохо? Нет, наверное, нет.

Эй, давай подумаем, что мы можем делать внутри методов класса и мутаторов свойств? Класс может быть производным от родителя, не забывайте, что есть ключевое слово super, а super.xxx может быть задействовано внутри установщика / получателя.

Затем нам нужно сначала скомпилировать вывод класса! Но у плагинов есть порядок: если вы сначала выполните преобразование класса, все будет сделано, тогда у вас не будет возможности запустить плагин, чтобы что-нибудь взломать.

Грустная история, давай закончим вавилонской беседой.

Машинопись

Станет ли это нашим предпочтением? Типскрипт получил действительно простой вывод с преобразованием класса, я сделал пример как с геттером / сеттером, так и с деривацией. Вот ввод:

пример кода

class P { 
  f() { 
    return 1
  }
}
class Child extends P {
  constructor() {
    super()
  }
  get value() {
    return super.f()
  }
}
let instance = new Child()
console.log(instance.value)

Результат выглядит так:

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var P = /** @class */ (function () {
    function P() {
    }
    P.prototype.f = function () {
        return 1;
    };
    return P;
}());
var Child = /** @class */ (function (_super) {
    __extends(Child, _super);
    function Child() {
        return _super.call(this) || this;
    }
    Object.defineProperty(Child.prototype, "value", {
        get: function () {
            return _super.prototype.f.call(this);
        },
        enumerable: true,
        configurable: true
    });
    return Child;
}(P));
var instance = new Child();
console.log(instance.value);

Выглядит отлично, это почти то, что нам нужно! Почти? Не совсем так? Расскажу тебе позже…

Хорошие новости: я нашел несколько плагинов машинописного текста для накопления:

  1. Https://github.com/ezolenko/rollup-plugin-typescript2
  2. Https://github.com/rollup/rollup-plugin-typescript

И я интегрирую rollup-plugin-typescript2 с rollup, настраиваю его также для файлов javascript, отключаю некоторые конкретные параметры только для файлов ts (например, создание объявления типов, принудительная проверка типизации…).

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

// default config
{
  include: ["*.ts+(|x)", "**/*.ts+(|x)"],
  // ...
}

Разработчику также будет любопытно и непонятно, почему что-то частично поддерживается машинописным текстом, а не полностью настроено. Вы устанавливаете весь пакет машинописного текста и просто хотите, чтобы он помог вам скомпилировать ES6 js в ES5. Это слишком продумано?

Я удалил машинописный текст, наблюдая за авторскими правами Microsoft в сгенерированных скомпилированных файлах.

Бубле

С помощью нашего примера кода узнайте, что с этим будет делать Bublé. Вы можете поиграть с ним в онлайн-приложении repl: https://buble.surge.sh

Вот так

var P = function P () {};
P.prototype.f = function f () { 
  return 1
};
var Child = (function (P) {
  function Child() {
    P.call(this)
  }
  if ( P ) Child.__proto__ = P;
  Child.prototype = Object.create( P && P.prototype );
  Child.prototype.constructor = Child;
  var prototypeAccessors = { value: { configurable: true } };
  prototypeAccessors.value.get = function () {
    return P.prototype.f.call(this)
  };
  Object.defineProperties( Child.prototype, prototypeAccessors );
  return Child;
}(P));
var instance = new Child()
console.log(instance.value)

Во-первых: никаких строковых свойств, все, что определено в прототипе или создано definedProperties, отлично Во-вторых: сгенерированный код класса действительно простой и достаточно короткий, мне нравится prototypeAccessors

Здесь проблем не обнаружено, вывод выполняется правильно, класс ES5 тоже тонкий. Думаю, это окончательный выбор.

История закрыта

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

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

Вы можете ознакомиться с исходным кодом бандлера здесь: https://github.com/huozhi/bunchee.

исходное сообщение блога на моем сайте: https://huozhi.github.io/post/Comparing-Different-Ways-of-Transpile-ES6-Class-To-ES5