UNPKG

15.4 kBMarkdownView Raw
1[![Build Status](https://travis-ci.org/Polymer/polymer-build.svg?branch=master)](https://travis-ci.org/Polymer/polymer-build)
2[![NPM version](http://img.shields.io/npm/v/polymer-build.svg)](https://www.npmjs.com/package/polymer-build)
3
4# Polymer Build
5
6polymer-build is a library for building Polymer projects.
7
8
9## Installation
10
11```
12npm install --save-dev polymer-build
13```
14
15
16## Relationship to Polymer CLI
17
18The [Polymer CLI](https://github.com/Polymer/polymer-cli) uses polymer-build under the hood, so you can think of the CLI's `build` command like running a pre-configured polymer-build pipeline. Setting this up for you makes the CLI easy to use, but as a command-line wrapper its customization options are more limited. polymer-build allows you to completely customize your build and combine additional streams and build tasks in any order.
19
20Consider using polymer-build instead of the CLI if you:
21
22- Want to customize your build(s) without using the Polymer CLI
23- Need to run your source code through custom optimizers/processors before, after, or during your build
24- Need to hook additional work into any part of the build process
25
26
27## Usage
28
29While polymer-build was built to work easily with Gulp, it can be used in any Node.js environment. polymer-build is built on Node.js streams, and the build pipeline that you create with it is not much more than a series of connected streams. Files are represented as [Vinyl](https://github.com/gulpjs/vinyl) file objects, which means that polymer-build can interop with any existing Gulp/Vinyl streams.
30
31Check out the [custom-build generator](https://github.com/PolymerElements/generator-polymer-init-custom-build) for an example of how polymer-build can be used to build a project.
32
33
34### PolymerProject
35
36`PolymerProject` represents your project in the build pipeline. Once configured, it will give you access to a collection of streams and helpers for building your project.
37
38To create a new instance of `PolymerProject`, you'll need to give it some information about your application. If you already have a [`polymer.json`](https://www.polymer-project.org/2.0/docs/tools/polymer-json) configuration file in your project, you can create a new `PolymerProject` instance by loading it directly:
39
40```js
41const PolymerProject = require('polymer-build').PolymerProject;
42
43const project = new PolymerProject(require('./polymer.json'));
44```
45
46Or, you can pass in configuration options directly to the `PolymerProject` constructor:
47
48```js
49const PolymerProject = require('polymer-build').PolymerProject;
50
51const project = new PolymerProject({
52 entrypoint: 'index.html',
53 shell: 'src/my-app.js',
54 fragments: [
55 'src/my-view1.js',
56 'src/my-view2.js',
57 'src/my-view3.js'
58 ]
59});
60```
61
62#### project.sources()
63
64Returns a readable stream of your project's source files. By default, these are the files in your project's `src/` directory, but if you have additional source files this can be configured via the `sources` property in [`ProjectOptions`](src/polymer-project.ts).
65
66#### project.dependencies()
67
68Returns a readable stream of your project's dependencies. This stream is automatically populated based on the files loaded inside of your project. You can include additional dependencies via the `extraDependencies` property in [`ProjectOptions`](src/polymer-project.ts) (this can be useful when the analyzer fails to detect a necessary dependency.)
69
70```js
71const gulp = require('gulp');
72const mergeStream = require('merge-stream');
73
74// Create a build pipeline to pipe both streams together to the 'build/' dir
75mergeStream(project.sources(), project.dependencies())
76 .pipe(gulp.dest('build/'));
77```
78
79
80### Handling Inlined CSS/JS
81
82#### HtmlSplitter
83
84Web components will sometimes include inlined CSS & JavaScript. This can pose a problem for tools that weren't built to read those languages from inside HTML. To solve this, you can create an `HtmlSplitter` instance to extract inlined CSS & JavaScript into individual files in your build stream for processing (and then rejoin them later).
85
86```js
87const gulpif = require('gulp-if');
88const uglify = require('gulp-uglify');
89const cssSlam = require('css-slam').gulp;
90const htmlMinifier = require('gulp-html-minifier');
91const HtmlSplitter = require('polymer-build').HtmlSplitter;
92
93const sourcesHtmlSplitter = new HtmlSplitter();
94const sourcesStream = project.sources()
95 .pipe(sourcesHtmlSplitter.split()) // split inline JS & CSS out into individual .js & .css files
96 .pipe(gulpif(/\.js$/, uglify()))
97 .pipe(gulpif(/\.css$/, cssSlam()))
98 .pipe(gulpif(/\.html$/, htmlMinifier()))
99 .pipe(sourcesHtmlSplitter.rejoin()); // rejoins those files back into their original location
100
101// NOTE: If you want to split/rejoin your dependencies stream as well, you'll need to create a new HtmlSplitter for that stream.
102```
103
104You can add splitters to any part of your build stream. We recommend using them to optimize your `sources()` and `dependencies()` streams individually as in the example above, but you can also optimize after merging the two together or even after bundling.
105
106
107### Bundling Files
108
109#### project.bundler()
110
111A stream that combines seperate files into code bundles based on your application's dependency graph. This can be a great way to [improve performance](https://developer.yahoo.com/performance/rules.html#num_http) by reducing the number of frontend requests needed.
112
113By default, the bundler will create one "shared-bundle.html" containing all shared dependencies. You can optimize even further by defining "fragments" in your project options. Fragments are lazy loaded parts of the application, typically views and other elements loaded on-demand. When fragments are defined, the bundler is able to create smaller bundles containing code that is only required for specific fragments.
114
115```js
116const gulp = require('gulp');
117const mergeStream = require('merge-stream');
118
119// Create a build pipeline to bundle our application before writing to the 'build/' dir
120mergeStream(project.sources(), project.dependencies())
121 .pipe(project.bundler())
122 .pipe(gulp.dest('build/'));
123```
124
125The bundler() method accepts an options object to configure bundling. See [Using polymer-bundler programmatically](https://github.com/Polymer/tools/tree/master/packages/bundler#using-polymer-bundler-programmatically) for a detailed list of accepted options.
126
127```js
128const {generateCountingSharedBundleUrlMapper,
129 generateSharedDepsMergeStrategy} = require('polymer-bundler');
130
131mergeStream(project.sources(), project.dependencies())
132 .pipe(project.bundler({
133 excludes: ['bower_components/polymer-code-mirror'],
134 sourcemaps: true,
135 stripComments: true,
136 strategy: generateSharedDepsMergeStrategy(3),
137 urlMapper: generateCountingSharedBundleUrlMapper('shared/bundle_')
138 }))
139 .pipe(gulp.dest('build/'));
140```
141
142NOTE: When working programmatically with Bundler, **Polymer 1.x** projects should include `rewriteUrlsInTemplates: true` when their projects rely on custom element definitions which use relative paths inside style tags and element attributes. **Polymer 2.x** uses the `assetpath` property added to dom-modules during bundling to resolve relative urls in style tags and provides the [importPath](https://www.polymer-project.org/2.0/docs/devguide/dom-template#urls-in-templates) binding to prefix relative paths in templates.
143
144### Generating a Service Worker
145
146#### generateServiceWorker()
147
148`generateServiceWorker()` will generate the service worker code based on your build. Unlike other parts of polymer-build, `generateServiceWorker()` returns a promise and not a stream. It can only be run **after** your build has finished writing to disk, so that it is able to analyze the entire build as it exists.
149
150For bundled builds, be sure to set the bundled option to `true`. See [AddServiceWorkerOptions](src/service-worker.ts) for a list of all supported options.
151
152```js
153const generateServiceWorker = require('polymer-build').generateServiceWorker;
154
155generateServiceWorker({
156 buildRoot: 'build/',
157 project: project,
158 bundled: true, // set if `project.bundler()` was used
159 swPrecacheConfig: {
160 // See https://github.com/GoogleChrome/sw-precache#options-parameter for all supported options
161 navigateFallback: '/index.html',
162 }
163}).then(() => { // ...
164```
165
166`generateServiceWorker()` is built on top of the [sw-precache](https://github.com/GoogleChrome/sw-precache) library. Any options it supports can be passed directly to that library via the `swPrecacheConfig` option. See [sw-preache](https://github.com/GoogleChrome/sw-precache#options-parameter) for a list of all supported options
167
168In some cases you may need to whitelist 3rd party services with sw-precache, so the Service Worker doesn't intercept them. For instance, if you're hosting your app on Firebase, you'll want to add the `navigateFallbackWhitelist: [/^(?!\/__)/]` option to your `swPrecacheConfig` as Firebase owns the `__` namespace, and intercepting it will cause things like OAuth to fail.
169
170#### addServiceWorker()
171
172Like `generateServiceWorker()`, but writes the generated service worker to the file path you specify in the `path` option ("service-worker.js" by default).
173
174```js
175const addServiceWorker = require('polymer-build').addServiceWorker;
176
177addServiceWorker({
178 buildRoot: 'build/',
179 project: project,
180}).then(() => { // ...
181```
182
183
184### Generating an HTTP/2 Push Manifest
185
186`polymer-build` can automatically generate a [push manifest](https://github.com/GoogleChrome/http2-push-manifest) for your application. This JSON file can be read by any HTTP/2 push-enabled web server to more easily construct the appropriate `Link: <URL>; rel=preload; as=<TYPE>` headers(s) for HTTP/2 push/preload. Check out [http2push-gae](https://github.com/GoogleChrome/http2push-gae) for an example Google Apps Engine server that supports this.
187
188The generated push manifest describes the following behavior: Requesting the shell should push any shell dependencies as well. Requesting a fragment should push any dependencies of that fragment *that were not already pushed by the shell.* If no shell was defined for your build, `polymer-build` will use the application entrypoint URL instead (default: `index.html`).
189
190
191#### project.addPushManifest()
192
193This method will return a transform stream that injects a new push manifest into your build (default: `push-manifest.json`). The push manifest is based off the application import graph, so make sure that this stream is added after all changes to the application source code.
194
195Use the `filePath` argument to configure the name of the generated file (relative to your application root).
196
197Use the `prefix` argument to prepend a string to all resource paths in the generated manifest. This can
198be useful when a build is going to be served from a sub-directory on the server.
199
200```js
201const gulp = require('gulp');
202const mergeStream = require('merge-stream');
203
204mergeStream(project.sources(), project.dependencies())
205 .pipe(project.addPushManifest())
206 .pipe(gulp.dest('build/'));
207```
208
209### Custom Elements ES5 Adapter
210
211If your build pipeline outputs ES5 custom elements (either from source or by compilation with a tool like Babel), it is critical to include the [Custom Elements ES5 Adapter](https://github.com/webcomponents/custom-elements/blob/master/src/native-shim.js). This adapter provides compatibility between custom elements defined as ES5-style classes and browsers with native implementations of the Custom Elements API, such as Chrome. See the [adapter documentation](https://github.com/webcomponents/custom-elements/blob/master/src/native-shim.js) for details of why this is necessary.
212
213#### project.addCustomElementsEs5Adapter()
214
215This method will return a transform stream that identifies your entrypoint HTML file (by looking for a webcomponents polyfill import) and injects a block of HTML that loads the Custom Elements ES5 Adapter. You can use this in a build pipeline to conditionally inject the adapter only when you output ES5 (as it is not needed when you output ES6).
216
217```js
218const gulp = require('gulp');
219const mergeStream = require('merge-stream');
220
221mergeStream(project.sources(), project.dependencies())
222 .pipe(project.addCustomElementsEs5Adapter())
223 .pipe(gulp.dest('build/'));
224```
225
226
227### Multiple Builds
228
229#### forkStream(stream)
230
231Sometimes you'll want to pipe a build to multiple destinations. `forkStream()` creates a new stream that copies the original stream, cloning all files that pass through it.
232
233```js
234const gulp = require('gulp');
235const mergeStream = require('merge-stream');
236const forkStream = require('polymer-build').forkStream;
237
238// Create a combined build stream of your application files
239const buildStream = mergeStream(project.sources(), project.dependencies());
240
241// Fork your build stream to write directly to the 'build/unbundled' dir
242const unbundledBuildStream = forkStream(buildStream)
243 .pipe(gulp.dest('build/unbundled'));
244
245// Fork your build stream to bundle your application and write to the 'build/bundled' dir
246const bundledBuildStream = forkStream(buildStream)
247 .pipe(project.bundler())
248 .pipe(gulp.dest('build/bundled'));
249```
250
251#### project.updateBaseTag()
252
253This method will return a transform stream that finds a `<base>` tag in your configured entrypoint HTML file, and updates it with the specified value. This can be useful when multiple builds are served each from their own sub-directory on the same host, in conjunction with a convention of using relative URLs for static resources. Your entrypoint must already contain a `<base href="/">` or similar tag in its `<head>`, before any imports.
254
255Note that *only the entrypoint will be updated*. Fragments with `<base>` tags will not be modified. Fragments should typically use relative URLs to refer to other artifacts in the build, so that they are agnostic to their serving path. The entrypoint gets special treatment here because it is typically served from paths that do not correspond to its location relative to other build artifacts.
256
257```js
258const gulp = require('gulp');
259const mergeStream = require('merge-stream');
260const forkStream = require('polymer-build').forkStream;
261
262const buildStream = mergeStream(project.sources(), project.dependencies());
263
264const unbundledBuildStream = forkStream(buildStream)
265 // This build will be served from http://example.com/unbundled/
266 .pipe(project.updateBaseTag('/unbundled/'))
267 .pipe(gulp.dest('build/unbundled'));
268
269const bundledBuildStream = forkStream(buildStream)
270 .pipe(project.bundler())
271 // While this build will be served from http://example.com/bundled/
272 .pipe(project.updateBaseTag('/bundled/'))
273 .pipe(gulp.dest('build/bundled'));
274```
275
276
277## Contributing
278
2791. Fork it!
2802. Create your feature branch: `git checkout -b my-new-feature`
2813. Commit your changes: `git commit -am 'Add some feature'`
2824. Push to the branch: `git push origin my-new-feature`
2835. Submit a pull request :D
284
285You can compile polymer-build from source by cloning the repo and then running `npm run build`. Make sure you have already run `npm install` before compiling.
286
287
288## Supported Node.js Versions
289
290polymer-build officially supports the latest current & active [LTS](https://github.com/nodejs/LTS) versions of Node.js. See our [.travis.yml](/.travis.yml) for the current versions of Node.js under test and visit the [Polymer Tools Node.js Support Policy](https://www.polymer-project.org/2.0/docs/tools/node-support) for more information.