/** * @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 { 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 { return new Promise( (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!`); }