Pro Yearly is on sale from $80 to $50! »

EmberConf 2017 – Confessions of an Ember Addon Author

EmberConf 2017 – Confessions of an Ember Addon Author

This talk was presented at EmberConf 2017.

Video: https://www.youtube.com/watch?v=ln_DvmQsvis

Addons are one of the best things about the Ember eco-system. With one command, you can opt into using a well tested addon that does some of the heavy-lifting for you when building complex applications. The next best thing is that sharing your solution for solving problems is very simple; it's not a big leap going from Ember developer to addon author!

A healthy addon eco-system is one of the key strengths of Ember, and in this talk we'll discover some best practices, tips and tricks and other exciting confessions from a self-confessed addon addict!

C8fccffc013096c4b465b50c284a5208?s=128

Lauren Tan

March 29, 2017
Tweet

Transcript

  1. EmberConf 2017 Confessions of an Ember Addon Author Confessions of

    an Ember Addon Author PRESENTED BY Lauren Tan sugarpirate_ poteto Cinemagraph by /u/orbojunglist
  2. EmberConf 2017 Confessions of an Ember Addon Author Lauren Tan

    sugarpirate_ poteto
  3. EmberConf 2017 Confessions of an Ember Addon Author

  4. EmberConf 2017 Confessions of an Ember Addon Author

  5. EmberConf 2017 Confessions of an Ember Addon Author

  6. EmberConf 2017 Confessions of an Ember Addon Author

  7. EmberConf 2017 Confessions of an Ember Addon Author

  8. EmberConf 2017 Confessions of an Ember Addon Author

  9. EmberConf 2017 Confessions of an Ember Addon Author

  10. EmberConf 2017 Confessions of an Ember Addon Author

  11. EmberConf 2017 Confessions of an Ember Addon Author What makes

    an addon good?
  12. EmberConf 2017 Confessions of an Ember Addon Author

  13. EmberConf 2017 Confessions of an Ember Addon Author

  14. EmberConf 2017 Confessions of an Ember Addon Author

  15. EmberConf 2017 Confessions of an Ember Addon Author

  16. EmberConf 2017 Confessions of an Ember Addon Author

  17. EmberConf 2017 Confessions of an Ember Addon Author

  18. EmberConf 2017 Confessions of an Ember Addon Author

  19. EmberConf 2017 Confessions of an Ember Addon Author

  20. EmberConf 2017 Confessions of an Ember Addon Author Documentation Driven

    Development
  21. EmberConf 2017 Confessions of an Ember Addon Author

  22. EmberConf 2017 Confessions of an Ember Addon Author If it's

    not documented, it doesn't exist YES, IT'S TRUE
  23. EmberConf 2017 Confessions of an Ember Addon Author defmodule KVServer.Command

    do @doc ~S""" Parses the given `line` into a command. "## Examples iex> KVServer.Command.parse "CREATE shopping\r\n" {:ok, {:create, "shopping"}} """ def parse(line) do :not_implemented end end
  24. EmberConf 2017 Confessions of an Ember Addon Author

  25. EmberConf 2017 Confessions of an Ember Addon Author

  26. EmberConf 2017 Confessions of an Ember Addon Author

  27. EmberConf 2017 Confessions of an Ember Addon Author If you

    build it, they will might (not) come YOU'VE BEEN LIED TO
  28. EmberConf 2017 Confessions of an Ember Addon Author Any successful

    project requires two things Nathan Marz, 2014
  29. EmberConf 2017 Confessions of an Ember Addon Author It solves

    a useful problem TWO THINGS
  30. EmberConf 2017 Confessions of an Ember Addon Author People are

    convinced your solution is the best for their problem TWO THINGS
  31. EmberConf 2017 Confessions of an Ember Addon Author https://youtu.be/_0T5OSSzxms

  32. EmberConf 2017 Confessions of an Ember Addon Author

  33. EmberConf 2017 Confessions of an Ember Addon Author ȗ 

  34. EmberConf 2017 Confessions of an Ember Addon Author

  35. EmberConf 2017 Confessions of an Ember Addon Author

  36. EmberConf 2017 Confessions of an Ember Addon Author

  37. EmberConf 2017 Confessions of an Ember Addon Author I. Addon

    Anatomy II. One Weird Trick III. Open Sourcery
  38. EmberConf 2017 Confessions of an Ember Addon Author I. Addon

    Anatomy II. One Weird Trick III. Open Sourcery
  39. EmberConf 2017 Confessions of an Ember Addon Author Addon Structure

    Cinemagraph by /u/Hyperoperation
  40. EmberConf 2017 Confessions of an Ember Addon Author Ember Land

  41. EmberConf 2017 Confessions of an Ember Addon Author Node Land

  42. EmberConf 2017 Confessions of an Ember Addon Author addon ├──

    -private │ ├── closure-action.js │ ├── create-multi-array-helper.js │ └── create-needle-haystack-helper.js ├── helpers │ ├── append.js │ ├── ""... │ └── without.js ├── index.js └── utils ├── comparison.js ├── get-index.js ├── includes.js ├── is-equal.js ├── is-object.js └── is-promise.js Internals
  43. EmberConf 2017 Confessions of an Ember Addon Author export {

    default, append } from 'ember-composable-helpers/helpers/append';
  44. EmberConf 2017 Confessions of an Ember Addon Author app └──

    helpers ├── append.js ├── ""... └── without.js Merged into consuming app
  45. EmberConf 2017 Confessions of an Ember Addon Author "ember-addon": {

    "configPath": "tests/dummy/config", "after": [ "ember-changeset" ] }
  46. EmberConf 2017 Confessions of an Ember Addon Author Internals are

    not really private A WORD OF CAUTION import isPromise from 'ember-changeset/utils/is-promise'; import ACTION from 'ember-composable-helpers/-private/closure-action';
  47. EmberConf 2017 Confessions of an Ember Addon Author

  48. EmberConf 2017 Confessions of an Ember Addon Author

  49. EmberConf 2017 Confessions of an Ember Addon Author export default

    function validate<%= classifiedModuleName %>("/* options = {} "*/) { return ("/* key, newValue, oldValue, changes, content "*/) "=> { return true; }; }
  50. EmberConf 2017 Confessions of an Ember Addon Author dasherizedPackageName classifiedPackageName

    dasherizedModuleName classifiedModuleName camelizedModuleName https:"//ember-cli.com/api/classes/Blueprint.html
  51. EmberConf 2017 Confessions of an Ember Addon Author config blueprintsPath

    includedCommands serverMiddleware testemMiddleware postBuild preBuild outputReady buildError included shouldIncludeChildAddon setupPreprocessorRegistry preprocessTree postprocessTree lintTree contentFor treeFor Addon hooks are powerful WORKING IN NODE LAND https://ember-cli.com/api/classes/Addon.html Cinemagraph by /u/orbojunglist
  52. EmberConf 2017 Confessions of an Ember Addon Author

  53. EmberConf 2017 Confessions of an Ember Addon Author I. setupPreprocessorRegistry

    II. treeForAddon
  54. EmberConf 2017 Confessions of an Ember Addon Author SETUP PREPROCESSOR

    REGISTRY Strip test selectors from templates https://ember-cli.com/api/classes/Addon.html#method_setupPreprocessorRegistry
  55. EmberConf 2017 Confessions of an Ember Addon Author <button data-test-selector="my-button"

    onclick={{action "doSomething"}}> Click me "</button>
  56. EmberConf 2017 Confessions of an Ember Addon Author TREE FOR

    ADDON Remove addon from build https://ember-cli.com/api/classes/Addon.html#method_treeForAddon
  57. EmberConf 2017 Confessions of an Ember Addon Author

  58. EmberConf 2017 Confessions of an Ember Addon Author Remove unused

    helpers from build TREE FOR ADDON
  59. EmberConf 2017 Confessions of an Ember Addon Author treeForAddon: function()

    { "// see: https:"//github.com/ember-cli/ember-cli/issues/4463 var tree = this._super.treeForAddon.apply(this, arguments); return this.filterHelpers(tree, new RegExp('^modules\/' + this.name + '\/helpers\/', 'i')); }, filterHelpers: function(tree, regex) { var whitelist = this.whitelist; var blacklist = this.blacklist; var _this = this; "// exit early if no opts defined if (whitelist.length ""=== 0 "&& blacklist.length ""=== 0) { return new Funnel(tree); } return new Funnel(tree, { exclude: [function(name) { return _this.exclusionFilter(name, regex, { whitelist: whitelist, blacklist: blacklist }); }] }); }
  60. EmberConf 2017 Confessions of an Ember Addon Author

  61. EmberConf 2017 Confessions of an Ember Addon Author Augment ember-cli

    INCLUDED COMMANDS
  62. EmberConf 2017 Confessions of an Ember Addon Author includedCommands: function()

    { return { 'deploy': require('./lib/deploy'), 'deploy:activate': require('./lib/activate'), 'deploy:list': require('./lib/list') }; }
  63. EmberConf 2017 Confessions of an Ember Addon Author I. Addon

    Anatomy II. One Weird Trick III. Open Sourcery
  64. EmberConf 2017 Confessions of an Ember Addon Author Cinemagraph by

    /u/ibru Developer Experience
  65. EmberConf 2017 Confessions of an Ember Addon Author Tell a

    story WRITING GOOD DOCUMENTATION
  66. EmberConf 2017 Confessions of an Ember Addon Author “I used

    ES6 classes and my startup failed AMA”
  67. EmberConf 2017 Confessions of an Ember Addon Author

  68. EmberConf 2017 Confessions of an Ember Addon Author What is

    the public API? WRITING GOOD DOCUMENTATION
  69. EmberConf 2017 Confessions of an Ember Addon Author

  70. EmberConf 2017 Confessions of an Ember Addon Author

  71. EmberConf 2017 Confessions of an Ember Addon Author Show, don't

    tell WRITING GOOD DOCUMENTATION
  72. EmberConf 2017 Confessions of an Ember Addon Author

  73. EmberConf 2017 Confessions of an Ember Addon Author

  74. EmberConf 2017 Confessions of an Ember Addon Author

  75. EmberConf 2017 Confessions of an Ember Addon Author

  76. EmberConf 2017 Confessions of an Ember Addon Author Cinemagraph by

    /u/barracuda415 Testing Your Addon
  77. EmberConf 2017 Confessions of an Ember Addon Author module.exports =

    { scenarios: [ { name: 'ember-1.13', bower: { "/* ""..."*/ }, npm: { "/* ""..."*/ } }, { name: 'ember-lts-2.8', bower: { "/* ""..."*/ }, npm: { "/* ""..."*/ } }, { name: 'ember-release', bower: { "/* ""..."*/ }, npm: { "/* ""..."*/ } }, { name: 'ember-beta', bower: { "/* ""..."*/ }, npm: { "/* ""..."*/ } }, { name: 'ember-canary', bower: { "/* ""..."*/ }, npm: { "/* ""..."*/ } }, { name: 'ember-default', bower: { "/* ""..."*/ }, npm: { "/* ""..."*/ } } ] }; Ensure Compatibility With Ember-Try
  78. EmberConf 2017 Confessions of an Ember Addon Author env: -

    EMBER_TRY_SCENARIO=ember-lts-2.4 - EMBER_TRY_SCENARIO=ember-lts-2.8 - EMBER_TRY_SCENARIO=ember-release - EMBER_TRY_SCENARIO=ember-beta - EMBER_TRY_SCENARIO=ember-canary - EMBER_TRY_SCENARIO=ember-default
  79. EmberConf 2017 Confessions of an Ember Addon Author

  80. EmberConf 2017 Confessions of an Ember Addon Author Test With

    A Real Browser addons: apt: sources: - google-chrome packages: - google-chrome-stable before_install: # Setup for Chrome - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start https://github.com/machty/ember-concurrency/blob/master/.travis.yml
  81. EmberConf 2017 Confessions of an Ember Addon Author Configuration

  82. EmberConf 2017 Confessions of an Ember Addon Author File in

    /app
  83. EmberConf 2017 Confessions of an Ember Addon Author "// app/transitions.js

    export default function() { this.transition( this.fromRoute('people.index'), this.toRoute('people.detail'), this.use('toLeft'), this.reverse('toRight') ); };
  84. EmberConf 2017 Confessions of an Ember Addon Author if (owner.factoryFor)

    { let maybeConfig = owner.factoryFor('transitions:main'); config = maybeConfig "&& maybeConfig.class; } else { config = owner._lookupFactory('transitions:main'); }
  85. EmberConf 2017 Confessions of an Ember Addon Author

  86. EmberConf 2017 Confessions of an Ember Addon Author

  87. EmberConf 2017 Confessions of an Ember Addon Author define("my-app/transitions", ["exports"],

    function (exports) { "// ""... });
  88. EmberConf 2017 Confessions of an Ember Addon Author export default

    { url: 'http:"//localhost:3000', timeout: 1000, isThing: true }
  89. EmberConf 2017 Confessions of an Ember Addon Author config/environment.js

  90. EmberConf 2017 Confessions of an Ember Addon Author metricsAdapters: [

    { name: 'Mixpanel', environments: ['development'], config: { token: 'abcd1234' } }, { name: 'GoogleAnalytics', environments: ['production'], config: { id: 'UA-XXXXXXXX-X' } } ]
  91. EmberConf 2017 Confessions of an Ember Addon Author "// app/initializers/metrics.js

    import config from '"../config/environment'; export function initialize() { const application = arguments[1] "|| arguments[0]; const { metricsAdapters = [] } = config; const { environment = 'development' } = config; const options = { metricsAdapters, environment }; application.register('config:metrics', options, { instantiate: false }); application.inject('service:metrics', 'options', 'config:metrics'); } export default { name: 'metrics', initialize };
  92. EmberConf 2017 Confessions of an Ember Addon Author import Ember

    from 'ember'; const { Service, get } = Ember; export default Service.extend({ init() { this._super(""...arguments); let { metricsAdapters, environment } = get(this, 'options'); "// do stuff } });
  93. EmberConf 2017 Confessions of an Ember Addon Author ember-cli-build.js

  94. EmberConf 2017 Confessions of an Ember Addon Author module.exports =

    function(defaults) { var app = new EmberApp(defaults, { 'ember-composable-helpers': { only: ['inc', 'dec', 'pipe'], except: ['pipe', 'filter-by'] } }); }
  95. EmberConf 2017 Confessions of an Ember Addon Author included: function(app)

    { this._super.included.apply(this, arguments); "// see: https:"//github.com/ember-cli/ember-cli/issues/3718 if (typeof app.import ""!== 'function' "&& app.app) { app = app.app; } this.app = app; this.app.options = this.app.options "|| {}; var config = this.app.options[this.name] "|| {}; this.whitelist = this.generateWhitelist(config); this.blacklist = this.generateBlacklist(config); }
  96. EmberConf 2017 Confessions of an Ember Addon Author Extensibility

  97. EmberConf 2017 Confessions of an Ember Addon Author Ember Land

  98. EmberConf 2017 Confessions of an Ember Addon Author "// addon/metrics-adapters/base.js

    import Ember from 'ember'; const { Object: EmberObject } = Ember; export default EmberObject.extend({ "// base methods }); Not Managed By Ember's Container
  99. EmberConf 2017 Confessions of an Ember Addon Author "// addon/metrics-adapters/google-analytics.js

    import BaseAdapter from './base'; export default BaseAdapter.extend({ "// custom code });
  100. EmberConf 2017 Confessions of an Ember Addon Author /** *

    Looks up the adapter from the resolver. Prioritizes the consuming app's * adapters over the addon's adapters. * * @method _lookupAdapter * @param {String} adapterName * @private * @return {Adapter} a local adapter or an adapter from the addon "*/ _lookupAdapter(adapterName) { assert('[ember-metrics] Could not find metrics adapter without a name.', adapterName); const dasherizedAdapterName = dasherize(adapterName); const availableAdapter = getOwner(this).lookup(`ember-metrics@metrics-adapter:${dasherizedAdapterName}`); const localAdapter = getOwner(this).lookup(`metrics-adapter:${dasherizedAdapterName}`); return localAdapter ? localAdapter : availableAdapter; }
  101. EmberConf 2017 Confessions of an Ember Addon Author /** *

    Instantiates an adapter if one is found. * * @method _activateAdapter * @param {Object} * @private * @return {Adapter} "*/ _activateAdapter({ name, config } = {}) { const Adapter = this._lookupAdapter(name); assert(`[ember-metrics] Could not find metrics adapter ${name}.`, Adapter); return Adapter.create({ this, config }); }
  102. EmberConf 2017 Confessions of an Ember Addon Author Not-Ember Land

  103. EmberConf 2017 Confessions of an Ember Addon Author "/* globals

    requirejs, requireModule "*/ import Ember from 'ember'; import defaultMessages from 'ember-changeset-validations/utils/messages'; const { A: emberArray, isPresent } = Ember; const { keys } = Object; const matchRegex = /validations\/messages$/gi; let cachedRef = null; /** * Find and load messages module on consuming app. Defaults to addon messages. * To define a custom message map, create `my-app/app/validations/messages.js` * and export an object. * * @param {Object} moduleMap * @param {Boolean} useCache Pass `false` to ignore cached key * @return {Object} "*/ export default function getMessages(moduleMap = requirejs.entries, useCache = true) { if (useCache "&& isPresent(cachedRef)) { return cachedRef; } let moduleKey = emberArray(keys(moduleMap)) .find((module) "=> isPresent(module.match(matchRegex))); let messagesModule = isPresent(moduleKey) ? requireModule(moduleKey).default : defaultMessages; cachedRef = messagesModule; return messagesModule; } Probably A Hack
  104. EmberConf 2017 Confessions of an Ember Addon Author "/* globals

    requirejs, requireModule "*/ import Ember from 'ember'; import defaultMessages from 'ember-changeset-validations/utils/messages'; const { A: emberArray, isPresent } = Ember; const { keys } = Object; const matchRegex = /validations\/messages$/gi; let cachedRef = null; /** * Find and load messages module on consuming app. Defaults to addon messages. * To define a custom message map, create `my-app/app/validations/messages.js` * and export an object. * * @param {Object} moduleMap * @param {Boolean} useCache Pass `false` to ignore cached key * @return {Object} "*/ export default function getMessages(moduleMap = requirejs.entries, useCache = true) { if (useCache "&& isPresent(cachedRef)) { return cachedRef; } let moduleKey = emberArray(keys(moduleMap)) .find((module) "=> isPresent(module.match(matchRegex))); let messagesModule = isPresent(moduleKey) ? requireModule(moduleKey).default : defaultMessages; cachedRef = messagesModule; return messagesModule; } Probably A Hack Don't do this
  105. EmberConf 2017 Confessions of an Ember Addon Author

  106. EmberConf 2017 Confessions of an Ember Addon Author Child Addons

  107. EmberConf 2017 Confessions of an Ember Addon Author

  108. EmberConf 2017 Confessions of an Ember Addon Author { "name":

    "my-ember-service-worker-plugin", "version": "0.0.0", "keywords": [ "ember-addon", "ember-service-worker-plugin" ] }
  109. EmberConf 2017 Confessions of an Ember Addon Author postprocessTree(type, appTree)

    { if (type ""!== 'all') { return appTree; } let plugins = this._findPluginsFor(this.project); "// Add the project itself as a possible plugin, this way user can add custom "// service-worker code in their app, without needing to build a plugin. plugins = [this].concat(plugins, this.project); "// other setup }, _findPluginsFor(project) { let addons = project.addons "|| []; return addonUtils.filterByKeyword(addons, 'ember-service-worker-plugin'); }
  110. EmberConf 2017 Confessions of an Ember Addon Author Ember Internals

  111. EmberConf 2017 Confessions of an Ember Addon Author https://kapeli.com/dash

  112. EmberConf 2017 Confessions of an Ember Addon Author

  113. EmberConf 2017 Confessions of an Ember Addon Author

  114. EmberConf 2017 Confessions of an Ember Addon Author

  115. EmberConf 2017 Confessions of an Ember Addon Author

  116. EmberConf 2017 Confessions of an Ember Addon Author

  117. EmberConf 2017 Confessions of an Ember Addon Author I. Addon

    Anatomy II. One Weird Trick III. Open Sourcery
  118. EmberConf 2017 Confessions of an Ember Addon Author Kitten OPEN

    SOURCE LEVEL
  119. EmberConf 2017 Confessions of an Ember Addon Author Cat OPEN

    SOURCE LEVEL
  120. EmberConf 2017 Confessions of an Ember Addon Author Veteran OPEN

    SOURCE LEVEL
  121. EmberConf 2017 Confessions of an Ember Addon Author wycats OPEN

    SOURCE LEVEL
  122. EmberConf 2017 Confessions of an Ember Addon Author Relinquish control

    KEEPING YOUR ADDON ALIVE
  123. EmberConf 2017 Confessions of an Ember Addon Author The issue

    backlog can be overwhelming STAYING SANE
  124. EmberConf 2017 Confessions of an Ember Addon Author It's okay

    to say no AVOID BURNOUT
  125. EmberConf 2017 Confessions of an Ember Addon Author Please SEMVER

  126. EmberConf 2017 Confessions of an Ember Addon Author

  127. EmberConf 2017 Confessions of an Ember Addon Author I. Addon

    Anatomy II. One Weird Trick III. Open Sourcery
  128. EmberConf 2017 Confessions of an Ember Addon Author I. Addon

    Anatomy II. One Weird Trick III. Open Sourcery
  129. EmberConf 2017 Confessions of an Ember Addon Author I. Addon

    Anatomy II. One Weird Trick III. Open Sourcery
  130. EmberConf 2017 Confessions of an Ember Addon Author Lauren Tan

    sugarpirate_ poteto
  131. EmberConf 2017 Confessions of an Ember Addon Author Cinemagraph by

    /u/fezzo Thank you sugarpirate_ poteto