UNPKG

14.3 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
5 * This code may only be used under the BSD style license found at
6 * http://polymer.github.io/LICENSE.txt
7 * The complete set of authors may be found at
8 * http://polymer.github.io/AUTHORS.txt
9 * The complete set of contributors may be found at
10 * http://polymer.github.io/CONTRIBUTORS.txt
11 * Code distributed by Google as part of the polymer project is also
12 * subject to an additional IP rights grant found at
13 * http://polymer.github.io/PATENTS.txt
14 */
15var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
16 return new (P || (P = Promise))(function (resolve, reject) {
17 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
18 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
19 function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
20 step((generator = generator.apply(thisArg, _arguments || [])).next());
21 });
22};
23var __asyncValues = (this && this.__asyncValues) || function (o) {
24 if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
25 var m = o[Symbol.asyncIterator], i;
26 return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
27 function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
28 function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
29};
30var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
31var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
32 if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
33 var g = generator.apply(thisArg, _arguments || []), i, q = [];
34 return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
35 function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
36 function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
37 function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
38 function fulfill(value) { resume("next", value); }
39 function reject(value) { resume("throw", value); }
40 function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
41};
42Object.defineProperty(exports, "__esModule", { value: true });
43const assert = require("assert");
44const dom5 = require("dom5/lib/index-next");
45const parse5 = require("parse5");
46const osPath = require("path");
47const File = require("vinyl");
48const streams_1 = require("./streams");
49const pred = dom5.predicates;
50const extensionsForType = {
51 'text/ecmascript-6': 'js',
52 'application/javascript': 'js',
53 'text/javascript': 'js',
54 'application/x-typescript': 'ts',
55 'text/x-typescript': 'ts',
56 'module': 'js',
57};
58/**
59 * HTMLSplitter represents the shared state of files as they are passed through
60 * a splitting stream and then a rejoining stream. Creating a new instance of
61 * HTMLSplitter and adding its streams to the build pipeline is the
62 * supported user interface for splitting out and rejoining inlined CSS & JS in
63 * the build process.
64 */
65class HtmlSplitter {
66 constructor() {
67 this._splitFiles = new Map();
68 this._parts = new Map();
69 }
70 /**
71 * Returns a new `Transform` stream that splits inline script and styles into
72 * new, separate files that are passed out of the stream.
73 */
74 split() {
75 return new HtmlSplitTransform(this);
76 }
77 /**
78 * Returns a new `Transform` stream that rejoins inline scripts and styles
79 * that were originally split from this `HTMLSplitter`'s `split()` back into
80 * their parent HTML files.
81 */
82 rejoin() {
83 return new HtmlRejoinTransform(this);
84 }
85 isSplitFile(parentPath) {
86 return this._splitFiles.has(parentPath);
87 }
88 getSplitFile(parentPath) {
89 // TODO(justinfagnani): rewrite so that processing a parent file twice
90 // throws to protect against bad configurations of multiple streams that
91 // contain the same file multiple times.
92 let splitFile = this._splitFiles.get(parentPath);
93 if (!splitFile) {
94 splitFile = new SplitFile(parentPath);
95 this._splitFiles.set(parentPath, splitFile);
96 }
97 return splitFile;
98 }
99 addSplitPath(parentPath, childPath) {
100 const splitFile = this.getSplitFile(parentPath);
101 splitFile.addPartPath(childPath);
102 this._parts.set(childPath, splitFile);
103 }
104 getParentFile(childPath) {
105 return this._parts.get(childPath);
106 }
107}
108exports.HtmlSplitter = HtmlSplitter;
109const htmlSplitterAttribute = 'html-splitter';
110/**
111 * Returns whether the given script tag was an inline script that was split out
112 * into a fake file by HtmlSplitter.
113 */
114function scriptWasSplitByHtmlSplitter(script) {
115 return dom5.hasAttribute(script, htmlSplitterAttribute);
116}
117exports.scriptWasSplitByHtmlSplitter = scriptWasSplitByHtmlSplitter;
118/**
119 * Return whether the given Vinyl file was created by the HtmlSplitter from an
120 * HTML document script tag.
121 */
122function isHtmlSplitterFile(file) {
123 return file.fromHtmlSplitter === true;
124}
125exports.isHtmlSplitterFile = isHtmlSplitterFile;
126/**
127 * Represents a file that is split into multiple files.
128 */
129class SplitFile {
130 constructor(path) {
131 this.parts = new Map();
132 this.outstandingPartCount = 0;
133 this.vinylFile = null;
134 this.path = path;
135 }
136 addPartPath(path) {
137 this.parts.set(path, null);
138 this.outstandingPartCount++;
139 }
140 setPartContent(path, content) {
141 assert(this.parts.get(path) !== undefined, `Trying to save unexpected file part "${path}".`);
142 assert(this.parts.get(path) === null, `Trying to save already-saved file part "${path}".`);
143 assert(this.outstandingPartCount > 0, `Trying to save valid file part "${path}", ` +
144 `but somehow no file parts are outstanding.`);
145 this.parts.set(path, content);
146 this.outstandingPartCount--;
147 }
148 get isComplete() {
149 return this.outstandingPartCount === 0 && this.vinylFile != null;
150 }
151}
152exports.SplitFile = SplitFile;
153/**
154 * Splits HTML files, extracting scripts and styles into separate File objects.
155 */
156class HtmlSplitTransform extends streams_1.AsyncTransformStream {
157 constructor(splitter) {
158 super({ objectMode: true });
159 this._state = splitter;
160 }
161 _transformIter(files) {
162 return __asyncGenerator(this, arguments, function* _transformIter_1() {
163 var e_1, _a;
164 try {
165 for (var files_1 = __asyncValues(files), files_1_1; files_1_1 = yield __await(files_1.next()), !files_1_1.done;) {
166 const file = files_1_1.value;
167 const filePath = osPath.normalize(file.path);
168 if (!(file.contents && filePath.endsWith('.html'))) {
169 yield yield __await(file);
170 continue;
171 }
172 const contents = yield __await(streams_1.getFileContents(file));
173 const doc = parse5.parse(contents, { locationInfo: true });
174 dom5.removeFakeRootElements(doc);
175 const scriptTags = [...dom5.queryAll(doc, pred.hasTagName('script'))];
176 for (let i = 0; i < scriptTags.length; i++) {
177 const scriptTag = scriptTags[i];
178 const source = dom5.getTextContent(scriptTag);
179 const typeAttribute = dom5.getAttribute(scriptTag, 'type') || 'application/javascript';
180 const extension = extensionsForType[typeAttribute];
181 // If we don't recognize the script type attribute, don't split
182 // out.
183 if (!extension) {
184 continue;
185 }
186 const isInline = !dom5.hasAttribute(scriptTag, 'src');
187 if (isInline) {
188 const childFilename = `${osPath.basename(filePath)}_script_${i}.${extension}`;
189 const childPath = osPath.join(osPath.dirname(filePath), childFilename);
190 scriptTag.childNodes = [];
191 dom5.setAttribute(scriptTag, 'src', childFilename);
192 dom5.setAttribute(scriptTag, htmlSplitterAttribute, '');
193 const scriptFile = new File({
194 cwd: file.cwd,
195 base: file.base,
196 path: childPath,
197 contents: Buffer.from(source),
198 });
199 scriptFile.fromHtmlSplitter = true;
200 scriptFile.isModule = typeAttribute === 'module';
201 this._state.addSplitPath(filePath, childPath);
202 this.push(scriptFile);
203 }
204 }
205 const splitContents = parse5.serialize(doc);
206 const newFile = new File({
207 cwd: file.cwd,
208 base: file.base,
209 path: filePath,
210 contents: Buffer.from(splitContents),
211 });
212 yield yield __await(newFile);
213 }
214 }
215 catch (e_1_1) { e_1 = { error: e_1_1 }; }
216 finally {
217 try {
218 if (files_1_1 && !files_1_1.done && (_a = files_1.return)) yield __await(_a.call(files_1));
219 }
220 finally { if (e_1) throw e_1.error; }
221 }
222 });
223 }
224}
225/**
226 * Joins HTML files originally split by `Splitter`, based on the relationships
227 * stored in its HTMLSplitter state.
228 */
229class HtmlRejoinTransform extends streams_1.AsyncTransformStream {
230 constructor(splitter) {
231 super({ objectMode: true });
232 this._state = splitter;
233 }
234 _transformIter(files) {
235 return __asyncGenerator(this, arguments, function* _transformIter_2() {
236 var e_2, _a;
237 try {
238 for (var files_2 = __asyncValues(files), files_2_1; files_2_1 = yield __await(files_2.next()), !files_2_1.done;) {
239 const file = files_2_1.value;
240 const filePath = osPath.normalize(file.path);
241 if (this._state.isSplitFile(filePath)) {
242 // this is a parent file
243 const splitFile = this._state.getSplitFile(filePath);
244 splitFile.vinylFile = file;
245 if (splitFile.isComplete) {
246 yield yield __await(yield __await(this._rejoin(splitFile)));
247 }
248 else {
249 splitFile.vinylFile = file;
250 }
251 }
252 else {
253 const parentFile = this._state.getParentFile(filePath);
254 if (parentFile) {
255 // this is a child file
256 parentFile.setPartContent(filePath, file.contents.toString());
257 if (parentFile.isComplete) {
258 yield yield __await(yield __await(this._rejoin(parentFile)));
259 }
260 }
261 else {
262 yield yield __await(file);
263 }
264 }
265 }
266 }
267 catch (e_2_1) { e_2 = { error: e_2_1 }; }
268 finally {
269 try {
270 if (files_2_1 && !files_2_1.done && (_a = files_2.return)) yield __await(_a.call(files_2));
271 }
272 finally { if (e_2) throw e_2.error; }
273 }
274 });
275 }
276 _rejoin(splitFile) {
277 return __awaiter(this, void 0, void 0, function* () {
278 const file = splitFile.vinylFile;
279 if (file == null) {
280 throw new Error(`Internal error: no vinylFile found for splitfile: ${splitFile.path}`);
281 }
282 const filePath = osPath.normalize(file.path);
283 const contents = yield streams_1.getFileContents(file);
284 const doc = parse5.parse(contents, { locationInfo: true });
285 dom5.removeFakeRootElements(doc);
286 const scriptTags = dom5.queryAll(doc, HtmlRejoinTransform.isExternalScript);
287 for (const scriptTag of scriptTags) {
288 const srcAttribute = dom5.getAttribute(scriptTag, 'src');
289 const scriptPath = osPath.join(osPath.dirname(splitFile.path), srcAttribute);
290 const scriptSource = splitFile.parts.get(scriptPath);
291 if (scriptSource != null) {
292 dom5.setTextContent(scriptTag, scriptSource);
293 dom5.removeAttribute(scriptTag, 'src');
294 dom5.removeAttribute(scriptTag, htmlSplitterAttribute);
295 }
296 }
297 const joinedContents = parse5.serialize(doc);
298 return new File({
299 cwd: file.cwd,
300 base: file.base,
301 path: filePath,
302 contents: Buffer.from(joinedContents),
303 });
304 });
305 }
306}
307HtmlRejoinTransform.isExternalScript = pred.AND(pred.hasTagName('script'), pred.hasAttr('src'));
308//# sourceMappingURL=html-splitter.js.map
\No newline at end of file