ES6 is the new DHTML
Anyone remember DHTML? I remember it being awesome. Suddenly we were able to do all kinds of exciting things like displaying fireworks when our mouse is clicked, creating our own tooltips with funky styling and scrolling stuff automatically. And all in the global scope! Those were the good old days ☺
Now that a lot of ES6 is supported either by browsers themselves, or Babel/Traceur, we at Adaptavist are eager to embrace the cool new features that the ES6 standard provides, which are somewhat more nerdy than the examples above, but no less exciting!
- Modules
- Object and array destructuring
- Constants
- Arrow functions
- String interpolation
- Spread operator
- Default parameter values
Our ES6 stack
At the moment our typical technology stack for including ES6 in an Atlassian add-on looks a bit like this: the frontend-maven-plugin allows us to download/install Node and NPM and run Grunt to compile/convert/batch our ES6 files into JS using Babel, Uglify and any other dependencies we need. We're currently using Karma to run unit tests.
Both JIRA and Confluence use almond.js as their JS module loader, so we use the following Babel config (or something similar) in our Gruntfile.js to turn our ES6 files into well named JS modules.
grunt.initConfig({
// Convert ES6 to ES5
babel: {
options: {
sourceMap: false, // or inline
moduleIds: true,
presets: ["es2015"],
getModuleId: function (name) {
return name.replace(/^.*src\/main\/frontend\/web-resource\/(.+)$/, '$1')
},
// If I remember right, this is mainly to allow us to put a "/" at the start of
// module names so our IDE resolves the module names to the correct files
resolveModuleSource: function(source) {
return source.replace(/^\//, ""); // Strip the leading / from module names
}
},
main: {
options: {
sourceRoot: 'src/main/frontend/web-resource'
},
files: [
{
expand: true,
cwd: 'src/main/frontend',
src: ['**/*.es6'],
dest: 'target/generated-resources',
ext: '.js',
extDot: 'first'
}
]
}
}
});
Module names
The configuration above generates module names based on the relative file path of our ES6 files. So if we have a file at src/main/frontend/web-resource/example-app/lib/utils.es6
the contents of that file will be converted into a file in target/generated-resources/web-resource/example-app/lib/utils.js
with a module name of example-app/lib/utils
.
Before:
import $ from "jquery";
import flag from "aui/flag"; // Check me out, using a module provided by AUI!
export function somethingGreat(){};
After:
define("example-app/lib/utils", ["exports", "jquery", "aui/flag"], function(exports, _jquery, _auiFlag) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.somethingGreat = somethingGreat;
//... removed for brevity
});
Build lifecycle
To speed up development we use grunt-contrib-watch to automagically recompile our ES6 files when they change.
With the following Gruntfile.js configuration all we need to do when we make changes to our source files is to refresh the page in the browser!
grunt.initConfig({
watch: {
modules: {
files: [
'src/main/frontend/**/*.es6'
],
tasks: ['build']
}
},
// the babel config from above goes here...
});
// This creates a new task called 'build' that will run the 'babel' task and
// the 'concat' task defined elsewhere.
grunt.registerTask('build', [
'babel',
'concat',
//'uglify'
]);
Typically, when I kick off the atlas-debug
command for a project in a terminal I'll open up a new terminal tab/session and run grunt build watch
as well.
We also have a useful grunt clean
command for removing only the generated Javascript files:
grunt.registerTask('clean', function() {
grunt.file.delete("target/generated-resources");
grunt.file.delete("target/generated-test-resources");
});
Source structure
When we're developing JS-heavy Atlassian add-ons it can take a lot of time having to maintain an up-to-date web-resource configuration in the atlassian-plugin.xml.
To reduce the manual XML editing overhead we developed a dynamic web modules add-on that will automatically add web-resources to your plugin without you needed to specify each one.
Things to note
A couple of the things that took me a little while to wrap my head around are:
Modules don't auto-run. Module code won't execute unless we want it to. They are not IIFEs. We need to a) load the modules we want into our application, and b) call an exported function of those modules to start the execution. Often we have a vanila JS file that uses the almond.js library to load the relevant modules for our application, and then calls some of the exported functions of those modules to 'start' them.
// setup.js require(['example-app/setup', 'adaptavist/analytics'], function(setup, analytics){ setup.bindEvents(); setup.renderApp(); analytics.register(); });
Modules are singletons, so any state within a module is shared whenever that module is imported. If we want to create different 'instances' of whatever our module represents, we need to ensure our module exports some kind of constructor which encapsulates the state.
- 09 Oct 2018 » A strange bug on AWS Lambda
- 17 Jan 2018 » How to run Karma tests in browsers in Docker
- 07 Dec 2017 » Switching from Javascript to Typescript
- 30 Oct 2017 » Fun with React event handlers
- 17 Jul 2017 » Switching from Groovy to Java
- 24 May 2017 » Useful Git Aliases
- 27 Mar 2017 » Practical Ratpack Promises
- 03 Nov 2016 » Custom Content in Forms for Confluence Connect
- 04 Oct 2016 » Checking user permissions from REST calls
- 30 Sep 2016 » Using the reflection API in Confluence
- 28 Sep 2016 » Creating a custom Confluence Blueprint
- 06 Sep 2016 » ReactJS in Forms for Confluence Connect
- 25 Apr 2016 » Migrating to ES6 in Atlassian Add-ons
- 17 Mar 2016 » All kinds of things I learnt trying to performance test against Fisheye/Crucible
- 24 Dec 2015 » Adaptavist’s Holiday Gift of Atlassian Deployment Automation
- 17 Dec 2015 » Getting a Custom Field value safely
- 07 Dec 2015 » Putting Google Analytics to work with plugins for Confluence
- 02 Dec 2015 » Devoxx Voting, A retrospective
- 25 Nov 2015 » Some things I've learnt about SingleSelect
- 15 Oct 2015 » Using SOY for JIRA actions
- 26 Sep 2015 » Object Reflection in Groovy
- 22 Sep 2015 » Introducing Adaptavist Labs