Updated March 23, 2018. An earlier version of this post listed the wrong release versions. 3.0.0-pre.11 is the last version before the changes described here. 3.0.0-pre.12 is the first version with the new changes. These numbers have been corrected in the post. For hands-on information about the latest preview, see the next blog post.

Today we're announcing an update to the way we will package the Polymer 3.0 library and our elements. In one sentence: we're now importing modules from other packages using the package name, rather than the path to the package.

This change brings Polymer in line with standard npm practice, and makes it easier to integrate Polymer with other tools and projects. However, because browsers don't yet support importing modules by name, it means you'll need a transform step to run Polymer modules natively in the browser. The Polymer CLI and related tools are being updated to do this transformation automatically.

If the quick summary doesn't clarify everything for you, read on...

The story so far

If you've been following the Polymer 3.0 announcements, you know that in contrast with other libraries and frameworks, we've been publishing web-compatible source on npm. By web-compatible, we mean:

  • Native ES Modules, not CommonJS or AMD modules.
  • Specifying modules in imports with paths, as required by the HTML specification.

The great thing about packaging your code this way is that modern browsers can run it directly as installed. That means that even in development, you can run the code without requiring any special tools or build steps. For production, you can still compile and bundle your code using your favorite tools.

However, specifying modules by paths is different from what most of the JavaScript ecosystem is doing. Most Node.js libraries and most web-focused libraries import modules by name.

There are several differences between paths and names:

  • The browser requires ES modules to be specified by path:

    import {Element as PolymerElement} from  '../node_modules/@polymer/polymer/polymer-element.js';
    

    Module paths in the browser need to start with "/", "./" or "../". A module needs to have the exact path to any dependency in order to import it. The browser resolves module paths the same way it resolves any path into a URL.

    This is just like HTML imports in Polymer 2.x. Like Polymer 2.x, the initial 3.0 preview used paths and required a flat installation structure (like Bower) to make dependencies manageable.

    ES6 modules using paths are the only type of module with native browser support.

  • Node code and many modern web libraries typically use package names, also known as bare module specifiers:

    import * as lodash from 'lodash';
    

    Since Node doesn't support ES6 modules yet, these imports are compiled to require() calls. The Node runtime uses Node module resolution to find the correct module. The module importing lodash doesn't need to know exactly where lodash is installed.

    Most build tools like Webpack and Rollup, and compilers like TypeScript, also support bare module specifiers, and resolve them to paths at build time. Most web libraries that use modules also use names, which means they require a build or transform step to run in the browser.

ES6 modules using paths have native browser support, but the developer experience isn't great: you need to install all of your packages flat using yarn, and you need to know exactly where dependencies are installed.

On the other hand, there are some good reasons for using package names: one package doesn't have to know exactly where its dependencies are installed. You can restructure your app without changing all of the relative import paths. And installers like npm can deduplicate common dependencies of multiple packages and only put one copy on disk, while still nesting dependencies when version conflicts prevent deduplication. Also, tools can easily alias one package used in 3rd-party source for another package.

The Node.js community has gotten used to this flexibility, and forcing all existing packages to adopt path-based flat installations is a hard sell.

So what now?

The current preview release (3.0-pre.11) still uses paths, so for now, keep doing what you're doing.

Starting with the next preview release (3.0-pre.12), we're going to publish packages that use bare module specifiers to import from other packages.

For example, where previous previews required a path like this:

../@polymer/polymer/polymer-element.js

Or:

/node_modules/@polymer/polymer/polymer-element.js

With the next release, you can refer to the Polymer package by name (@polymer/polymer), without regard to where it is installed:

@polymer/polymer/polymer-element.js

That is:

  • The name of the package (@polymer/polymer) replaces the path to the package folder. For
    scoped packages like the Polymer packages, the scope is part of the package name.
  • For packages with a main module (identified in the main section of package.json), you can just use the package name instead of specifying the file path. For example, instead of 'lodash/index.js' for the lodash package, you can just import 'lodash'.

This change does mean that you'll require some kind of build or transform step to run your Polymer code in the browser. The Polymer CLI tools will do this for you on the fly when you run polymer serve or WCT, and the Polymer CLI will rewrite names to paths at build time.

If you have a working project with paths, it should continue to work with the next release. (However, you'll still need to use a transform or build tool.)

You're still free to author with paths if you like, but we strongly recommend using bare module specifiers everywhere—especially for reusable elements—because they're compatible with a wider range of tools.

Here are the specific tooling changes we're making:

  • Modulizer already has support for converting paths to bare specifiers when rewriting HTML imports to ES modules.

  • We're adding support for bare specifiers to the Polymer CLI and related tools, as well as other projects like prpl-node-server.

  • You can already preview code using bare module specifiers using polymer serve:

    polymer serve --module-resolution=node
    

Our support for bare specifiers is independent of our other build steps, like bundling. This step simply resolves specifiers and rewrites them as paths—polyserve will still serve modules, and polymer-build will output modules for browsers that support them. Bundling isn't required.

Renaming Polymer.Element

All of the Polymer modules are published on NPM under the @polymer namespace. The namespace is treated as part of the package name in the module specifier.

Since we expect most developers to change their import lines with the next release, we're taking the opportunity to rename Element to PolymerElement at the same time. In previous previews we recommended renaming it manually to avoid conflicts with the DOM's built-in Element class.

So the import for element will change from:

import {Element as PolymerElement} from '../@polymer/polymer/polymer-element.js';

To:

import {PolymerElement} from '@polymer/polymer/polymer-element.js';

Moving the platform forward

You could argue that supporting bare module specifiers doesn't fit with our "Use the Platform" motto. But pushing the platform forward is also an important part of our mission.

At the moment, there's renewed interest from various parties inside the web standards and Node communities in bringing support for bare module specifiers to the web. We're working with groups like WHATWG and the Node TSC to try and advance these proposals. We want ES imports to succeed and we think these changes will help adoption.

In the meantime, we think this approach is the pragmatic choice. It's more in line with the current mainstream of JavaScript development, compatible with most JavaScript tools, and it's easier to use. And it ensures that Polymer code will be ready for when the platform evolves to support bare specifiers.

Stay tuned

When we ship the next preview, we'll announce it here, and provide some more hands-on instructions for using Polymer with bare module specifiers.