Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

EmberConf 2017 Confessions of an Ember Addon Author Lauren Tan sugarpirate_ poteto

Slide 3

Slide 3 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 4

Slide 4 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 5

Slide 5 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 6

Slide 6 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 7

Slide 7 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 8

Slide 8 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 9

Slide 9 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 10

Slide 10 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 11

Slide 11 text

EmberConf 2017 Confessions of an Ember Addon Author What makes an addon good?

Slide 12

Slide 12 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 13

Slide 13 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 14

Slide 14 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 15

Slide 15 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 16

Slide 16 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 17

Slide 17 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 18

Slide 18 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 19

Slide 19 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 20

Slide 20 text

EmberConf 2017 Confessions of an Ember Addon Author Documentation Driven Development

Slide 21

Slide 21 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 22

Slide 22 text

EmberConf 2017 Confessions of an Ember Addon Author If it's not documented, it doesn't exist YES, IT'S TRUE

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 25

Slide 25 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 26

Slide 26 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

EmberConf 2017 Confessions of an Ember Addon Author Any successful project requires two things Nathan Marz, 2014

Slide 29

Slide 29 text

EmberConf 2017 Confessions of an Ember Addon Author It solves a useful problem TWO THINGS

Slide 30

Slide 30 text

EmberConf 2017 Confessions of an Ember Addon Author People are convinced your solution is the best for their problem TWO THINGS

Slide 31

Slide 31 text

EmberConf 2017 Confessions of an Ember Addon Author https://youtu.be/_0T5OSSzxms

Slide 32

Slide 32 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 33

Slide 33 text

EmberConf 2017 Confessions of an Ember Addon Author ȗ 

Slide 34

Slide 34 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 35

Slide 35 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 36

Slide 36 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

EmberConf 2017 Confessions of an Ember Addon Author Addon Structure Cinemagraph by /u/Hyperoperation

Slide 40

Slide 40 text

EmberConf 2017 Confessions of an Ember Addon Author Ember Land

Slide 41

Slide 41 text

EmberConf 2017 Confessions of an Ember Addon Author Node Land

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

EmberConf 2017 Confessions of an Ember Addon Author export { default, append } from 'ember-composable-helpers/helpers/append';

Slide 44

Slide 44 text

EmberConf 2017 Confessions of an Ember Addon Author app └── helpers ├── append.js ├── ""... └── without.js Merged into consuming app

Slide 45

Slide 45 text

EmberConf 2017 Confessions of an Ember Addon Author "ember-addon": { "configPath": "tests/dummy/config", "after": [ "ember-changeset" ] }

Slide 46

Slide 46 text

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';

Slide 47

Slide 47 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 48

Slide 48 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 53

Slide 53 text

EmberConf 2017 Confessions of an Ember Addon Author I. setupPreprocessorRegistry II. treeForAddon

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

EmberConf 2017 Confessions of an Ember Addon Author Click me "

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 58

Slide 58 text

EmberConf 2017 Confessions of an Ember Addon Author Remove unused helpers from build TREE FOR ADDON

Slide 59

Slide 59 text

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 }); }] }); }

Slide 60

Slide 60 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 61

Slide 61 text

EmberConf 2017 Confessions of an Ember Addon Author Augment ember-cli INCLUDED COMMANDS

Slide 62

Slide 62 text

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') }; }

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

EmberConf 2017 Confessions of an Ember Addon Author Cinemagraph by /u/ibru Developer Experience

Slide 65

Slide 65 text

EmberConf 2017 Confessions of an Ember Addon Author Tell a story WRITING GOOD DOCUMENTATION

Slide 66

Slide 66 text

EmberConf 2017 Confessions of an Ember Addon Author “I used ES6 classes and my startup failed AMA”

Slide 67

Slide 67 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 68

Slide 68 text

EmberConf 2017 Confessions of an Ember Addon Author What is the public API? WRITING GOOD DOCUMENTATION

Slide 69

Slide 69 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 70

Slide 70 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 71

Slide 71 text

EmberConf 2017 Confessions of an Ember Addon Author Show, don't tell WRITING GOOD DOCUMENTATION

Slide 72

Slide 72 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 73

Slide 73 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 74

Slide 74 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 75

Slide 75 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 76

Slide 76 text

EmberConf 2017 Confessions of an Ember Addon Author Cinemagraph by /u/barracuda415 Testing Your Addon

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

EmberConf 2017 Confessions of an Ember Addon Author Configuration

Slide 82

Slide 82 text

EmberConf 2017 Confessions of an Ember Addon Author File in /app

Slide 83

Slide 83 text

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') ); };

Slide 84

Slide 84 text

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'); }

Slide 85

Slide 85 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 86

Slide 86 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 87

Slide 87 text

EmberConf 2017 Confessions of an Ember Addon Author define("my-app/transitions", ["exports"], function (exports) { "// ""... });

Slide 88

Slide 88 text

EmberConf 2017 Confessions of an Ember Addon Author export default { url: 'http:"//localhost:3000', timeout: 1000, isThing: true }

Slide 89

Slide 89 text

EmberConf 2017 Confessions of an Ember Addon Author config/environment.js

Slide 90

Slide 90 text

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' } } ]

Slide 91

Slide 91 text

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 };

Slide 92

Slide 92 text

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 } });

Slide 93

Slide 93 text

EmberConf 2017 Confessions of an Ember Addon Author ember-cli-build.js

Slide 94

Slide 94 text

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'] } }); }

Slide 95

Slide 95 text

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); }

Slide 96

Slide 96 text

EmberConf 2017 Confessions of an Ember Addon Author Extensibility

Slide 97

Slide 97 text

EmberConf 2017 Confessions of an Ember Addon Author Ember Land

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

EmberConf 2017 Confessions of an Ember Addon Author "// addon/metrics-adapters/google-analytics.js import BaseAdapter from './base'; export default BaseAdapter.extend({ "// custom code });

Slide 100

Slide 100 text

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; }

Slide 101

Slide 101 text

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 }); }

Slide 102

Slide 102 text

EmberConf 2017 Confessions of an Ember Addon Author Not-Ember Land

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 106

Slide 106 text

EmberConf 2017 Confessions of an Ember Addon Author Child Addons

Slide 107

Slide 107 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 108

Slide 108 text

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" ] }

Slide 109

Slide 109 text

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'); }

Slide 110

Slide 110 text

EmberConf 2017 Confessions of an Ember Addon Author Ember Internals

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 113

Slide 113 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 114

Slide 114 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 115

Slide 115 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 116

Slide 116 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

EmberConf 2017 Confessions of an Ember Addon Author Kitten OPEN SOURCE LEVEL

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

EmberConf 2017 Confessions of an Ember Addon Author Relinquish control KEEPING YOUR ADDON ALIVE

Slide 123

Slide 123 text

EmberConf 2017 Confessions of an Ember Addon Author The issue backlog can be overwhelming STAYING SANE

Slide 124

Slide 124 text

EmberConf 2017 Confessions of an Ember Addon Author It's okay to say no AVOID BURNOUT

Slide 125

Slide 125 text

EmberConf 2017 Confessions of an Ember Addon Author Please SEMVER

Slide 126

Slide 126 text

EmberConf 2017 Confessions of an Ember Addon Author

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

EmberConf 2017 Confessions of an Ember Addon Author Lauren Tan sugarpirate_ poteto

Slide 131

Slide 131 text

EmberConf 2017 Confessions of an Ember Addon Author Cinemagraph by /u/fezzo Thank you sugarpirate_ poteto