Polymer Application Structure Tips
If you're diving into Polymer and the new world of Web Components, you may be wondering how to organize and structure your project. We don't have all the answers, but here's what we've found so far.
The structure of an application can be just as important for developer productivity as the frameworks and technologies upon which it's built. When something entirely new (like Polymer) comes along, it can take some time to settle into best practices. At Divshot we've been building small apps and a few larger ones using Polymer for the past year or so. This post summarizes what we've learned thus far about structuring apps.
Note: This isn't an introduction to Polymer but an exploration of best practices for those already using it. Try the official tutorial for a proper intro.
The Basic Structure
When we start building a Polymer app, we don't start with a complex generator toolchain. In fact, we don't start with a build process at all. It can make sense to add one in later depending on how large and complex your app gets, but Web Components are browser-native and it's nice to be able to take advantage of that!
The directory structure of our Polymer apps start something like this:
public/
bower_components/
components/
index.html
.gitignore
bower.json
divshot.json
README.md
Pretty self-explanatory. The divshot.json
is there for hosting on Divshot (of course), but also to use its custom routing to make index.html
work for any URL during local development in true Single-Page App fashion.
Our .gitignore
simply makes sure that we aren't checking in the contents of our Bower install:
/public/bower_components
Bower is Your Friend
If you want to be productive, you absolutely need Bower. It's how the Polymer team is managing their own code, and you won't want to fight it.
So make sure you run bower init
and create a .bowerrc
file in your project's directory that points to the proper place. For many of our projects, the .bowerrc
looks like:
{"directory":"public/bower_components"}
If you plan to use Polymer UI Elements or Paper Elements, each comes with a "Kitchen Sink" package that installs all of its elements. While useful during development, you're unlikely to use every single one of them. There's a trick to make this easier: install the aggregate packages locally for development, but only add to bower.json
the specific ones that you're using. So start with:
bower install Polymer/paper-elements
bower install PolymerLabs/polymer-ui-elements
And the first time you use one of the components (e.g. paper-ui-button
) in your app, install it to your bower.json
:
bower install --save Polymer/paper-ui-button
Naming Elements
We've found it to be a helpful practice to come up with a short prefix that you apply to all of your application specific elements. This might just be app
, but probably works better with your actual application name. For example, Polymer's Topeka example app prefixes its elements with topeka-
.
Managing Element Dependencies
The public/components
directory is where you'll be placing all of the custom elements that you build. In the beginning, this will likely be a simple list of .html
files named after the elements, potentially with .css
files to go along with them. A typical file will look like:
<!-- public/components/app-my-element.html -->
<link rel="import" href="../bower_components/some-element/some-element.html">
<link rel="import" href="app-another-element.html">
<polymer-element name="app-my-element">
<template>
<link rel="stylesheet" href="app-my-element.css">
<!-- ... -->
</template>
<script>
Polymer('app-my-element', {
// ...
});
</script>
</polymer-element>
Your dependencies will either come from inside the components directory or from your bower_components
. It's your call whether to use absolute (e.g. /bower_components/some-element/some-element.html
) or relative (e.g. ../bower_components/some-element/some-element.html
) paths. Each has advantages and drawbacks, and we haven't settled on a clearly superior option yet.
You should always import all required dependencies at the top of an element. Even if you are using the same element in many files, you should have an import link at the top of each one. This way if you move things around, you won't accidentally break dependencies.
The only exception we've carved out to this rule is polymer.html
which we directly import in the index.html
file just after platform.js
since every element we build requires it.
Alternative: Separate Elements Repository
Again referencing the Polymer teams' Topeka app, they to created a separate topeka-elements repository for all of their app's custom elements. While we can only speculate, it's likely that they did this so that they could install all the elements with Bower and consistently use relative path imports (e.g. ../some-element/some-element.html
) in their code.
There are definitely advantages to this method, and it's one we're evaluating to see if it feels superior or more productive. The greatest challenge is in the need to constantly commit to two repositories that are tightly coupled as your application grows and changes.
Building Reusable Elements
Web Components are composable and reusable like nothing that has come before. In the course of making your app, you're likely to come up with a few elements that could easily be used for other purposes. The Polymer team has a great guide on creating reusable elements that we highly recommend, but it can be tough to work on one in the context of a larger app. Here's how we do it:
- Follow the linked guide above, but put the project folder for your new element inside
public/bower_components
. - Initialize it as a separate git repository. Since its parent folder is ignored by git, you won't have a conflict in doing this.
- Publish your new element to GitHub. You don't have to register it with Bower just yet.
- Manually add a dependency to your
bower.json
for your new reusable component.
So long as you remember to push your changes up from your reusable component, your colleagues will be able to install it alongside all their other Bower dependencies.
Bundling Elements
One of the biggest "a ha!" moments we had while planning out structure was how to manage groups of related components. The answer was to utilize one of the web's oldest features: the directory index file!
As you begin to group elements together (maybe by feature, by function, whatever makes sense to you) you can create subdirectories in your public/components
directory to house them. Then just create an index.html
file that imports all of the grouped components. For example:
<!-- public/components/blog/index.html -->
<link rel="import" href="blog-post.html">
<link rel="import" href="blog-category.html">
<link rel="import" href="blog-comments.html">
Now when you want to be able to use these related elements, you can do so with a single import:
<link rel="import" href="components/blog/">
It's when you do things like this that you understand just how wonderfully well-designed the Web Components architecture is, and how well it fits into the existing HTML world.
Concatenation and Build Process
Most of our apps are still in the development phase and not quite ready for public consumption. As such, we don't have deep experience with trying to optimize Polymer apps for speed or network performance (yet).
Vulcanize is going to be a good place to start looking, though.
These are some of the tips and tricks that we've learned so far. Are you building Polymer apps? If so, what have you learned to be as productive as possible? Let us know!
This article is part of Divshot's Web Components Week. Check out our other posts if you just can't get enough Web Components!