UNPKG

5.18 kBPlain TextView Raw
1/**
2 * @license
3 * Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
4 * This code may only be used under the BSD style license found at
5 * http://polymer.github.io/LICENSE.txt
6 * The complete set of authors may be found at
7 * http://polymer.github.io/AUTHORS.txt
8 * The complete set of contributors may be found at
9 * http://polymer.github.io/CONTRIBUTORS.txt
10 * Code distributed by Google as part of the polymer project is also
11 * subject to an additional IP rights grant found at
12 * http://polymer.github.io/PATENTS.txt
13 */
14
15import File = require('vinyl');
16import {ResolvedUrl} from 'polymer-analyzer';
17import {Bundler, Options, BundleManifest, generateShellMergeStrategy} from 'polymer-bundler';
18import {ProjectConfig} from 'polymer-project-config';
19
20import {BuildAnalyzer} from './analyzer';
21import {FileMapUrlLoader} from './file-map-url-loader';
22import {pathFromUrl, urlFromPath, LocalFsPath} from './path-transformers';
23import {AsyncTransformStream} from './streams';
24
25export {Options} from 'polymer-bundler';
26
27export class BuildBundler extends AsyncTransformStream<File, File> {
28 config: ProjectConfig;
29
30 private _buildAnalyzer: BuildAnalyzer;
31 private _bundler: Bundler;
32
33 // A map of urls to file objects. As the transform stream handleds files
34 // coming into the stream, it collects all files here. After bundlling,
35 // we remove files from this set that have been inlined and replace
36 // entrypoint/fragment files with bundled versions.
37 files = new Map<ResolvedUrl, File>();
38
39 constructor(
40 config: ProjectConfig, buildAnalyzer: BuildAnalyzer,
41 options: Options = {}) {
42 super({objectMode: true});
43
44 this.config = config;
45
46 this._buildAnalyzer = buildAnalyzer;
47
48 const bundlerOptions = {...options};
49
50 const urlLoader = new FileMapUrlLoader(
51 this.files, bundlerOptions.analyzer || buildAnalyzer.analyzer);
52 bundlerOptions.analyzer =
53 (bundlerOptions.analyzer || buildAnalyzer.analyzer)._fork({urlLoader});
54
55 if (bundlerOptions.strategy === undefined &&
56 this.config.shell !== undefined) {
57 bundlerOptions.strategy = generateShellMergeStrategy(
58 bundlerOptions.analyzer.resolveUrl(urlFromPath(
59 this.config.root as LocalFsPath,
60 this.config.shell as LocalFsPath))!);
61 }
62
63 this._bundler = new Bundler(bundlerOptions);
64 }
65
66 protected async *
67 _transformIter(files: AsyncIterable<File>): AsyncIterable<File> {
68 for await (const file of files) {
69 this._mapFile(file);
70 }
71 await this._buildBundles();
72 for (const file of this.files.values()) {
73 yield file;
74 }
75 }
76
77 private async _buildBundles() {
78 // Tell the analyzer about changed files so it can purge them from its cache
79 // before using the analyzer for bundling.
80 await this._bundler.analyzer.filesChanged(
81 this._getFilesChangedSinceInitialAnalysis());
82
83 const {documents, manifest} =
84 await this._bundler.bundle(await this._generateBundleManifest());
85 // Remove the bundled files from the file map so they are not emitted later.
86 this._unmapBundledFiles(manifest);
87
88 // Map the bundles into the file map.
89 for (const [url, document] of documents) {
90 this._mapFile(new File({
91 path: pathFromUrl(
92 this.config.root as LocalFsPath,
93 this._bundler.analyzer.urlResolver.relative(url)),
94 contents: Buffer.from(document.content),
95 }));
96 }
97 }
98
99 private async _generateBundleManifest(): Promise<BundleManifest> {
100 const entrypoints = this.config.allFragments.map(
101 (e) => this._bundler.analyzer.resolveUrl(
102 urlFromPath(this.config.root as LocalFsPath, e as LocalFsPath))!);
103 return this._bundler.generateManifest(entrypoints);
104 }
105
106 private _getFilesChangedSinceInitialAnalysis(): string[] {
107 const filesChanged = [];
108 for (const [url, originalFile] of this._buildAnalyzer.files) {
109 const downstreamFile =
110 this.files.get(this._buildAnalyzer.analyzer.resolveUrl(url)!);
111 if (downstreamFile == null) {
112 throw new Error(
113 `Internal error: could not find downstreamFile at ${url}`);
114 }
115 if (downstreamFile.contents!.toString() !==
116 originalFile.contents!.toString()) {
117 filesChanged.push(url);
118 }
119 }
120 return filesChanged;
121 }
122
123 private _mapFile(file: File) {
124 this.files.set(
125 this._buildAnalyzer.analyzer.resolveUrl(urlFromPath(
126 this.config.root as LocalFsPath, file.path as LocalFsPath))!,
127 file);
128 }
129
130 /**
131 * Removes all of the inlined files in a bundle manifest from the filemap.
132 */
133 private _unmapBundledFiles(manifest: BundleManifest) {
134 for (const {
135 files,
136 inlinedHtmlImports,
137 inlinedScripts,
138 inlinedStyles,
139 } of manifest.bundles.values()) {
140 for (const url
141 of [...files,
142 ...inlinedHtmlImports,
143 ...inlinedScripts,
144 ...inlinedStyles]) {
145 // Don't unmap the bundle file url itself.
146 if (!manifest.bundles.has(url)) {
147 this.files.delete(url);
148 }
149 }
150 }
151 }
152}