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

Side by Side Diff: web_components/lib/build/script_compactor.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 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
OLDNEW
(Empty)
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
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.
4 library web_components.build.script_compactor;
5
6 import 'dart:async';
7 import 'package:analyzer/analyzer.dart';
8 import 'package:barback/barback.dart';
9 import 'package:code_transformers/assets.dart';
10 import 'package:code_transformers/messages/build_logger.dart';
11 import 'package:html/dom.dart' as dom;
12 import 'package:html/parser.dart' as parser;
13 import 'package:path/path.dart' as path;
14 import 'package:source_maps/refactor.dart' show TextEditTransaction;
15 import 'package:source_span/source_span.dart';
16 import 'common.dart';
17 import 'import_crawler.dart';
18 import 'messages.dart';
19
20 /// Transformer which combines all dart scripts found in html imports into one
21 /// new bootstrap file, and replaces the old entry point script with that file.
22 ///
23 /// Note: Does not delete the original script files (it can't because the
24 /// imports may live in other packages). The [ImportInlinerTransformer] will not
25 /// copy scripts when inlining imports into your entry point to compensate for
26 /// this.
27 class ScriptCompactorTransformer extends Transformer {
28 final List<String> entryPoints;
29
30 ScriptCompactorTransformer([this.entryPoints]);
31
32 bool isPrimary(AssetId id) {
33 if (entryPoints != null) return entryPoints.contains(id.path);
34 // If no entry point is supplied, then any html file under web/ or test/ is
35 // an entry point.
36 return (id.path.startsWith('web/') || id.path.startsWith('test/')) &&
37 id.path.endsWith('.html');
38 }
39
40 apply(Transform transform) {
41 var logger = new BuildLogger(transform);
42 return new ScriptCompactor(transform, transform.primaryInput.id, logger)
43 .run()
44 .then((Asset bootstrap) {
45 if (bootstrap == null) return null;
46 return transform.primaryInput.readAsString().then((html) {
47 var doc = parser.parse(html);
48 var mainScriptTag = doc.querySelector('script[type="$dartType"]');
49 mainScriptTag.attributes['src'] =
50 _importPath(bootstrap.id, transform.primaryInput.id);
51 mainScriptTag.text = '';
52
53 transform.addOutput(
54 new Asset.fromString(transform.primaryInput.id, doc.outerHtml));
55 });
56 });
57 }
58 }
59
60 /// Helper class which does all the script compacting for a single entry point.
61 class ScriptCompactor {
62 /// Can be an AggregateTransform or Transform
63 final transform;
64
65 /// The primary input to start from.
66 final AssetId primaryInput;
67
68 /// The logger to use.
69 final BuildLogger logger;
70
71 /// How many inline scripts were extracted.
72 int inlineScriptCounter = 0;
73
74 /// Id representing the dart script which lives in the primaryInput.
75 AssetId mainScript;
76
77 /// Ids of all the scripts found in html imports.
78 final Set<AssetId> importScripts = new Set<AssetId>();
79
80 ScriptCompactor(this.transform, this.primaryInput, this.logger);
81
82 Future<Asset> run() {
83 var crawler = new ImportCrawler(transform, primaryInput, logger);
84 return crawler.crawlImports().then((imports) {
85 Future extractScripts(id) =>
86 _extractInlineScripts(id, imports[id].document);
87
88 return Future.forEach(imports.keys, extractScripts).then((_) {
89 if (mainScript == null) {
90 logger.error(
91 exactlyOneScriptPerEntryPoint.create({'url': primaryInput.path}));
92 return null;
93 }
94
95 var primaryDocument = imports[primaryInput].document;
96 assert(primaryDocument != null);
97
98 // Create the new bootstrap file and return its AssetId.
99 return _buildBootstrapFile(mainScript, importScripts);
100 });
101 });
102 }
103
104 /// Builds the bootstrap file and returns the path to it relative to
105 /// [primaryInput].
106 Asset _buildBootstrapFile(AssetId mainScript, Set<AssetId> importScripts) {
107 var bootstrapId = new AssetId(primaryInput.package,
108 primaryInput.path.replaceFirst('.html', '.bootstrap.dart'));
109
110 var buffer = new StringBuffer();
111 buffer.writeln('library ${_libraryNameFor(bootstrapId)};');
112 buffer.writeln();
113 var i = 0;
114 for (var script in importScripts) {
115 var path = _importPath(script, primaryInput);
116 buffer.writeln("import '$path' as i$i;");
117 i++;
118 }
119 var mainScriptPath = _importPath(mainScript, primaryInput);
120 buffer.writeln("import '$mainScriptPath' as i$i;");
121 buffer.writeln();
122 buffer.writeln('main() => i$i.main();');
123
124 var bootstrap = new Asset.fromString(bootstrapId, '$buffer');
125 transform.addOutput(bootstrap);
126 return bootstrap;
127 }
128
129 /// Split inline scripts into their own files. We need to do this for dart2js
130 /// to be able to compile them.
131 ///
132 /// This also validates that there weren't any duplicate scripts.
133 Future _extractInlineScripts(AssetId asset, dom.Document doc) {
134 var scripts = doc.querySelectorAll('script[type="$dartType"]');
135 return Future.forEach(scripts, (script) {
136 var type = script.attributes['type'];
137 var src = script.attributes['src'];
138
139 if (src != null) {
140 return _addScript(
141 asset, uriToAssetId(asset, src, logger, script.sourceSpan),
142 span: script.sourceSpan);
143 }
144
145 final count = inlineScriptCounter++;
146 var code = script.text;
147 // TODO(sigmund): ensure this path is unique (dartbug.com/12618).
148 var newId = primaryInput.addExtension('.$count.dart');
149 if (!_hasLibraryDirective(code)) {
150 var libName = _libraryNameFor(primaryInput, count);
151 code = "library $libName;\n$code";
152 }
153
154 // Normalize dart import paths.
155 code = _normalizeDartImports(code, asset, primaryInput);
156
157 // Write out the file and record it.
158 transform.addOutput(new Asset.fromString(newId, code));
159
160 return _addScript(asset, newId, validate: false).then((_) {
161 // If in the entry point, replace the inline script with one pointing to
162 // the new source file.
163 if (primaryInput == asset) {
164 script.text = '';
165 script.attributes['src'] = path.url.relative(newId.path,
166 from: path.url.dirname(primaryInput.path));
167 }
168 });
169 });
170 }
171
172 // Normalize dart import paths when moving code from one asset to another.
173 String _normalizeDartImports(String code, AssetId from, AssetId to) {
174 var unit = parseDirectives(code, suppressErrors: true);
175 var file = new SourceFile(code, url: spanUrlFor(from, to, logger));
176 var output = new TextEditTransaction(code, file);
177 var foundLibraryDirective = false;
178 for (Directive directive in unit.directives) {
179 if (directive is UriBasedDirective) {
180 var uri = directive.uri.stringValue;
181 var span = getSpan(file, directive.uri);
182
183 var id = uriToAssetId(from, uri, logger, span, errorOnAbsolute: false);
184 if (id == null) continue;
185
186 var primaryId = primaryInput;
187 var newUri = assetUrlFor(id, primaryId, logger);
188 if (newUri != uri) {
189 output.edit(span.start.offset, span.end.offset, "'$newUri'");
190 }
191 } else if (directive is LibraryDirective) {
192 foundLibraryDirective = true;
193 }
194 }
195
196 if (!output.hasEdits) return code;
197
198 // TODO(sigmund): emit source maps when barback supports it (see
199 // dartbug.com/12340)
200 return (output.commit()..build(file.url.toString())).text;
201 }
202
203 Future _addScript(AssetId from, AssetId scriptId,
204 {bool validate: true, SourceSpan span}) {
205 var validateFuture;
206 if (validate && !importScripts.contains(scriptId)) {
207 validateFuture = transform.hasInput(scriptId);
208 } else {
209 validateFuture = new Future.value(true);
210 }
211 return validateFuture.then((exists) {
212 if (!exists) {
213 logger.warning(scriptFileNotFound.create({'url': scriptId}),
214 span: span);
215 }
216
217 if (from == primaryInput) {
218 if (mainScript != null) {
219 logger
220 .error(exactlyOneScriptPerEntryPoint.create({'url': from.path}));
221 }
222 mainScript = scriptId;
223 } else {
224 importScripts.add(scriptId);
225 }
226 });
227 }
228 }
229
230 /// Generate a library name for an asset.
231 String _libraryNameFor(AssetId id, [int suffix]) {
232 var name = '${path.withoutExtension(id.path)}_'
233 '${path.extension(id.path).substring(1)}';
234 if (name.startsWith('lib/')) name = name.substring(4);
235 name = name.split('/').map((part) {
236 part = part.replaceAll(_invalidLibCharsRegex, '_');
237 if (part.startsWith(_numRegex)) part = '_${part}';
238 return part;
239 }).join(".");
240 var suffixString = suffix != null ? '_$suffix' : '';
241 return '${id.package}.${name}$suffixString';
242 }
243
244 /// Parse [code] and determine whether it has a library directive.
245 bool _hasLibraryDirective(String code) =>
246 parseDirectives(code, suppressErrors: true).directives
247 .any((d) => d is LibraryDirective);
248
249 /// Returns the dart import path to reach [id] relative to [primaryInput].
250 String _importPath(AssetId id, AssetId primaryInput) {
251 var parts = path.url.split(id.path);
252 if (parts[0] == 'lib') {
253 parts[0] = id.package;
254 return 'package:${path.url.joinAll(parts)}';
255 }
256 return path.url.relative(id.path, from: path.url.dirname(primaryInput.path));
257 }
258
259 // Constant and final variables
260 final _invalidLibCharsRegex = new RegExp('[^a-z0-9_]');
261 final _numRegex = new RegExp('[0-9]');
OLDNEW
« no previous file with comments | « web_components/lib/build/mirrors_remover.dart ('k') | web_components/lib/build/test_compatibility.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698