diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js b/app/assets/javascripts/blob/blob_ci_yaml.js
deleted file mode 100644
index ec1c018424d12c68bc6d8714037786cad935b1bb..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/blob_ci_yaml.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/* eslint-disable no-param-reassign, comma-dangle */
-/* global Api */
-
-require('./template_selector');
-
-((global) => {
-  class BlobCiYamlSelector extends gl.TemplateSelector {
-    requestFile(query) {
-      return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this));
-    }
-
-    requestFileSuccess(file) {
-      return super.requestFileSuccess(file);
-    }
-  }
-
-  global.BlobCiYamlSelector = BlobCiYamlSelector;
-
-  class BlobCiYamlSelectors {
-    constructor({ editor, $dropdowns } = {}) {
-      this.editor = editor;
-      this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector');
-      this.initSelectors();
-    }
-
-    initSelectors() {
-      const editor = this.editor;
-      this.$dropdowns.each((i, dropdown) => {
-        const $dropdown = $(dropdown);
-        return new BlobCiYamlSelector({
-          editor,
-          pattern: /(.gitlab-ci.yml)/,
-          data: $dropdown.data('data'),
-          wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
-          dropdown: $dropdown
-        });
-      });
-    }
-  }
-
-  global.BlobCiYamlSelectors = BlobCiYamlSelectors;
-})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/blob/blob_dockerfile_selector.js b/app/assets/javascripts/blob/blob_dockerfile_selector.js
deleted file mode 100644
index d4f60cc6ecdf0e81ca441312433def45fbf17ed8..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/blob_dockerfile_selector.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* global Api */
-
-require('./template_selector');
-
-(() => {
-  const global = window.gl || (window.gl = {});
-
-  class BlobDockerfileSelector extends gl.TemplateSelector {
-    requestFile(query) {
-      return Api.dockerfileYml(query.name, this.requestFileSuccess.bind(this));
-    }
-
-    requestFileSuccess(file) {
-      return super.requestFileSuccess(file);
-    }
-  }
-
-  global.BlobDockerfileSelector = BlobDockerfileSelector;
-})();
diff --git a/app/assets/javascripts/blob/blob_dockerfile_selectors.js b/app/assets/javascripts/blob/blob_dockerfile_selectors.js
deleted file mode 100644
index 9cee79fa5d56756846ab5c01cbe46f33d1e887f7..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/blob_dockerfile_selectors.js
+++ /dev/null
@@ -1,27 +0,0 @@
-(() => {
-  const global = window.gl || (window.gl = {});
-
-  class BlobDockerfileSelectors {
-    constructor({ editor, $dropdowns } = {}) {
-      this.editor = editor;
-      this.$dropdowns = $dropdowns || $('.js-dockerfile-selector');
-      this.initSelectors();
-    }
-
-    initSelectors() {
-      const editor = this.editor;
-      this.$dropdowns.each((i, dropdown) => {
-        const $dropdown = $(dropdown);
-        return new gl.BlobDockerfileSelector({
-          editor,
-          pattern: /(Dockerfile)/,
-          data: $dropdown.data('data'),
-          wrapper: $dropdown.closest('.js-dockerfile-selector-wrap'),
-          dropdown: $dropdown,
-        });
-      });
-    }
-  }
-
-  global.BlobDockerfileSelectors = BlobDockerfileSelectors;
-})();
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index 8f6bf162d6e43a0dedabc48590c4cdddb844b184..c9fe23aec75c063e6ffc22a6d847ba60d0073abc 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -1,66 +1,63 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, camelcase, object-shorthand, quotes, comma-dangle, prefer-arrow-callback, no-unused-vars, prefer-template, no-useless-escape, no-alert, max-len */
+/* eslint-disable func-names, object-shorthand, prefer-arrow-callback */
 /* global Dropzone */
 
