Статьи

Как писать пакеты Atom с использованием Vanilla JavaScript

Эта статья была рецензирована Vildan Softic . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!

Atom — это современный, по сути, взломанный редактор. Это здорово, но разработчикам, которые не владеют CoffeeScript , трудно следовать документации. Понимание экосистемы Atom может привести к путанице. Давайте рассмотрим все аспекты того, как работает написание пакета Atom в JavaScript.

Понимание атома

Atom — это приложение на основе Node.js и Chromium , написанное с использованием среды GitHub Electron . Это означает, что технически это веб-приложение, работающее на рабочем столе. Внутренняя функциональность Atom разделена на крошечные базовые пакеты ; они разработаны так же, как и любой другой пакет сообщества. Хотя все они написаны на CoffeeScript, их можно либо написать на простом JavaScript, либо перенести их через Babel .

Активация полной поддержки ES2015 с Babel

Babel — это компилятор исходного кода ; превращение кода ECMAScript 2015 (ранее известного как ES6) в код ECMAScript 5. Поскольку среда Chromium, уже доступно множество поддерживаемых функций ES2015 . Но вместо того, чтобы постоянно искать, какие из них реализованы, я рекомендую использовать Babel для переноса вашего кода. В более позднем выпуске — когда ES2015 лучше поддерживается в Chromium — вы можете снова деактивировать Babel и оставить свою базу кода (почти) нетронутой.

Для активации транспилирования с помощью Babel каждому файлу требуется 'use babel'; оператор в начале, аналогично строгому режиму в ECMAScript 5. Это также дает вам возможность решить, какие файлы следует передавать, а какие нет, пропустив оператор.

Package.json

Это помогает просматривать пакет Atom как модуль npm . Вы имеете такой же доступ к API, как и любой инструмент, работающий на Node.js. Поэтому возможно добавить любую необходимую зависимость от npm. Также требуется package.json , содержащий все метаданные для вашего проекта. Основной файл должен быть следующим:

 { "name": "your-package", "main": "./lib/main", "version": "0.1.0", "description": "A short description of your package", "keywords": [ "awesome" ], "repository": "https://github.com/<your-name>/<package>", "license": "MIT", "engines": { "atom": ">=1.0.0 <2.0.0" }, "dependencies": { } } 

Важные ключи являются main — определяют основную точку входа вашего пакета (по умолчанию index.js / index.coffee ) — и enginesindex.coffee Atom, на какой версии работает ваш пакет. Также имеется набор дополнительных ключей, документированных в документации пакета «wordcount» (раздел package.json ) .

Исходный код пакета

Весь код вашего пакета находится в каталоге верхнего уровня lib/ . Я также рекомендую, чтобы ваша точка входа находилась в этой папке, поскольку она сохраняет структуру в чистоте и облегчает сканирование проекта.

Ваш основной файл должен быть одноэлементным объектом, который поддерживает весь жизненный цикл вашего пакета. Даже если ваш пакет состоит только из одного представления, все это будет управляться из этого объекта. Ваша точка входа требует один метод activate() , но также должен иметь дополнительные функции deactivate() и serialize() .

 // lib/main.js 'use babel'; // This is your main singleton. // The whole state of your package will be stored and managed here. const YourPackage = { activate (state) { // Activates and restores the previous session of your package. }, deactivate () { // When the user or Atom itself kills a window, this method is called. }, serialize () { // To save the current package's state, this method should return // an object containing all required data. } }; export default YourPackage; 

Активируйте ваш пакет

Функция activate() является единственным обязательным методом. Инициализируйте все свои модули, представления или помощники здесь. Передается объект, содержащий предыдущее сериализованное состояние вашего пакета. Если вы ничего не сериализуете в своем пакете, это будет пустой объект. Это означает, что от вас и вашей архитектуры пакета зависит, что сериализировать.

деактивация

Метод deactivate() является необязательным, но важным. Он будет вызываться Atom, когда окно закрывается, или пользователь отключает его в настройках. Когда ваш пакет деактивируется пользователем, и вы не избавляетесь от добавленных событий / команд, они все еще доступны . Это не проблема, когда Atom закрывает окно. Это будет сносить события и команды. Но если ваш пакет просматривает файлы или выполняет какую-либо другую работу, вы должны выпустить их в deactivate() .

Подписка на события

Пакет обычно подписывается на несколько событий, таких как добавление пользовательских команд, прослушивание изменений или измененных файлов. Можно объединить их в экземпляр CompositeDisposable() , и таким образом они могут быть утилизированы одновременно.

 // lib/main.js import { CompositeDisposable } from 'atom'; const YourPackage = { subscriptions: null, activate (state) { // Assign a new instance of CompositeDisposable... this.subscriptions = new CompositeDisposable(); // ...and adding commands. this.subscriptions.add( atom.commands.add('atom-workspace', { 'your-package:toggle': this.togglePackage }) ); }, // When your package get's deactivated, all added // subscriptions will be disposed of at once. deactivate () { this.subscriptions.dispose(); }, togglePackage () { // Code to toggle the package state. } }; 

