OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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 library web_components.build.script_compactor; | 4 library web_components.build.script_compactor; |
5 | 5 |
6 import 'dart:async'; | 6 import 'dart:async'; |
7 import 'package:analyzer/analyzer.dart'; | 7 import 'package:analyzer/analyzer.dart'; |
8 import 'package:barback/barback.dart'; | 8 import 'package:barback/barback.dart'; |
9 import 'package:code_transformers/assets.dart'; | 9 import 'package:code_transformers/assets.dart'; |
10 import 'package:code_transformers/messages/build_logger.dart'; | 10 import 'package:code_transformers/messages/build_logger.dart'; |
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
101 }); | 101 }); |
102 } | 102 } |
103 | 103 |
104 /// Builds the bootstrap file and returns the path to it relative to | 104 /// Builds the bootstrap file and returns the path to it relative to |
105 /// [primaryInput]. | 105 /// [primaryInput]. |
106 Asset _buildBootstrapFile(AssetId mainScript, Set<AssetId> importScripts) { | 106 Asset _buildBootstrapFile(AssetId mainScript, Set<AssetId> importScripts) { |
107 var bootstrapId = new AssetId(primaryInput.package, | 107 var bootstrapId = new AssetId(primaryInput.package, |
108 primaryInput.path.replaceFirst('.html', '.bootstrap.dart')); | 108 primaryInput.path.replaceFirst('.html', '.bootstrap.dart')); |
109 | 109 |
110 var buffer = new StringBuffer(); | 110 var buffer = new StringBuffer(); |
111 buffer.writeln('library ${_libraryNameFor(bootstrapId)};'); | 111 buffer.writeln('library ${_libraryNameFor(bootstrapId, logger)};'); |
112 buffer.writeln(); | 112 buffer.writeln(); |
113 var i = 0; | 113 var i = 0; |
114 for (var script in importScripts) { | 114 for (var script in importScripts) { |
115 var path = _importPath(script, primaryInput); | 115 var path = _importPath(script, primaryInput); |
116 buffer.writeln("import '$path' as i$i;"); | 116 buffer.writeln("import '$path' as i$i;"); |
117 i++; | 117 i++; |
118 } | 118 } |
119 var mainScriptPath = _importPath(mainScript, primaryInput); | 119 var mainScriptPath = _importPath(mainScript, primaryInput); |
120 buffer.writeln("import '$mainScriptPath' as i$i;"); | 120 buffer.writeln("import '$mainScriptPath' as i$i;"); |
121 buffer.writeln(); | 121 buffer.writeln(); |
122 buffer.writeln('main() => i$i.main();'); | 122 buffer.writeln('main() => i$i.main();'); |
123 | 123 |
124 var bootstrap = new Asset.fromString(bootstrapId, '$buffer'); | 124 var bootstrap = new Asset.fromString(bootstrapId, '$buffer'); |
125 transform.addOutput(bootstrap); | 125 transform.addOutput(bootstrap); |
126 return bootstrap; | 126 return bootstrap; |
127 } | 127 } |
128 | 128 |
129 /// Split inline scripts into their own files. We need to do this for dart2js | 129 /// Split inline scripts into their own files. We need to do this for dart2js |
130 /// to be able to compile them. | 130 /// to be able to compile them. |
131 /// | 131 /// |
132 /// This also validates that there weren't any duplicate scripts. | 132 /// This also validates that there weren't any duplicate scripts. |
133 Future _extractInlineScripts(AssetId asset, dom.Document doc) { | 133 Future _extractInlineScripts(AssetId asset, dom.Document doc) { |
134 var scripts = doc.querySelectorAll('script[type="$dartType"]'); | 134 var scripts = doc.querySelectorAll('script[type="$dartType"]'); |
135 return Future.forEach(scripts, (script) { | 135 return Future.forEach(scripts, (script) { |
136 var type = script.attributes['type']; | |
137 var src = script.attributes['src']; | 136 var src = script.attributes['src']; |
138 | 137 |
139 if (src != null) { | 138 if (src != null) { |
140 return _addScript( | 139 return _addScript( |
141 asset, uriToAssetId(asset, src, logger, script.sourceSpan), | 140 asset, uriToAssetId(asset, src, logger, script.sourceSpan), |
142 span: script.sourceSpan); | 141 span: script.sourceSpan); |
143 } | 142 } |
144 | 143 |
145 final count = inlineScriptCounter++; | 144 final count = inlineScriptCounter++; |
146 var code = script.text; | 145 var code = script.text; |
147 // TODO(sigmund): ensure this path is unique (dartbug.com/12618). | 146 // TODO(sigmund): ensure this path is unique (dartbug.com/12618). |
148 var newId = primaryInput.addExtension('.$count.dart'); | 147 var newId = primaryInput.addExtension('.$count.dart'); |
149 if (!_hasLibraryDirective(code)) { | 148 if (!_hasLibraryDirective(code)) { |
150 var libName = _libraryNameFor(primaryInput, count); | 149 var libName = _libraryNameFor(primaryInput, logger, count); |
151 code = "library $libName;\n$code"; | 150 code = "library $libName;\n$code"; |
152 } | 151 } |
153 | 152 |
154 // Normalize dart import paths. | 153 // Normalize dart import paths. |
155 code = _normalizeDartImports(code, asset, primaryInput); | 154 code = _normalizeDartImports(code, asset, primaryInput); |
156 | 155 |
157 // Write out the file and record it. | 156 // Write out the file and record it. |
158 transform.addOutput(new Asset.fromString(newId, code)); | 157 transform.addOutput(new Asset.fromString(newId, code)); |
159 | 158 |
160 return _addScript(asset, newId, validate: false).then((_) { | 159 return _addScript(asset, newId, validate: false).then((_) { |
161 // If in the entry point, replace the inline script with one pointing to | 160 // If in the entry point, replace the inline script with one pointing to |
162 // the new source file. | 161 // the new source file. |
163 if (primaryInput == asset) { | 162 if (primaryInput == asset) { |
164 script.text = ''; | 163 script.text = ''; |
165 script.attributes['src'] = path.url.relative(newId.path, | 164 script.attributes['src'] = path.url |
166 from: path.url.dirname(primaryInput.path)); | 165 .relative(newId.path, from: path.url.dirname(primaryInput.path)); |
167 } | 166 } |
168 }); | 167 }); |
169 }); | 168 }); |
170 } | 169 } |
171 | 170 |
172 // Normalize dart import paths when moving code from one asset to another. | 171 // Normalize dart import paths when moving code from one asset to another. |
173 String _normalizeDartImports(String code, AssetId from, AssetId to) { | 172 String _normalizeDartImports(String code, AssetId from, AssetId to) { |
174 var unit = parseDirectives(code, suppressErrors: true); | 173 var unit = parseDirectives(code, suppressErrors: true); |
175 var file = new SourceFile(code, url: spanUrlFor(from, to, logger)); | 174 var file = new SourceFile(code, url: spanUrlFor(from, to, logger)); |
176 var output = new TextEditTransaction(code, file); | 175 var output = new TextEditTransaction(code, file); |
177 var foundLibraryDirective = false; | |
178 for (Directive directive in unit.directives) { | 176 for (Directive directive in unit.directives) { |
179 if (directive is UriBasedDirective) { | 177 if (directive is UriBasedDirective) { |
180 var uri = directive.uri.stringValue; | 178 var uri = directive.uri.stringValue; |
181 var span = getSpan(file, directive.uri); | 179 var span = getSpan(file, directive.uri); |
182 | 180 |
183 var id = uriToAssetId(from, uri, logger, span, errorOnAbsolute: false); | 181 var id = uriToAssetId(from, uri, logger, span, errorOnAbsolute: false); |
184 if (id == null) continue; | 182 if (id == null) continue; |
185 | 183 |
186 var primaryId = primaryInput; | 184 var primaryId = primaryInput; |
187 var newUri = assetUrlFor(id, primaryId, logger); | 185 var newUri = assetUrlFor(id, primaryId, logger); |
188 if (newUri != uri) { | 186 if (newUri != uri) { |
189 output.edit(span.start.offset, span.end.offset, "'$newUri'"); | 187 output.edit(span.start.offset, span.end.offset, "'$newUri'"); |
190 } | 188 } |
191 } else if (directive is LibraryDirective) { | |
192 foundLibraryDirective = true; | |
193 } | 189 } |
194 } | 190 } |
195 | 191 |
196 if (!output.hasEdits) return code; | 192 if (!output.hasEdits) return code; |
197 | 193 |
198 // TODO(sigmund): emit source maps when barback supports it (see | 194 // TODO(sigmund): emit source maps when barback supports it (see |
199 // dartbug.com/12340) | 195 // dartbug.com/12340) |
200 return (output.commit()..build(file.url.toString())).text; | 196 return (output.commit()..build(file.url.toString())).text; |
201 } | 197 } |
202 | 198 |
(...skipping 18 matching lines...) Expand all Loading... |
221 } | 217 } |
222 mainScript = scriptId; | 218 mainScript = scriptId; |
223 } else { | 219 } else { |
224 importScripts.add(scriptId); | 220 importScripts.add(scriptId); |
225 } | 221 } |
226 }); | 222 }); |
227 } | 223 } |
228 } | 224 } |
229 | 225 |
230 /// Generate a library name for an asset. | 226 /// Generate a library name for an asset. |
231 String _libraryNameFor(AssetId id, [int suffix]) { | 227 String _libraryNameFor(AssetId id, BuildLogger logger, [int suffix]) { |
| 228 if (_isInvalidPackageName(id.package)) { |
| 229 logger.error('Invalid package name `${id.package}`. Package names should ' |
| 230 'be valid dart identifiers, as indicated at ' |
| 231 'https://www.dartlang.org/tools/pub/pubspec.html#name.'); |
| 232 } |
232 var name = '${path.withoutExtension(id.path)}_' | 233 var name = '${path.withoutExtension(id.path)}_' |
233 '${path.extension(id.path).substring(1)}'; | 234 '${path.extension(id.path).substring(1)}'; |
234 if (name.startsWith('lib/')) name = name.substring(4); | 235 if (name.startsWith('lib/')) name = name.substring(4); |
235 name = name.split('/').map((part) { | 236 validLibName(String name) { |
236 part = part.replaceAll(_invalidLibCharsRegex, '_'); | 237 name = name.replaceAll(_invalidLibCharsRegex, '_'); |
237 if (part.startsWith(_numRegex)) part = '_${part}'; | 238 if (name.startsWith(_numRegex)) name = '_${name}'; |
238 return part; | 239 return name; |
239 }).join("."); | 240 } |
| 241 name = name.split('/').map(validLibName).join("."); |
240 var suffixString = suffix != null ? '_$suffix' : ''; | 242 var suffixString = suffix != null ? '_$suffix' : ''; |
241 return '${id.package}.${name}$suffixString'; | 243 return '${id.package}.${name}$suffixString'; |
242 } | 244 } |
243 | 245 |
244 /// Parse [code] and determine whether it has a library directive. | 246 /// Parse [code] and determine whether it has a library directive. |
245 bool _hasLibraryDirective(String code) => | 247 bool _hasLibraryDirective(String code) => |
246 parseDirectives(code, suppressErrors: true).directives | 248 parseDirectives(code, suppressErrors: true) |
| 249 .directives |
247 .any((d) => d is LibraryDirective); | 250 .any((d) => d is LibraryDirective); |
248 | 251 |
249 /// Returns the dart import path to reach [id] relative to [primaryInput]. | 252 /// Returns the dart import path to reach [id] relative to [primaryInput]. |
250 String _importPath(AssetId id, AssetId primaryInput) { | 253 String _importPath(AssetId id, AssetId primaryInput) { |
251 var parts = path.url.split(id.path); | 254 var parts = path.url.split(id.path); |
252 if (parts[0] == 'lib') { | 255 if (parts[0] == 'lib') { |
253 parts[0] = id.package; | 256 parts[0] = id.package; |
254 return 'package:${path.url.joinAll(parts)}'; | 257 return 'package:${path.url.joinAll(parts)}'; |
255 } | 258 } |
256 return path.url.relative(id.path, from: path.url.dirname(primaryInput.path)); | 259 return path.url.relative(id.path, from: path.url.dirname(primaryInput.path)); |
257 } | 260 } |
258 | 261 |
| 262 bool _isInvalidPackageName(String name) { |
| 263 return name.split('.').any((part) { |
| 264 return part.isEmpty || part.contains(_invalidLibCharsRegex); |
| 265 }); |
| 266 } |
| 267 |
259 // Constant and final variables | 268 // Constant and final variables |
260 final _invalidLibCharsRegex = new RegExp('[^a-z0-9_]'); | 269 final _invalidLibCharsRegex = new RegExp('[^a-z0-9_]'); |
261 final _numRegex = new RegExp('[0-9]'); | 270 final _numRegex = new RegExp('[0-9]'); |
OLD | NEW |