Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(89)

Side by Side Diff: pkg/polymer/lib/src/build/import_inliner.dart

Issue 225043004: Replace bootstrap logic with 'boot.js', use 'component/dart' mime-type and add (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /// Transfomer that inlines polymer-element definitions from html imports. 5 /// Transfomer that inlines polymer-element definitions from html imports.
6 library polymer.src.build.import_inliner; 6 library polymer.src.build.import_inliner;
7 7
8 import 'dart:async'; 8 import 'dart:async';
9 import 'dart:convert'; 9 import 'dart:convert';
10 10
11 import 'package:analyzer/src/generated/ast.dart'; 11 import 'package:analyzer/src/generated/ast.dart';
12 import 'package:barback/barback.dart'; 12 import 'package:barback/barback.dart';
13 import 'package:code_transformers/assets.dart'; 13 import 'package:code_transformers/assets.dart';
14 import 'package:path/path.dart' as path; 14 import 'package:path/path.dart' as path;
15 import 'package:html5lib/dom.dart' show 15 import 'package:html5lib/dom.dart' show
16 Document, DocumentFragment, Element, Node; 16 Document, DocumentFragment, Element, Node;
17 import 'package:html5lib/dom_parsing.dart' show TreeVisitor; 17 import 'package:html5lib/dom_parsing.dart' show TreeVisitor;
18 import 'package:source_maps/refactor.dart' show TextEditTransaction; 18 import 'package:source_maps/refactor.dart' show TextEditTransaction;
19 import 'package:source_maps/span.dart'; 19 import 'package:source_maps/span.dart';
20 20
21 import 'common.dart'; 21 import 'common.dart';
22 22
23 class _HtmlInliner extends PolymerTransformer { 23 class _HtmlInliner extends PolymerTransformer {
Jennifer Messerly 2014/04/04 19:39:50 do you mind adding a TODO here, that this transfor
Siggi Cherem (dart-lang) 2014/04/04 21:09:11 Sure thing. I created a bug too: dartbug.com/18037
24 final TransformOptions options; 24 final TransformOptions options;
25 final Transform transform; 25 final Transform transform;
26 final TransformLogger logger; 26 final TransformLogger logger;
27 final AssetId docId; 27 final AssetId docId;
28 final seen = new Set<AssetId>(); 28 final seen = new Set<AssetId>();
29 final scriptIds = <AssetId>[]; 29 final scriptIds = <AssetId>[];
30 30
31 /// The number of extracted inline Dart scripts. Used as a counter to give 31 /// The number of extracted inline Dart scripts. Used as a counter to give
32 /// unique-ish filenames. 32 /// unique-ish filenames.
33 int inlineScriptCounter = 0; 33 int inlineScriptCounter = 0;
34 34
35 _HtmlInliner(this.options, Transform transform) 35 _HtmlInliner(this.options, Transform transform)
36 : transform = transform, 36 : transform = transform,
37 logger = transform.logger, 37 logger = transform.logger,
38 docId = transform.primaryInput.id; 38 docId = transform.primaryInput.id;
39 39
40 Future apply() { 40 Future apply() {
41 seen.add(docId); 41 seen.add(docId);
42 42
43 Document document; 43 Document document;
44 bool changed; 44 bool changed;
45 45
46 return readPrimaryAsHtml(transform).then((doc) { 46 return readPrimaryAsHtml(transform).then((doc) {
47 document = doc; 47 document = doc;
48 // Add the main script's ID, or null if none is present. 48 // Add the main script's ID, or null if none is present.
49 // This will be used by ScriptCompactor. 49 // This will be used by ScriptCompactor.
50 changed = _extractScripts(document, docId); 50 changed = _extractScripts(document, docId);
51 return _visitImports(document); 51 return _visitImports(document);
52 }).then((importsFound) { 52 }).then((importsFound) {
53 changed = changed || importsFound; 53 bool scriptsRemoved = _removeScripts(document);
54 changed = changed || importsFound || scriptsRemoved;
54 55
55 var output = transform.primaryInput; 56 var output = transform.primaryInput;
56 if (changed) output = new Asset.fromString(docId, document.outerHtml); 57 if (changed) output = new Asset.fromString(docId, document.outerHtml);
57 transform.addOutput(output); 58 transform.addOutput(output);
58 59
59 // We produce a secondary asset with extra information for later phases. 60 // We produce a secondary asset with extra information for later phases.
60 transform.addOutput(new Asset.fromString( 61 transform.addOutput(new Asset.fromString(
61 docId.addExtension('.scriptUrls'), 62 docId.addExtension('.scriptUrls'),
62 JSON.encode(scriptIds, toEncodable: (id) => id.serialize()))); 63 JSON.encode(scriptIds, toEncodable: (id) => id.serialize())));
63 }); 64 });
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
113 // Should we do the same? Alternatively could we inline head into head and 114 // Should we do the same? Alternatively could we inline head into head and
114 // body into body and avoid this whole thing? 115 // body into body and avoid this whole thing?
115 void _moveHeadToBody(Document doc) { 116 void _moveHeadToBody(Document doc) {
116 var insertionPoint = doc.body.firstChild; 117 var insertionPoint = doc.body.firstChild;
117 for (var node in doc.head.nodes.toList(growable: false)) { 118 for (var node in doc.head.nodes.toList(growable: false)) {
118 if (node is! Element) continue; 119 if (node is! Element) continue;
119 var tag = node.localName; 120 var tag = node.localName;
120 var type = node.attributes['type']; 121 var type = node.attributes['type'];
121 var rel = node.attributes['rel']; 122 var rel = node.attributes['rel'];
122 if (tag == 'style' || tag == 'script' && 123 if (tag == 'style' || tag == 'script' &&
123 (type == null || type == TYPE_JS || type == TYPE_DART) || 124 (type == null || type == TYPE_JS || type == TYPE_DART_APP ||
125 type == TYPE_DART_COMPONENT) ||
124 tag == 'link' && (rel == 'stylesheet' || rel == 'import')) { 126 tag == 'link' && (rel == 'stylesheet' || rel == 'import')) {
125 // Move the node into the body, where its contents will be placed. 127 // Move the node into the body, where its contents will be placed.
126 doc.body.insertBefore(node, insertionPoint); 128 doc.body.insertBefore(node, insertionPoint);
127 } 129 }
128 } 130 }
129 } 131 }
130 132
131 /// Loads an asset identified by [id], visits its imports and collects its 133 /// Loads an asset identified by [id], visits its imports and collects its
132 /// html imports. Then inlines it into the main document. 134 /// html imports. Then inlines it into the main document.
133 Future _inlineImport(AssetId id, Element link) { 135 Future _inlineImport(AssetId id, Element link) {
134 return readAsHtml(id, transform).then((doc) { 136 return readAsHtml(id, transform).then((doc) {
135 new _UrlNormalizer(transform, id).visit(doc); 137 new _UrlNormalizer(transform, id).visit(doc);
136 return _visitImports(doc).then((_) { 138 return _visitImports(doc).then((_) {
137 _extractScripts(doc, id); 139 _extractScripts(doc, id);
138 _removeScripts(doc);
139 140
140 // TODO(jmesserly): figure out how this is working in vulcanizer. 141 // TODO(jmesserly): figure out how this is working in vulcanizer.
141 // Do they produce a <body> tag with a <head> and <body> inside? 142 // Do they produce a <body> tag with a <head> and <body> inside?
142 var imported = new DocumentFragment(); 143 var imported = new DocumentFragment();
143 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes); 144 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes);
144 link.replaceWith(imported); 145 link.replaceWith(imported);
145 }); 146 });
146 }); 147 });
147 } 148 }
148 149
149 Future _inlineStylesheet(AssetId id, Element link) { 150 Future _inlineStylesheet(AssetId id, Element link) {
150 return transform.readInputAsString(id).then((css) { 151 return transform.readInputAsString(id).then((css) {
151 css = new _UrlNormalizer(transform, id).visitCss(css); 152 css = new _UrlNormalizer(transform, id).visitCss(css);
152 link.replaceWith(new Element.tag('style')..text = css); 153 link.replaceWith(new Element.tag('style')..text = css);
153 }); 154 });
154 } 155 }
155 156
156 /// Remove scripts from HTML imports, and remember their [AssetId]s for later 157 /// Remove "component/dart" scripts and remember their [AssetId]s for later
157 /// use. 158 /// use.
158 /// 159 ///
159 /// Dartium only allows a single script tag per page, so we can't inline 160 /// Dartium only allows a single script tag per page, so we can't inline
160 /// the script tags. Instead we remove them entirely. 161 /// the script tags. Instead we remove them entirely.
161 void _removeScripts(Document doc) { 162 bool _removeScripts(Document doc) {
163 bool changed = false;
162 for (var script in doc.querySelectorAll('script')) { 164 for (var script in doc.querySelectorAll('script')) {
163 if (script.attributes['type'] == TYPE_DART) { 165 if (script.attributes['type'] == TYPE_DART_COMPONENT) {
166 changed = true;
164 script.remove(); 167 script.remove();
165 var src = script.attributes['src']; 168 var src = script.attributes['src'];
166 scriptIds.add(uriToAssetId(docId, src, logger, script.sourceSpan)); 169 scriptIds.add(uriToAssetId(docId, src, logger, script.sourceSpan));
167
168 // only the first script needs to be added.
169 // others are already removed by _extractScripts
170 return;
171 } 170 }
172 } 171 }
172 return changed;
173 } 173 }
174 174
175 /// Split inline scripts into their own files. We need to do this for dart2js 175 /// Split inline scripts into their own files. We need to do this for dart2js
176 /// to be able to compile them. 176 /// to be able to compile them.
177 /// 177 ///
178 /// This also validates that there weren't any duplicate scripts. 178 /// This also validates that there weren't any duplicate scripts.
179 bool _extractScripts(Document doc, AssetId sourceId) { 179 bool _extractScripts(Document doc, AssetId sourceId) {
180 bool changed = false; 180 bool changed = false;
181 bool first = true; 181 bool first = true;
182 for (var script in doc.querySelectorAll('script')) { 182 for (var script in doc.querySelectorAll('script')) {
183 if (script.attributes['type'] != TYPE_DART) continue; 183 var type = script.attributes['type'];
184 if (type != TYPE_DART_COMPONENT && type != TYPE_DART_APP) continue;
184 185
185 // only one Dart script per document is supported in Dartium. 186 // only one Dart script per document is supported in Dartium.
186 if (!first) { 187 if (type == TYPE_DART_APP) {
187 // Remove the script. It's invalid to have more than one in Dartium. 188 if (!first) logger.warning(COMPONENT_WARNING, span: script.sourceSpan);
188 script.remove(); 189 first = false;
189 changed = true;
190
191 // TODO(jmesserly): remove this when we are running linter.
192 logger.warning('more than one Dart script per HTML '
193 'document is not supported. Script will be ignored.',
194 span: script.sourceSpan);
195 continue;
196 } 190 }
197 191
198 first = false;
199
200 var src = script.attributes['src']; 192 var src = script.attributes['src'];
201 if (src != null) continue; 193 if (src != null) continue;
202 194
203 final filename = path.url.basename(docId.path); 195 final filename = path.url.basename(docId.path);
204 final count = inlineScriptCounter++; 196 final count = inlineScriptCounter++;
205 var code = script.text; 197 var code = script.text;
206 // TODO(sigmund): ensure this path is unique (dartbug.com/12618). 198 // TODO(sigmund): ensure this path is unique (dartbug.com/12618).
207 script.attributes['src'] = src = '$filename.$count.dart'; 199 script.attributes['src'] = src = '$filename.$count.dart';
208 script.text = ''; 200 script.text = '';
209 changed = true; 201 changed = true;
210 202
211 var newId = docId.addExtension('.$count.dart'); 203 var newId = docId.addExtension('.$count.dart');
212 // TODO(jmesserly): consolidate this check with our other parsing of the 204 // TODO(jmesserly): consolidate this check with our other parsing of the
213 // Dart code, so we only parse it once. 205 // Dart code, so we only parse it once.
214 if (!_hasLibraryDirective(code)) { 206 if (!_hasLibraryDirective(code)) {
215 // Inject a library tag with an appropriate library name. 207 // Inject a library tag with an appropriate library name.
216 208
217 // Transform AssetId into a package name. For example: 209 // Transform AssetId into a package name. For example:
218 // myPkgName|lib/foo/bar.html -> myPkgName.foo.bar_html 210 // myPkgName|lib/foo/bar.html -> myPkgName.foo.bar_html
219 // myPkgName|web/foo/bar.html -> myPkgName.web.foo.bar_html 211 // myPkgName|web/foo/bar.html -> myPkgName.web.foo.bar_html
220 // This should roughly match the recommended library name conventions. 212 // This should roughly match the recommended library name conventions.
221 var libName = '${path.withoutExtension(sourceId.path)}_' 213 var libName = '${path.withoutExtension(sourceId.path)}_'
222 '${path.extension(sourceId.path).substring(1)}'; 214 '${path.extension(sourceId.path).substring(1)}';
223 if (libName.startsWith('lib/')) libName = libName.substring(4); 215 if (libName.startsWith('lib/')) libName = libName.substring(4);
224 libName = libName.replaceAll('/', '.').replaceAll('-', '_'); 216 libName = libName.replaceAll('/', '.').replaceAll('-', '_');
225 libName = '${sourceId.package}.$libName'; 217 libName = '${sourceId.package}.${libName}_$count';
226 218
227 code = "library $libName;\n$code"; 219 code = "library $libName;\n$code";
228 } 220 }
229 transform.addOutput(new Asset.fromString(newId, code)); 221 transform.addOutput(new Asset.fromString(newId, code));
230 } 222 }
231 return changed; 223 return changed;
232 } 224 }
233 } 225 }
234 226
235 /// Parse [code] and determine whether it has a library directive. 227 /// Parse [code] and determine whether it has a library directive.
(...skipping 15 matching lines...) Expand all
251 ImportInliner(this.options); 243 ImportInliner(this.options);
252 244
253 /// Only run on entry point .html files. 245 /// Only run on entry point .html files.
254 Future<bool> isPrimary(Asset input) => 246 Future<bool> isPrimary(Asset input) =>
255 new Future.value(options.isHtmlEntryPoint(input.id)); 247 new Future.value(options.isHtmlEntryPoint(input.id));
256 248
257 Future apply(Transform transform) => 249 Future apply(Transform transform) =>
258 new _HtmlInliner(options, transform).apply(); 250 new _HtmlInliner(options, transform).apply();
259 } 251 }
260 252
261 const TYPE_DART = 'application/dart'; 253 const TYPE_DART_APP = 'application/dart';
254 const TYPE_DART_COMPONENT = 'component/dart';
262 const TYPE_JS = 'text/javascript'; 255 const TYPE_JS = 'text/javascript';
263 256
264 /// Internally adjusts urls in the html that we are about to inline. 257 /// Internally adjusts urls in the html that we are about to inline.
265 class _UrlNormalizer extends TreeVisitor { 258 class _UrlNormalizer extends TreeVisitor {
266 final Transform transform; 259 final Transform transform;
267 260
268 /// Asset where the original content (and original url) was found. 261 /// Asset where the original content (and original url) was found.
269 final AssetId sourceId; 262 final AssetId sourceId;
270 263
271 _UrlNormalizer(this.transform, this.sourceId); 264 _UrlNormalizer(this.transform, this.sourceId);
272 265
273 visitElement(Element node) { 266 visitElement(Element node) {
274 node.attributes.forEach((name, value) { 267 node.attributes.forEach((name, value) {
275 if (_urlAttributes.contains(name)) { 268 if (_urlAttributes.contains(name)) {
276 if (value != '' && !value.trim().startsWith('{{')) { 269 if (value != '' && !value.trim().startsWith('{{')) {
277 node.attributes[name] = _newUrl(value, node.sourceSpan); 270 node.attributes[name] = _newUrl(value, node.sourceSpan);
278 } 271 }
279 } 272 }
280 }); 273 });
281 if (node.localName == 'style') { 274 if (node.localName == 'style') {
282 node.text = visitCss(node.text); 275 node.text = visitCss(node.text);
283 } else if (node.localName == 'script' && 276 } else if (node.localName == 'script') {
284 node.attributes['type'] == TYPE_DART) { 277 var type = node.attributes['type'];
285 // TODO(jmesserly): we might need to visit JS too to handle ES Harmony 278 // TODO(jmesserly): we might need to visit JS too to handle ES Harmony
286 // modules. 279 // modules.
287 node.text = visitInlineDart(node.text); 280 if (type == TYPE_DART_APP || type == TYPE_DART_COMPONENT) {
281 node.text = visitInlineDart(node.text);
282 }
288 } 283 }
289 super.visitElement(node); 284 super.visitElement(node);
290 } 285 }
291 286
292 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); 287 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true);
293 static final _QUOTE = new RegExp('["\']', multiLine: true); 288 static final _QUOTE = new RegExp('["\']', multiLine: true);
294 289
295 /// Visit the CSS text and replace any relative URLs so we can inline it. 290 /// Visit the CSS text and replace any relative URLs so we can inline it.
296 // Ported from: 291 // Ported from:
297 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378 acc691f/lib/vulcan.js#L149 292 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378 acc691f/lib/vulcan.js#L149
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
384 'formaction', // in button, input 379 'formaction', // in button, input
385 'href', // in a, area, link, base, command 380 'href', // in a, area, link, base, command
386 'icon', // in command 381 'icon', // in command
387 'manifest', // in html 382 'manifest', // in html
388 'poster', // in video 383 'poster', // in video
389 'src', // in audio, embed, iframe, img, input, script, source, track, 384 'src', // in audio, embed, iframe, img, input, script, source, track,
390 // video 385 // video
391 ]; 386 ];
392 387
393 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); 388 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end);
389
390 const COMPONENT_WARNING =
391 'More than one Dart script per HTML document is not supported, but in the '
392 'near future Dartium will execute each tag as a separate isolate. If this '
393 'code is meant to load definitions that are part of the same application '
394 'you should switch it to use the "component/dart" mime-type instead.';
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698