Сериализация всех вещей!

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

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

 // lib/fancy-component.js class FancyComponent { constructor (configData) { this.data = configData; } // This method will be called when the class // is restored by Atom. static deserialize (config) { return new FancyComponent(config); } // The returned object will be used to restore // or save your data by Atom. // The "deserializer" key must be the name of your class. serialize () { return { deserializer: 'FancyComponent', data: this.data }; } doSomethingWithData () {} } // Add class to Atom's deserialization system atom.deserializers.add(FancyComponent); export default FancyComponent; 

Чтобы сделать все это полезным, этот компонент должен вызываться и сериализоваться в основной пакет Singleton.

 // lib/main.js import FancyComponent from './fancy-component'; import SomeView from './some-view'; const YourPackage = { fancyComponent: null, someView: null, activate (state) { // If the component has been saved at a previous session of Atom, // it will be restored from the deserialization system. It calls your // your components static 'deserialize()' method. if (state.fancy) { this.fancyComponent = atom.deserializers.deserialize(state.fancy); } else { this.fancyComponent = new FancyComponent({ otherData: 'will be used instead' }); } // More activation logic. }, // As well as your component, your package has a serialize method // to save the current state. serialize () { return { fancy: this.fancyComponent.serialize(), view: this.someView.serialize() }; } }; 

Все объекты, которые вы хотите сериализовать, нуждаются в методе serialize() . Он должен возвращать «сериализуемый объект» и ключ deserializer с именем зарегистрированного десериализатора. По словам Атома, «обычно это название самого класса» . В дополнение к этому классу также необходим статический метод deserialize() . Этот метод преобразует объект из предыдущего состояния в подлинный объект.

Чтобы сделать все это возможным, вы должны добавить свой класс в систему десериализации с помощью atom.deserializers.add() .

Панели и виды

Панель — это отдельное окно в Atom. Он содержит все открытые вкладки, называемые «элементы». Эти панели хранятся в объекте atom.workspace . С atom.workspace.getActivePane() вы запрашиваете текущую активную панель. Объект панели не содержит никаких элементов DOM, но содержит все экземпляры внутренних компонентов Atom (например, TextEditor , GutterContainer , NotificationManager ). Понимание этих панелей важно для создания пользовательских представлений для вашего пакета.

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

 // lib/custom-view-element.js export default class YourPackageView { constructor (state) { this.data = state; this.element = document.createElement('div'); this.message = document.createElement('span'); this.textNode = document.createTextNode(this.data.content); this.element.classList.add('your-package'); this.message.classList.add('your-package-message'); this.message.appendChild(this.textNode); this.element.appendChild(this.message); } serialize () { return { data: this.data }; } destroy () { this.element.remove(); } getElement () { return this.element; } doSomethingWithData () {} } 
 // lib/main.js import YourPackageView from './custom-view-element'; import { CompositeDisposable } from 'atom'; const YourPackage = { subscriptions: null, packageView: null, modal: null, activate (state) { this.subscriptions = new CompositeDisposable(); // We don't use the serialization system here because we assume // that our view won't work with any data. this.packageView = new YourPackageView(state.viewState); // Here we add the custom view to the modal panel of Atom. this.modal = atom.workspace.addModalPanel({ item: this.packageView.getElement(), visible: false }); this.subscriptions.add( atom.commands.add('atom-workspace', { 'your-package:toggle-modal': this.toggleModal() }) ); }, // We destroy both the custom view and Atom's modal. deactivate () { this.subscriptions.dispose(); this.packageView.destroy(); this.modal.destroy(); }, serialize () { return { viewState: this.packageView.serialize() }; }, toggleView () { if (this.modal.isVisible()) { this.modal.hide(); } else { this.modal.show(); } }, doSomethingElseWithView () {} }; export default YourPackage; 

Метод atom.workspace.addModalPanel() добавляет модальный элемент в рабочую область Atom. Если вы хотите добавить пользовательский вид на панель (например, для страницы настроек), вам потребуется немного больше работы.

Создание настраиваемого пакета

Конфигурация пакета должна быть описана в схеме JSON . Чтобы добавить настройки, вашему объекту пакета необходим ключ config с данными. В качестве альтернативы вы можете переместить конфигурацию в файл config-schema.json и import ее. Это позволяет отделить вашу конфигурацию и организовать вашу архитектуру.

 // lib/config-schema.json { "activateHyperMode": { "description": "Turns the package into hyper mode.", "type": "boolean", "default": false }, "setRange": { "type": "integer", "default": 42, "minium": 1, "maximum": 9000 } } 
 // lib/main.js import packageConfig from './config-schema.json'; const YourPackage = { config: packageConfig, subscriptions: null, activate (state) { // ... } }; 

