This tutorial will cover how to make a component addon from start to finish, including:
- generating the files
- adding stylesheets that other apps can use
- seeing changes locally
- getting your addon included on Ember Observer after it is published
Creating the addon
First, we need to create an addon's file structure. Run this command outside of an existing Ember app:
ember addon <addon-name> [options]
Generating a component
If we want to create an addon component that can be shared between apps, the process is a lot like creating a normal app component:
ember generate component <component-name>
However, in the context of an addon, this creates more files than we would see in an app:
installing component
create addon/components/<component-name>.hbs
skip addon/components/<component-name>.js
tip to add a class, run `ember generate component-class <component-name>`
installing component-test
create tests/integration/components/<component-name>-test.js
installing component-addon
create app/components/<component-name>.js
Some files go in the app
directory, while others go into the addon
directory. We'll start by looking at the addon directory. Whatever we put in the <component-name>.hbs
file is what could be used immediately in an app, and will be referenced in templates as {{component-name}}
.
Let's say that our addon should wrap some content in a button tag. The addon template should look like this:
<button>{{@buttonLabel}}</button>
Our goal is to be able to pass the buttonLabel
value to the addon, just like we'd pass it to a normal component within an app:
<AddonName @buttonLabel="Register" />
Trying out the addon template in an app
There are several options to see the addon in action. We could use npm link
, yarn link
, or pnpm link
to try it out locally or publish the addon online. We'll use link
while we are still developing and testing.
From the addon project directory:
- Since our addon uses a template, we need the template precompiler to be a
dependency
and not adevDependency
. In the addon'spackage.json
, move the entry forember-cli-htmlbars
into thedependencies
listing. If this step is missed, there is a clear error message when we try to start the app that uses our addon. pnpm install
,yarn install
, ornpm install
- Run the command
yarn link
ornpm link
. This step may be skipped if usingpnpm
.
From the directory of the app using the addon:
pnpm link ../path/to-/the-addon
,yarn link <addon-name>
ornpm link <addon-name>
.- In the Ember app's
package.json
, add adevDependencies
entry for your addon, like"addon-name": "*"
. The*
means that it will include all version numbers of our addon. - Run
pnpm install
,yarn install
, ornpm install
in the app. (If you are using the app for the first time, you can usenpm install --prefer-offline
ornpm install --offline
instead. These alternative commands can speed up installation, becausenpm install
checks the online npm registry for your addon instead of your local storage.) - Add a reference to your addon's component somewhere in an app template, like
<ComponentName @buttonLabel="Register" />
- Run a local server with
npm start
orember serve
, and visit http://localhost:4200
We should now see our addon in action!
Having problems?
- Check to make sure that your
package.json
is valid, looking for missing commas or trailing commas. - "Template precompiler" errors mean that you skipped Step 1 and 2 above.
404 not found
means we forgot topnpm install
,yarn install
ornpm install
- Make sure all the files have been saved.
- Did you rename or relocate any files after they were created? This is prone to mistakes, and the resulting errors can be really strange. It is best to create files using the CLI.
Making a UI component available in block form
In an Ember app, components can be used in "simple" or "block" form. Addon templates have the same capabilities. The simple form allows data objects or configuration values to be passed to the addon. The block form allows a developer to pass in their own template, content, and interactivity.
In an Ember app, a block style component uses the {{yield}}
helper as a placeholder for where the passed-in content will go. It is the same in an Ember addon.
Let's change our button addon we made earlier so that developers can pass in their own handlebars content by using the {{yield}}
helper:
<button>{{yield}}</button>
Now, an app can use the addon with their own content inside:
<AddonName>
Register <img href="./images/some-cute-icon.png" alt="">
</AddonName>
Whatever goes inside the block form addon will show up where the {{yield}}
was. This is the markup that renders in the app:
<!-- markup rendered by running the app -->
<button>
Register <img href="./images/some-cute-icon.png" alt="">
</button>
Styling a UI component addon
Addon developers have many options for handling styles within their addons. For example, we could stick to plain old CSS, or use a preprocessor like Less or Sass. Most addon authors prefer Sass. We could automatically style the UI elements when they are used in an app, or we could let the developer who installed the addon choose which stylesheets to include. Here are a few different approaches. Luckily, the Ember CLI handles most of the work for us and we don't have to worry about the inner workings of asset compilation.
Automatically including CSS stylesheets in addons
To automatically include CSS styling for your addon, create a styles
directory in the addon
directory, and place your CSS files in it. For example, we could create addon/styles/our-addon-name.css
.
When our addon is used in an app, these CSS rules will be added to the end of the app's vendor.css
when it is built or served. The rules will be in the same scope as the rest of the app's CSS, so name your class selectors wisely! Otherwise they will clash with the styles of other addons or the app's own styling.
For example, writing a CSS rule for div
is problematic because it will affect all div
s in the app, but a rule targeting .my-app-name div
is probably fine.
Let's add a class to our template and some styles to target the class:
<button class="addon-name-button">{{yield}}</button>
.addon-name-button {
padding: 10px;
}
Now any buttons made using our addon will have the padding: 10px
rule applied.
CSS stylesheets that require importing from addons
For some addons, it makes sense to give the developer the option to import the stylesheet we provide, or import no stylesheets at all. Using this approach, we could even offer the developer a few themes to choose from.
We can do this by creating stylesheets in the app/styles/
directory instead. These stylesheets share a file namespace with the consuming app and all the other addons someone is using, so name them wisely. For example, if we name our stylesheet addon.css
, that's likely to clash. Just as before, it's important to choose uniquely named targets for the CSS rules so that they don't clash with other addons or the app.
Let's create app/styles/my-addon-name.css
and add a rule to it:
.addon-name-button {
border: black solid 2px;
}
For the stylesheet to be active in the app the addon is used in, the developer for that app must explicitly import
the stylesheet by name. This must be done at the very top of the app's app.css
file.
@import 'my-addon-name.css'
Then, restart your local server to see the changes in action.
If there are any problems getting this to work, one strategy is to build the addon with ember build
and look inside the dist
folder. The dist
folder may be hidden by default in some code editors. The dist
folder gives clues about what the consuming app sees as the file structure of the addon. See if the stylesheets are in dist/assets/
. Then, in the Ember app, run ember build
and look in the dist
folder. We should see our stylesheets in dist/assets
of the app too.
Using CSS preprocessors for the addon's stylesheets
While this guide focuses on the "out of the box" behavior of addons and the Ember CLI, there are some well-established patterns for handling stylesheets in a way that is scalable and maintainable. A CSS preprocessor like Sass allows you to nest style rules, use variables, and do simple mathematical operations.
The best way to learn how to use CSS preprocessors in your addon is to consult the documentation for the preprocessor addon of your choice, and study how other addon authors have implemented stylesheets. For example, ember-styleguide is a UI component library that was made for the main Ember websites. It uses ember-cli-sass to manage styles. You can search Ember Observer for many more examples of styling in action!
Adding UI functionality with JavaScript
There are two main types of JavaScript functionality that an addon can provide:
- API methods that developers can use after importing your addon
- Interactive features that are part of UI components.
We'll cover UI use cases first.
Interactivity in an addon can be handled the same way that it is done for an Ember app's component. Every addon component template has a corresponding JavaScript file to go with it. For example, an addon can have its own actions, computed properties, and imported dependencies. Developers using the addon can pass in their own actions and attributes.
For more information about building interactivity for your addon, reference the Ember Guides components section. Remember to test as you work!
Providing multiple templates in one addon
Writing a JavaScript utilities addon
Many addons have no UI components in them, or they offer a combination of JavaScript utilities and template helpers. In the regular npm ecosystem, JavaScript utility libraries are some of the most common packages. Although we could write a normal node package, providing an Ember addon to developers has some advantages. The developers don't need to worry about how to import a normal npm package. They can use ember install our-addon-name
and get going right away. An addon can also take advantage of Ember or Ember CLI-specific APIs.
Providing public API methods in the addon
After we've created our addon file structure with ember addon <addon-name>
, we can write some functions that will be available for an app to use. Such functions are commonly referred to as "public API." If the behavior of public API changes, it's convention in the Ember community to follow SemVer and change the major version of the addon. SemVer is a cross-program-language versioning scheme that helps other developers or coworkers know which versions of a library will require them to refactor their apps.
All npm packages have an entry point. By default, the entry point is named {addonName}/index.js
, at the top level inside the addon. The files exported from addon/index.js
will be available to developers using the addon in their apps.
Let's add some public methods to our addon! Don't forget to export
your methods.
const moreEnthusiasm = function (phrase) {
return phrase + '!!!';
}
export { moreEnthusiasm }
Now, let's use the methods in an app:
// The JavaScript file of some component in an app
import Component from '@ember/component';
import { moreEnthusiasm } from 'ember-addon-name';
export default Component.extend({
actions: {
confirmExcitement() {
console.log(moreEnthusiasm('We made an addon'))
// prints "We made an addon!!!" to the console
}
}
});
Organizing public API code
One common pattern for managing an addon's JavaScript code is to define the methods in many separate files, perhaps grouped into subdirectories, import them into index.js
, and then export them.
For example, an index.js
file might contain nothing more than imports and exports:
import { moreEnthusiasm, curbedEnthusiasm } from 'our-app-name/utilities/enthusiasm.js'
export { moreEnthusiasm, curbedEnthusiasm };
How to keep learning
This is a very tiny example of what addons can do in terms of providing JavaScript utilities to apps. For more advanced techniques, study other well established addons. Just like there are many ways and reasons to build an Ember app, the same is true for addons!
Writing an npm package wrapper
In-repo addons
In-Repo addons live in the lib/
directory of an Ember application. You can read more about
them in the dedicated section!
Other kinds of addons
Documenting addons
For other developers to discover and use our addon, we need to teach them how to use it!
Here are the most common ways that addons provide user-facing documentation:
- A detailed README on the GitHub project
- Creating a documentation website in the addon's dummy app, found in
/tests/dummy/
- A combination of these two
It can be a lot of work to document an addon, so some seasoned addon contributors created ember-cli-addon-docs, which provides templates for creating a site that shows off your addon. It's a documentation resource and demo in one. Many addon authors choose to host their documentation sites on GitHub pages, which is free and built into GitHub.
What about documentation of the code itself? Many JavaScript documentation tools have not caught up to ES6-style modules and classes, so the best bet is to look at how some popular addons handle code comments and find a style that works for you. If your code is well commented, it will help out new contributors and reduce the number of issues that others open.
Lastly, be sure to provide a few notes about how others can contribute to the project! These notes commonly go in a README.md
or CONTRIBUTING.md
file.
Testing an addon
Common addon configurations
Addons are configured using the ember-addon
hash in the package.json
file.
"ember-addon": {
"configPath": "tests/dummy/config",
"before": "single-addon",
"defaultBlueprint": "blueprint-that-isnt-package-name",
"demoURL": "http://example.com/ember-addon/demo.html",
"after": [
"after-addon-1",
"after-addon-2"
]
}
By default, the configPath
property is defined to point to the config directory of the test dummy application. All other properties are optional.
before and after
These properties specify whether your ember-addon must run "before" or "after" any other Ember CLI addons. Both of these properties can take either a string or an array of strings. The string is the name of another Ember CLI addon, as defined in the package.json
of the other addon.
defaultBlueprint
Addons have a default blueprint that will automatically run when the addon is installed. By convention, Ember will run the blueprint named after the name
property in package.json
You may specify a different name using defaultBlueprint
. See the addon blueprints for more information on the default blueprint.
demoURL
You may specify the demoURL
property with the fully qualified URL of a website showing your addon in action. Ember Observer will display a link to "demoURL".