-(function() {
-  this.BlobFileDropzone = (function() {
-    function BlobFileDropzone(form, method) {
-      var dropzone, form_dropzone, submitButton;
-      form_dropzone = form.find('.dropzone');
-      Dropzone.autoDiscover = false;
-      dropzone = form_dropzone.dropzone({
-        autoDiscover: false,
-        autoProcessQueue: false,
-        url: form.attr('action'),
-        // Rails uses a hidden input field for PUT
-        // http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails
-        method: method,
-        clickable: true,
-        uploadMultiple: false,
-        paramName: "file",
-        maxFilesize: gon.max_file_size || 10,
-        parallelUploads: 1,
-        maxFiles: 1,
-        addRemoveLinks: true,
-        previewsContainer: '.dropzone-previews',
-        headers: {
-          "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
-        },
-        init: function() {
-          this.on('addedfile', function(file) {
-            $('.dropzone-alerts').html('').hide();
-          });
-          this.on('success', function(header, response) {
-            window.location.href = response.filePath;
-          });
-          this.on('maxfilesexceeded', function(file) {
-            this.removeFile(file);
-          });
-          return this.on('sending', function(file, xhr, formData) {
-            formData.append('target_branch', form.find('input[name="target_branch"]').val());
-            formData.append('create_merge_request', form.find('.js-create-merge-request').val());
-            formData.append('commit_message', form.find('.js-commit-message').val());
-          });
-        },
-        // Override behavior of adding error underneath preview
-        error: function(file, errorMessage) {
-          var stripped;
-          stripped = $("<div/>").html(errorMessage).text();
-          $('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show();
+export default class BlobFileDropzone {
+  constructor(form, method) {
+    const formDropzone = form.find('.dropzone');
+    Dropzone.autoDiscover = false;
+
+    const dropzone = formDropzone.dropzone({
+      autoDiscover: false,
+      autoProcessQueue: false,
+      url: form.attr('action'),
+      // Rails uses a hidden input field for PUT
+      // http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails
+      method: method,
+      clickable: true,
+      uploadMultiple: false,
+      paramName: 'file',
+      maxFilesize: gon.max_file_size || 10,
+      parallelUploads: 1,
+      maxFiles: 1,
+      addRemoveLinks: true,
+      previewsContainer: '.dropzone-previews',
+      headers: {
+        'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'),
+      },
+      init: function () {
+        this.on('addedfile', function () {
+          $('.dropzone-alerts').html('').hide();
+        });
+        this.on('success', function (header, response) {
+          window.location.href = response.filePath;
+        });
+        this.on('maxfilesexceeded', function (file) {
           this.removeFile(file);
-        }
-      });
-      submitButton = form.find('#submit-all')[0];
-      submitButton.addEventListener('click', function(e) {
-        e.preventDefault();
-        e.stopPropagation();
-        if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
-          alert("Please select a file");
-        }
-        dropzone[0].dropzone.processQueue();
-        return false;
-      });
-    }
+        });
+        this.on('sending', function (file, xhr, formData) {
+          formData.append('target_branch', form.find('input[name="target_branch"]').val());
+          formData.append('create_merge_request', form.find('.js-create-merge-request').val());
+          formData.append('commit_message', form.find('.js-commit-message').val());
+        });
+      },
+      // Override behavior of adding error underneath preview
+      error: function (file, errorMessage) {
+        const stripped = $('<div/>').html(errorMessage).text();
+        $('.dropzone-alerts').html(`Error uploading file: "${stripped}"`).show();
+        this.removeFile(file);
+      },
+    });
 
-    return BlobFileDropzone;
-  })();
-}).call(window);
+    const submitButton = form.find('#submit-all')[0];
+    submitButton.addEventListener('click', function (e) {
+      e.preventDefault();
+      e.stopPropagation();
+      if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
+        // eslint-disable-next-line no-alert
+        alert('Please select a file');
+      }
+      dropzone[0].dropzone.processQueue();
+      return false;
+    });
+  }
+}
diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js b/app/assets/javascripts/blob/blob_gitignore_selector.js
deleted file mode 100644
index de20eab9cd1c74b37d7a671499bd14e3ea7bc2ad..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/blob_gitignore_selector.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params */
-/* global Api */
-
-require('./template_selector');
-
-(function() {
-  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
-    hasProp = {}.hasOwnProperty;
-
-  this.BlobGitignoreSelector = (function(superClass) {
-    extend(BlobGitignoreSelector, superClass);
-
-    function BlobGitignoreSelector() {
-      return BlobGitignoreSelector.__super__.constructor.apply(this, arguments);
-    }
-
-    BlobGitignoreSelector.prototype.requestFile = function(query) {
-      return Api.gitignoreText(query.name, this.requestFileSuccess.bind(this));
-    };
-
-    return BlobGitignoreSelector;
-  })(gl.TemplateSelector);
-}).call(window);
diff --git a/app/assets/javascripts/blob/blob_gitignore_selectors.js b/app/assets/javascripts/blob/blob_gitignore_selectors.js
deleted file mode 100644
index 43e5c0a56419ff9bf1019b970b8724fa3274f755..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/blob_gitignore_selectors.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-cond-assign, no-sequences, comma-dangle, max-len */
-/* global BlobGitignoreSelector */
-
-(function() {
-  this.BlobGitignoreSelectors = (function() {
-    function BlobGitignoreSelectors(opts) {
-      var ref;
-      this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-gitignore-selector'), this.editor = opts.editor;
-      this.$dropdowns.each((function(_this) {
-        return function(i, dropdown) {
-          var $dropdown;
-          $dropdown = $(dropdown);
-          return new BlobGitignoreSelector({
-            pattern: /(.gitignore)/,
-            data: $dropdown.data('data'),
-            wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
-            dropdown: $dropdown,
-            editor: _this.editor
-          });
-        };
-      })(this));
-    }
-
-    return BlobGitignoreSelectors;
-  })();
-}).call(window);
diff --git a/app/assets/javascripts/blob/blob_license_selector.js b/app/assets/javascripts/blob/blob_license_selector.js
deleted file mode 100644
index b582052a76e5ddf74f8567b2bd561f734dfe9d6f..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/blob_license_selector.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, comma-dangle */
-/* global Api */
-
-require('./template_selector');
-
-(function() {
-  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
-    hasProp = {}.hasOwnProperty;
-
-  this.BlobLicenseSelector = (function(superClass) {
-    extend(BlobLicenseSelector, superClass);
-
-    function BlobLicenseSelector() {
-      return BlobLicenseSelector.__super__.constructor.apply(this, arguments);
-    }
-
-    BlobLicenseSelector.prototype.requestFile = function(query) {
-      var data;
-      data = {
-        project: this.dropdown.data('project'),
-        fullname: this.dropdown.data('fullname')
-      };
-      return Api.licenseText(query.id, data, this.requestFileSuccess.bind(this));
-    };
-
-    return BlobLicenseSelector;
-  })(gl.TemplateSelector);
-}).call(window);
diff --git a/app/assets/javascripts/blob/blob_license_selectors.js b/app/assets/javascripts/blob/blob_license_selectors.js
deleted file mode 100644
index c5067b0feae4736c87e572029af70f62ece8f7d6..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/blob_license_selectors.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable no-unused-vars, no-param-reassign */
-/* global BlobLicenseSelector */
-
-((global) => {
-  class BlobLicenseSelectors {
-    constructor({ $dropdowns, editor }) {
-      this.$dropdowns = $('.js-license-selector');
-      this.editor = editor;
-      this.$dropdowns.each((i, dropdown) => {
-        const $dropdown = $(dropdown);
-        return new BlobLicenseSelector({
-          editor,
-          pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
-          data: $dropdown.data('data'),
-          wrapper: $dropdown.closest('.js-license-selector-wrap'),
-          dropdown: $dropdown,
-        });
-      });
-    }
-  }
-
-  global.BlobLicenseSelectors = BlobLicenseSelectors;
-})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js
deleted file mode 100644
index 7e03ec3b3919762e0e9288972942c25fae1d1f38..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/template_selector.js
+++ /dev/null
@@ -1,101 +0,0 @@
-/* eslint-disable comma-dangle, object-shorthand, func-names, space-before-function-paren, arrow-parens, no-unused-vars, class-methods-use-this, no-var, consistent-return, no-param-reassign, max-len */
-
-((global) => {
-  class TemplateSelector {
-    constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) {
-      this.onClick = this.onClick.bind(this);
-      this.dropdown = dropdown;
-      this.data = data;
-      this.pattern = pattern;
-      this.wrapper = wrapper;
-      this.editor = editor;
-      this.fileEndpoint = fileEndpoint;
-      this.$input = $input || $('#file_name');
-      this.dropdownIcon = $('.fa-chevron-down', this.dropdown);
-      this.buildDropdown();
-      this.bindEvents();
-      this.onFilenameUpdate();
-
-      this.autosizeUpdateEvent = document.createEvent('Event');
-      this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
-    }
-
-    buildDropdown() {
-      return this.dropdown.glDropdown({
-        data: this.data,
-        filterable: true,
-        selectable: true,
-        toggleLabel: this.toggleLabel,
-        search: {
-          fields: ['name']
-        },
-        clicked: this.onClick,
-        text: function(item) {
-          return item.name;
-        }
-      });
-    }
-
-    bindEvents() {
-      return this.$input.on('keyup blur', (e) => this.onFilenameUpdate());
-    }
-
-    toggleLabel(item) {
-      return item.name;
-    }
-
-    onFilenameUpdate() {
-      var filenameMatches;
-      if (!this.$input.length) {
-        return;
-      }
-      filenameMatches = this.pattern.test(this.$input.val().trim());
-      if (!filenameMatches) {
-        this.wrapper.addClass('hidden');
-        return;
-      }
-      return this.wrapper.removeClass('hidden');
-    }
-
-    onClick(item, el, e) {
-      e.preventDefault();
-      return this.requestFile(item);
-    }
-
-    requestFile(item) {
-      // This `requestFile` method is an abstract method that should
-      // be added by all subclasses.
-    }
-
-    // To be implemented on the extending class
-    // e.g.
-    // Api.gitignoreText item.name, @requestFileSuccess.bind(@)
-    requestFileSuccess(file, { skipFocus } = {}) {
-      if (!file) return;
-
-      const oldValue = this.editor.getValue();
-      const newValue = file.content;
-
-      this.editor.setValue(newValue, 1);
-      if (!skipFocus) this.editor.focus();
-
-      if (this.editor instanceof jQuery) {
-        this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
-      }
-    }
-
-    startLoadingSpinner() {
-      this.dropdownIcon
-        .addClass('fa-spinner fa-spin')
-        .removeClass('fa-chevron-down');
-    }
-
-    stopLoadingSpinner() {
-      this.dropdownIcon
-        .addClass('fa-chevron-down')
-        .removeClass('fa-spinner fa-spin');
-    }
-  }
-
-  global.TemplateSelector = TemplateSelector;
-})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js b/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js
new file mode 100644
index 0000000000000000000000000000000000000000..5a5954e7751ebf0aebb9e30dc1e142b2299fa76a
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js
@@ -0,0 +1,9 @@
+/* global Api */
+
+import TemplateSelector from './template_selector';
+
+export default class BlobCiYamlSelector extends TemplateSelector {
+  requestFile(query) {
+    return Api.gitlabCiYml(query.name, (file, config) => this.setEditorContent(file, config));
+  }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js
new file mode 100644
index 0000000000000000000000000000000000000000..7a4d6a42a034a7ae3e6a25f5b66469dd16b66e05
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js
@@ -0,0 +1,23 @@
+/* global Api */
+
+import BlobCiYamlSelector from './blob_ci_yaml_selector';
+
+export default class BlobCiYamlSelectors {
+  constructor({ editor, $dropdowns }) {
+    this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector');
+    this.initSelectors(editor);
+  }
+
+  initSelectors(editor) {
+    this.$dropdowns.each((i, dropdown) => {
+      const $dropdown = $(dropdown);
+      return new BlobCiYamlSelector({
+        editor,
+        pattern: /(.gitlab-ci.yml)/,
+        data: $dropdown.data('data'),
+        wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
+        dropdown: $dropdown,
+      });
+    });
+  }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js b/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js
new file mode 100644
index 0000000000000000000000000000000000000000..19f8820a0cb2c97d6103956df9d996c4d3a0e3e1
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js
@@ -0,0 +1,9 @@
+/* global Api */
+
+import TemplateSelector from './template_selector';
+
+export default class BlobDockerfileSelector extends TemplateSelector {
+  requestFile(query) {
+    return Api.dockerfileYml(query.name, (file, config) => this.setEditorContent(file, config));
+  }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js
new file mode 100644
index 0000000000000000000000000000000000000000..da067035b435f816cefa042e9db58bddded66b24
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js
@@ -0,0 +1,23 @@
+import BlobDockerfileSelector from './blob_dockerfile_selector';
+
+export default class BlobDockerfileSelectors {
+  constructor({ editor, $dropdowns }) {
+    this.editor = editor;
+    this.$dropdowns = $dropdowns || $('.js-dockerfile-selector');
+    this.initSelectors();
+  }
+
+  initSelectors() {
+    const editor = this.editor;
+    this.$dropdowns.each((i, dropdown) => {
+      const $dropdown = $(dropdown);
+      return new BlobDockerfileSelector({
+        editor,
+        pattern: /(Dockerfile)/,
+        data: $dropdown.data('data'),
+        wrapper: $dropdown.closest('.js-dockerfile-selector-wrap'),
+        dropdown: $dropdown,
+      });
+    });
+  }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js b/app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js
new file mode 100644
index 0000000000000000000000000000000000000000..0b6b02fc2b33bc57a1e31ccd616474a42ce7533b
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js
@@ -0,0 +1,9 @@
+/* global Api */
+
+import TemplateSelector from './template_selector';
+
+export default class BlobGitignoreSelector extends TemplateSelector {
+  requestFile(query) {
+    return Api.gitignoreText(query.name, (file, config) => this.setEditorContent(file, config));
+  }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js
new file mode 100644
index 0000000000000000000000000000000000000000..dc485d97677d3d400df47d4d7282b212f7635a79
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js
@@ -0,0 +1,23 @@
+import BlobGitignoreSelector from './blob_gitignore_selector';
+
+export default class BlobGitignoreSelectors {
+  constructor({ editor, $dropdowns }) {
+    this.$dropdowns = $dropdowns || $('.js-gitignore-selector');
+    this.editor = editor;
+    this.initSelectors();
+  }
+
+  initSelectors() {
+    this.$dropdowns.each((i, dropdown) => {
+      const $dropdown = $(dropdown);
+
+      return new BlobGitignoreSelector({
+        pattern: /(.gitignore)/,
+        data: $dropdown.data('data'),
+        wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
+        dropdown: $dropdown,
+        editor: this.editor,
+      });
+    });
+  }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_license_selector.js b/app/assets/javascripts/blob/template_selectors/blob_license_selector.js
new file mode 100644
index 0000000000000000000000000000000000000000..e9cb31cc2dc1dcca2962f0621dd92ac2144df7ae
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/blob_license_selector.js
@@ -0,0 +1,13 @@
+/* global Api */
+
+import TemplateSelector from './template_selector';
+
+export default class BlobLicenseSelector extends TemplateSelector {
+  requestFile(query) {
+    const data = {
+      project: this.dropdown.data('project'),
+      fullname: this.dropdown.data('fullname'),
+    };
+    return Api.licenseText(query.id, data, (file, config) => this.setEditorContent(file, config));
+  }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/blob_license_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_license_selectors.js
new file mode 100644
index 0000000000000000000000000000000000000000..a44f4f78b2de4e26d5d9d134c23abdd8aee002d4
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/blob_license_selectors.js
@@ -0,0 +1,24 @@
+/* eslint-disable no-unused-vars, no-param-reassign */
+
+import BlobLicenseSelector from './blob_license_selector';
+
+export default class BlobLicenseSelectors {
+  constructor({ $dropdowns, editor }) {
+    this.$dropdowns = $dropdowns || $('.js-license-selector');
+    this.initSelectors(editor);
+  }
+
+  initSelectors(editor) {
+    this.$dropdowns.each((i, dropdown) => {
+      const $dropdown = $(dropdown);
+
+      return new BlobLicenseSelector({
+        editor,
+        pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
+        data: $dropdown.data('data'),
+        wrapper: $dropdown.closest('.js-license-selector-wrap'),
+        dropdown: $dropdown,
+      });
+    });
+  }
+}
diff --git a/app/assets/javascripts/blob/template_selectors/template_selector.js b/app/assets/javascripts/blob/template_selectors/template_selector.js
new file mode 100644
index 0000000000000000000000000000000000000000..d7c1c32efbd40336ea881746c25c9d694cdd48a5
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selectors/template_selector.js
@@ -0,0 +1,92 @@
+/* eslint-disable class-methods-use-this, no-unused-vars */
+
+export default class TemplateSelector {
+  constructor({ dropdown, data, pattern, wrapper, editor, $input } = {}) {
+    this.pattern = pattern;
+    this.editor = editor;
+    this.dropdown = dropdown;
+    this.$dropdownContainer = wrapper;
+    this.$filenameInput = $input || $('#file_name');
+    this.$dropdownIcon = $('.fa-chevron-down', dropdown);
+
+    this.initDropdown(dropdown, data);
+    this.listenForFilenameInput();
+    this.renderMatchedDropdown();
+    this.initAutosizeUpdateEvent();
+  }
+
+  initDropdown(dropdown, data) {
+    return $(dropdown).glDropdown({
+      data,
+      filterable: true,
+      selectable: true,
+      toggleLabel: item => item.name,
+      search: {
+        fields: ['name'],
+      },
+      clicked: (item, el, e) => this.fetchFileTemplate(item, el, e),
+      text: item => item.name,
+    });
+  }
+
+  initAutosizeUpdateEvent() {
+    this.autosizeUpdateEvent = document.createEvent('Event');
+    this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
+  }
+
+  listenForFilenameInput() {
+    return this.$filenameInput.on('keyup blur', e => this.renderMatchedDropdown(e));
+  }
+
+  renderMatchedDropdown() {
+    if (!this.$filenameInput.length) {
+      return null;
+    }
+
+    const filenameMatches = this.pattern.test(this.$filenameInput.val().trim());
+
+    if (!filenameMatches) {
+      return this.$dropdownContainer.addClass('hidden');
+    }
+    return this.$dropdownContainer.removeClass('hidden');
+  }
+
+  fetchFileTemplate(item, el, e) {
+    e.preventDefault();
+    return this.requestFile(item);
+  }
+
+  requestFile(item) {
+    // This `requestFile` method is an abstract method that should
+    // be added by all subclasses.
+  }
+
+  // To be implemented on the extending class
+  // e.g. Api.gitlabCiYml(query.name, file => this.setEditorContent(file));
+
+  setEditorContent(file, { skipFocus } = {}) {
+    if (!file) return;
+
+    const newValue = file.content;
+
+    this.editor.setValue(newValue, 1);
+
+    if (!skipFocus) this.editor.focus();
+
+    if (this.editor instanceof jQuery) {
+      this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
+    }
+  }
+
+  startLoadingSpinner() {
+    this.$dropdownIcon
+      .addClass('fa-spinner fa-spin')
+      .removeClass('fa-chevron-down');
+  }
+
+  stopLoadingSpinner() {
+    this.$dropdownIcon
+      .addClass('fa-chevron-down')
+      .removeClass('fa-spinner fa-spin');
+  }
+}
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
new file mode 100644
index 0000000000000000000000000000000000000000..c5deccf631e69c98fa88bb9108096497413d3e7c
--- /dev/null
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -0,0 +1,32 @@
+/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, max-len */
+/* global EditBlob */
+/* global NewCommitForm */
+
+import EditBlob from './edit_blob';
+import BlobFileDropzone from '../blob/blob_file_dropzone';
+
+$(() => {
+  const editBlobForm = $('.js-edit-blob-form');
+  const uploadBlobForm = $('.js-upload-blob-form');
+
+  if (editBlobForm.length) {
+    const urlRoot = editBlobForm.data('relative-url-root');
+    const assetsPath = editBlobForm.data('assets-prefix');
+    const blobLanguage = editBlobForm.data('blob-language');
+
+    new EditBlob(`${urlRoot}${assetsPath}`, blobLanguage);
+    new NewCommitForm(editBlobForm);
+  }
+
+  if (uploadBlobForm.length) {
+    const method = uploadBlobForm.data('method');
+
+    new BlobFileDropzone(uploadBlobForm, method);
+    new NewCommitForm(uploadBlobForm);
+
+    window.gl.utils.disableButtonIfEmptyField(
+      uploadBlobForm.find('.js-commit-message'),
+      '.btn-upload-file',
+    );
+  }
+});
diff --git a/app/assets/javascripts/blob_edit/blob_edit_bundle.js b/app/assets/javascripts/blob_edit/blob_edit_bundle.js
deleted file mode 100644
index 0436bbb0eaf65988364a62ac64d124bebdc81c4b..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob_edit/blob_edit_bundle.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, max-len */
-/* global EditBlob */
-/* global NewCommitForm */
-
-require('./edit_blob');
-
-(function() {
-  $(function() {
-    var url = $(".js-edit-blob-form").data("relative-url-root");
-    url += $(".js-edit-blob-form").data("assets-prefix");
-
-    var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language'));
-    new NewCommitForm($('.js-edit-blob-form'));
-  });
-}).call(window);
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index a1127b9e30e7460fe89d2475cfb829eb9c172f92..d3560d5df3b8354bd7f65b3b8552584db2b20bbf 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -1,88 +1,99 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, no-param-reassign, quotes, prefer-template, no-new, comma-dangle, one-var, one-var-declaration-per-line, prefer-arrow-callback, no-else-return, no-unused-vars, max-len */
 /* global ace */
-/* global BlobGitignoreSelectors */
-
-(function() {
-  var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
-
-  this.EditBlob = (function() {
-    function EditBlob(assets_path, ace_mode) {
-      if (ace_mode == null) {
-        ace_mode = null;
-      }
-      this.editModeLinkClickHandler = bind(this.editModeLinkClickHandler, this);
-      ace.config.set("modePath", assets_path + "/ace");
-      ace.config.loadModule("ace/ext/searchbox");
-      this.editor = ace.edit("editor");
-      this.editor.focus();
-      if (ace_mode) {
-        this.editor.getSession().setMode("ace/mode/" + ace_mode);
-      }
-      $('form').submit((function(_this) {
-        return function() {
-          return $("#file-content").val(_this.editor.getValue());
-        };
-      // Before a form submission, move the content from the Ace editor into the
-      // submitted textarea
-      })(this));
-      this.initModePanesAndLinks();
-      this.initSoftWrap();
-      new gl.BlobLicenseSelectors({
-        editor: this.editor
-      });
+
+import BlobLicenseSelectors from '../blob/template_selectors/blob_license_selectors';
+import BlobGitignoreSelectors from '../blob/template_selectors/blob_gitignore_selectors';
+import BlobCiYamlSelectors from '../blob/template_selectors/blob_ci_yaml_selectors';
+import BlobDockerfileSelectors from '../blob/template_selectors/blob_dockerfile_selectors';
+
+export default class EditBlob {
+  constructor(assetsPath, aceMode) {
+    this.configureAceEditor(aceMode, assetsPath);
+    this.prepFileContentForSubmit();
+    this.initModePanesAndLinks();
+    this.initSoftWrap();
+    this.initFileSelectors();
+  }
+
+  configureAceEditor(aceMode, assetsPath) {
+    ace.config.set('modePath', `${assetsPath}/ace`);
+    ace.config.loadModule('ace/ext/searchbox');
+
+    this.editor = ace.edit('editor');
+    this.editor.focus();
+
+    if (aceMode) {
+      this.editor.getSession().setMode(`ace/mode/${aceMode}`);
+    }
+  }
+
+  prepFileContentForSubmit() {
+    $('form').submit(() => {
+      $('#file-content').val(this.editor.getValue());
+    });
+  }
+
+  initFileSelectors() {
+    this.blobTemplateSelectors = [
+      new BlobLicenseSelectors({
+        editor: this.editor,
+      }),
       new BlobGitignoreSelectors({
-        editor: this.editor
-      });
-      new gl.BlobCiYamlSelectors({
-        editor: this.editor
-      });
-      new gl.BlobDockerfileSelectors({
-        editor: this.editor
+        editor: this.editor,
+      }),
+      new BlobCiYamlSelectors({
+        editor: this.editor,
+      }),
+      new BlobDockerfileSelectors({
+        editor: this.editor,
+      }),
+    ];
+  }
+
+  initModePanesAndLinks() {
+    this.$editModePanes = $('.js-edit-mode-pane');
+    this.$editModeLinks = $('.js-edit-mode a');
+    this.$editModeLinks.on('click', e => this.editModeLinkClickHandler(e));
+  }
+
+  editModeLinkClickHandler(e) {
+    e.preventDefault();
+
+    const currentLink = $(e.target);
+    const paneId = currentLink.attr('href');
+    const currentPane = this.$editModePanes.filter(paneId);
+
+    this.$editModeLinks.parent().removeClass('active hover');
+
+    currentLink.parent().addClass('active hover');
+
+    this.$editModePanes.hide();
+
+    currentPane.fadeIn(200);
+
+    if (paneId === '#preview') {
+      this.$toggleButton.hide();
+      return $.post(currentLink.data('preview-url'), {
+        content: this.editor.getValue(),
+      }, (response) => {
+        currentPane.empty().append(response);
+        return currentPane.renderGFM();
       });
     }
 
-    EditBlob.prototype.initModePanesAndLinks = function() {
-      this.$editModePanes = $(".js-edit-mode-pane");
-      this.$editModeLinks = $(".js-edit-mode a");
-      return this.$editModeLinks.click(this.editModeLinkClickHandler);
-    };
-
-    EditBlob.prototype.editModeLinkClickHandler = function(event) {
-      var currentLink, currentPane, paneId;
-      event.preventDefault();
-      currentLink = $(event.target);
-      paneId = currentLink.attr("href");
-      currentPane = this.$editModePanes.filter(paneId);
-      this.$editModeLinks.parent().removeClass("active hover");
-      currentLink.parent().addClass("active hover");
-      this.$editModePanes.hide();
-      currentPane.fadeIn(200);
-      if (paneId === "#preview") {
-        this.$toggleButton.hide();
-        return $.post(currentLink.data("preview-url"), {
-          content: this.editor.getValue()
-        }, function(response) {
-          currentPane.empty().append(response);
-          return currentPane.renderGFM();
-        });
-      } else {
-        this.$toggleButton.show();
-        return this.editor.focus();
-      }
-    };
-
-    EditBlob.prototype.initSoftWrap = function() {
-      this.isSoftWrapped = false;
-      this.$toggleButton = $('.soft-wrap-toggle');
-      this.$toggleButton.on('click', this.toggleSoftWrap.bind(this));
-    };
-
-    EditBlob.prototype.toggleSoftWrap = function(e) {
-      this.isSoftWrapped = !this.isSoftWrapped;
-      this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
-      this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
-    };
-
-    return EditBlob;
-  })();
-}).call(window);
+    this.$toggleButton.show();
+
+    return this.editor.focus();
+  }
+
+  initSoftWrap() {
+    this.isSoftWrapped = false;
+    this.$toggleButton = $('.soft-wrap-toggle');
+    this.$toggleButton.on('click', () => this.toggleSoftWrap());
+  }
+
+  toggleSoftWrap() {
+    this.isSoftWrapped = !this.isSoftWrapped;
+    this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
+    this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
+  }
+}
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index b1ca0dc091d6a51e590c45283c6953515c159f3f..9007d661d01dc9b2aa8157dad78a0e2af08d8799 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -47,15 +47,6 @@ import { installGlEmojiElement } from './behaviors/gl_emoji';
 installGlEmojiElement();
 
 // blob
-import './blob/blob_ci_yaml';
-import './blob/blob_dockerfile_selector';
-import './blob/blob_dockerfile_selectors';
-import './blob/blob_file_dropzone';
-import './blob/blob_gitignore_selector';
-import './blob/blob_gitignore_selectors';
-import './blob/blob_license_selector';
-import './blob/blob_license_selectors';
-import './blob/template_selector';
 import './blob/create_branch_dropdown';
 import './blob/target_branch_dropdown';
 
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js b/app/assets/javascripts/templates/issuable_template_selector.js
index e9e9aafd71ad13954318a24c8ffadc1b09cd471b..32067ed1feec54ddbe8114c62e7c9cb2394aba4e 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js
+++ b/app/assets/javascripts/templates/issuable_template_selector.js
@@ -1,15 +1,15 @@
 /* eslint-disable comma-dangle, max-len, no-useless-return, no-param-reassign, max-len */
 /* global Api */
 
-require('../blob/template_selector');
+import TemplateSelector from '../blob/template_selectors/template_selector';
 
 ((global) => {
-  class IssuableTemplateSelector extends gl.TemplateSelector {
+  class IssuableTemplateSelector extends TemplateSelector {
     constructor(...args) {
       super(...args);
       this.projectPath = this.dropdown.data('project-path');
       this.namespacePath = this.dropdown.data('namespace-path');
-      this.issuableType = this.wrapper.data('issuable-type');
+      this.issuableType = this.$dropdownContainer.data('issuable-type');
       this.titleInput = $(`#${this.issuableType}_title`);
 
       const initialQuery = {
@@ -41,16 +41,16 @@ require('../blob/template_selector');
     }
 
     setInputValueToTemplateContent() {
-      // `this.requestFileSuccess` sets the value of the description input field
+      // `this.setEditorContent` sets the value of the description input field
       // to the content of the template selected.
       if (this.titleInput.val() === '') {
         // If the title has not yet been set, focus the title input and
         // skip focusing the description input by setting `true` as the
-        // `skipFocus` option to `requestFileSuccess`.
-        this.requestFileSuccess(this.currentTemplate, { skipFocus: true });
+        // `skipFocus` option to `setEditorContent`.
+        this.setEditorContent(this.currentTemplate, { skipFocus: true });
         this.titleInput.focus();
       } else {
-        this.requestFileSuccess(this.currentTemplate, { skipFocus: false });
+        this.setEditorContent(this.currentTemplate, { skipFocus: false });
       }
       return;
     }
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index 4924c73cf8ea270c1513b49aa7e5c25176a313c8..e14885f264b615b661759cafd4e25d14242f36dc 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -5,7 +5,7 @@
         %a.close{ href: "#", "data-dismiss" => "modal" } ×
         %h3.page-title= title
       .modal-body
-        = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal' do
+        = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal', data: { method: method } do
           .dropzone
             .dropzone-previews.blob-upload-dropzone-previews
               %p.dz-message.light
@@ -24,8 +24,5 @@
               .inline.prepend-left-10
                 = commit_in_fork_help
 
-
-:javascript
-  gl.utils.disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file');
-  new BlobFileDropzone($('.js-upload-blob-form'), '#{method}');
-  new NewCommitForm($('.js-upload-blob-form'))
+- content_for :page_specific_javascripts do
+  = page_specific_javascript_bundle_tag('blob')
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index 3bcddcb37f1aa58e4a0d4dd4232e265d3a21422f..afe0b5dba454fdb7680666151b8ed2f16b1a64c5 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -2,7 +2,7 @@
 - page_title "Edit", @blob.path, @ref
 - content_for :page_specific_javascripts do
   = page_specific_javascript_tag('lib/ace.js')
-  = page_specific_javascript_bundle_tag('blob_edit')
+  = page_specific_javascript_bundle_tag('blob')
 = render "projects/commits/head"
 
 %div{ class: container_class }
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index e0ce8cc9601cd08f3dd1a89960ff773c3a8672a5..4c449e040eeb4b9a2fa2e7fb24a0a88f5b12a84e 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -1,7 +1,7 @@
 - page_title "New File", @path.presence, @ref
 - content_for :page_specific_javascripts do
   = page_specific_javascript_tag('lib/ace.js')
-  = page_specific_javascript_bundle_tag('blob_edit')
+  = page_specific_javascript_bundle_tag('blob')
 
 %h3.page-title
   New File
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 3cf94b9b43559ea92eba98befaabe29bc719291d..0859c8416c86d48df13ccde8a0798a8e575106d6 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -21,7 +21,7 @@ var config = {
     common_vue:           ['vue', './vue_shared/common_vue.js'],
     common_d3:            ['d3'],
     main:                 './main.js',
-    blob_edit:            './blob_edit/blob_edit_bundle.js',
+    blob:                 './blob_edit/blob_bundle.js',
     boards:               './boards/boards_bundle.js',
     simulate_drag:        './test_utils/simulate_drag.js',
     cycle_analytics:      './cycle_analytics/cycle_analytics_bundle.js',
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 15465588223c362a741fce860c8f9a3cb97b760b..d658f680f97b151355d0945a64b7bb5355a36ba3 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -35,7 +35,8 @@ testsContext.keys().forEach(function (path) {
 if (process.env.BABEL_ENV === 'coverage') {
   // exempt these files from the coverage report
   const troubleMakers = [
-    './blob_edit/blob_edit_bundle.js',
+    './blob_edit/blob_bundle.js',
+    './boards/boards_bundle.js',
     './cycle_analytics/components/stage_plan_component.js',
     './cycle_analytics/components/stage_staging_component.js',
     './cycle_analytics/components/stage_test_component.js',