├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CODEOWNERS ├── COPYRIGHT_TRANSFER ├── Dockerfile ├── LICENSE ├── README.md ├── analysis_options.yaml ├── docs.yml ├── example ├── index.html ├── test.css └── test.dart ├── lib └── dart_to_js_script_rewriter.dart ├── pubspec.yaml ├── test ├── expected.html ├── integration_test.dart ├── test_data │ └── test_file.html ├── transformer_mocks.dart └── unit_test.dart └── tool └── dev.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | .pub/ 4 | .settings/ 5 | build 6 | coverage/ 7 | packages 8 | pubspec.lock 9 | .packages 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | dart: 3 | - 1.24.3 4 | script: 5 | - pub run dart_dev format --check 6 | - pub run dart_dev analyze 7 | - pub run dart_dev test 8 | - pub run dart_dev coverage --no-html 9 | - bash <(curl -s https://codecov.io/bash) -f coverage/coverage.lcov 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.2 2 | 3 | * Updated dependencies on `html` and `dartdoc`. 4 | 5 | ## 0.1.0+4 6 | 7 | * [fix] only rewrite dart.js from browser package 8 | 9 | ## 0.1.0+3 10 | 11 | * Update dependencies 12 | 13 | ## 0.1.0+2 14 | 15 | * Removed special handling of the csp mode - dart2js no longer uses a special 16 | output file name 17 | 18 | ## 0.1.0+1 19 | 20 | * Added tests 21 | 22 | ## 0.0.2 23 | 24 | * CSP support (thanks to claudiodangelis) 25 | 26 | ## 0.0.1+3 27 | 28 | * Bump dependency versions (thanks to kaendfinger) 29 | 30 | ## 0.0.1+2 31 | 32 | * Add a "why?" section to the README. 33 | 34 | ## 0.0.1+1 35 | 36 | * Tweak readme. 37 | 38 | ## 0.0.1 39 | 40 | * Initial version. 41 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @Workiva/app-frameworks 2 | 3 | -------------------------------------------------------------------------------- /COPYRIGHT_TRANSFER: -------------------------------------------------------------------------------- 1 | I, Seth Ladd, hereby assign copyright of dart_to_js_script_rewriter to Workiva Inc. 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM google/dart:1.24.3 2 | 3 | WORKDIR /build/ 4 | ADD pubspec.yaml /build 5 | RUN pub get 6 | ARG BUILD_ARTIFACTS_AUDIT=/build/pubspec.lock 7 | FROM scratch 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Seth Ladd. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dart_to_js_script_rewriter 2 | 3 | Pub 4 | Build Status 5 | codecov.io 6 | 7 | 8 | A pub transformer that rewrites Dart script tags to 9 | JavaScript script tags, eliminating 10 | 404s and speeding up initial loads. 11 | Useful when building for deployment. 12 | 13 | ## Why? Load your app quicker! 14 | 15 | A traditional Dart application is deployed with HTML that looks something 16 | like this: 17 | 18 | 19 | 20 | 21 | This is a performance problem for initial startup, because: 22 | 23 | * Some browsers will attempt to download test.dart, but that file is not 24 | deployed. This causes unnecessary server strain and noisy 404s. 25 | * The browser needs to run dart.js to replace a script tag in the DOM, 26 | so that the actual JavaScript version of the app can be downloaded. This is 27 | an unnecessary delay, since today no production browser includes Dart VM 28 | and thus only the JavaScript version is required. 29 | 30 | With this transformer, you can address the above issues, speed up the load 31 | time of your apps, and make happier users. 32 | 33 | ## Configuring 34 | 35 | Add the transformer to your pubspec.yaml: 36 | 37 | transformers: 38 | - dart_to_js_script_rewriter 39 | 40 | (Assuming you already added this package to your pubspec.yaml file.) 41 | 42 | ## How it works 43 | 44 | **When run in "release" mode**, this transformer does two things: 45 | 46 | * Removes script tags that point to `browser/dart.js`. 47 | * Rewrites a Dart script tag to a JavaScript script tag. 48 | 49 | For example, this code: 50 | 51 | 52 | 53 | 54 | is turned into this code: 55 | 56 | 57 | 58 | ## Pub, modes, and this transformer 59 | 60 | **This transformer only runs when pub is running in release mode.** 61 | 62 | This transformer only makes sense when you want to build your app for a 63 | production deployment. You probably do not want to run this transformer 64 | during the normal develop/reload cycles. 65 | 66 | Pub can run in different _modes_, which have different semantics. The 67 | _debug mode_, for example, can disable minification. The _release mode_ 68 | can turn on optimizations. 69 | 70 | By default, `pub serve` runs in _debug_ mode. By default, `pub build` 71 | runs in _release_ mode. 72 | 73 | See the [pub docs][pubdocs] for more on modes. 74 | 75 | ## Reporting issues 76 | 77 | Please use the [issue tracker][issues]. 78 | 79 | [issues]: https://github.com/Workiva/dart_to_js_script_rewriter/issues 80 | [pubdocs]: https://www.dartlang.org/tools/pub/ 81 | 82 | Thanks to Seth Ladd, , for creating the original version of this [library](https://github.com/sethladd/dart_to_js_script_rewriter). 83 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: true 3 | 4 | linter: 5 | rules: 6 | - cancel_subscriptions 7 | - close_sinks -------------------------------------------------------------------------------- /docs.yml: -------------------------------------------------------------------------------- 1 | title: dart_to_js_script_rewriter 2 | base: github:Workiva/dart_to_js_script_rewriter/ 3 | src: README.md 4 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Test 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Test

16 | 17 |

Hello world from Dart!

18 | 19 |
20 |

Click me!

21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/test.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #F8F8F8; 4 | font-family: 'Open Sans', sans-serif; 5 | font-size: 14px; 6 | font-weight: normal; 7 | line-height: 1.2em; 8 | margin: 15px; 9 | } 10 | 11 | h1, p { 12 | color: #333; 13 | } 14 | 15 | #sample_container_id { 16 | width: 100%; 17 | height: 400px; 18 | position: relative; 19 | border: 1px solid #ccc; 20 | background-color: #fff; 21 | } 22 | 23 | #sample_text_id { 24 | font-size: 24pt; 25 | text-align: center; 26 | margin-top: 140px; 27 | -webkit-user-select: none; 28 | user-select: none; 29 | } 30 | -------------------------------------------------------------------------------- /example/test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html'; 2 | 3 | void main() { 4 | querySelector("#sample_text_id") 5 | ..text = "Click me!" 6 | ..onClick.listen(reverseText); 7 | } 8 | 9 | void reverseText(MouseEvent event) { 10 | var text = querySelector("#sample_text_id").text; 11 | var buffer = new StringBuffer(); 12 | for (int i = text.length - 1; i >= 0; i--) { 13 | buffer.write(text[i]); 14 | } 15 | querySelector("#sample_text_id").text = buffer.toString(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/dart_to_js_script_rewriter.dart: -------------------------------------------------------------------------------- 1 | library dart_to_js_script_rewriter; 2 | 3 | import 'dart:async' show Future; 4 | 5 | import 'package:barback/barback.dart' 6 | show Asset, AssetId, BarbackMode, BarbackSettings, Transform, Transformer; 7 | import 'package:html/dom.dart' show Document, Element; 8 | 9 | /// Finds script tags with type equals `application/dart` and rewrites them to 10 | /// point to the JS version. This eliminates a 404 get on the .dart file and 11 | /// speeds up initial loads. Win! 12 | class DartToJsScriptRewriter extends Transformer { 13 | final BarbackSettings settings; 14 | 15 | DartToJsScriptRewriter.asPlugin(this.settings); 16 | 17 | bool isPrimary(AssetId id) => 18 | settings.mode == BarbackMode.RELEASE && 19 | ['.html', '.htm'].contains(id.extension) && 20 | !id.path.startsWith('lib'); 21 | 22 | Future apply(Transform transform) async { 23 | final htmlSource = await transform.primaryInput.readAsString(); 24 | Document document = new Document.html(htmlSource); 25 | 26 | // only apply changes to files with either a dart script (which has to be 27 | // rewritten), or a browser/dart.js script (which has to be removed) 28 | if (!document.querySelectorAll('script').any((script) => 29 | scriptShouldBeRewritten(script) || scriptShouldBeRemoved(script))) { 30 | return new Future.value(null); 31 | } 32 | removeBrowserPackageScript(document); 33 | rewriteDartScriptTag(document); 34 | 35 | final id = transform.primaryInput.id; 36 | transform.addOutput(new Asset.fromString(id, document.outerHtml)); 37 | } 38 | 39 | void removeBrowserPackageScript(Document document) { 40 | document 41 | .querySelectorAll('script') 42 | .where((tag) => scriptShouldBeRemoved(tag)) 43 | .forEach((tag) => tag.remove()); 44 | } 45 | 46 | void rewriteDartScriptTag(Document document) { 47 | document 48 | .querySelectorAll('script') 49 | .where((tag) => scriptShouldBeRewritten(tag)) 50 | .forEach((tag) { 51 | final src = tag.attributes['src']; 52 | 53 | tag.attributes['src'] = src.replaceFirstMapped( 54 | new RegExp(r'\.dart($|[\?#])'), (match) => '.dart.js${match[1]}'); 55 | tag.attributes.remove('type'); 56 | }); 57 | } 58 | 59 | bool scriptShouldBeRewritten(Element el) => 60 | el.attributes['type'] == 'application/dart' && 61 | el.attributes['src'] != null; 62 | 63 | bool scriptShouldBeRemoved(Element el) => 64 | el.attributes['src'] != null && 65 | el.attributes['src'].endsWith('browser/dart.js'); 66 | } 67 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart_to_js_script_rewriter 2 | description: Replaces Dart script tags with JavaScript script tags, 3 | speeding up initial loads and reducing 404s. Use when 4 | ready to deploy. 5 | authors: 6 | - Seth Ladd 7 | - Workiva Client Platform Team 8 | - Dustin Lessard 9 | - Evan Weible 10 | - Jay Udey 11 | - Max Peterson 12 | - Trent Grover 13 | version: 1.0.3 14 | homepage: https://github.com/Workiva/dart_to_js_script_rewriter 15 | environment: 16 | sdk: ">=1.12.0 <2.0.0" 17 | dependencies: 18 | barback: "^0.15.2+7" 19 | html: ">=0.12.2+1 <0.14.0" 20 | dev_dependencies: 21 | browser: "^0.10.0+2" 22 | coverage: "^0.7.2" 23 | dart_dev: "^1.0.6" 24 | dart_style: ">=0.1.8 <0.3.0" 25 | dartdoc: ">=0.4.0 <0.10.0" 26 | grinder: "^0.8.0+1" 27 | mockito: any 28 | test: "^0.12.6+2" 29 | transformers: 30 | - dart_to_js_script_rewriter 31 | - test/pub_serve: 32 | $include: test/**_test{.*,}.dart 33 | -------------------------------------------------------------------------------- /test/expected.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Test

13 | 14 |

Hello world from Dart!

15 | 16 |
17 |

Click me!

18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/integration_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('vm') 2 | library dart_to_js_script_rewriter.test.integration_test; 3 | 4 | import 'package:test/test.dart'; 5 | import 'package:grinder/grinder.dart'; 6 | import 'dart:io'; 7 | 8 | void main() { 9 | test('transformer is activated in pub build release', () async { 10 | Pub.build(mode: 'release', directories: ['example']); 11 | final testHtml = new File('build/example/index.html'); 12 | final content = await testHtml.readAsString(); 13 | 14 | File expectedHtml = new File('test/expected.html'); 15 | expect(content, await expectedHtml.readAsString()); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /test/test_data/test_file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/transformer_mocks.dart: -------------------------------------------------------------------------------- 1 | library dart_to_js_script_rewriter.test.transformer_mocks; 2 | 3 | import 'package:barback/barback.dart'; 4 | import 'package:mockito/mockito.dart'; 5 | 6 | class MockAsset extends Mock implements Asset {} 7 | 8 | class MockTransform extends Mock implements Transform {} 9 | -------------------------------------------------------------------------------- /test/unit_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('vm') 2 | library dart_to_js_script_rewriter.test.unit_test; 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:barback/barback.dart'; 7 | import 'package:html/dom.dart'; 8 | import 'package:mockito/mockito.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | import 'package:dart_to_js_script_rewriter/dart_to_js_script_rewriter.dart'; 12 | 13 | import 'transformer_mocks.dart'; 14 | 15 | main() { 16 | final transformer = new DartToJsScriptRewriter.asPlugin( 17 | new BarbackSettings({}, BarbackMode.RELEASE)); 18 | 19 | group('apply()', () { 20 | test('when run in release mode', () async { 21 | AssetId fakeInputFileAssetId = 22 | new AssetId('testid', 'test/test_data/test_file.html'); 23 | 24 | MockAsset inputFile; 25 | MockTransform mockTransform; 26 | 27 | String transformedFile; 28 | 29 | inputFile = new MockAsset(); 30 | mockTransform = new MockTransform(); 31 | 32 | when(inputFile.id).thenReturn(fakeInputFileAssetId); 33 | when(inputFile.readAsString()).thenReturn( 34 | new File.fromUri(Uri.parse('test/test_data/test_file.html')) 35 | .readAsString()); 36 | 37 | when(mockTransform.primaryInput).thenReturn(inputFile); 38 | when(mockTransform.readInputAsString(fakeInputFileAssetId)) 39 | .thenAnswer((_) { 40 | return new File.fromUri(Uri.parse('test/test_data/test_file.html')) 41 | .readAsString(); 42 | }); 43 | 44 | await transformer.apply(mockTransform); 45 | 46 | Asset fileAsset = 47 | verify(mockTransform.addOutput(captureAny)).captured.first; 48 | 49 | transformedFile = await fileAsset.readAsString(); 50 | expect( 51 | transformedFile.contains( 52 | ''), 53 | isFalse); 54 | expect( 55 | transformedFile.contains( 56 | ''), 57 | isFalse); 58 | expect( 59 | transformedFile 60 | .contains(''), 61 | isTrue); 62 | }); 63 | }); 64 | 65 | group('rewriteDartScriptTag', () { 66 | testScriptShouldBeRewritten(String script, bool shouldRewrite) { 67 | final document = documentFromScript(script); 68 | final oldScript = document.querySelector('script'); 69 | expect(transformer.scriptShouldBeRewritten(oldScript), shouldRewrite); 70 | 71 | transformer.rewriteDartScriptTag(document); 72 | 73 | final scripts = document.querySelectorAll('script'); 74 | if (shouldRewrite) { 75 | expect(scripts.first.attributes["src"], 'main.dart.js'); 76 | } else { 77 | expect(scripts.first.attributes["src"], oldScript.attributes["src"]); 78 | } 79 | } 80 | 81 | test('do rewrite script with src specified and of type application/dart', 82 | () { 83 | final script = 84 | ''; 85 | testScriptShouldBeRewritten(script, true); 86 | }); 87 | 88 | test('don\'t rewrite inline scripts', () { 89 | final script = ''; 90 | testScriptShouldBeRewritten(script, false); 91 | }); 92 | 93 | test('don\'t rewrite script of type type="text/javascript"', () { 94 | final script = ''; 95 | testScriptShouldBeRewritten(script, false); 96 | }); 97 | 98 | test('don\'t rewrite script of without type', () { 99 | final script = ''; 100 | testScriptShouldBeRewritten(script, false); 101 | }); 102 | }); 103 | 104 | group('removeBrowserPackageScript', () { 105 | testScriptShouldBeRemoved(String script, bool shouldRemove) { 106 | final document = documentFromScript(script); 107 | final oldScript = document.querySelector('script'); 108 | expect(transformer.scriptShouldBeRemoved(oldScript), shouldRemove); 109 | 110 | transformer.removeBrowserPackageScript(document); 111 | final dartJsScripts = document.querySelectorAll('script'); 112 | if (shouldRemove) { 113 | expect(dartJsScripts, isEmpty); 114 | } else { 115 | expect(dartJsScripts, isNotEmpty); 116 | } 117 | } 118 | 119 | test('do rewrite scripts where src="browser/dart.js"', () { 120 | final script = ''; 121 | testScriptShouldBeRemoved(script, true); 122 | }); 123 | 124 | test('don\'t remove other scripts where src ends with dart.js', () { 125 | final script = ''; 126 | testScriptShouldBeRemoved(script, false); 127 | }); 128 | }); 129 | 130 | group('isPrimary', () { 131 | test('do touch html files in web', () { 132 | AssetId assetId = new AssetId('my_package', 'web/index.html'); 133 | expect(transformer.isPrimary(assetId), isTrue); 134 | }); 135 | 136 | test('do touch html files in example', () { 137 | AssetId assetId = new AssetId('my_package', 'example/index.html'); 138 | expect(transformer.isPrimary(assetId), isTrue); 139 | }); 140 | 141 | test('do not touch html files in lib', () { 142 | AssetId assetId = new AssetId('my_package', 'lib/app.component.html'); 143 | expect(transformer.isPrimary(assetId), isFalse); 144 | }); 145 | 146 | test('don\'t touch html in DEBUG mode', () { 147 | final settings = new BarbackSettings({}, BarbackMode.DEBUG); 148 | final transformer = new DartToJsScriptRewriter.asPlugin(settings); 149 | AssetId assetId = new AssetId('my_package', 'web/index.html'); 150 | expect(transformer.isPrimary(assetId), isFalse); 151 | }); 152 | 153 | test('don\'t touch dart files', () { 154 | AssetId assetId = new AssetId('my_package', 'web/main.dart'); 155 | expect(transformer.isPrimary(assetId), isFalse); 156 | }); 157 | }); 158 | } 159 | 160 | Document documentFromScript(String script) => new Document.html(''' 161 | 162 | 163 | 164 | 165 | 166 | 167 | Test 168 | $script 169 | 170 | 171 | 172 |

Test

173 | 174 |

Hello world from Dart!

175 | 176 |
177 |

Click me!

178 |
179 | 180 | 181 | 182 | '''); 183 | -------------------------------------------------------------------------------- /tool/dev.dart: -------------------------------------------------------------------------------- 1 | library dart_to_js_script_rewriter.tool.dev; 2 | 3 | import 'package:dart_dev/dart_dev.dart' show dev, config; 4 | 5 | main(List args) async { 6 | List directories = ['example/', 'lib/', 'test/', 'tool/']; 7 | 8 | config.analyze.entryPoints = directories; 9 | config.analyze.strong = true; 10 | config.format.directories = directories; 11 | config.test 12 | ..platforms = ['vm'] 13 | ..pubServe = true 14 | ..unitTests = ['test/unit_test.dart'] 15 | ..integrationTests = ['test/integration_test.dart']; 16 | 17 | config.coverage.pubServe = true; 18 | 19 | await dev(args); 20 | } 21 | --------------------------------------------------------------------------------