Skip to content

Commit 94bc996

Browse files
authored
Using watcher to update resource files inside the app. (#3228)
1 parent d0912e8 commit 94bc996

13 files changed

+130
-61
lines changed

app/lib/frontend/static_files.dart

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,19 @@ String resolveAppDir() {
4545
throw Exception('Unknown script: ${Platform.script}');
4646
}
4747

48-
String _resolveStaticDirPath() {
48+
/// Returns the path of /static on the local filesystem.
49+
String resolveStaticDirPath() {
4950
return path.join(resolveAppDir(), '../static');
5051
}
5152

52-
String _resolveWebAppDirPath() {
53+
/// Returns the path of pkg/web_app on the local filesystem.
54+
String resolveWebAppDirPath() {
5355
return Directory(path.join(resolveAppDir(), '../pkg/web_app'))
5456
.resolveSymbolicLinksSync();
5557
}
5658

57-
String _resolveWebCssDirPath() {
59+
/// Returns the path of pkg/web_css on the local filesystem.
60+
String resolveWebCssDirPath() {
5861
return Directory(path.join(resolveAppDir(), '../pkg/web_css'))
5962
.resolveSymbolicLinksSync();
6063
}
@@ -71,7 +74,7 @@ class StaticFileCache {
7174
StaticFileCache();
7275

7376
StaticFileCache.withDefaults() {
74-
_addDirectory(Directory(_resolveStaticDirPath()).absolute);
77+
_addDirectory(Directory(resolveStaticDirPath()).absolute);
7578
final thirdPartyDir = _resolveDir('third_party');
7679
_addDirectory(_resolveDir('third_party/highlight'), baseDir: thirdPartyDir);
7780
_addDirectory(_resolveDir('third_party/css'), baseDir: thirdPartyDir);
@@ -240,49 +243,65 @@ Future _runPubGet(Directory dir) async {
240243
}
241244
}
242245

243-
Future updateLocalBuiltFiles() async {
244-
final staticDir = Directory(_resolveStaticDirPath());
246+
/// Updates the built resources if their sources changed:
247+
/// - `script.dart.js` is updated if `pkg/web_app` changed.
248+
/// - `style.css` is updated if `pkg/web_css` changed.
249+
Future updateLocalBuiltFilesIfNeeded() async {
250+
final staticDir = Directory(resolveStaticDirPath());
245251

246-
final webAppDir = Directory(_resolveWebAppDirPath());
252+
final webAppDir = Directory(resolveWebAppDirPath());
247253
final webAppLastModified = await _detectLastModified(webAppDir);
248254
final scriptJs = File(path.join(staticDir.path, 'js', 'script.dart.js'));
249255
if (!scriptJs.existsSync() ||
250256
(scriptJs.lastModifiedSync().isBefore(webAppLastModified))) {
251257
await scriptJs.parent.create(recursive: true);
252-
await _runPubGet(webAppDir);
253-
final pr = await runProc(
254-
'/bin/sh',
255-
['build.sh'],
256-
workingDirectory: webAppDir.path,
257-
timeout: const Duration(minutes: 2),
258-
);
259-
if (pr.exitCode != 0) {
260-
final message = 'Unable to compile script.dart\n\n'
261-
'exitCode: ${pr.exitCode}\n'
262-
'STDOUT:\n${pr.stdout}\n\n'
263-
'STDERR:\n${pr.stderr}';
264-
throw Exception(message);
265-
}
258+
await updateWebAppBuild();
266259
}
267260

268-
final webCssDir = Directory(_resolveWebCssDirPath());
261+
final webCssDir = Directory(resolveWebCssDirPath());
269262
final webCssLastModified = await _detectLastModified(webCssDir);
270263
final styleCss = File(path.join(staticDir.path, 'css', 'style.css'));
271264
if (!styleCss.existsSync() ||
272265
(styleCss.lastModifiedSync().isBefore(webCssLastModified))) {
273-
await _runPubGet(webCssDir);
274-
final pr = await runProc(
275-
'/bin/sh',
276-
['build.sh'],
277-
workingDirectory: webCssDir.path,
278-
timeout: const Duration(minutes: 2),
279-
);
280-
if (pr.exitCode != 0) {
281-
final message = 'Unable to compile style.scss\n\n'
282-
'exitCode: ${pr.exitCode}\n'
283-
'STDOUT:\n${pr.stdout}\n\n'
284-
'STDERR:\n${pr.stderr}';
285-
throw Exception(message);
286-
}
266+
await updateWebCssBuild();
267+
}
268+
}
269+
270+
/// Runs build.sh in pkg/web_app
271+
Future<void> updateWebAppBuild() async {
272+
final webAppDir = Directory(resolveWebAppDirPath());
273+
274+
await _runPubGet(webAppDir);
275+
final pr = await runProc(
276+
'/bin/sh',
277+
['build.sh'],
278+
workingDirectory: webAppDir.path,
279+
timeout: const Duration(minutes: 2),
280+
);
281+
if (pr.exitCode != 0) {
282+
final message = 'Unable to compile script.dart\n\n'
283+
'exitCode: ${pr.exitCode}\n'
284+
'STDOUT:\n${pr.stdout}\n\n'
285+
'STDERR:\n${pr.stderr}';
286+
throw Exception(message);
287+
}
288+
}
289+
290+
/// Runs build.sh in pkg/web_css
291+
Future<void> updateWebCssBuild() async {
292+
final webCssDir = Directory(resolveWebCssDirPath());
293+
await _runPubGet(webCssDir);
294+
final pr = await runProc(
295+
'/bin/sh',
296+
['build.sh'],
297+
workingDirectory: webCssDir.path,
298+
timeout: const Duration(minutes: 2),
299+
);
300+
if (pr.exitCode != 0) {
301+
final message = 'Unable to compile style.scss\n\n'
302+
'exitCode: ${pr.exitCode}\n'
303+
'STDOUT:\n${pr.stdout}\n\n'
304+
'STDERR:\n${pr.stderr}';
305+
throw Exception(message);
287306
}
288307
}

app/lib/frontend/templates/_cache.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,23 @@ import '../static_files.dart' show resolveAppDir, staticUrls;
1212

1313
final templateCache = TemplateCache();
1414

15+
/// The directory that contains the mustache files.
16+
final templateViewsDir =
17+
path.join(resolveAppDir(), 'lib/frontend/templates/views');
18+
1519
/// Loads, parses, caches and renders mustache templates.
1620
class TemplateCache {
1721
/// A cache which keeps all used mustache templates parsed in memory.
18-
final Map<String, mustache.Template> _parsedMustacheTemplates = {};
22+
final _parsedMustacheTemplates = <String, mustache.Template>{};
1923

2024
TemplateCache() {
21-
_loadDirectory(path.join(resolveAppDir(), 'lib/frontend/templates/views'));
25+
update();
26+
}
27+
28+
/// Updates all the cached templates by reloading them from disk.
29+
void update() {
30+
_parsedMustacheTemplates.clear();
31+
_loadDirectory(templateViewsDir);
2232
}
2333

2434
void _loadDirectory(String templateFolder) {

app/lib/service/entrypoint/frontend.dart

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@ import 'package:gcloud/service_scope.dart';
1212
import 'package:gcloud/storage.dart';
1313
import 'package:googleapis_auth/auth_io.dart' as auth;
1414
import 'package:logging/logging.dart';
15-
import 'package:shelf/shelf.dart' as shelf;
15+
import 'package:path/path.dart' as path;
16+
import 'package:stream_transform/stream_transform.dart' show RateLimit;
17+
import 'package:watcher/watcher.dart';
1618

1719
import '../../account/backend.dart';
1820
import '../../account/consent_backend.dart';
1921
import '../../analyzer/analyzer_client.dart';
2022
import '../../dartdoc/dartdoc_client.dart';
2123
import '../../frontend/handlers.dart';
2224
import '../../frontend/static_files.dart';
25+
import '../../frontend/templates/_cache.dart';
2326
import '../../package/backend.dart';
2427
import '../../package/deps_graph.dart';
2528
import '../../package/name_tracker.dart';
@@ -65,24 +68,29 @@ Future _main(FrontendEntryMessage message) async {
6568
message.protocolSendPort
6669
.send(FrontendProtocolMessage(statsConsumerPort: null));
6770

68-
await updateLocalBuiltFiles();
71+
await updateLocalBuiltFilesIfNeeded();
6972
await withServices(() async {
70-
final shelf.Handler apiHandler = await setupServices(activeConfiguration);
73+
await _setupUploadSigner();
7174

7275
final cron = CronJobs(await getOrCreateBucket(
7376
storageService,
7477
activeConfiguration.backupSnapshotBucketName,
7578
));
76-
final appHandler = createAppHandler(apiHandler);
79+
final appHandler =
80+
createAppHandler(packageBackend.pubServer.requestHandler);
81+
82+
if (envConfig.isRunningLocally) {
83+
await _watchForResourceChanges();
84+
}
85+
await popularityStorage.init();
86+
nameTracker.startTracking();
87+
7788
await runHandler(_logger, appHandler,
7889
sanitize: true, cronHandler: cron.handler);
7990
});
8091
}
8192

82-
Future<shelf.Handler> setupServices(Configuration configuration) async {
83-
await popularityStorage.init();
84-
nameTracker.startTracking();
85-
93+
Future<void> _setupUploadSigner() async {
8694
UploadSignerService uploadSigner;
8795
if (envConfig.isRunningLocally) {
8896
uploadSigner = ServiceAccountBasedUploadSigner();
@@ -91,11 +99,42 @@ Future<shelf.Handler> setupServices(Configuration configuration) async {
9199
registerScopeExitCallback(() async => authClient.close());
92100
final email = await obtainServiceAccountEmail();
93101
uploadSigner =
94-
IamBasedUploadSigner(configuration.projectId, email, authClient);
102+
IamBasedUploadSigner(activeConfiguration.projectId, email, authClient);
95103
}
96104
registerUploadSigner(uploadSigner);
105+
}
106+
107+
/// Setup local filesystem change notifications and force-reload resource files
108+
Future<void> _watchForResourceChanges() async {
109+
_logger.info('Watching for resource changes...');
110+
111+
void setupWatcher(String name, String path, FutureOr<void> updateFn()) {
112+
final w = Watcher(path, pollingDelay: Duration(seconds: 3));
113+
final subs = w.events.debounce(Duration(milliseconds: 200)).listen(
114+
(_) async {
115+
_logger.info('Updating $name...');
116+
await updateFn();
117+
_logger.info('$name updated.');
118+
},
119+
);
120+
registerScopeExitCallback(subs.cancel);
121+
}
122+
123+
// watch mustache templates
124+
setupWatcher(
125+
'mustache templates', templateViewsDir, () => templateCache.update());
126+
127+
// watch pkg/web_app
128+
setupWatcher('/pkg/web_app', path.join(resolveWebAppDirPath(), 'lib'),
129+
() => updateWebAppBuild());
130+
131+
// watch pkg/web_css
132+
setupWatcher('/pkg/web_css', path.join(resolveWebCssDirPath(), 'lib'),
133+
() => updateWebCssBuild());
97134

98-
return packageBackend.pubServer.requestHandler;
135+
// watch /static files
136+
setupWatcher('/static', resolveStaticDirPath(),
137+
() => registerStaticFileCacheForTest(StaticFileCache.withDefaults()));
99138
}
100139

101140
Future _worker(WorkerEntryMessage message) async {

app/pubspec.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ packages:
9191
name: build_daemon
9292
url: "https://pub.dartlang.org"
9393
source: hosted
94-
version: "2.1.2"
94+
version: "2.1.3"
9595
build_resolvers:
9696
dependency: transitive
9797
description:
@@ -105,7 +105,7 @@ packages:
105105
name: build_runner
106106
url: "https://pub.dartlang.org"
107107
source: hosted
108-
version: "1.7.2"
108+
version: "1.7.3"
109109
build_runner_core:
110110
dependency: transitive
111111
description:
@@ -672,7 +672,7 @@ packages:
672672
name: stream_transform
673673
url: "https://pub.dartlang.org"
674674
source: hosted
675-
version: "0.0.20"
675+
version: "1.1.0"
676676
string_scanner:
677677
dependency: transitive
678678
description:
@@ -744,7 +744,7 @@ packages:
744744
source: hosted
745745
version: "2.2.0"
746746
watcher:
747-
dependency: transitive
747+
dependency: "direct main"
748748
description:
749749
name: watcher
750750
url: "https://pub.dartlang.org"

app/pubspec.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ dependencies:
3838
shelf: '>=0.5.6 <0.8.0'
3939
shelf_router: ^0.7.0
4040
stack_trace: ^1.9.2
41-
stream_transform: ^0.0.20
41+
stream_transform: ^1.1.0
42+
watcher: ^0.9.7
4243
yaml: '^2.1.12'
4344
# pana version to be pinned
4445
pana: '0.13.3'

app/test/frontend/handlers/landing_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import '../../shared/test_services.dart';
1313
import '_utils.dart';
1414

1515
void main() {
16-
setUpAll(() => updateLocalBuiltFiles());
16+
setUpAll(() => updateLocalBuiltFilesIfNeeded());
1717

1818
group('ui', () {
1919
testWithServices('/', () async {

app/test/frontend/handlers/listing_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import '../../shared/test_services.dart';
1919
import '_utils.dart';
2020

2121
void main() {
22-
setUpAll(() => updateLocalBuiltFiles());
22+
setUpAll(() => updateLocalBuiltFilesIfNeeded());
2323

2424
group('old api', () {
2525
testWithServices('/packages.json', () async {

app/test/frontend/handlers/package_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import '../../shared/test_services.dart';
1212
import '_utils.dart';
1313

1414
void main() {
15-
setUpAll(() => updateLocalBuiltFiles());
15+
setUpAll(() => updateLocalBuiltFilesIfNeeded());
1616

1717
group('ui', () {
1818
testWithServices('/packages/foobar_pkg - found', () async {

app/test/frontend/handlers/redirects_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import '../../shared/test_services.dart';
1414
import '_utils.dart';
1515

1616
void main() {
17-
setUpAll(() => updateLocalBuiltFiles());
17+
setUpAll(() => updateLocalBuiltFilesIfNeeded());
1818

1919
group('redirects', () {
2020
testWithServices('pub.dartlang.org', () async {

app/test/frontend/static_files_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import 'package:test/test.dart';
1414
import 'package:pub_dev/frontend/static_files.dart';
1515

1616
void main() {
17-
setUpAll(() => updateLocalBuiltFiles());
17+
setUpAll(() => updateLocalBuiltFilesIfNeeded());
1818

1919
group('dartdoc assets', () {
2020
Future<void> checkAsset(String url, String path) async {

app/test/frontend/templates_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const String goldenDir = 'test/frontend/golden';
3939
final _regenerateGoldens = false;
4040

4141
void main() {
42-
setUpAll(() => updateLocalBuiltFiles());
42+
setUpAll(() => updateLocalBuiltFilesIfNeeded());
4343

4444
group('templates', () {
4545
StaticFileCache oldCache;

pkg/fake_pub_server/lib/fake_pub_server.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class FakePubServer {
4242
@required Configuration configuration,
4343
shelf.Handler extraHandler,
4444
}) async {
45-
await updateLocalBuiltFiles();
45+
await updateLocalBuiltFilesIfNeeded();
4646
await ss.fork(() async {
4747
final db = DatastoreDB(_datastore);
4848
registerDbService(db);

pkg/fake_pub_server/lib/fake_search_service.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class FakeSearchService {
3434
int port = 8082,
3535
@required Configuration configuration,
3636
}) async {
37-
await updateLocalBuiltFiles();
37+
await updateLocalBuiltFilesIfNeeded();
3838
await ss.fork(() async {
3939
final db = DatastoreDB(_datastore);
4040
registerDbService(db);

0 commit comments

Comments
 (0)