Мы - Code Barrel - группа бывших сотрудников атлассиан, усердно работающих над разработкой новых плагинов, которые помогут вашей команде быть максимально продуктивной!
Помня об этом, мы разработали дополнение NPS для JIRA, чтобы ваши команды могли получать отзывы, непосредственно от ваших клиентов, не покидая джира и не используя другие сторонние инструменты.
В этом посте мы хотели бы рассказать о совершенно новом процессе, которые мы использовали для разработки этого плагина, в надежде, что он поможет другим разработчикам плагинов в экосистеме Атлассиан.
Мы не придумали этот подход сами по себе, но мы были частью ядра Атлассиан, где впервые был применен этот подход. И он был так хорош, что мы решили украсть его и использовать.
Этот пост является первой частью серии постов состоящей из 2 частей. В сегодняшнем посте мы подробно рассмотрим конфигурацию сборки. Во второй части мы подробно рассмотрим методы разработки. В этом посте мы рассмотрим много технических деталей.
Дополнения разрабатывались более или менее одинаково в течение нескольких последних лет. Обычно это выглядит так: Данные хранятся на сервере (возможно, что с использованием AO или plugin settings)
Действия webwork1, определяется в atlassian-plugin.xml для предоставления этих данных (возможно, при помощи шаблона Velocity или Soy) Или, возможно, используется более современный ресурс REST для передачи данных от сервера клиенту.
Несколько веб-ресурсов JavaScript, определенных в atlassian-plugin.xml, используют этот ресурс и отображают данные на клиенте.
Проблема этого подхода заключается в том, что он усложняет разработку плагина для сервера и клауда (Atlassian Connect). Некоторые конструкции, такие как действия webwork1, недоступны в облачном плагине. Кроме того, большая часть JavaScript, который был написан традиционно, в значительной степени зависит от встроенного в Jira джаваскрипта - это что-то, что будет не очень хорошо работать в connect add-on (поскольку пользовательский интерфейс отображается на другом сервере и в домене)
Чтобы решить эту проблему, мы ввели несколько руководящих принципов, которые необходимо соблюдать при разработке плагина:
- доступ к данным сервера возможен только через четко определенный REST API.
JSON возвращаемый этим апи должен быть стандартным, чтобы не имело значения, исходит ли он от ресурса REST, определенного на сервере JIRA atlassian-plugin.xml или от ресурса REST на автономном сервере atlassian-connect.
Пользовательский интерфейс будет отображаться исключительно на стороне клиента с помощью автономных компонентов, не зависящих от JavaScript кода, предоставляемого самой JIRA.
В следующих разделах мы подробно рассмотрим как этот подход может быть реализован.
Теоретически, для достижения нашей второй цели связанной с независимыми компонентами пользовательского интерфейса, не требуется никаких специальных инструментов. Сегодня можно написать такие компоненнты на джаваскрипт. Но на практике это невероятно сложно и требует слишком большой дисциплиты от любой команды разработчиков, чтобы это могло быть реализовано.
Вот почему на нужно несколько новых практик и инструментов, чтобы сделать это возможным.
Сначала мы рассмотрим, какую структуру мы определим для нашего проекта. После этого мы рассмотрим конфигурацию и зависимости. Затем, наконец, мы покажем вам наш цикл разработки и как мы интегрируемся с нашей автоматизацией сборки и тестирования.
Чтобы отделить все наши компоненты пользовательского интерфейся от JIRA, нам нужна новая структура каталогов:
https://blog.developer.atlassian.com/wp-content/uploads/dac-import/nps-folder-structure.png
Это стандартный Jira-server плагин,
который использует Atlassian plugin SDK.
Единственное отличие в нем - это папка npscline на вернем уровне.
Эта папка исключена из стандартной jar-сборки maven.
Если же мы посмотрим на эту папку внимательнее,
то мы увидим, что это стандартный пакет npm.
https://blog.developer.atlassian.com/wp-content/uploads/dac-import/nps-client-folder.png
Этот пакет npm может фактически существовать за пределами плагина, но пока, для простоты, мы включаем его непосредственно в исходный репозиторий плагина.
Просмотрев папку, вы заметите, что используются еще несколько инструментов:
- Babel -- JavaScrip компилятор, позволяющий нам использовать функции ES6 (и даже ES7 - objects spreads!) И компилировать их для браузера, который поддерживает Javascript совместимый с ES 5.
- Webpack - bundler модулей джаваскрипта, который позволяет нам реализовать наш пользовательский интерфейс в виде ряда независимых модулей JS, а затем связать их для включения в финальный файл atlassian-plugin.xml в качестве стандартного веб-ресурса.
Webpack также прдлагает ряд инструментов что делает внутренний цикл разработки быстрее!
Рассматривая зависимости в package.json, мы замечаем, что используем еще несколько инструментов:
"dependencies": {
"babel-polyfill": "^6.7.4",
"isomorphic-fetch": "^2.2.1",
"react": "^15.0.0",
"react-dom": "^15.0.0",
"react-redux": "^4.4.5",
"redux": "^3.5.1",
"redux-thunk": "^2.0.1"
//other dependencies not shown for brevity
},
"devDependencies": {
"babel-core": "^6.2.0",
"babel-loader": "^6.2.0",
"babel-plugin-transform-object-rest-spread": "^6.6.5",
"babel-preset-es2015": "^6.2.0",
"babel-preset-react": "^6.2.0",
"fetch-mock": "^4.4.0",
"jsdom": "^7.2.2",
"mocha": "^2.3.4",
"react-addons-test-utils": "^15.0.0",
"react-addons-update": "^15.0.0",
"sinon": "^1.17.2",
"webpack": "^1.12.14",
"webpack-dev-server": "^1.14.1"
//other dependencies not shown for brevity
}
В частности мы используем:
-
React - если вы еще не слышали о React и вы занимаетесь веб-разработкой, то возможно вы выбрали неправильную профессию для себя. Для нас React - это отличный способ создания независимых компонентов.
-
Redux - более простая альтернатива React Flux. Подробнее об этом мы поговорим позже.
-
Polyfills - такие как babel-polyfill или isomorphic-fetch для заполнения пробелов, когда браузеры еще не предоставляют определенные API-интерфейсы JavaScript, а вы хотите чтобы эти функции работали во всех браузерах.
-
Moche - фреймворк модульного тестирования JavaScript, облегчающий написание как синхронных, так и асинхронных тестов. Подробнее о тестировании мы поговорим позже.
-
jsdom - Node js реализация DOM, позволяющая вам запускать модульные тесты JavaScript без использования браузера!
-
Sinon - популярный фреймворк для JavaScript, используемый в наших модульных тестах.
Конфигурация .babelrc очень проста:
{
"presets": ["react", "es2015"],
"plugins": ["transform-object-rest-spread"]
}
Он сообщает Babel, что мы используем React и ES6 и что мы также хотим использовать новый ES7 spread operator. Теперь Babel может скомпилировать его в код JavaScript, совместимый с браузером поддерживающим ES5.
Теперь, когда мы рассмотрели все различные инструменты, как мы можем их объединить чтобы выполнять разработку в удобном для нас цикле?
Все начинается с package.json в папке npsclient.
Этот файл содержит ряд скриптов для упаковки приложения, для последующего включения его в JIRA:
"scripts": {
"clean": "rm -rf node_modules/ lib/* npm-debug.log",
"dev": "./node_modules/.bin/webpack-dev-server --config webpack.config.dev.standalone.js",
"test": "./node_modules/.bin/mocha --compilers js:babel-core/register "./src/**/*test.js" --colors --require test-setup",
"test-maven": "./node_modules/.bin/mocha --compilers js:babel-core/register "./src/**/*test.js" --no-colors --require test-setup --reporter mocha-multi --reporter-options spec=-,mocha-junit-reporter=-",
"dev-jira": "./node_modules/.bin/webpack --config webpack.config.dev.jira.js --watch",
"prod-jira": "./node_modules/.bin/webpack --config webpack.config.prod.jira.js"
},
Важным сценарием для разработки является скрипт dev-jira.
чтобы начать разработку плагина NPS,
мы просто запускаем JIRA в консоли,
введя команду atlas-debug
.
А затем, в еще одной консоли
из папки npsclient
мы запускаем команду
npm run dev-jira
.
Это запускает Webpack с конфигурацией dev и запускает слежение за файлами на предмет изменений, поэтому любые изменения в вашем коде вызовут повторную сборку, чтобы сделать упакованный JavaScript доступным для JIRA.
Давайте посмотрим на конфигурацию Webpack подробно:
require('webpack');
const path = require('path');
module.exports = {
entry: {
npsconfig: ['./src/polyfills.js', './src/entry/config/config.js'],
npsreports: ['./src/polyfills.js', './src/entry/reports/reports.js'],
npssurvey: ['./src/polyfills.js', './src/entry/survey/survey.js'],
},
output: {
path: path.join(__dirname, '../src/main/resources/client'),
filename: '[name].pack.js',
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel',
query: {
cacheDirectory: true,
},
exclude: /node_modules/,
},
],
},
plugins: [],
externals: {
i18nStrings: 'require("jira/nps/i18n")',
},
};
Здесь происходит много вещей:
- Мы определяем три точки входа: этот вебпак говорит о трех пакетах, которые мы хотим создать.
- Babel loader: он просто подключает комплиятор babel к сборке Webpack, чтобы скомпилировать наш JavaScript, используя опции, которые мы ранее определили в .babelrc.
- внешний модуль i18Strings: мы расмотри i18n более подробно во втором посте из этого цикла постов.
Как мы можем подключить это к atlassian-plugin.xml? Просто:
<web-resource key="nps-survey-embeddable-pack">
<resource location="/client/npssurvey.pack.js" name="npssurvey.js" type="download"/>
</web-resource>
Webpack собирает наш внешний JavaScript в папку src/main/resources/client внутри проекта плагина. И в результате мы можем просто объявить вебпак бандл как обычный веб-ресурс в atlassian-plugin.xml
Если мы внесем изменения в модуль npm,
процесс Webpack, который мы запустили ранее,
автоматически перенастроит файл *.pack.js
,
и, поскольку мы работаем в режиме dev
,
Atlassian plugin SDK автоматически загрузит файл с диска
при перезагрузке браузера.
На практике это означает,
что наш цикл разработки состоит из двух действий:
"изменить файл" -> "обновить браузер"
Окончательный файл jar, который мы выпускаем, собран при помощи Maven.
Maven необходим для компиляции
наших исходных файлов Java
и для правильного bundle
плагина
при помощи maven-jira-plugin
Эта сборка теперь должна также
запускать сборку npm Webpack,
чтобы убедиться
что мы bundle
ресурсы JavaScript npm
в jar файл плагина.
Для этого просто добавьте следующее определение maven-exec-plugin definition для build -> pluginx в Maven файле pom.xml :
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.4.0</version>
<executions>
<execution>
<id>exec-npm-install</id>
<phase>initialize</phase>
<configuration>
<workingDirectory>./npsclient</workingDirectory>
<executable>npm</executable>
<arguments>
<argument>install</argument>
</arguments>
<skip>${skipNpm}</skip>
</configuration>
<goals>
<goal>exec</goal>
</goals>
</execution>
<execution>
<id>exec-npm-build</id>
<phase>generate-resources</phase>
<configuration>
<workingDirectory>./npsclient</workingDirectory>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>prod-jira</argument>
</arguments>
<skip>${skipNpm}</skip>
</configuration>
<goals>
<goal>exec</goal>
</goals>
</execution>
<execution>
<id>exec-npm-tests</id>
<phase>test</phase>
<configuration>
<workingDirectory>./npsclient</workingDirectory>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>test-maven</argument>
</arguments>
<environmentVariables>
<MOCHA_FILE>${project.build.directory}/surefire-reports/TEST-mochajsTest.xml</MOCHA_FILE>
</environmentVariables>
<skip>${skipTests}</skip>
</configuration>
<goals>
<goal>exec</goal>
</goals>
</execution>
</executions>
</plugin>
Это делает несколько вещей:
Когда сборка Maven инициализируется,
она запускает npm install
для загрузки всех зависимостей npm.
При генерации исходников
это запускает npm run prod-jira
,
который в свою очередь
запускает немного другую конфигурацию webpack,
что также минимизирует окончательные пакеты JavaScript.
Наконец, мы также интегрируетмся с фазой
тестирования maven,
и запускаем target npm run tests-maven
,
которая вызовет наши модульные тесты JavaScript
и создаст отчет junit,
который может быть проанализирован различными CI-серверами,
такими как Bamboo.
Мы рассмотрим тестирование более подробно во второй части нашей серии постов.
Ранее мы упоминали,
что одним из наших руководящих
принципов
является разработка компонентов,
автономных от JIRA,
Webpack еще раз делает это проще с dev сервером.
Просто запустив скрипт dev,
определенный в package.json
,
через npm run dev
,
мы можем запустить автономную версию модуля npm
и протестировать клиент вне JIRA.
Сервер разработки Webpack
запускается невероятно быстро
и автоматически перезагружает изменения в браузере!
Вот пример компонента конфигурации React,
работающего в автономной версии сервера Webpack:
https://blog.developer.atlassian.com/wp-content/uploads/dac-import/nps-config-standalone.png
Мы многое уже рассказали. Мы рассмотрели, что не так с нынешним подходом к разработке плагинов, обрисовали в общих чертах основные принципы решения описанных проблем И рассказали о том, как структура, инструменты и сборка нашего плагина помогают нам успешно добиваться выполнения этих основных принципов.
Во второй части этой серии статей, мы познакомимся с тем, как все работает: мы подробно рассмотрим, как реализованы некоторые из новых компонентов, как мы их тестируем и интернационализируем, и как мы применяем Redux к более сложным частям нашего приложения! Спасибо за чтение!