Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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!

Lauren Tan

March 29, 2017
Tweet

More Decks by Lauren Tan

Other Decks in Programming

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 If it's

    not documented, it doesn't exist YES, IT'S TRUE
  3. 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
  4. EmberConf 2017 Confessions of an Ember Addon Author If you

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

    project requires two things Nathan Marz, 2014
  6. EmberConf 2017 Confessions of an Ember Addon Author People are

    convinced your solution is the best for their problem TWO THINGS
  7. EmberConf 2017 Confessions of an Ember Addon Author I. Addon

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

    Anatomy II. One Weird Trick III. Open Sourcery
  9. 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
  10. EmberConf 2017 Confessions of an Ember Addon Author export {

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

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

    "configPath": "tests/dummy/config", "after": [ "ember-changeset" ] }
  13. 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';
  14. EmberConf 2017 Confessions of an Ember Addon Author export default

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

    dasherizedModuleName classifiedModuleName camelizedModuleName https:"//ember-cli.com/api/classes/Blueprint.html
  16. 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
  17. 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
  18. 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
  19. 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 }); }] }); }
  20. 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') }; }
  21. EmberConf 2017 Confessions of an Ember Addon Author I. Addon

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

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

    the public API? WRITING GOOD DOCUMENTATION
  24. 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
  25. 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
  26. 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
  27. 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') ); };
  28. 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'); }
  29. EmberConf 2017 Confessions of an Ember Addon Author export default

    { url: 'http:"//localhost:3000', timeout: 1000, isThing: true }
  30. 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' } } ]
  31. 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 };
  32. 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 } });
  33. 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'] } }); }
  34. 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); }
  35. 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
  36. EmberConf 2017 Confessions of an Ember Addon Author "// addon/metrics-adapters/google-analytics.js

    import BaseAdapter from './base'; export default BaseAdapter.extend({ "// custom code });
  37. 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; }
  38. 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 }); }
  39. 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
  40. 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
  41. 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" ] }
  42. 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'); }
  43. EmberConf 2017 Confessions of an Ember Addon Author I. Addon

    Anatomy II. One Weird Trick III. Open Sourcery
  44. EmberConf 2017 Confessions of an Ember Addon Author The issue

    backlog can be overwhelming STAYING SANE
  45. EmberConf 2017 Confessions of an Ember Addon Author I. Addon

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

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

    Anatomy II. One Weird Trick III. Open Sourcery