SlideShare a Scribd company logo
Anatomy of an Addon Ecosystem
Figuring Out an Addon Ecosystem by Reading Code
Line... By... Line
Figuring Out an Addon Ecosystem by Reading Code
Line... By... Line
Special Assistance Provided by
3
• A lifelong Muppet fan
• A maintainer for ember-service-worker
• Web developer for almost 20 years
• Remember before open source was a "thing"
Lisa Backer
Senior Software Engineer, DockYard
3
• A lifelong Muppet fan
• A maintainer for ember-service-worker
• Web developer for almost 20 years
• Remember before open source was a "thing"
Lisa Backer
Senior Software Engineer, DockYard
• Yes... I'm feeling old right now
4
Intro: Service Worker
• Script in your browser that runs in the
background
4
Intro: Service Worker
• Script in your browser that runs in the
background
• Can enable offline cache, intercept network
requests, push notifications, background
synchronizations, and more
4
Intro: Service Worker
• Script in your browser that runs in the
background
• Can enable offline cache, intercept network
requests, push notifications, background
synchronizations, and more
• Must be registered from JavaScript by loading
a definition file
4
Intro: Service Worker
Intro: Ember-Service-Worker5
Intro: Ember-Service-Worker
• An addon that provides a framework
5
Intro: Ember-Service-Worker
• An addon that provides a framework
• Compiles and register custom service
workers
5
Intro: Ember-Service-Worker
• An addon that provides a framework
• Compiles and register custom service
workers
• The plugins define the actual service
worker scripts.
5
Intro: Ember-Service-Worker
• An addon that provides a framework
• Compiles and register custom service
workers
• The plugins define the actual service
worker scripts.
5
7
Hand-Waving Demo
7
Hand-Waving Demo
Study Up
Study Goals
9
Study Goals
9
• What is a "plugin"
Study Goals
9
• What is a "plugin"
• How does registration process work
Study Goals
9
• What is a "plugin"
• How does registration process work
• How do we locate plugins that the
application has installed
Study Goals
9
• What is a "plugin"
• How does registration process work
• How do we locate plugins that the
application has installed
• How do we test it all
10
What Is a Plugin?
A plugin is a bundle that adds functionality
to an application, called the host application,
through some well-defined architecture for
extensibility
10
What Is a Plugin?
A plugin is a bundle that adds functionality
to an application, called the host application,
through some well-defined architecture for
extensibility
• Enable developers to extend an application
10
What Is a Plugin?
A plugin is a bundle that adds functionality
to an application, called the host application,
through some well-defined architecture for
extensibility
• Enable developers to extend an application
• Reduce the size of the application
10
What Is a Plugin?
A plugin is a bundle that adds functionality
to an application, called the host application,
through some well-defined architecture for
extensibility
• Enable developers to extend an application
• Reduce the size of the application
• Provides a well-defined API
10
What Is a Plugin?
A plugin is a bundle that adds functionality
to an application, called the host application,
through some well-defined architecture for
extensibility
An Addon Is a Plugin
11
Starting With Docs
Starting With Docs
Continuing With Docs
Continuing With Docs
Continuing With Docs
Continuing With Docs
17
Searching the Community
17
Searching the Community
Time To Dive in
// Configures Ember CLI's build system to not add a fingerprint to sw.js
this.app.options.fingerprint = this.app.options.fingerprint || {};
this.app.options.fingerprint.exclude = this.app.options.fingerprint.exclude || [];
this.app.options.fingerprint.exclude.push('sw.js');
included(app) {
if (this._super.included) {
this._super.included.apply(this, arguments);
}
}
this.app = app;
this.app.options = this.app.options || {};
let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {}
options.registrationStrategy = options.registrationStrategy || 'default';
if (process.env.SW_DISABLED) {
options.enabled = false;
}
if (options.registrationStrategy === 'after-ember' && options.enabled !== false) {
app.import('vendor/ember-service-worker/load-registration-script.js');
}
if (options.enabled === undefined) {
options.enabled = this.app.env !== 'test';
}
// ember-service-worker: index.js
included(app) {
if (this._super.included) {
this._super.included.apply(this, arguments);
}
}
this.app = app;
this.app.options = this.app.options || {};
let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {}
options.registrationStrategy = options.registrationStrategy || 'default';
if (process.env.SW_DISABLED) {
options.enabled = false;
}
if (options.registrationStrategy === 'after-ember' && options.enabled !== false) {
app.import('vendor/ember-service-worker/load-registration-script.js');
}
if (options.enabled === undefined) {
options.enabled = this.app.env !== 'test';
}
// ember-service-worker: index.js
included(app) {
if (this._super.included) {
this._super.included.apply(this, arguments);
}
}
this.app = app;
this.app.options = this.app.options || {};
// ember-service-worker: index.js
included(app) {
if (this._super.included) {
this._super.included.apply(this, arguments);
}
}
this.app = app;
this.app.options = this.app.options || {};
// ember-service-worker: index.js
included(app) {
if (this._super.included) {
this._super.included.apply(this, arguments);
}
}
this.app = app;
this.app.options = this.app.options || {};
// ember-service-worker: index.js
included(app) {
if (this._super.included) {
this._super.included.apply(this, arguments);
}
}
this.app = app;
this.app.options = this.app.options || {};
let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {}
// ember-service-worker: index.js
included(app) {
if (this._super.included) {
this._super.included.apply(this, arguments);
}
}
this.app = app;
this.app.options = this.app.options || {};
let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {}
// ember-service-worker: index.js
included(app) {
if (this._super.included) {
this._super.included.apply(this, arguments);
}
}
this.app = app;
this.app.options = this.app.options || {};
let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {}
options.registrationStrategy = options.registrationStrategy || 'default';
// ember-service-worker: index.js
included(app) {
if (this._super.included) {
this._super.included.apply(this, arguments);
}
}
this.app = app;
this.app.options = this.app.options || {};
let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {}
options.registrationStrategy = options.registrationStrategy || 'default';
if (options.enabled === undefined) {
options.enabled = this.app.env !== 'test';
}
// ember-service-worker: index.js
included(app) {
if (this._super.included) {
this._super.included.apply(this, arguments);
}
}
this.app = app;
this.app.options = this.app.options || {};
let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {}
options.registrationStrategy = options.registrationStrategy || 'default';
if (process.env.SW_DISABLED) {
options.enabled = false;
}
if (options.enabled === undefined) {
options.enabled = this.app.env !== 'test';
}
// ember-service-worker: index.js
included(app) {
if (this._super.included) {
this._super.included.apply(this, arguments);
}
}
this.app = app;
this.app.options = this.app.options || {};
let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {}
options.registrationStrategy = options.registrationStrategy || 'default';
if (process.env.SW_DISABLED) {
options.enabled = false;
}
if (options.enabled === undefined) {
options.enabled = this.app.env !== 'test';
}
if (options.registrationStrategy === 'after-ember' && options.enabled !== false) {
app.import('vendor/ember-service-worker/load-registration-script.js');
}
// ember-service-worker: index.js
Searching for the
Import
27
app.import('vendor/ember-service-worker/load-registration-script.js');
Searching for the
Import
27
app.import('vendor/ember-service-worker/load-registration-script.js');
Searching for the
Import
treeForVendor() {
return writeFile('ember-service-worker/load-
registration-script.js', `
.......
`);
}
28
app.import('vendor/ember-service-worker/load-registration-script.js');
29
Checking the Docs
30
Checking the Docs
32
Searching for Broccoli
33
http://www.oligriffiths.com/broccolijs/
Searching for Broccoli
Broccoli Trees
34
Image credit: http://www.oligriffiths.com/broccolijs/
Broccoli Trees
Are actually now called broccoli nodes
34
Image credit: http://www.oligriffiths.com/broccolijs/
Broccoli Trees
Are actually now called broccoli nodes
Represent a set of files that can be transformed by
plugins
34
Image credit: http://www.oligriffiths.com/broccolijs/
Broccoli Trees
Are actually now called broccoli nodes
Represent a set of files that can be transformed by
plugins
A build function is called on each plugin
34
Image credit: http://www.oligriffiths.com/broccolijs/
Broccoli Trees
Are actually now called broccoli nodes
Represent a set of files that can be transformed by
plugins
A build function is called on each plugin
Broccoli itself is the tool that handles plugging all this
together so that we end up with our build output.
34
Image credit: http://www.oligriffiths.com/broccolijs/
35
Searching for Broccoli
included(app) {
...
if (options.registrationStrategy === 'after-ember' && options.enabled !== false) {
app.import('vendor/ember-service-worker/load-registration-script.js');
}
},
treeForVendor() {
return writeFile('ember-service-worker/load-registration-script.js', `
(function() {
if (typeof FastBoot === 'undefined') {
var script = document.createElement('script')
script.src = '${this._getRootURL()}sw-registration.js';
document.body.appendChild(script);
}
})();
`);
}
// ember-service-worker: index.js
Breathe
postprocessTree(type, appTree) {
let options = this._getOptions();
if (type !== 'all' || options.enabled === false) {
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);
let serviceWorkerBuilder = new ServiceWorkerBuilder({
app: this,
appTree,
minifyJS: this.app.options.minifyJS,
fingerprint: this.app.options.fingerprint.enabled,
plugins,
rootURL: this._getRootURL(),
sourcemaps: this.app.options.sourcemaps,
registrationDistPath: options.registrationDistPath
});
let serviceWorkerTree = serviceWorkerBuilder.build('service-worker');
let serviceWorkerRegistrationTree =
serviceWorkerBuilder.build('service-worker-registration');
return mergeTrees([
appTree,
serviceWorkerTree,
serviceWorkerRegistrationTree
], { overwrite: true });
// ember-service-worker: index.js
39
Checking the Docs
postprocessTree(type, appTree) {
let options = this._getOptions();
if (type !== 'all' || options.enabled === false) {
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);
let serviceWorkerBuilder = new ServiceWorkerBuilder({
app: this,
appTree,
minifyJS: this.app.options.minifyJS,
fingerprint: this.app.options.fingerprint.enabled,
plugins,
rootURL: this._getRootURL(),
sourcemaps: this.app.options.sourcemaps,
registrationDistPath: options.registrationDistPath
});
let serviceWorkerTree = serviceWorkerBuilder.build('service-worker');
let serviceWorkerRegistrationTree =
serviceWorkerBuilder.build('service-worker-registration');
return mergeTrees([
appTree,
serviceWorkerTree,
serviceWorkerRegistrationTree
], { overwrite: true });
// ember-service-worker: index.js
_findPluginsFor(project) {
let addons = project.addons || [];
return addonUtils.filterByKeyword(addons, 'ember-service-worker-plugin');
}
postprocessTree(type, appTree) {
let options = this._getOptions();
if (type !== 'all' || options.enabled === false) {
return appTree;
}
}
let plugins = this._findPluginsFor(this.project);
// ember-service-worker: index.js
_findPluginsFor(project) {
let addons = project.addons || [];
return addonUtils.filterByKeyword(addons, 'ember-service-worker-plugin');
}
postprocessTree(type, appTree) {
let options = this._getOptions();
if (type !== 'all' || options.enabled === false) {
return appTree;
}
}
let plugins = this._findPluginsFor(this.project);
// ember-service-worker: index.js
postprocessTree(type, appTree) {
let options = this._getOptions();
if (type !== 'all' || options.enabled === false) {
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);
// ember-service-worker: index.js
let serviceWorkerBuilder = new ServiceWorkerBuilder({
app: this,
appTree,
minifyJS: this.app.options.minifyJS,
fingerprint: this.app.options.fingerprint.enabled,
plugins,
rootURL: this._getRootURL(),
sourcemaps: this.app.options.sourcemaps,
registrationDistPath: options.registrationDistPath
});
postprocessTree(type, appTree) {
let options = this._getOptions();
if (type !== 'all' || options.enabled === false) {
return appTree;
}
}
// 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);
// ember-service-worker: index.js
let plugins = this._findPluginsFor(this.project);
let serviceWorkerTree = serviceWorkerBuilder.build('service-worker');
let serviceWorkerRegistrationTree =
serviceWorkerBuilder.build('service-worker-registration');
let serviceWorkerBuilder = new ServiceWorkerBuilder({
app: this,
appTree,
minifyJS: this.app.options.minifyJS,
fingerprint: this.app.options.fingerprint.enabled,
plugins,
rootURL: this._getRootURL(),
sourcemaps: this.app.options.sourcemaps,
registrationDistPath: options.registrationDistPath
});
postprocessTree(type, appTree) {
let options = this._getOptions();
if (type !== 'all' || options.enabled === false) {
return appTree;
}
}
// 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);
// ember-service-worker: index.js
let plugins = this._findPluginsFor(this.project);
• Detect and include specific directories
• treeFor hooks to process generated trees
Ember-Service-
Worker Plugin API
46
Ember-Service-
Worker Plugin API
47
• Detect and include specific directories
/service-worker/*
• treeFor hooks to process generated trees
treeForServiceWorker
Ember-Service-
Worker Plugin API
47
• Detect and include specific directories
/service-worker/*
• treeFor hooks to process generated trees
treeForServiceWorker
Ember-Service-
Worker Plugin API
47
• Detect and include specific directories
/service-worker/*
• treeFor hooks to process generated trees
treeForServiceWorker
Ember-Service-
Worker Plugin API
48
• Detect and include specific directories
/service-worker-registration/*
• treeFor hooks to process generated trees
treeForServiceWorkerRegistration
'use strict';
const EntryPoint = require('./entry-point');
const Funnel = require('broccoli-funnel');
const Rollup = require('./rollup-with-dependencies');
const fs = require('fs');
const mergeTrees = require('broccoli-merge-trees');
const path = require('path');
const rollupReplace = require('rollup-plugin-replace');
const uglify = require('broccoli-uglify-sourcemap');
const TREE_FOR_METHODS = {
'service-worker': 'treeForServiceWorker',
'service-worker-registration': 'treeForServiceWorkerRegistration'
};
const ENTRY_POINT_FILENAMES = {
'service-worker': 'sw.js',
'service-worker-registration': 'sw-registration.js'
};
module.exports = class ServiceWorkerBuilder {
constructor(options) {
this.options = options || {};
this.plugins = options.plugins;
this.appTree = options.appTree;
this.app = options.app;
}
build(type) {
let trees = this._treesForPlugins(type);
return this._compileTrees(trees, ENTRY_POINT_FILENAMES[type]);
}
_treesForPlugins(type) {
return this.plugins.reduce((trees, plugin) => {
let pluginTree = this._treeForPlugin(plugin, type);
// ember-service-worker: lib/service-worker-builder.js
// same as above but for windows paths like D:my-emberember-service-workerwhateversw.js; which are updated before this step to D:/
my-ember/ember-service/worker/whatever/sw.js
// the original one doesn't work when the source code and the %TMP% folder, which ember started to use after v3.5, are located on
different logical drives
// which is quite common in Windows.
/^[a-z]:/(?:[^/:*?"<>|rn]+/)*ember-service-worker/(?:[^/:*?"<>|rn]+/)*[^/:*?"<>|rn]*.js$/i
],
delimiters: ['{{', '}}'],
ROOT_URL: this.options.rootURL
};
return new Rollup(tree, {
inputFiles: '**/*.js',
rollup: {
input: entryFile,
output: {
file: destFile || entryFile,
format: 'iife',
},
exports: 'none',
plugins: [
rollupReplace(rollupReplaceConfig)
]
}
});
}
_uglifyTree(tree) {
if (this.options.minifyJS && this.options.minifyJS.enabled) {
let options = this.options.minifyJS.options || {};
options.sourceMapConfig = this.options.sourcemaps;
return uglify(tree, options);
}
return tree;
}
_babelTranspile(tree) {
let emberCliBabel = this.app.project.addons.filter((a) => a.name === 'ember-cli-babel')[0];
return emberCliBabel.transpileTree(tree, { 'ember-cli-babel': { compileModules: false } });
}
}
const TREE_FOR_METHODS = {
'service-worker': 'treeForServiceWorker',
'service-worker-registration': 'treeForServiceWorkerRegistration'
};
module.exports = class ServiceWorkerBuilder {
build(type) {
let trees = this._treesForPlugins(type);
return this._compileTrees(trees, ENTRY_POINT_FILENAMES[type]);
}
// ember-service-worker: lib/service-worker-builder.js
const TREE_FOR_METHODS = {
'service-worker': 'treeForServiceWorker',
'service-worker-registration': 'treeForServiceWorkerRegistration'
};
module.exports = class ServiceWorkerBuilder {
build(type) {
let trees = this._treesForPlugins(type);
return this._compileTrees(trees, ENTRY_POINT_FILENAMES[type]);
}
_treesForPlugins(type) {
return this.plugins.reduce((trees, plugin) => {
let pluginTree = this._treeForPlugin(plugin, type);
if (pluginTree) {
return trees.concat(pluginTree);
}
return trees
}, []);
}
// ember-service-worker: lib/service-worker-builder.js
const TREE_FOR_METHODS = {
'service-worker': 'treeForServiceWorker',
'service-worker-registration': 'treeForServiceWorkerRegistration'
};
module.exports = class ServiceWorkerBuilder {
_treeForPlugin(plugin, type) {
let pluginPath = path.resolve(plugin.root, type);
let treeForMethod = TREE_FOR_METHODS[type];
let tree;
}
if (fs.existsSync(pluginPath)) {
tree = this.app.treeGenerator(pluginPath);
}
if (plugin[treeForMethod]) {
tree = plugin[treeForMethod](tree, this.appTree);
}
if (tree) {
return new Funnel(tree, {
destDir: plugin.pkg.name + '/' + type
});
}
// ember-service-worker: lib/service-worker-builder.js
const TREE_FOR_METHODS = {
'service-worker': 'treeForServiceWorker'
};
module.exports = class ServiceWorkerBuilder {
_treeForPlugin(plugin) {
let pluginPath = path.resolve(plugin.root,'service-worker');
let treeForMethod = 'treeForServiceWorker';
let tree;
}
if (fs.existsSync(pluginPath)) {
tree = this.app.treeGenerator(pluginPath);
}
if (plugin[treeForMethod]) {
tree = plugin[treeForMethod](tree, this.appTree);
}
if (tree) {
return new Funnel(tree, {
destDir: plugin.pkg.name + '/service-worker'
});
}
// ember-service-worker: lib/service-worker-builder.js
const TREE_FOR_METHODS = {
'service-worker': 'treeForServiceWorker'
};
module.exports = class ServiceWorkerBuilder {
_treeForPlugin(plugin) {
let pluginPath = path.resolve(plugin.root,'service-worker');
let treeForMethod = 'treeForServiceWorker';
let tree;
}
// ember-service-worker: lib/service-worker-builder.js
const TREE_FOR_METHODS = {
'service-worker': 'treeForServiceWorker'
};
module.exports = class ServiceWorkerBuilder {
_treeForPlugin(plugin) {
let pluginPath = path.resolve(plugin.root,'service-worker');
let treeForMethod = 'treeForServiceWorker';
let tree;
}
// ember-service-worker: lib/service-worker-builder.js
if (fs.existsSync(pluginPath)) {
tree = this.app.treeGenerator(pluginPath);
}
const TREE_FOR_METHODS = {
'service-worker': 'treeForServiceWorker'
};
module.exports = class ServiceWorkerBuilder {
_treeForPlugin(plugin) {
let pluginPath = path.resolve(plugin.root,'service-worker');
let treeForMethod = 'treeForServiceWorker';
let tree;
}
if (fs.existsSync(pluginPath)) {
tree = this.app.treeGenerator(pluginPath);
}
if (plugin[treeForMethod]) {
tree = plugin[treeForMethod](tree, this.appTree);
}
// ember-service-worker: lib/service-worker-builder.js
const TREE_FOR_METHODS = {
'service-worker': 'treeForServiceWorker'
};
module.exports = class ServiceWorkerBuilder {
_treeForPlugin(plugin) {
let pluginPath = path.resolve(plugin.root,'service-worker');
let treeForMethod = 'treeForServiceWorker';
let tree;
}
if (fs.existsSync(pluginPath)) {
tree = this.app.treeGenerator(pluginPath);
}
if (plugin[treeForMethod]) {
tree = plugin[treeForMethod](tree, this.appTree);
}
if (tree) {
return new Funnel(tree, {
destDir: plugin.pkg.name + '/service-worker'
});
}
// ember-service-worker: lib/service-worker-builder.js
const TREE_FOR_METHODS = {
'service-worker': 'treeForServiceWorker'
};
module.exports = class ServiceWorkerBuilder {
_treeForPlugin(plugin) {
let pluginPath = path.resolve(plugin.root,'service-worker');
let treeForMethod = 'treeForServiceWorker';
let tree;
}
if (fs.existsSync(pluginPath)) {
tree = this.app.treeGenerator(pluginPath);
}
if (plugin[treeForMethod]) {
tree = plugin[treeForMethod](tree, this.appTree);
}
if (tree) {
return new Funnel(tree, {
destDir: plugin.pkg.name + '/service-worker'
});
}
// ember-service-worker: lib/service-worker-builder.js
const TREE_FOR_METHODS = {
'service-worker': 'treeForServiceWorker',
'service-worker-registration': 'treeForServiceWorkerRegistration'
};
module.exports = class ServiceWorkerBuilder {
build(type) {
let trees = this._treesForPlugins(type);
return this._compileTrees(trees, ENTRY_POINT_FILENAMES[type]);
}
// ember-service-worker: lib/service-worker-builder.js
const ENTRY_POINT_FILENAMES = {
'service-worker': 'sw.js',
'service-worker-registration': 'sw-registration.js'
};
module.exports = class ServiceWorkerBuilder {
build(type) {
let trees = this._treesForPlugins(type);
return this._compileTrees(trees, ENTRY_POINT_FILENAMES[type]);
}
// ember-service-worker: lib/service-worker-builder.js
const ENTRY_POINT_FILENAMES = {
'service-worker': 'sw.js'
};
module.exports = class ServiceWorkerBuilder {
build(type) {
let trees = this._treesForPlugins('service-worker');
return this._compileTrees(trees, 'sw.js');
}
// ember-service-worker: lib/service-worker-builder.js
_compileTrees(trees, 'sw.js') {
let entryPoint = new EntryPoint(trees, { entryPoint: 'sw.js' });
let tree = mergeTrees(trees.concat(entryPoint), { overwrite: true });
tree = this._babelTranspile(tree);
tree = this._rollupTree(tree, 'sw.js', this.treeDistPath);
tree = this._uglifyTree(tree);
return tree;
}
const ENTRY_POINT_FILENAMES = {
'service-worker': 'sw.js'
};
module.exports = class ServiceWorkerBuilder {
build(type) {
let trees = this._treesForPlugins('service-worker');
return this._compileTrees(trees, 'sw.js');
}
// ember-service-worker: lib/service-worker-builder.js
_compileTrees(trees, 'sw.js') {
let entryPoint = new EntryPoint(trees, { entryPoint: 'sw.js' });
}
const ENTRY_POINT_FILENAMES = {
'service-worker': 'sw.js'
};
module.exports = class ServiceWorkerBuilder {
build(type) {
let trees = this._treesForPlugins('service-worker');
return this._compileTrees(trees, 'sw.js');
}
// ember-service-worker: lib/service-worker-builder.js
const Plugin = require('broccoli-plugin');
const fs = require('fs');
const path = require('path');
const glob = require('glob');
module.exports = class EntryPoint extends Plugin {
constructor(inputNodes, options) {
super(inputNodes, {
name: options && options.name,
annotation: options && options.annotation
});
this.entryPoint = options && options.entryPoint;
}
build() {
let entryPointJS = this.inputPaths.reduce((lines, inputPath) => {
let indexJS = glob.sync('**/index.js', { cwd: inputPath })[0];
if (indexJS) {
let entryPointModuleName = path.dirname(indexJS);
return lines.concat(`import "${entryPointModuleName}";n`);
}
return lines;
}, '');
fs.writeFileSync(path.join(this.outputPath, this.entryPoint), entryPointJS);
}
};
// ember-service-worker: lib/entry-point.js
const Plugin = require('broccoli-plugin');
const fs = require('fs');
const path = require('path');
const glob = require('glob');
module.exports = class EntryPoint extends Plugin {
};
// ember-service-worker: lib/entry-point.js
const Plugin = require('broccoli-plugin');
const fs = require('fs');
const path = require('path');
const glob = require('glob');
module.exports = class EntryPoint extends Plugin {
};
constructor(inputNodes, options) {
super(inputNodes, {
name: options && options.name,
annotation: options && options.annotation
});
this.entryPoint = options && options.entryPoint;
}
// ember-service-worker: lib/entry-point.js
const Plugin = require('broccoli-plugin');
const fs = require('fs');
const path = require('path');
const glob = require('glob');
module.exports = class EntryPoint extends Plugin {
};
constructor(inputNodes, options) {
super(inputNodes, {
name: options && options.name,
annotation: options && options.annotation
});
this.entryPoint = options && options.entryPoint;
}
build() {
let entryPointJS = this.inputPaths.reduce((lines, inputPath) => {
}, '');
}
// ember-service-worker: lib/entry-point.js
const Plugin = require('broccoli-plugin');
const fs = require('fs');
const path = require('path');
const glob = require('glob');
module.exports = class EntryPoint extends Plugin {
};
constructor(inputNodes, options) {
super(inputNodes, {
name: options && options.name,
annotation: options && options.annotation
});
this.entryPoint = options && options.entryPoint;
}
build() {
let entryPointJS = this.inputPaths.reduce((lines, inputPath) => {
}, '');
}
let indexJS = glob.sync('**/index.js', { cwd: inputPath })[0];
if (indexJS) {
let entryPointModuleName = path.dirname(indexJS);
return lines.concat(`import "${entryPointModuleName}";n`);
}
return lines;
// ember-service-worker: lib/entry-point.js
const Plugin = require('broccoli-plugin');
const fs = require('fs');
const path = require('path');
const glob = require('glob');
module.exports = class EntryPoint extends Plugin {
};
constructor(inputNodes, options) {
super(inputNodes, {
name: options && options.name,
annotation: options && options.annotation
});
this.entryPoint = options && options.entryPoint;
}
build() {
let entryPointJS = this.inputPaths.reduce((lines, inputPath) => {
}, '');
}
let indexJS = glob.sync('**/index.js', { cwd: inputPath })[0];
if (indexJS) {
let entryPointModuleName = path.dirname(indexJS);
return lines.concat(`import "${entryPointModuleName}";n`);
}
return lines;
// ember-service-worker: lib/entry-point.js
const Plugin = require('broccoli-plugin');
const fs = require('fs');
const path = require('path');
const glob = require('glob');
module.exports = class EntryPoint extends Plugin {
};
constructor(inputNodes, options) {
super(inputNodes, {
name: options && options.name,
annotation: options && options.annotation
});
this.entryPoint = options && options.entryPoint;
}
build() {
let entryPointJS = this.inputPaths.reduce((lines, inputPath) => {
}, '');
}
let indexJS = glob.sync('**/index.js', { cwd: inputPath })[0];
if (indexJS) {
let entryPointModuleName = path.dirname(indexJS);
return lines.concat(`import "${entryPointModuleName}";n`);
}
return lines;
fs.writeFileSync(path.join(this.outputPath, this.entryPoint), entryPointJS);
// ember-service-worker: lib/entry-point.js
_compileTrees(trees, 'sw.js') {
let entryPoint = new EntryPoint(trees, { entryPoint: 'sw.js' });
let tree = mergeTrees(trees.concat(entryPoint), { overwrite: true });
}
const ENTRY_POINT_FILENAMES = {
'service-worker': 'sw.js'
};
module.exports = class ServiceWorkerBuilder {
build(type) {
let trees = this._treesForPlugins('service-worker');
return this._compileTrees(trees, 'sw.js');
}
// ember-service-worker: lib/service-worker-builder.js
_compileTrees(trees, 'sw.js') {
let entryPoint = new EntryPoint(trees, { entryPoint: 'sw.js' });
let tree = mergeTrees(trees.concat(entryPoint), { overwrite: true });
}
const ENTRY_POINT_FILENAMES = {
'service-worker': 'sw.js'
};
module.exports = class ServiceWorkerBuilder {
build(type) {
let trees = this._treesForPlugins('service-worker');
return this._compileTrees(trees, 'sw.js');
}
// ember-service-worker: lib/service-worker-builder.js
_compileTrees(trees, 'sw.js') {
let entryPoint = new EntryPoint(trees, { entryPoint: 'sw.js' });
let tree = mergeTrees(trees.concat(entryPoint), { overwrite: true });
}
const ENTRY_POINT_FILENAMES = {
'service-worker': 'sw.js'
};
module.exports = class ServiceWorkerBuilder {
build(type) {
let trees = this._treesForPlugins('service-worker');
return this._compileTrees(trees, 'sw.js');
}
// ember-service-worker: lib/service-worker-builder.js
tree = this._babelTranspile(tree);
tree = this._rollupTree(tree, 'sw.js', this.treeDistPath);
tree = this._uglifyTree(tree);
return tree;
let serviceWorkerTree = serviceWorkerBuilder.build('service-worker');
let serviceWorkerRegistrationTree =
serviceWorkerBuilder.build('service-worker-registration');
let serviceWorkerBuilder = new ServiceWorkerBuilder({
app: this,
appTree,
minifyJS: this.app.options.minifyJS,
fingerprint: this.app.options.fingerprint.enabled,
plugins,
rootURL: this._getRootURL(),
sourcemaps: this.app.options.sourcemaps,
registrationDistPath: options.registrationDistPath
});
postprocessTree(type, appTree) {
let options = this._getOptions();
if (type !== 'all' || options.enabled === false) {
return appTree;
}
}
// 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);
// ember-service-worker: index.js
let plugins = this._findPluginsFor(this.project);
postprocessTree(type, appTree) {
let options = this._getOptions();
if (type !== 'all' || options.enabled === false) {
return appTree;
}
}
return mergeTrees([
appTree,
serviceWorkerTree,
serviceWorkerRegistrationTree
], { overwrite: true });
let serviceWorkerTree = serviceWorkerBuilder.build('service-worker');
let serviceWorkerRegistrationTree =
serviceWorkerBuilder.build('service-worker-registration');
let serviceWorkerBuilder = new ServiceWorkerBuilder({
app: this,
appTree,
minifyJS: this.app.options.minifyJS,
fingerprint: this.app.options.fingerprint.enabled,
plugins,
rootURL: this._getRootURL(),
sourcemaps: this.app.options.sourcemaps,
registrationDistPath: options.registrationDistPath
});
// 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);
// ember-service-worker: index.js
let plugins = this._findPluginsFor(this.project);
postprocessTree(type, appTree) {
let options = this._getOptions();
if (type !== 'all' || options.enabled === false) {
return appTree;
}
}
return mergeTrees([
appTree,
serviceWorkerTree,
serviceWorkerRegistrationTree
], { overwrite: true });
let serviceWorkerTree = serviceWorkerBuilder.build('service-worker');
let serviceWorkerRegistrationTree =
serviceWorkerBuilder.build('service-worker-registration');
let serviceWorkerBuilder = new ServiceWorkerBuilder({
app: this,
appTree,
minifyJS: this.app.options.minifyJS,
fingerprint: this.app.options.fingerprint.enabled,
plugins,
rootURL: this._getRootURL(),
sourcemaps: this.app.options.sourcemaps,
registrationDistPath: options.registrationDistPath
});
// 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);
// ember-service-worker: index.js
let plugins = this._findPluginsFor(this.project);
Testing
The
Waters
Testing
79
• Not like testing an Ember application
Testing
79
• Not like testing an Ember application
• Testing the build for an application
Testing
79
• Not like testing an Ember application
• Testing the build for an application
• Testing in Node.js
Testing
79
• Not like testing an Ember application
• Testing the build for an application
• Testing in Node.js
• Mocha
Testing
79
• Not like testing an Ember application
• Testing the build for an application
• Testing in Node.js
• Mocha
Testing
80
81
Testing
81
Testing
• Unit tests for core functionality
81
Testing
• Unit tests for core functionality
• Host addon should test that the API is called
when expected
81
Testing
• Unit tests for core functionality
• Host addon should test that the API is called
when expected
• Plugins should test that they implement
required hooks
81
Testing
• Unit tests for core functionality
• Host addon should test that the API is called
when expected
• Plugins should test that they implement
required hooks
• Both should test the build output
81
Testing
Testing Broccoli Builds
82
var helper = require('broccoli-test-helper');
var createBuilder = helper.createBuilder;
// ember-service-worker: node-tests/service-worker-builder-test.js
var helper = require('broccoli-test-helper');
var createBuilder = helper.createBuilder;
it('transpiles code with babel', async () => {
let plugins = [generatePlugin('test-project', 'builder-test/babel')];
});
// ember-service-worker: node-tests/service-worker-builder-test.js
var helper = require('broccoli-test-helper');
var createBuilder = helper.createBuilder;
it('transpiles code with babel', async () => {
let plugins = [generatePlugin('test-project', 'builder-test/babel')];
});
let subject = new ServiceWorkerBuilder({ app, plugins });
let output = createBuilder(subject);
// ember-service-worker: node-tests/service-worker-builder-test.js
var helper = require('broccoli-test-helper');
var createBuilder = helper.createBuilder;
it('transpiles code with babel', async () => {
let plugins = [generatePlugin('test-project', 'builder-test/babel')];
});
let subject = new ServiceWorkerBuilder({ app, plugins });
let output = createBuilder(subject);
await output.build();
// ember-service-worker: node-tests/service-worker-builder-test.js
var helper = require('broccoli-test-helper');
var createBuilder = helper.createBuilder;
it('transpiles code with babel', async () => {
let plugins = [generatePlugin('test-project', 'builder-test/babel')];
});
let subject = new ServiceWorkerBuilder({ app, plugins });
let output = createBuilder(subject);
let files = output.read();
assert.property(files, 'sw.js');
assert.equal(files['sw.js'], expected);
// ember-service-worker: node-tests/service-worker-builder-test.js
await output.build();
let expected = ....;
What We Learned
88
What We Learned
88
• Plugin architecture is just a fancy way of talking about what
we already know
What We Learned
88
• Plugin architecture is just a fancy way of talking about what
we already know
• Ember addons implement a plugin architecture via hooks
and can provide their own API for other addons
What We Learned
88
• Plugin architecture is just a fancy way of talking about what
we already know
• Ember addons implement a plugin architecture via hooks
and can provide their own API for other addons
• Broccoli, Babel, and Rollup implement a plugin architecture
What We Learned
88
• Plugin architecture is just a fancy way of talking about what
we already know
• Ember addons implement a plugin architecture via hooks
and can provide their own API for other addons
• Broccoli, Babel, and Rollup implement a plugin architecture
• None of these are scary but are usually more complicated
than a hand-wavy demo
What We Learned
88
• Plugin architecture is just a fancy way of talking about what
we already know
• Ember addons implement a plugin architecture via hooks
and can provide their own API for other addons
• Broccoli, Babel, and Rollup implement a plugin architecture
• None of these are scary but are usually more complicated
than a hand-wavy demo
• We can take advantage of these concepts to write our own
addon ecosystems
Reading the Source
Lisa Backer, DockYard

eshtadc
@eshtadc
@eshtadc
Thank You!
90

More Related Content

What's hot

GAEO
GAEOGAEO
slingmodels
slingmodelsslingmodels
slingmodels
Ankur Chauhan
 
Ruby on Rails testing with Rspec
Ruby on Rails testing with RspecRuby on Rails testing with Rspec
Ruby on Rails testing with Rspec
Bunlong Van
 
RSpec 2 Best practices
RSpec 2 Best practicesRSpec 2 Best practices
RSpec 2 Best practices
Andrea Reginato
 
Supercharging WordPress Development - Wordcamp Brighton 2019
Supercharging WordPress Development - Wordcamp Brighton 2019Supercharging WordPress Development - Wordcamp Brighton 2019
Supercharging WordPress Development - Wordcamp Brighton 2019
Adam Tomat
 
Entry-level PHP for WordPress
Entry-level PHP for WordPressEntry-level PHP for WordPress
Entry-level PHP for WordPress
sprclldr
 
You don't know people
You don't know peopleYou don't know people
You don't know people
Evan Solomon
 
APIdays Helsinki 2019 - Specification-Driven Development of REST APIs with Al...
APIdays Helsinki 2019 - Specification-Driven Development of REST APIs with Al...APIdays Helsinki 2019 - Specification-Driven Development of REST APIs with Al...
APIdays Helsinki 2019 - Specification-Driven Development of REST APIs with Al...
apidays
 
OSCON Google App Engine Codelab - July 2010
OSCON Google App Engine Codelab - July 2010OSCON Google App Engine Codelab - July 2010
OSCON Google App Engine Codelab - July 2010
ikailan
 
Django for mobile applications
Django for mobile applicationsDjango for mobile applications
Django for mobile applications
Hassan Abid
 
OCM Java 開發人員認證與設計模式
OCM Java 開發人員認證與設計模式OCM Java 開發人員認證與設計模式
OCM Java 開發人員認證與設計模式
CodeData
 
RxJS + Redux + React = Amazing
RxJS + Redux + React = AmazingRxJS + Redux + React = Amazing
RxJS + Redux + React = Amazing
Jay Phelps
 
APIdays Helsinki 2019 - API Versioning with REST, JSON and Swagger with Thoma...
APIdays Helsinki 2019 - API Versioning with REST, JSON and Swagger with Thoma...APIdays Helsinki 2019 - API Versioning with REST, JSON and Swagger with Thoma...
APIdays Helsinki 2019 - API Versioning with REST, JSON and Swagger with Thoma...
apidays
 
Web Performance Culture and Tools at Etsy
Web Performance Culture and Tools at EtsyWeb Performance Culture and Tools at Etsy
Web Performance Culture and Tools at Etsy
Mike Brittain
 
AWS Elastic Beanstalk
AWS Elastic BeanstalkAWS Elastic Beanstalk
AWS Elastic Beanstalk
Amazon Web Services
 
Django for IoT: From hackathon to production (DjangoCon US)
Django for IoT: From hackathon to production (DjangoCon US)Django for IoT: From hackathon to production (DjangoCon US)
Django for IoT: From hackathon to production (DjangoCon US)
Anna Schneider
 
Real World Dependency Injection - oscon13
Real World Dependency Injection - oscon13Real World Dependency Injection - oscon13
Real World Dependency Injection - oscon13
Stephan Hochdörfer
 
Real-time Insights, powered by Reactive Programming
Real-time Insights, powered by Reactive ProgrammingReal-time Insights, powered by Reactive Programming
Real-time Insights, powered by Reactive Programming
Jay Phelps
 
Socket applications
Socket applicationsSocket applications
Socket applications
João Moura
 

What's hot (19)

GAEO
GAEOGAEO
GAEO
 
slingmodels
slingmodelsslingmodels
slingmodels
 
Ruby on Rails testing with Rspec
Ruby on Rails testing with RspecRuby on Rails testing with Rspec
Ruby on Rails testing with Rspec
 
RSpec 2 Best practices
RSpec 2 Best practicesRSpec 2 Best practices
RSpec 2 Best practices
 
Supercharging WordPress Development - Wordcamp Brighton 2019
Supercharging WordPress Development - Wordcamp Brighton 2019Supercharging WordPress Development - Wordcamp Brighton 2019
Supercharging WordPress Development - Wordcamp Brighton 2019
 
Entry-level PHP for WordPress
Entry-level PHP for WordPressEntry-level PHP for WordPress
Entry-level PHP for WordPress
 
You don't know people
You don't know peopleYou don't know people
You don't know people
 
APIdays Helsinki 2019 - Specification-Driven Development of REST APIs with Al...
APIdays Helsinki 2019 - Specification-Driven Development of REST APIs with Al...APIdays Helsinki 2019 - Specification-Driven Development of REST APIs with Al...
APIdays Helsinki 2019 - Specification-Driven Development of REST APIs with Al...
 
OSCON Google App Engine Codelab - July 2010
OSCON Google App Engine Codelab - July 2010OSCON Google App Engine Codelab - July 2010
OSCON Google App Engine Codelab - July 2010
 
Django for mobile applications
Django for mobile applicationsDjango for mobile applications
Django for mobile applications
 
OCM Java 開發人員認證與設計模式
OCM Java 開發人員認證與設計模式OCM Java 開發人員認證與設計模式
OCM Java 開發人員認證與設計模式
 
RxJS + Redux + React = Amazing
RxJS + Redux + React = AmazingRxJS + Redux + React = Amazing
RxJS + Redux + React = Amazing
 
APIdays Helsinki 2019 - API Versioning with REST, JSON and Swagger with Thoma...
APIdays Helsinki 2019 - API Versioning with REST, JSON and Swagger with Thoma...APIdays Helsinki 2019 - API Versioning with REST, JSON and Swagger with Thoma...
APIdays Helsinki 2019 - API Versioning with REST, JSON and Swagger with Thoma...
 
Web Performance Culture and Tools at Etsy
Web Performance Culture and Tools at EtsyWeb Performance Culture and Tools at Etsy
Web Performance Culture and Tools at Etsy
 
AWS Elastic Beanstalk
AWS Elastic BeanstalkAWS Elastic Beanstalk
AWS Elastic Beanstalk
 
Django for IoT: From hackathon to production (DjangoCon US)
Django for IoT: From hackathon to production (DjangoCon US)Django for IoT: From hackathon to production (DjangoCon US)
Django for IoT: From hackathon to production (DjangoCon US)
 
Real World Dependency Injection - oscon13
Real World Dependency Injection - oscon13Real World Dependency Injection - oscon13
Real World Dependency Injection - oscon13
 
Real-time Insights, powered by Reactive Programming
Real-time Insights, powered by Reactive ProgrammingReal-time Insights, powered by Reactive Programming
Real-time Insights, powered by Reactive Programming
 
Socket applications
Socket applicationsSocket applications
Socket applications
 

Similar to Anatomy of an Addon Ecosystem - EmberConf 2019

JavaScript code generator with Yeoman
JavaScript code generator with YeomanJavaScript code generator with Yeoman
JavaScript code generator with Yeoman
tomi vanek
 
Quality Use Of Plugin
Quality Use Of PluginQuality Use Of Plugin
Quality Use Of Plugin
Yasuo Harada
 
Deploy, Manage, and Scale Your Apps with OpsWorks and Elastic Beanstalk
Deploy, Manage, and Scale Your Apps with OpsWorks and Elastic BeanstalkDeploy, Manage, and Scale Your Apps with OpsWorks and Elastic Beanstalk
Deploy, Manage, and Scale Your Apps with OpsWorks and Elastic Beanstalk
Amazon Web Services
 
Building Offline Ready and Installable Web App
Building Offline Ready and Installable Web AppBuilding Offline Ready and Installable Web App
Building Offline Ready and Installable Web App
Muhammad Samu
 
Plugging into plugins
Plugging into pluginsPlugging into plugins
Plugging into plugins
Josh Harrison
 
Dev ops
Dev opsDev ops
The Web on OSGi: Here's How
The Web on OSGi: Here's HowThe Web on OSGi: Here's How
The Web on OSGi: Here's How
mrdon
 
Writing extensible plugins
Writing extensible pluginsWriting extensible plugins
Writing extensible plugins
Imran Nathani
 
Progressive Web Apps 101
Progressive Web Apps 101Progressive Web Apps 101
Progressive Web Apps 101
Muhammad Samu
 
Making Continuous Security a Reality with OWASP’s AppSec Pipeline - Matt Tesa...
Making Continuous Security a Reality with OWASP’s AppSec Pipeline - Matt Tesa...Making Continuous Security a Reality with OWASP’s AppSec Pipeline - Matt Tesa...
Making Continuous Security a Reality with OWASP’s AppSec Pipeline - Matt Tesa...
Matt Tesauro
 
Drilling Cyber Security Data With Apache Drill
Drilling Cyber Security Data With Apache DrillDrilling Cyber Security Data With Apache Drill
Drilling Cyber Security Data With Apache Drill
Charles Givre
 
Developing Highly Instrumented Applications with Minimal Effort
Developing Highly Instrumented Applications with Minimal EffortDeveloping Highly Instrumented Applications with Minimal Effort
Developing Highly Instrumented Applications with Minimal Effort
Tim Hobson
 
Open Social In The Enterprise
Open Social In The EnterpriseOpen Social In The Enterprise
Open Social In The Enterprise
Tim Moore
 
Uncovering breaking changes behind UI on mobile applications
Uncovering breaking changes behind UI on mobile applicationsUncovering breaking changes behind UI on mobile applications
Uncovering breaking changes behind UI on mobile applications
Kazuaki Matsuo
 
Scaling with Automation
Scaling with AutomationScaling with Automation
Scaling with Automation
Uchit Vyas ☁
 
Use Jenkins For Continuous Load Testing And Mobile Test Automation
Use Jenkins For Continuous Load Testing And Mobile Test AutomationUse Jenkins For Continuous Load Testing And Mobile Test Automation
Use Jenkins For Continuous Load Testing And Mobile Test Automation
Clever Moe
 
Django deployment best practices
Django deployment best practicesDjango deployment best practices
Django deployment best practices
Erik LaBianca
 
Continuous Integration, Deploy, Test From Beginning To End 2014
Continuous Integration, Deploy, Test From Beginning To End 2014Continuous Integration, Deploy, Test From Beginning To End 2014
Continuous Integration, Deploy, Test From Beginning To End 2014
Clever Moe
 
SMC304 Serverless Orchestration with AWS Step Functions
SMC304 Serverless Orchestration with AWS Step FunctionsSMC304 Serverless Orchestration with AWS Step Functions
SMC304 Serverless Orchestration with AWS Step Functions
Amazon Web Services
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013
Michelangelo van Dam
 

Similar to Anatomy of an Addon Ecosystem - EmberConf 2019 (20)

JavaScript code generator with Yeoman
JavaScript code generator with YeomanJavaScript code generator with Yeoman
JavaScript code generator with Yeoman
 
Quality Use Of Plugin
Quality Use Of PluginQuality Use Of Plugin
Quality Use Of Plugin
 
Deploy, Manage, and Scale Your Apps with OpsWorks and Elastic Beanstalk
Deploy, Manage, and Scale Your Apps with OpsWorks and Elastic BeanstalkDeploy, Manage, and Scale Your Apps with OpsWorks and Elastic Beanstalk
Deploy, Manage, and Scale Your Apps with OpsWorks and Elastic Beanstalk
 
Building Offline Ready and Installable Web App
Building Offline Ready and Installable Web AppBuilding Offline Ready and Installable Web App
Building Offline Ready and Installable Web App
 
Plugging into plugins
Plugging into pluginsPlugging into plugins
Plugging into plugins
 
Dev ops
Dev opsDev ops
Dev ops
 
The Web on OSGi: Here's How
The Web on OSGi: Here's HowThe Web on OSGi: Here's How
The Web on OSGi: Here's How
 
Writing extensible plugins
Writing extensible pluginsWriting extensible plugins
Writing extensible plugins
 
Progressive Web Apps 101
Progressive Web Apps 101Progressive Web Apps 101
Progressive Web Apps 101
 
Making Continuous Security a Reality with OWASP’s AppSec Pipeline - Matt Tesa...
Making Continuous Security a Reality with OWASP’s AppSec Pipeline - Matt Tesa...Making Continuous Security a Reality with OWASP’s AppSec Pipeline - Matt Tesa...
Making Continuous Security a Reality with OWASP’s AppSec Pipeline - Matt Tesa...
 
Drilling Cyber Security Data With Apache Drill
Drilling Cyber Security Data With Apache DrillDrilling Cyber Security Data With Apache Drill
Drilling Cyber Security Data With Apache Drill
 
Developing Highly Instrumented Applications with Minimal Effort
Developing Highly Instrumented Applications with Minimal EffortDeveloping Highly Instrumented Applications with Minimal Effort
Developing Highly Instrumented Applications with Minimal Effort
 
Open Social In The Enterprise
Open Social In The EnterpriseOpen Social In The Enterprise
Open Social In The Enterprise
 
Uncovering breaking changes behind UI on mobile applications
Uncovering breaking changes behind UI on mobile applicationsUncovering breaking changes behind UI on mobile applications
Uncovering breaking changes behind UI on mobile applications
 
Scaling with Automation
Scaling with AutomationScaling with Automation
Scaling with Automation
 
Use Jenkins For Continuous Load Testing And Mobile Test Automation
Use Jenkins For Continuous Load Testing And Mobile Test AutomationUse Jenkins For Continuous Load Testing And Mobile Test Automation
Use Jenkins For Continuous Load Testing And Mobile Test Automation
 
Django deployment best practices
Django deployment best practicesDjango deployment best practices
Django deployment best practices
 
Continuous Integration, Deploy, Test From Beginning To End 2014
Continuous Integration, Deploy, Test From Beginning To End 2014Continuous Integration, Deploy, Test From Beginning To End 2014
Continuous Integration, Deploy, Test From Beginning To End 2014
 
SMC304 Serverless Orchestration with AWS Step Functions
SMC304 Serverless Orchestration with AWS Step FunctionsSMC304 Serverless Orchestration with AWS Step Functions
SMC304 Serverless Orchestration with AWS Step Functions
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013
 

Recently uploaded

Fundamentals of Programming and Language Processors
Fundamentals of Programming and Language ProcessorsFundamentals of Programming and Language Processors
Fundamentals of Programming and Language Processors
Rakesh Kumar R
 
Liberarsi dai framework con i Web Component.pptx
Liberarsi dai framework con i Web Component.pptxLiberarsi dai framework con i Web Component.pptx
Liberarsi dai framework con i Web Component.pptx
Massimo Artizzu
 
YAML crash COURSE how to write yaml file for adding configuring details
YAML crash COURSE how to write yaml file for adding configuring detailsYAML crash COURSE how to write yaml file for adding configuring details
YAML crash COURSE how to write yaml file for adding configuring details
NishanthaBulumulla1
 
14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision
ShulagnaSarkar2
 
GreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-JurisicGreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-Jurisic
Green Software Development
 
UI5con 2024 - Bring Your Own Design System
UI5con 2024 - Bring Your Own Design SystemUI5con 2024 - Bring Your Own Design System
UI5con 2024 - Bring Your Own Design System
Peter Muessig
 
Project Management: The Role of Project Dashboards.pdf
Project Management: The Role of Project Dashboards.pdfProject Management: The Role of Project Dashboards.pdf
Project Management: The Role of Project Dashboards.pdf
Karya Keeper
 
Using Query Store in Azure PostgreSQL to Understand Query Performance
Using Query Store in Azure PostgreSQL to Understand Query PerformanceUsing Query Store in Azure PostgreSQL to Understand Query Performance
Using Query Store in Azure PostgreSQL to Understand Query Performance
Grant Fritchey
 
Modelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - AmsterdamModelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - Amsterdam
Alberto Brandolini
 
zOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL DifferenceszOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL Differences
YousufSait3
 
Microservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we workMicroservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we work
Sven Peters
 
J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...
J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...
J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...
Bert Jan Schrijver
 
ALGIT - Assembly Line for Green IT - Numbers, Data, Facts
ALGIT - Assembly Line for Green IT - Numbers, Data, FactsALGIT - Assembly Line for Green IT - Numbers, Data, Facts
ALGIT - Assembly Line for Green IT - Numbers, Data, Facts
Green Software Development
 
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CDKuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
rodomar2
 
Artificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension FunctionsArtificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension Functions
Octavian Nadolu
 
一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理
dakas1
 
Oracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptxOracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptx
Remote DBA Services
 
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
dakas1
 
All you need to know about Spring Boot and GraalVM
All you need to know about Spring Boot and GraalVMAll you need to know about Spring Boot and GraalVM
All you need to know about Spring Boot and GraalVM
Alina Yurenko
 
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
mz5nrf0n
 

Recently uploaded (20)

Fundamentals of Programming and Language Processors
Fundamentals of Programming and Language ProcessorsFundamentals of Programming and Language Processors
Fundamentals of Programming and Language Processors
 
Liberarsi dai framework con i Web Component.pptx
Liberarsi dai framework con i Web Component.pptxLiberarsi dai framework con i Web Component.pptx
Liberarsi dai framework con i Web Component.pptx
 
YAML crash COURSE how to write yaml file for adding configuring details
YAML crash COURSE how to write yaml file for adding configuring detailsYAML crash COURSE how to write yaml file for adding configuring details
YAML crash COURSE how to write yaml file for adding configuring details
 
14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision
 
GreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-JurisicGreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-Jurisic
 
UI5con 2024 - Bring Your Own Design System
UI5con 2024 - Bring Your Own Design SystemUI5con 2024 - Bring Your Own Design System
UI5con 2024 - Bring Your Own Design System
 
Project Management: The Role of Project Dashboards.pdf
Project Management: The Role of Project Dashboards.pdfProject Management: The Role of Project Dashboards.pdf
Project Management: The Role of Project Dashboards.pdf
 
Using Query Store in Azure PostgreSQL to Understand Query Performance
Using Query Store in Azure PostgreSQL to Understand Query PerformanceUsing Query Store in Azure PostgreSQL to Understand Query Performance
Using Query Store in Azure PostgreSQL to Understand Query Performance
 
Modelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - AmsterdamModelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - Amsterdam
 
zOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL DifferenceszOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL Differences
 
Microservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we workMicroservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we work
 
J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...
J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...
J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...
 
ALGIT - Assembly Line for Green IT - Numbers, Data, Facts
ALGIT - Assembly Line for Green IT - Numbers, Data, FactsALGIT - Assembly Line for Green IT - Numbers, Data, Facts
ALGIT - Assembly Line for Green IT - Numbers, Data, Facts
 
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CDKuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
 
Artificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension FunctionsArtificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension Functions
 
一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理
 
Oracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptxOracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptx
 
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
 
All you need to know about Spring Boot and GraalVM
All you need to know about Spring Boot and GraalVMAll you need to know about Spring Boot and GraalVM
All you need to know about Spring Boot and GraalVM
 
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
 

Anatomy of an Addon Ecosystem - EmberConf 2019

  • 1. Anatomy of an Addon Ecosystem
  • 2.
  • 3. Figuring Out an Addon Ecosystem by Reading Code Line... By... Line
  • 4. Figuring Out an Addon Ecosystem by Reading Code Line... By... Line Special Assistance Provided by
  • 5. 3 • A lifelong Muppet fan • A maintainer for ember-service-worker • Web developer for almost 20 years • Remember before open source was a "thing" Lisa Backer Senior Software Engineer, DockYard
  • 6. 3 • A lifelong Muppet fan • A maintainer for ember-service-worker • Web developer for almost 20 years • Remember before open source was a "thing" Lisa Backer Senior Software Engineer, DockYard • Yes... I'm feeling old right now
  • 8. • Script in your browser that runs in the background 4 Intro: Service Worker
  • 9. • Script in your browser that runs in the background • Can enable offline cache, intercept network requests, push notifications, background synchronizations, and more 4 Intro: Service Worker
  • 10. • Script in your browser that runs in the background • Can enable offline cache, intercept network requests, push notifications, background synchronizations, and more • Must be registered from JavaScript by loading a definition file 4 Intro: Service Worker
  • 12. Intro: Ember-Service-Worker • An addon that provides a framework 5
  • 13. Intro: Ember-Service-Worker • An addon that provides a framework • Compiles and register custom service workers 5
  • 14. Intro: Ember-Service-Worker • An addon that provides a framework • Compiles and register custom service workers • The plugins define the actual service worker scripts. 5
  • 15. Intro: Ember-Service-Worker • An addon that provides a framework • Compiles and register custom service workers • The plugins define the actual service worker scripts. 5
  • 16.
  • 17.
  • 20.
  • 23. Study Goals 9 • What is a "plugin"
  • 24. Study Goals 9 • What is a "plugin" • How does registration process work
  • 25. Study Goals 9 • What is a "plugin" • How does registration process work • How do we locate plugins that the application has installed
  • 26. Study Goals 9 • What is a "plugin" • How does registration process work • How do we locate plugins that the application has installed • How do we test it all
  • 27. 10 What Is a Plugin? A plugin is a bundle that adds functionality to an application, called the host application, through some well-defined architecture for extensibility
  • 28. 10 What Is a Plugin? A plugin is a bundle that adds functionality to an application, called the host application, through some well-defined architecture for extensibility
  • 29. • Enable developers to extend an application 10 What Is a Plugin? A plugin is a bundle that adds functionality to an application, called the host application, through some well-defined architecture for extensibility
  • 30. • Enable developers to extend an application • Reduce the size of the application 10 What Is a Plugin? A plugin is a bundle that adds functionality to an application, called the host application, through some well-defined architecture for extensibility
  • 31. • Enable developers to extend an application • Reduce the size of the application • Provides a well-defined API 10 What Is a Plugin? A plugin is a bundle that adds functionality to an application, called the host application, through some well-defined architecture for extensibility
  • 32. An Addon Is a Plugin 11
  • 41.
  • 43. // Configures Ember CLI's build system to not add a fingerprint to sw.js this.app.options.fingerprint = this.app.options.fingerprint || {}; this.app.options.fingerprint.exclude = this.app.options.fingerprint.exclude || []; this.app.options.fingerprint.exclude.push('sw.js'); included(app) { if (this._super.included) { this._super.included.apply(this, arguments); } } this.app = app; this.app.options = this.app.options || {}; let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {} options.registrationStrategy = options.registrationStrategy || 'default'; if (process.env.SW_DISABLED) { options.enabled = false; } if (options.registrationStrategy === 'after-ember' && options.enabled !== false) { app.import('vendor/ember-service-worker/load-registration-script.js'); } if (options.enabled === undefined) { options.enabled = this.app.env !== 'test'; } // ember-service-worker: index.js
  • 44. included(app) { if (this._super.included) { this._super.included.apply(this, arguments); } } this.app = app; this.app.options = this.app.options || {}; let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {} options.registrationStrategy = options.registrationStrategy || 'default'; if (process.env.SW_DISABLED) { options.enabled = false; } if (options.registrationStrategy === 'after-ember' && options.enabled !== false) { app.import('vendor/ember-service-worker/load-registration-script.js'); } if (options.enabled === undefined) { options.enabled = this.app.env !== 'test'; } // ember-service-worker: index.js
  • 45. included(app) { if (this._super.included) { this._super.included.apply(this, arguments); } } this.app = app; this.app.options = this.app.options || {}; // ember-service-worker: index.js
  • 46. included(app) { if (this._super.included) { this._super.included.apply(this, arguments); } } this.app = app; this.app.options = this.app.options || {}; // ember-service-worker: index.js
  • 47. included(app) { if (this._super.included) { this._super.included.apply(this, arguments); } } this.app = app; this.app.options = this.app.options || {}; // ember-service-worker: index.js
  • 48. included(app) { if (this._super.included) { this._super.included.apply(this, arguments); } } this.app = app; this.app.options = this.app.options || {}; let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {} // ember-service-worker: index.js
  • 49. included(app) { if (this._super.included) { this._super.included.apply(this, arguments); } } this.app = app; this.app.options = this.app.options || {}; let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {} // ember-service-worker: index.js
  • 50. included(app) { if (this._super.included) { this._super.included.apply(this, arguments); } } this.app = app; this.app.options = this.app.options || {}; let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {} options.registrationStrategy = options.registrationStrategy || 'default'; // ember-service-worker: index.js
  • 51. included(app) { if (this._super.included) { this._super.included.apply(this, arguments); } } this.app = app; this.app.options = this.app.options || {}; let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {} options.registrationStrategy = options.registrationStrategy || 'default'; if (options.enabled === undefined) { options.enabled = this.app.env !== 'test'; } // ember-service-worker: index.js
  • 52. included(app) { if (this._super.included) { this._super.included.apply(this, arguments); } } this.app = app; this.app.options = this.app.options || {}; let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {} options.registrationStrategy = options.registrationStrategy || 'default'; if (process.env.SW_DISABLED) { options.enabled = false; } if (options.enabled === undefined) { options.enabled = this.app.env !== 'test'; } // ember-service-worker: index.js
  • 53. included(app) { if (this._super.included) { this._super.included.apply(this, arguments); } } this.app = app; this.app.options = this.app.options || {}; let options = this.app.options['ember-service-worker'] = this.app.options['ember-service-worker'] || {} options.registrationStrategy = options.registrationStrategy || 'default'; if (process.env.SW_DISABLED) { options.enabled = false; } if (options.enabled === undefined) { options.enabled = this.app.env !== 'test'; } if (options.registrationStrategy === 'after-ember' && options.enabled !== false) { app.import('vendor/ember-service-worker/load-registration-script.js'); } // ember-service-worker: index.js
  • 56. Searching for the Import treeForVendor() { return writeFile('ember-service-worker/load- registration-script.js', ` ....... `); } 28 app.import('vendor/ember-service-worker/load-registration-script.js');
  • 59.
  • 60.
  • 63. Broccoli Trees 34 Image credit: http://www.oligriffiths.com/broccolijs/
  • 64. Broccoli Trees Are actually now called broccoli nodes 34 Image credit: http://www.oligriffiths.com/broccolijs/
  • 65. Broccoli Trees Are actually now called broccoli nodes Represent a set of files that can be transformed by plugins 34 Image credit: http://www.oligriffiths.com/broccolijs/
  • 66. Broccoli Trees Are actually now called broccoli nodes Represent a set of files that can be transformed by plugins A build function is called on each plugin 34 Image credit: http://www.oligriffiths.com/broccolijs/
  • 67. Broccoli Trees Are actually now called broccoli nodes Represent a set of files that can be transformed by plugins A build function is called on each plugin Broccoli itself is the tool that handles plugging all this together so that we end up with our build output. 34 Image credit: http://www.oligriffiths.com/broccolijs/
  • 69. included(app) { ... if (options.registrationStrategy === 'after-ember' && options.enabled !== false) { app.import('vendor/ember-service-worker/load-registration-script.js'); } }, treeForVendor() { return writeFile('ember-service-worker/load-registration-script.js', ` (function() { if (typeof FastBoot === 'undefined') { var script = document.createElement('script') script.src = '${this._getRootURL()}sw-registration.js'; document.body.appendChild(script); } })(); `); } // ember-service-worker: index.js
  • 70.
  • 72. postprocessTree(type, appTree) { let options = this._getOptions(); if (type !== 'all' || options.enabled === false) { 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); let serviceWorkerBuilder = new ServiceWorkerBuilder({ app: this, appTree, minifyJS: this.app.options.minifyJS, fingerprint: this.app.options.fingerprint.enabled, plugins, rootURL: this._getRootURL(), sourcemaps: this.app.options.sourcemaps, registrationDistPath: options.registrationDistPath }); let serviceWorkerTree = serviceWorkerBuilder.build('service-worker'); let serviceWorkerRegistrationTree = serviceWorkerBuilder.build('service-worker-registration'); return mergeTrees([ appTree, serviceWorkerTree, serviceWorkerRegistrationTree ], { overwrite: true }); // ember-service-worker: index.js
  • 74. postprocessTree(type, appTree) { let options = this._getOptions(); if (type !== 'all' || options.enabled === false) { 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); let serviceWorkerBuilder = new ServiceWorkerBuilder({ app: this, appTree, minifyJS: this.app.options.minifyJS, fingerprint: this.app.options.fingerprint.enabled, plugins, rootURL: this._getRootURL(), sourcemaps: this.app.options.sourcemaps, registrationDistPath: options.registrationDistPath }); let serviceWorkerTree = serviceWorkerBuilder.build('service-worker'); let serviceWorkerRegistrationTree = serviceWorkerBuilder.build('service-worker-registration'); return mergeTrees([ appTree, serviceWorkerTree, serviceWorkerRegistrationTree ], { overwrite: true }); // ember-service-worker: index.js
  • 75. _findPluginsFor(project) { let addons = project.addons || []; return addonUtils.filterByKeyword(addons, 'ember-service-worker-plugin'); } postprocessTree(type, appTree) { let options = this._getOptions(); if (type !== 'all' || options.enabled === false) { return appTree; } } let plugins = this._findPluginsFor(this.project); // ember-service-worker: index.js
  • 76. _findPluginsFor(project) { let addons = project.addons || []; return addonUtils.filterByKeyword(addons, 'ember-service-worker-plugin'); } postprocessTree(type, appTree) { let options = this._getOptions(); if (type !== 'all' || options.enabled === false) { return appTree; } } let plugins = this._findPluginsFor(this.project); // ember-service-worker: index.js
  • 77. postprocessTree(type, appTree) { let options = this._getOptions(); if (type !== 'all' || options.enabled === false) { 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); // ember-service-worker: index.js
  • 78. let serviceWorkerBuilder = new ServiceWorkerBuilder({ app: this, appTree, minifyJS: this.app.options.minifyJS, fingerprint: this.app.options.fingerprint.enabled, plugins, rootURL: this._getRootURL(), sourcemaps: this.app.options.sourcemaps, registrationDistPath: options.registrationDistPath }); postprocessTree(type, appTree) { let options = this._getOptions(); if (type !== 'all' || options.enabled === false) { return appTree; } } // 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); // ember-service-worker: index.js let plugins = this._findPluginsFor(this.project);
  • 79. let serviceWorkerTree = serviceWorkerBuilder.build('service-worker'); let serviceWorkerRegistrationTree = serviceWorkerBuilder.build('service-worker-registration'); let serviceWorkerBuilder = new ServiceWorkerBuilder({ app: this, appTree, minifyJS: this.app.options.minifyJS, fingerprint: this.app.options.fingerprint.enabled, plugins, rootURL: this._getRootURL(), sourcemaps: this.app.options.sourcemaps, registrationDistPath: options.registrationDistPath }); postprocessTree(type, appTree) { let options = this._getOptions(); if (type !== 'all' || options.enabled === false) { return appTree; } } // 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); // ember-service-worker: index.js let plugins = this._findPluginsFor(this.project);
  • 80.
  • 81. • Detect and include specific directories • treeFor hooks to process generated trees Ember-Service- Worker Plugin API 46
  • 82. Ember-Service- Worker Plugin API 47 • Detect and include specific directories /service-worker/* • treeFor hooks to process generated trees treeForServiceWorker
  • 83. Ember-Service- Worker Plugin API 47 • Detect and include specific directories /service-worker/* • treeFor hooks to process generated trees treeForServiceWorker
  • 84. Ember-Service- Worker Plugin API 47 • Detect and include specific directories /service-worker/* • treeFor hooks to process generated trees treeForServiceWorker
  • 85. Ember-Service- Worker Plugin API 48 • Detect and include specific directories /service-worker-registration/* • treeFor hooks to process generated trees treeForServiceWorkerRegistration
  • 86. 'use strict'; const EntryPoint = require('./entry-point'); const Funnel = require('broccoli-funnel'); const Rollup = require('./rollup-with-dependencies'); const fs = require('fs'); const mergeTrees = require('broccoli-merge-trees'); const path = require('path'); const rollupReplace = require('rollup-plugin-replace'); const uglify = require('broccoli-uglify-sourcemap'); const TREE_FOR_METHODS = { 'service-worker': 'treeForServiceWorker', 'service-worker-registration': 'treeForServiceWorkerRegistration' }; const ENTRY_POINT_FILENAMES = { 'service-worker': 'sw.js', 'service-worker-registration': 'sw-registration.js' }; module.exports = class ServiceWorkerBuilder { constructor(options) { this.options = options || {}; this.plugins = options.plugins; this.appTree = options.appTree; this.app = options.app; } build(type) { let trees = this._treesForPlugins(type); return this._compileTrees(trees, ENTRY_POINT_FILENAMES[type]); } _treesForPlugins(type) { return this.plugins.reduce((trees, plugin) => { let pluginTree = this._treeForPlugin(plugin, type); // ember-service-worker: lib/service-worker-builder.js
  • 87. // same as above but for windows paths like D:my-emberember-service-workerwhateversw.js; which are updated before this step to D:/ my-ember/ember-service/worker/whatever/sw.js // the original one doesn't work when the source code and the %TMP% folder, which ember started to use after v3.5, are located on different logical drives // which is quite common in Windows. /^[a-z]:/(?:[^/:*?"<>|rn]+/)*ember-service-worker/(?:[^/:*?"<>|rn]+/)*[^/:*?"<>|rn]*.js$/i ], delimiters: ['{{', '}}'], ROOT_URL: this.options.rootURL }; return new Rollup(tree, { inputFiles: '**/*.js', rollup: { input: entryFile, output: { file: destFile || entryFile, format: 'iife', }, exports: 'none', plugins: [ rollupReplace(rollupReplaceConfig) ] } }); } _uglifyTree(tree) { if (this.options.minifyJS && this.options.minifyJS.enabled) { let options = this.options.minifyJS.options || {}; options.sourceMapConfig = this.options.sourcemaps; return uglify(tree, options); } return tree; } _babelTranspile(tree) { let emberCliBabel = this.app.project.addons.filter((a) => a.name === 'ember-cli-babel')[0]; return emberCliBabel.transpileTree(tree, { 'ember-cli-babel': { compileModules: false } }); } }
  • 88. const TREE_FOR_METHODS = { 'service-worker': 'treeForServiceWorker', 'service-worker-registration': 'treeForServiceWorkerRegistration' }; module.exports = class ServiceWorkerBuilder { build(type) { let trees = this._treesForPlugins(type); return this._compileTrees(trees, ENTRY_POINT_FILENAMES[type]); } // ember-service-worker: lib/service-worker-builder.js
  • 89. const TREE_FOR_METHODS = { 'service-worker': 'treeForServiceWorker', 'service-worker-registration': 'treeForServiceWorkerRegistration' }; module.exports = class ServiceWorkerBuilder { build(type) { let trees = this._treesForPlugins(type); return this._compileTrees(trees, ENTRY_POINT_FILENAMES[type]); } _treesForPlugins(type) { return this.plugins.reduce((trees, plugin) => { let pluginTree = this._treeForPlugin(plugin, type); if (pluginTree) { return trees.concat(pluginTree); } return trees }, []); } // ember-service-worker: lib/service-worker-builder.js
  • 90. const TREE_FOR_METHODS = { 'service-worker': 'treeForServiceWorker', 'service-worker-registration': 'treeForServiceWorkerRegistration' }; module.exports = class ServiceWorkerBuilder { _treeForPlugin(plugin, type) { let pluginPath = path.resolve(plugin.root, type); let treeForMethod = TREE_FOR_METHODS[type]; let tree; } if (fs.existsSync(pluginPath)) { tree = this.app.treeGenerator(pluginPath); } if (plugin[treeForMethod]) { tree = plugin[treeForMethod](tree, this.appTree); } if (tree) { return new Funnel(tree, { destDir: plugin.pkg.name + '/' + type }); } // ember-service-worker: lib/service-worker-builder.js
  • 91. const TREE_FOR_METHODS = { 'service-worker': 'treeForServiceWorker' }; module.exports = class ServiceWorkerBuilder { _treeForPlugin(plugin) { let pluginPath = path.resolve(plugin.root,'service-worker'); let treeForMethod = 'treeForServiceWorker'; let tree; } if (fs.existsSync(pluginPath)) { tree = this.app.treeGenerator(pluginPath); } if (plugin[treeForMethod]) { tree = plugin[treeForMethod](tree, this.appTree); } if (tree) { return new Funnel(tree, { destDir: plugin.pkg.name + '/service-worker' }); } // ember-service-worker: lib/service-worker-builder.js
  • 92. const TREE_FOR_METHODS = { 'service-worker': 'treeForServiceWorker' }; module.exports = class ServiceWorkerBuilder { _treeForPlugin(plugin) { let pluginPath = path.resolve(plugin.root,'service-worker'); let treeForMethod = 'treeForServiceWorker'; let tree; } // ember-service-worker: lib/service-worker-builder.js
  • 93. const TREE_FOR_METHODS = { 'service-worker': 'treeForServiceWorker' }; module.exports = class ServiceWorkerBuilder { _treeForPlugin(plugin) { let pluginPath = path.resolve(plugin.root,'service-worker'); let treeForMethod = 'treeForServiceWorker'; let tree; } // ember-service-worker: lib/service-worker-builder.js if (fs.existsSync(pluginPath)) { tree = this.app.treeGenerator(pluginPath); }
  • 94. const TREE_FOR_METHODS = { 'service-worker': 'treeForServiceWorker' }; module.exports = class ServiceWorkerBuilder { _treeForPlugin(plugin) { let pluginPath = path.resolve(plugin.root,'service-worker'); let treeForMethod = 'treeForServiceWorker'; let tree; } if (fs.existsSync(pluginPath)) { tree = this.app.treeGenerator(pluginPath); } if (plugin[treeForMethod]) { tree = plugin[treeForMethod](tree, this.appTree); } // ember-service-worker: lib/service-worker-builder.js
  • 95. const TREE_FOR_METHODS = { 'service-worker': 'treeForServiceWorker' }; module.exports = class ServiceWorkerBuilder { _treeForPlugin(plugin) { let pluginPath = path.resolve(plugin.root,'service-worker'); let treeForMethod = 'treeForServiceWorker'; let tree; } if (fs.existsSync(pluginPath)) { tree = this.app.treeGenerator(pluginPath); } if (plugin[treeForMethod]) { tree = plugin[treeForMethod](tree, this.appTree); } if (tree) { return new Funnel(tree, { destDir: plugin.pkg.name + '/service-worker' }); } // ember-service-worker: lib/service-worker-builder.js
  • 96. const TREE_FOR_METHODS = { 'service-worker': 'treeForServiceWorker' }; module.exports = class ServiceWorkerBuilder { _treeForPlugin(plugin) { let pluginPath = path.resolve(plugin.root,'service-worker'); let treeForMethod = 'treeForServiceWorker'; let tree; } if (fs.existsSync(pluginPath)) { tree = this.app.treeGenerator(pluginPath); } if (plugin[treeForMethod]) { tree = plugin[treeForMethod](tree, this.appTree); } if (tree) { return new Funnel(tree, { destDir: plugin.pkg.name + '/service-worker' }); } // ember-service-worker: lib/service-worker-builder.js
  • 97. const TREE_FOR_METHODS = { 'service-worker': 'treeForServiceWorker', 'service-worker-registration': 'treeForServiceWorkerRegistration' }; module.exports = class ServiceWorkerBuilder { build(type) { let trees = this._treesForPlugins(type); return this._compileTrees(trees, ENTRY_POINT_FILENAMES[type]); } // ember-service-worker: lib/service-worker-builder.js
  • 98. const ENTRY_POINT_FILENAMES = { 'service-worker': 'sw.js', 'service-worker-registration': 'sw-registration.js' }; module.exports = class ServiceWorkerBuilder { build(type) { let trees = this._treesForPlugins(type); return this._compileTrees(trees, ENTRY_POINT_FILENAMES[type]); } // ember-service-worker: lib/service-worker-builder.js
  • 99. const ENTRY_POINT_FILENAMES = { 'service-worker': 'sw.js' }; module.exports = class ServiceWorkerBuilder { build(type) { let trees = this._treesForPlugins('service-worker'); return this._compileTrees(trees, 'sw.js'); } // ember-service-worker: lib/service-worker-builder.js
  • 100. _compileTrees(trees, 'sw.js') { let entryPoint = new EntryPoint(trees, { entryPoint: 'sw.js' }); let tree = mergeTrees(trees.concat(entryPoint), { overwrite: true }); tree = this._babelTranspile(tree); tree = this._rollupTree(tree, 'sw.js', this.treeDistPath); tree = this._uglifyTree(tree); return tree; } const ENTRY_POINT_FILENAMES = { 'service-worker': 'sw.js' }; module.exports = class ServiceWorkerBuilder { build(type) { let trees = this._treesForPlugins('service-worker'); return this._compileTrees(trees, 'sw.js'); } // ember-service-worker: lib/service-worker-builder.js
  • 101. _compileTrees(trees, 'sw.js') { let entryPoint = new EntryPoint(trees, { entryPoint: 'sw.js' }); } const ENTRY_POINT_FILENAMES = { 'service-worker': 'sw.js' }; module.exports = class ServiceWorkerBuilder { build(type) { let trees = this._treesForPlugins('service-worker'); return this._compileTrees(trees, 'sw.js'); } // ember-service-worker: lib/service-worker-builder.js
  • 102.
  • 103. const Plugin = require('broccoli-plugin'); const fs = require('fs'); const path = require('path'); const glob = require('glob'); module.exports = class EntryPoint extends Plugin { constructor(inputNodes, options) { super(inputNodes, { name: options && options.name, annotation: options && options.annotation }); this.entryPoint = options && options.entryPoint; } build() { let entryPointJS = this.inputPaths.reduce((lines, inputPath) => { let indexJS = glob.sync('**/index.js', { cwd: inputPath })[0]; if (indexJS) { let entryPointModuleName = path.dirname(indexJS); return lines.concat(`import "${entryPointModuleName}";n`); } return lines; }, ''); fs.writeFileSync(path.join(this.outputPath, this.entryPoint), entryPointJS); } }; // ember-service-worker: lib/entry-point.js
  • 104. const Plugin = require('broccoli-plugin'); const fs = require('fs'); const path = require('path'); const glob = require('glob'); module.exports = class EntryPoint extends Plugin { }; // ember-service-worker: lib/entry-point.js
  • 105. const Plugin = require('broccoli-plugin'); const fs = require('fs'); const path = require('path'); const glob = require('glob'); module.exports = class EntryPoint extends Plugin { }; constructor(inputNodes, options) { super(inputNodes, { name: options && options.name, annotation: options && options.annotation }); this.entryPoint = options && options.entryPoint; } // ember-service-worker: lib/entry-point.js
  • 106. const Plugin = require('broccoli-plugin'); const fs = require('fs'); const path = require('path'); const glob = require('glob'); module.exports = class EntryPoint extends Plugin { }; constructor(inputNodes, options) { super(inputNodes, { name: options && options.name, annotation: options && options.annotation }); this.entryPoint = options && options.entryPoint; } build() { let entryPointJS = this.inputPaths.reduce((lines, inputPath) => { }, ''); } // ember-service-worker: lib/entry-point.js
  • 107. const Plugin = require('broccoli-plugin'); const fs = require('fs'); const path = require('path'); const glob = require('glob'); module.exports = class EntryPoint extends Plugin { }; constructor(inputNodes, options) { super(inputNodes, { name: options && options.name, annotation: options && options.annotation }); this.entryPoint = options && options.entryPoint; } build() { let entryPointJS = this.inputPaths.reduce((lines, inputPath) => { }, ''); } let indexJS = glob.sync('**/index.js', { cwd: inputPath })[0]; if (indexJS) { let entryPointModuleName = path.dirname(indexJS); return lines.concat(`import "${entryPointModuleName}";n`); } return lines; // ember-service-worker: lib/entry-point.js
  • 108. const Plugin = require('broccoli-plugin'); const fs = require('fs'); const path = require('path'); const glob = require('glob'); module.exports = class EntryPoint extends Plugin { }; constructor(inputNodes, options) { super(inputNodes, { name: options && options.name, annotation: options && options.annotation }); this.entryPoint = options && options.entryPoint; } build() { let entryPointJS = this.inputPaths.reduce((lines, inputPath) => { }, ''); } let indexJS = glob.sync('**/index.js', { cwd: inputPath })[0]; if (indexJS) { let entryPointModuleName = path.dirname(indexJS); return lines.concat(`import "${entryPointModuleName}";n`); } return lines; // ember-service-worker: lib/entry-point.js
  • 109. const Plugin = require('broccoli-plugin'); const fs = require('fs'); const path = require('path'); const glob = require('glob'); module.exports = class EntryPoint extends Plugin { }; constructor(inputNodes, options) { super(inputNodes, { name: options && options.name, annotation: options && options.annotation }); this.entryPoint = options && options.entryPoint; } build() { let entryPointJS = this.inputPaths.reduce((lines, inputPath) => { }, ''); } let indexJS = glob.sync('**/index.js', { cwd: inputPath })[0]; if (indexJS) { let entryPointModuleName = path.dirname(indexJS); return lines.concat(`import "${entryPointModuleName}";n`); } return lines; fs.writeFileSync(path.join(this.outputPath, this.entryPoint), entryPointJS); // ember-service-worker: lib/entry-point.js
  • 110. _compileTrees(trees, 'sw.js') { let entryPoint = new EntryPoint(trees, { entryPoint: 'sw.js' }); let tree = mergeTrees(trees.concat(entryPoint), { overwrite: true }); } const ENTRY_POINT_FILENAMES = { 'service-worker': 'sw.js' }; module.exports = class ServiceWorkerBuilder { build(type) { let trees = this._treesForPlugins('service-worker'); return this._compileTrees(trees, 'sw.js'); } // ember-service-worker: lib/service-worker-builder.js
  • 111. _compileTrees(trees, 'sw.js') { let entryPoint = new EntryPoint(trees, { entryPoint: 'sw.js' }); let tree = mergeTrees(trees.concat(entryPoint), { overwrite: true }); } const ENTRY_POINT_FILENAMES = { 'service-worker': 'sw.js' }; module.exports = class ServiceWorkerBuilder { build(type) { let trees = this._treesForPlugins('service-worker'); return this._compileTrees(trees, 'sw.js'); } // ember-service-worker: lib/service-worker-builder.js
  • 112. _compileTrees(trees, 'sw.js') { let entryPoint = new EntryPoint(trees, { entryPoint: 'sw.js' }); let tree = mergeTrees(trees.concat(entryPoint), { overwrite: true }); } const ENTRY_POINT_FILENAMES = { 'service-worker': 'sw.js' }; module.exports = class ServiceWorkerBuilder { build(type) { let trees = this._treesForPlugins('service-worker'); return this._compileTrees(trees, 'sw.js'); } // ember-service-worker: lib/service-worker-builder.js tree = this._babelTranspile(tree); tree = this._rollupTree(tree, 'sw.js', this.treeDistPath); tree = this._uglifyTree(tree); return tree;
  • 113. let serviceWorkerTree = serviceWorkerBuilder.build('service-worker'); let serviceWorkerRegistrationTree = serviceWorkerBuilder.build('service-worker-registration'); let serviceWorkerBuilder = new ServiceWorkerBuilder({ app: this, appTree, minifyJS: this.app.options.minifyJS, fingerprint: this.app.options.fingerprint.enabled, plugins, rootURL: this._getRootURL(), sourcemaps: this.app.options.sourcemaps, registrationDistPath: options.registrationDistPath }); postprocessTree(type, appTree) { let options = this._getOptions(); if (type !== 'all' || options.enabled === false) { return appTree; } } // 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); // ember-service-worker: index.js let plugins = this._findPluginsFor(this.project);
  • 114. postprocessTree(type, appTree) { let options = this._getOptions(); if (type !== 'all' || options.enabled === false) { return appTree; } } return mergeTrees([ appTree, serviceWorkerTree, serviceWorkerRegistrationTree ], { overwrite: true }); let serviceWorkerTree = serviceWorkerBuilder.build('service-worker'); let serviceWorkerRegistrationTree = serviceWorkerBuilder.build('service-worker-registration'); let serviceWorkerBuilder = new ServiceWorkerBuilder({ app: this, appTree, minifyJS: this.app.options.minifyJS, fingerprint: this.app.options.fingerprint.enabled, plugins, rootURL: this._getRootURL(), sourcemaps: this.app.options.sourcemaps, registrationDistPath: options.registrationDistPath }); // 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); // ember-service-worker: index.js let plugins = this._findPluginsFor(this.project);
  • 115. postprocessTree(type, appTree) { let options = this._getOptions(); if (type !== 'all' || options.enabled === false) { return appTree; } } return mergeTrees([ appTree, serviceWorkerTree, serviceWorkerRegistrationTree ], { overwrite: true }); let serviceWorkerTree = serviceWorkerBuilder.build('service-worker'); let serviceWorkerRegistrationTree = serviceWorkerBuilder.build('service-worker-registration'); let serviceWorkerBuilder = new ServiceWorkerBuilder({ app: this, appTree, minifyJS: this.app.options.minifyJS, fingerprint: this.app.options.fingerprint.enabled, plugins, rootURL: this._getRootURL(), sourcemaps: this.app.options.sourcemaps, registrationDistPath: options.registrationDistPath }); // 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); // ember-service-worker: index.js let plugins = this._findPluginsFor(this.project);
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 123. • Not like testing an Ember application Testing 79
  • 124. • Not like testing an Ember application • Testing the build for an application Testing 79
  • 125. • Not like testing an Ember application • Testing the build for an application • Testing in Node.js Testing 79
  • 126. • Not like testing an Ember application • Testing the build for an application • Testing in Node.js • Mocha Testing 79
  • 127. • Not like testing an Ember application • Testing the build for an application • Testing in Node.js • Mocha Testing 80
  • 130. • Unit tests for core functionality 81 Testing
  • 131. • Unit tests for core functionality • Host addon should test that the API is called when expected 81 Testing
  • 132. • Unit tests for core functionality • Host addon should test that the API is called when expected • Plugins should test that they implement required hooks 81 Testing
  • 133. • Unit tests for core functionality • Host addon should test that the API is called when expected • Plugins should test that they implement required hooks • Both should test the build output 81 Testing
  • 135. var helper = require('broccoli-test-helper'); var createBuilder = helper.createBuilder; // ember-service-worker: node-tests/service-worker-builder-test.js
  • 136. var helper = require('broccoli-test-helper'); var createBuilder = helper.createBuilder; it('transpiles code with babel', async () => { let plugins = [generatePlugin('test-project', 'builder-test/babel')]; }); // ember-service-worker: node-tests/service-worker-builder-test.js
  • 137. var helper = require('broccoli-test-helper'); var createBuilder = helper.createBuilder; it('transpiles code with babel', async () => { let plugins = [generatePlugin('test-project', 'builder-test/babel')]; }); let subject = new ServiceWorkerBuilder({ app, plugins }); let output = createBuilder(subject); // ember-service-worker: node-tests/service-worker-builder-test.js
  • 138. var helper = require('broccoli-test-helper'); var createBuilder = helper.createBuilder; it('transpiles code with babel', async () => { let plugins = [generatePlugin('test-project', 'builder-test/babel')]; }); let subject = new ServiceWorkerBuilder({ app, plugins }); let output = createBuilder(subject); await output.build(); // ember-service-worker: node-tests/service-worker-builder-test.js
  • 139. var helper = require('broccoli-test-helper'); var createBuilder = helper.createBuilder; it('transpiles code with babel', async () => { let plugins = [generatePlugin('test-project', 'builder-test/babel')]; }); let subject = new ServiceWorkerBuilder({ app, plugins }); let output = createBuilder(subject); let files = output.read(); assert.property(files, 'sw.js'); assert.equal(files['sw.js'], expected); // ember-service-worker: node-tests/service-worker-builder-test.js await output.build(); let expected = ....;
  • 141. What We Learned 88 • Plugin architecture is just a fancy way of talking about what we already know
  • 142. What We Learned 88 • Plugin architecture is just a fancy way of talking about what we already know • Ember addons implement a plugin architecture via hooks and can provide their own API for other addons
  • 143. What We Learned 88 • Plugin architecture is just a fancy way of talking about what we already know • Ember addons implement a plugin architecture via hooks and can provide their own API for other addons • Broccoli, Babel, and Rollup implement a plugin architecture
  • 144. What We Learned 88 • Plugin architecture is just a fancy way of talking about what we already know • Ember addons implement a plugin architecture via hooks and can provide their own API for other addons • Broccoli, Babel, and Rollup implement a plugin architecture • None of these are scary but are usually more complicated than a hand-wavy demo
  • 145. What We Learned 88 • Plugin architecture is just a fancy way of talking about what we already know • Ember addons implement a plugin architecture via hooks and can provide their own API for other addons • Broccoli, Babel, and Rollup implement a plugin architecture • None of these are scary but are usually more complicated than a hand-wavy demo • We can take advantage of these concepts to write our own addon ecosystems