polymer-cli
Version:
A commandline tool for Polymer projects
183 lines (158 loc) • 6.64 kB
text/typescript
/**
* @license
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
import * as bower from 'bower';
import * as path from 'path';
import * as logging from 'plylog';
import {dest} from 'vinyl-fs';
import mergeStream = require('merge-stream');
import {forkStream, PolymerProject, addServiceWorker, SWConfig, HtmlSplitter} from 'polymer-build';
import {getOptimizeStreams} from 'polymer-build';
import {ProjectBuildOptions} from 'polymer-project-config';
import {waitFor, pipeStreams} from './streams';
import {loadServiceWorkerConfig} from './load-config';
import {LocalFsPath} from 'polymer-build/lib/path-transformers';
const logger = logging.getLogger('cli.build.build');
export const mainBuildDirectoryName = 'build';
/**
* Generate a single build based on the given `options` ProjectBuildOptions.
* Note that this function is only concerned with that single build, and does
* not care about the collection of builds defined on the config.
*/
export async function build(
options: ProjectBuildOptions,
polymerProject: PolymerProject): Promise<void> {
const buildName = options.name || 'default';
// If no name is provided, write directly to the build/ directory.
// If a build name is provided, write to that subdirectory.
const buildDirectory = path.join(mainBuildDirectoryName, buildName);
logger.debug(`"${buildDirectory}": Building with options:`, options);
// Fork the two streams to guarentee we are working with clean copies of each
// file and not sharing object references with other builds.
const sourcesStream = forkStream(polymerProject.sources());
const depsStream = forkStream(polymerProject.dependencies());
const bundled = !!(options.bundle);
let buildStream: NodeJS.ReadableStream =
mergeStream(sourcesStream, depsStream);
const compiledToES5 = (options.js === undefined) ?
false :
options.js.compile === true || options.js.compile === 'es5' ||
(typeof options.js.compile === 'object' &&
options.js.compile.target === 'es5');
if (compiledToES5) {
buildStream =
buildStream.pipe(polymerProject.addCustomElementsEs5Adapter());
}
async function getPolymerVersion(): Promise<string> {
return new Promise<string>(
(resolve, _reject) =>
bower.commands.list({}, {offline: true})
.on('end',
// tslint:disable-next-line: no-any
(result: any) => {
if (result && result.dependencies &&
result.dependencies.polymer &&
result.dependencies.polymer.pkgMeta &&
result.dependencies.polymer.pkgMeta.version) {
resolve(result.dependencies.polymer.pkgMeta.version);
} else {
resolve('');
}
})
.on('error', (oops: Error) => {
resolve('');
console.warn(oops.message);
}));
}
if (bundled) {
// Polymer 1.x and Polymer 2.x deal with relative urls in dom-module
// templates differently. Polymer CLI will attempt to provide a sensible
// default value for the `rewriteUrlsInTemplates` option passed to
// `polymer-bundler` based on the version of Polymer found in the project's
// folders. We will default to Polymer 1.x behavior unless 2.x is found.
const polymerVersion = await getPolymerVersion();
const bundlerOptions = {
rewriteUrlsInTemplates: !polymerVersion.startsWith('2.')
};
if (typeof options.bundle === 'object') {
Object.assign(bundlerOptions, options.bundle);
}
buildStream = buildStream.pipe(polymerProject.bundler(bundlerOptions));
}
const htmlSplitter = new HtmlSplitter();
buildStream = pipeStreams([
buildStream,
htmlSplitter.split(),
getOptimizeStreams({
html: options.html,
css: options.css,
js: {
...options.js,
moduleResolution: polymerProject.config.moduleResolution,
},
entrypointPath: polymerProject.config.entrypoint,
rootDir: polymerProject.config.root,
}),
htmlSplitter.rejoin()
]);
if (options.insertPrefetchLinks) {
buildStream = buildStream.pipe(polymerProject.addPrefetchLinks());
}
buildStream.once('data', () => {
logger.info(`(${buildName}) Building...`);
});
if (options.basePath) {
let basePath = options.basePath === true ? buildName : options.basePath;
if (!basePath.startsWith('/')) {
basePath = '/' + basePath;
}
if (!basePath.endsWith('/')) {
basePath = basePath + '/';
}
buildStream = buildStream.pipe(polymerProject.updateBaseTag(basePath));
}
if (options.addPushManifest) {
buildStream = buildStream.pipe(polymerProject.addPushManifest());
}
// Finish the build stream by piping it into the final build directory.
buildStream = buildStream.pipe(dest(buildDirectory));
// If a service worker was requested, parse the service worker config file
// while the build is in progress. Loading the config file during the build
// saves the user ~300ms vs. loading it afterwards.
const swPrecacheConfigPath = path.resolve(
polymerProject.config.root,
options.swPrecacheConfig || 'sw-precache-config.js');
let swConfig: SWConfig|null = null;
if (options.addServiceWorker) {
swConfig = await loadServiceWorkerConfig(swPrecacheConfigPath);
}
// There is nothing left to do, so wait for the build stream to complete.
await waitFor(buildStream);
if (options.addServiceWorker) {
logger.debug(`Generating service worker...`);
if (swConfig) {
logger.debug(`Service worker config found`, swConfig);
} else {
logger.debug(
`No service worker configuration found at ` +
`${swPrecacheConfigPath}, continuing with defaults`);
}
await addServiceWorker({
buildRoot: buildDirectory as LocalFsPath,
project: polymerProject,
swPrecacheConfig: swConfig || undefined,
bundled: bundled,
});
}
logger.info(`(${buildName}) Build complete!`);
}