Статьи

EcmaScript6: первая «ошибка» найдена и исправлена

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

Вот что я имею в виду:

// Meta/index.jsx
import Title from './Title';

export Title;

Кажется разумным, верно? Все же это выдает синтаксическую ошибку.

Конечно, это может быть ошибка в Babel.js, но я не думаю, что это так. Если я правильно читаю Спецификацию ECMAScript2015 , то вы никогда не должны были этого делать. Что еще хуже.

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

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

Version: webpack 1.11.0
Time: 662ms
   [0] multi main 40 bytes {0} [built] [1 error]
    + 7 hidden modules

ERROR in ./src/components/H1BGraph/Meta/index.jsx
Module build failed: SyntaxError: /Users/Swizec/Documents/random-coding/react-d3js-experiment/src/components/H1BGraph/Meta/index.jsx: Unexpected token (7:18)
   5 | import Title from './Title';
   6 | 
>  7 | export Title;
     |             ^
   8 |

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

В этом случае есть a Titleи a Description. Оба компонента наследуются от названного базового компонента BaseMeta, но используются отдельно. Имеет смысл поместить эти три файла и несколько помощников в каталог с именем Meta.

Принуждение программистов писать import Title from './Meta/Title'нарушает абстракцию. Вам не нужно знать, как Metaорганизованы внутренние компоненты и классы . Запись import Title from './Meta’должна быть возможной.

Правильно?

Конечно, можно утверждать, что оба из этих компонентов подпадают под общий Metaдолжен использоваться через родительский <Meta>компонент. Тогда вы могли бы использовать Metaи не беспокоиться об импорте либо Titleили Description. Во многом это правда.

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

Чтобы это работало, нам нужен такой грязный хак:

// Meta/index.jsx
import {Title as T} from './Title';

export class Title extends T {};

Гадкий, правда?

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

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

Хотелось бы, чтобы эта глупость не была нужна, но это так.

Возможно, ответ заключается в том, к чему этот код компилируется. Возможно, где-то в результирующем коде ES5 есть более глубокая причина, почему JavaScript не может реэкспортировать, как все остальные.

Когда Babel готов, наш импорт → экспорт выглядит так:

function(module, exports, __webpack_require__) {


    // Ugly but works :D

    'use strict';

    Object.defineProperty(exports, '__esModule', {
      value: true
    });

    var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };

    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

    function _inherits(subClass, superClass) { if (typeof superClass !== 'function' &amp;&amp; superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass &amp;&amp; superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

    var _Title = __webpack_require__(8);

    var Title = (function (_T) {
      _inherits(Title, _T);

      function Title() {
        _classCallCheck(this, Title);

        _get(Object.getPrototypeOf(Title.prototype), 'constructor', this).apply(this, arguments);
      }

      return Title;
    })(_Title.Title);

    exports.Title = Title;
    ;
/***/ }

Блин, много кода! Помните, как вы писали все это каждый раз, когда вам нужно наследование классов? Я тоже, винт наследования классов, если это то, что нужно. Неудивительно, что все так взволнованы модулями ES6.

Давайте посмотрим на ключевую часть: где Titleкласс определяется как расширение без особенностей T. Эта часть:

    var _Title = __webpack_require__(8);

    var Title = (function (_T) {
      _inherits(Title, _T);

      function Title() {
        _classCallCheck(this, Title);

        _get(Object.getPrototypeOf(Title.prototype), 'constructor', this).apply(this, arguments);
      }

      return Title;
    })(_Title.Title);

    exports.Title = Title;
    ;

Хорошо, это довольно знакомо. Если бы человек написал первую строку, это было бы что-то вроде var Foo = require('Foo’). require()дает класс, и мы присваиваем его переменной.

Затем мы создаем замыкание, которое оборачивает область вокруг функции. Думайте об этой функции как о фабрике классов — она ​​берет класс и создает новый расширенный класс.

Для создания этих новых классов замыкание создает функцию с именем Title. Эта функция действует как конструктор объекта, так же, как функции обычно делают в JavaScript. Он использует _getдля выполнения какой-то магии, которая за мной. Я думаю, что  она сама вызывает функцию, а также вызывает конструктор суперкласса, но я не уверен.

Просто чтобы сохранить интерес, Babel использует эту возможность, чтобы воспользоваться преимуществами поведения JavaScript, когда функции, которые не назначены переменной, всегда являются глобальными в текущей области видимости. Этот код вызывает _inheritsна Titleфункцию, но , прежде чем он создан. Судя по всему _inherits, он может заменить прототип подкласса экземпляром объекта суперкласса.

Это странно, но это работает. Слава богам с открытым исходным кодом, что Вавилон пишет эту магию для нас.

class X extends Y не только легче написать, чем этот суп из JavaScript, но и легче (читай: возможно) понять.

Теперь мы знаем, что export class X extends Y {}происходит, но мы до сих пор не ответили на главный вопрос: почему мы не можем просто реэкспортировать?

Я не думаю, что это потому, что JavaScript не может обработать что-то вроде этого:

var Title = (function (_T) {
 // ...
})(__webpack_require__(8));

Хотя это не самый чистый код, он * должен * работать. Но взгляните на последнюю строку в предыдущем примере, строку с надписью exports.Title = Title.

Если мы не сообщим коду, что экспортировать, как он узнает? Не будет Без имени скомпилированный код будет выглядеть так:

exports = __webpack_require__(8);

При этом мы можем реэкспортировать только код одного модуля. Который работает кстати, вы всегда можете написать export default X.

Если вы хотите реэкспортировать несколько модулей, вам придется придерживаться этого уродливого обходного пути. Я думаю, что это глупо, но это лучшее, что может сделать JavaScript, пока не получит отражение, и мы наконец-то можем ответить на вопрос «Эй, как программист назвал эту переменную?»

Я думаю, что это будет в ES7. Посмотрим.

PS: официальный способ реэкспорта модулей в ES6 выглядит так:

// to export a member
export { encrypt as en } from 'lib/crypto';
// or to export everything
export * from 'lib/crypto';

Но это все еще выглядит странно, не подходит для экспорта по умолчанию и не решает проблему «сохранить имя».

Благодарю Юре Чухалева, Анже Печара, Уилла Фангуя, Колетту Теске и Дэвида Байрда за чтение черновых версий этой статьи

связанные с

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