Это создаст конфигурацию на странице настроек вашего пакета автоматически. Список всех поддерживаемых типов можно найти на странице конфигурации документации API Atom. Ваш объект настроек вместе со всей остальной конфигурацией пакета хранится в объекте atom.config .

Получение и настройка

Вы можете получить и установить любой ключ вашей конфигурации с помощью методов get() и set() . Также можно получить общие настройки Atom или другие пакеты. Если вы хотите взаимодействовать с другими пакетами, вам необходимо предоставлять и использовать сервисы .

 atom.config.get('yourPackage'); // Returns the entire configuration object atom.config.get('yourPackage.activateHyperMode'); // Returns false atom.config.get('core.fileEncoding'); // Returns 'utf8' atom.config.get('differentPackage'); atom.config.set('yourPackage.activateHyperMode', true); atom.config.set('yourPackage.myNewValue', 'value'); 

Прослушивание изменений

Чтобы прослушать изменения, вы можете наблюдать за изменениями в конфигурации или иметь прослушиватель — onDidChange() — для пути к ключу. Они оба возвращают Disposable, который вы можете .dispose() отписаться.

Опять же, добавление их в экземпляр CompositeDisposable позволяет вам избавиться от нескольких событий одновременно:

 this.subscriptions = new CompositeDisposable(); this.subscriptions.add( atom.config.observe('core.themes', value => { // do something }) ); this.subscriptions.add( atom.config.onDidChange('yourPackage', ({oldValue, newValue}) => { // do something }) ); 

Или распоряжайтесь ими индивидуально:

 const observeConfig = atom.config.observe('core.themes', doSomethingCool); const onChangeConfig = atom.config.onDidChange('yourPackage', doSomethingOnChange); // later in your code observeConfig.dispose(); onChangeConfig.dispose(); 

Точная настройка с меню и раскладками клавиш

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

Добавление меню

Определение меню может быть сохранено как файл JSON в каталоге menus/ top-level или в ключе menus вашего package.json . В следующем примере команды добавляются в строку меню « Packages и в контекстное меню редактора. Контекстное меню появляется при щелчке правой кнопкой мыши внутри редактора.

 // menus/your-package.json "menu": [ { "label": "Packages", "submenu": [ { "label": "Your Package", "submenu": [ { "label": "Toggle", "command": "your-package:toggle" }, { "label": "Settings", "command": "your-package:show-settings" } ] } ] } ], "context-menu": { "atom-text-editor": [ { "label": "Toggle Your Package", "command": "your-package:toggle" } ] } 

Таблицы ключей

С помощью раскладок клавиш вы определяете сочетания клавиш для команд вашего пакета. Они привязаны к определенной области, где область представляет собой селектор CSS, такой как atom-text-editor , atom-text-editor:not([mini]) или atom-workspace . Когда элемент, соответствующий селектору, находится в фокусе, и используется комбинация клавиш, ваше пользовательское действие отправляется.

 // keymaps/your-package.json { "atom-text-editor": { "alt-shift-e": "your-package:toggle", "cmd-ctrl-alt-shift-enter-backspace": "your-package:do-something-crazy" }, "atom-text-editor[mini]": { "tab-escape": "your-package:transform" } } 

Помните, что эти команды должны быть зарегистрированы ( atom.commands.add() ) в вашей точке входа.

Отладка с помощью инструментов разработчика Chrome

Отладка в Atom не сильно отличается от отладки в сети. Вы можете активировать Инструменты разработчика Chrome в View > Developer > Toggle Developer Tools чтобы просмотреть сгенерированные ошибки, журналы из своего кода или понять разметку Atom.

Инструменты разработчика Chrome в редакторе Atom

Модульные тесты с жасмином

Atom использует платформу Jasmine для своих тестов. Тесты помещаются в каталог spec/ top-level, и файлы внутри должны заканчиваться на -spec (например, fancy-component-spec.js ). Тесты не требуются для запуска вашего пакета или его публикации, но они являются хорошим способом подтвердить качество вашего кода и убедиться, что ничего не нарушается при добавлении новых функций.

Чтобы запустить ваши тесты, вы можете использовать команду window:run-package-specs или перейти в View > Developer > Run Package Specs .

Если вы хотите запустить спецификации своего пакета на Travis CI , в блоге Atom есть краткое сообщение о том, как его настроить.

Поток пакетов

Это было много информации. Фактический порядок потока или выполнения Atom примерно такой (примечание: тесты не являются частью потока пакета).

  1. Атом загружается и читает ваш package.json
    • Меню, раскладки клавиш, таблицы стилей и все другие настройки применяются
    • Если activationCommands определены, они будут выполняться
  2. Основная точка входа (то есть, activate() ) выполняется
    • Волшебство вашего пакета (например, реагирование на ввод пользователя, создание представлений, изменение файла) вступает в действие
  3. Вы деактивируете свой пакет или закрыли Atom
    • Atom сериализует состояние пакета

Вывод

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

Какой пакет вы будете разрабатывать?