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

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

Powered by Google App Engine
This is Rietveld 408576698