OLD | NEW |
1 #!/usr/bin/env python2 | 1 #!/usr/bin/env python2 |
2 # -*- coding: utf-8 -*- | 2 # -*- coding: utf-8 -*- |
3 | 3 |
4 # Copyright 2015 Google Inc. All Rights Reserved. | 4 # Copyright 2015 Google Inc. All Rights Reserved. |
5 # | 5 # |
6 # Licensed under the Apache License, Version 2.0 (the "License"); | 6 # Licensed under the Apache License, Version 2.0 (the "License"); |
7 # you may not use this file except in compliance with the License. | 7 # you may not use this file except in compliance with the License. |
8 # You may obtain a copy of the License at | 8 # You may obtain a copy of the License at |
9 # | 9 # |
10 # http://www.apache.org/licenses/LICENSE-2.0 | 10 # http://www.apache.org/licenses/LICENSE-2.0 |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
62 | 62 |
63 # Name of the service worker static script. | 63 # Name of the service worker static script. |
64 SW_STATIC_SCRIPT_NAME = 'sw_static.js' | 64 SW_STATIC_SCRIPT_NAME = 'sw_static.js' |
65 | 65 |
66 # Largest number that the cache version can be. | 66 # Largest number that the cache version can be. |
67 MAX_CACHE_VERSION = 1000000 | 67 MAX_CACHE_VERSION = 1000000 |
68 | 68 |
69 # Where this file is located (so we can find resources). | 69 # Where this file is located (so we can find resources). |
70 SCRIPT_DIR = os.path.dirname(__file__) | 70 SCRIPT_DIR = os.path.dirname(__file__) |
71 | 71 |
| 72 # Maps dependency managers to the folder they install dependencies into. |
| 73 DEPENDENCY_MANAGER_INSTALL_FOLDER = { |
| 74 'bower': 'bower_components', |
| 75 'npm': 'node_modules', |
| 76 } |
| 77 |
72 SW_FORMAT_STRING = """/** | 78 SW_FORMAT_STRING = """/** |
73 * @file Service worker generated by Caterpillar. | 79 * @file Service worker generated by Caterpillar. |
74 */ | 80 */ |
75 | 81 |
76 /** | 82 /** |
77 * @const Current cache version. | 83 * @const Current cache version. |
78 * | 84 * |
79 * Increment this to force cache to clear. | 85 * Increment this to force cache to clear. |
80 */ | 86 */ |
81 var CACHE_VERSION = {cache_version}; | 87 var CACHE_VERSION = {cache_version}; |
(...skipping 258 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
340 output_dir: Directory of the web app to insert TODOs into. | 346 output_dir: Directory of the web app to insert TODOs into. |
341 """ | 347 """ |
342 logging.debug('Inserting TODOs.') | 348 logging.debug('Inserting TODOs.') |
343 dirwalk = os.walk(output_dir) | 349 dirwalk = os.walk(output_dir) |
344 for (dirpath, _, filenames) in dirwalk: | 350 for (dirpath, _, filenames) in dirwalk: |
345 for filename in filenames: | 351 for filename in filenames: |
346 if filename.endswith('.js'): | 352 if filename.endswith('.js'): |
347 path = os.path.join(dirpath, filename) | 353 path = os.path.join(dirpath, filename) |
348 insert_todos_into_file(path) | 354 insert_todos_into_file(path) |
349 | 355 |
350 | 356 def generate_service_worker(output_dir, ca_manifest, required_js_paths, |
351 def generate_service_worker(output_dir, ca_manifest, polyfill_paths, | |
352 boilerplate_dir): | 357 boilerplate_dir): |
353 """Generates code for a service worker. | 358 """Generates code for a service worker. |
354 | 359 |
355 Args: | 360 Args: |
356 output_dir: Directory of the web app that this service worker will run in. | 361 output_dir: Directory of the web app that this service worker will run in. |
357 ca_manifest: Chrome App manifest dictionary. | 362 ca_manifest: Chrome App manifest dictionary. |
358 polyfill_paths: List of paths to required polyfill scripts, relative to the | 363 required_js_paths: List of paths to required scripts, relative to the |
359 boilerplate directory. | 364 boilerplate directory. |
360 boilerplate_dir: Caterpillar script directory within output web app. | 365 boilerplate_dir: Caterpillar script directory within output web app. |
361 | 366 |
362 Returns: | 367 Returns: |
363 JavaScript string. | 368 JavaScript string. |
364 """ | 369 """ |
365 # Get the paths of files we will cache. | 370 # Get the paths of files we will cache. |
366 all_filepaths = [] | 371 all_filepaths = [] |
367 logging.debug('Looking for files to cache.') | 372 logging.debug('Looking for files to cache.') |
368 dirwalk = os.walk(output_dir) | 373 dirwalk = os.walk(output_dir) |
369 for (dirpath, _, filenames) in dirwalk: | 374 for (dirpath, _, filenames) in dirwalk: |
370 # Add the relative file paths of each file to the filepaths list. | 375 # Add the relative file paths of each file to the filepaths list. |
371 all_filepaths.extend( | 376 all_filepaths.extend( |
372 os.path.relpath(os.path.join(dirpath, filename), output_dir) | 377 os.path.relpath(os.path.join(dirpath, filename), output_dir) |
373 for filename in filenames) | 378 for filename in filenames) |
374 logging.debug('Cached files:\n\t%s', '\n\t'.join(all_filepaths)) | 379 logging.debug('Cached files:\n\t%s', '\n\t'.join(all_filepaths)) |
375 # Format the file paths as JavaScript strings. | 380 # Format the file paths as JavaScript strings. |
376 all_filepaths = ["'{}'".format(fp) for fp in all_filepaths] | 381 all_filepaths = ["'{}'".format(fp) for fp in all_filepaths] |
377 | 382 |
378 logging.debug('Generating service worker.') | 383 logging.debug('Generating service worker.') |
379 | 384 |
380 sw_js = SW_FORMAT_STRING.format( | 385 sw_js = SW_FORMAT_STRING.format( |
381 cache_version=random.randrange(MAX_CACHE_VERSION), | 386 cache_version=random.randrange(MAX_CACHE_VERSION), |
382 joined_filepaths=',\n '.join(all_filepaths), | 387 joined_filepaths=',\n '.join(all_filepaths), |
383 boilerplate_dir=boilerplate_dir | 388 boilerplate_dir=boilerplate_dir |
384 ) | 389 ) |
385 | 390 |
386 # The polyfills we get as input are relative to the boilerplate directory, but | 391 # The polyfills we get as input are relative to the boilerplate directory, but |
387 # the service worker is in the root directory, so we need to change the paths. | 392 # the service worker is in the root directory, so we need to change the paths. |
388 polyfills_paths = [os.path.join(boilerplate_dir, path) | 393 required_js_paths = [os.path.join(boilerplate_dir, path) |
389 for path in polyfill_paths] | 394 for path in required_js_paths] |
390 | 395 |
391 background_scripts = ca_manifest['app']['background'].get('scripts', []) | 396 background_scripts = ca_manifest['app']['background'].get('scripts', []) |
392 for script in polyfill_paths + background_scripts: | 397 for script in required_js_paths + background_scripts: |
393 logging.debug('Importing `%s` to the service worker.', script) | 398 logging.debug('Importing `%s` to the service worker.', script) |
394 sw_js += "importScripts('{}');\n".format(script) | 399 sw_js += "importScripts('{}');\n".format(script) |
395 | 400 |
396 return sw_js | 401 return sw_js |
397 | 402 |
398 | 403 |
399 def copy_script(script, directory): | 404 def copy_script(script, directory): |
400 """Copies a script from Caterpillar into the given directory. | 405 """Copies a script from Caterpillar into the given directory. |
401 | 406 |
402 Args: | 407 Args: |
403 script: Caterpillar JavaScript filename. | 408 script: Caterpillar JavaScript filename. |
404 directory: Path to directory. | 409 directory: Path to directory. |
405 """ | 410 """ |
406 path = os.path.join(SCRIPT_DIR, 'js', script) | 411 path = os.path.join(SCRIPT_DIR, 'js', script) |
407 new_path = os.path.join(directory, script) | 412 new_path = os.path.join(directory, script) |
408 logging.debug('Writing `%s` to `%s`.', path, new_path) | 413 logging.debug('Writing `%s` to `%s`.', path, new_path) |
409 shutil.copyfile(path, new_path) | 414 shutil.copyfile(path, new_path) |
410 | 415 |
411 | 416 def add_service_worker(output_dir, ca_manifest, required_js_paths, |
412 def add_service_worker(output_dir, ca_manifest, polyfill_paths, | |
413 boilerplate_dir): | 417 boilerplate_dir): |
414 """Adds service worker scripts to a web app. | 418 """Adds service worker scripts to a web app. |
415 | 419 |
416 Args: | 420 Args: |
417 output_dir: Path to web app to add service worker scripts to. | 421 output_dir: Path to web app to add service worker scripts to. |
418 ca_manifest: Chrome App manifest dictionary. | 422 ca_manifest: Chrome App manifest dictionary. |
419 polyfill_paths: List of paths to required polyfill scripts, relative to the | 423 required_js_paths: List of paths to required scripts, relative to the |
420 boilerplate directory. | 424 boilerplate directory. |
421 boilerplate_dir: Caterpillar script directory within web app. | 425 boilerplate_dir: Caterpillar script directory within web app. |
422 """ | 426 """ |
423 # We have to copy the other scripts before we generate the service worker | 427 # We have to copy the other scripts before we generate the service worker |
424 # caching script, or else they won't be cached. | 428 # caching script, or else they won't be cached. |
425 boilerplate_path = os.path.join(output_dir, boilerplate_dir) | 429 boilerplate_path = os.path.join(output_dir, boilerplate_dir) |
426 copy_script(REGISTER_SCRIPT_NAME, boilerplate_path) | 430 copy_script(REGISTER_SCRIPT_NAME, boilerplate_path) |
427 copy_script(SW_STATIC_SCRIPT_NAME, boilerplate_path) | 431 copy_script(SW_STATIC_SCRIPT_NAME, boilerplate_path) |
428 | 432 |
429 sw_js = generate_service_worker(output_dir, ca_manifest, polyfill_paths, | 433 sw_js = generate_service_worker(output_dir, ca_manifest, required_js_paths, |
430 boilerplate_dir) | 434 boilerplate_dir) |
431 | 435 |
432 # We can now write the service worker. Note that it must be in the root. | 436 # We can now write the service worker. Note that it must be in the root. |
433 sw_path = os.path.join(output_dir, SW_SCRIPT_NAME) | 437 sw_path = os.path.join(output_dir, SW_SCRIPT_NAME) |
434 logging.debug('Writing service worker to `%s`.', sw_path) | 438 logging.debug('Writing service worker to `%s`.', sw_path) |
435 with open(sw_path, 'w') as sw_file: | 439 with open(sw_path, 'w') as sw_file: |
436 sw_file.write(surrogateescape.encode(sw_js)) | 440 sw_file.write(surrogateescape.encode(sw_js)) |
437 | 441 |
438 | 442 |
439 class InstallationError(Exception): | 443 class InstallationError(Exception): |
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
586 for api in apis: | 590 for api in apis: |
587 if api in POLYFILLS: | 591 if api in POLYFILLS: |
588 polyfillable.append(api) | 592 polyfillable.append(api) |
589 else: | 593 else: |
590 not_polyfillable.append(api) | 594 not_polyfillable.append(api) |
591 | 595 |
592 logging.info('Polyfilled Chrome APIs: %s', ', '.join(polyfillable)) | 596 logging.info('Polyfilled Chrome APIs: %s', ', '.join(polyfillable)) |
593 logging.warning('Could not polyfill Chrome APIs: %s', | 597 logging.warning('Could not polyfill Chrome APIs: %s', |
594 ', '.join(not_polyfillable)) | 598 ', '.join(not_polyfillable)) |
595 | 599 |
| 600 # Read in the polyfill manifests and store their dependencies. We can't |
| 601 # install them yet, though, since that has to be done after editing code or |
| 602 # the dependencies will also be edited. |
| 603 polyfill_manifests = polyfill_manifest.load_many(polyfillable) |
| 604 dependencies = [dependency |
| 605 for manifest in polyfill_manifests.values() |
| 606 for dependency in manifest['dependencies']] |
| 607 |
596 # List of paths of static code to be copied from Caterpillar into the output | 608 # List of paths of static code to be copied from Caterpillar into the output |
597 # web app, relative to Caterpillar's JS source directory. | 609 # web app, relative to Caterpillar's JS source directory. |
598 required_js_paths = [ | 610 required_always_paths = [ |
599 'caterpillar.js', | 611 'caterpillar.js', |
600 REGISTER_SCRIPT_NAME, | 612 REGISTER_SCRIPT_NAME, |
601 ] + polyfill_paths(polyfillable) | 613 ] |
| 614 |
| 615 # The dependencies and polyfills are also requirements, but we need to handle |
| 616 # them differently, so they're split up into two lists. |
| 617 required_dependency_paths = [] |
| 618 for dependency in dependencies: |
| 619 # Note that dependencies are installed into the root, but we need paths |
| 620 # relative to Caterpillar's boilerplate directory. |
| 621 dependency_path = os.path.join('..', |
| 622 DEPENDENCY_MANAGER_INSTALL_FOLDER[dependency['manager']], |
| 623 dependency['name'], dependency['path']) |
| 624 required_dependency_paths.append(dependency_path) |
| 625 |
| 626 required_polyfill_paths = polyfill_paths(polyfillable) |
602 | 627 |
603 # Read in and check the manifest file. | 628 # Read in and check the manifest file. |
604 try: | 629 try: |
605 ca_manifest = chrome_app.manifest.get(input_dir) | 630 ca_manifest = chrome_app.manifest.get(input_dir) |
606 chrome_app.manifest.localize(ca_manifest, input_dir) | 631 chrome_app.manifest.localize(ca_manifest, input_dir) |
607 chrome_app.manifest.verify(ca_manifest) | 632 chrome_app.manifest.verify(ca_manifest) |
608 except ValueError as e: | 633 except ValueError as e: |
609 logging.error(e.message) | 634 logging.error(e.message) |
610 return | 635 return |
611 | 636 |
612 # TODO(alger): Identify background scripts and determine start_url. | 637 # TODO(alger): Identify background scripts and determine start_url. |
613 start_url = config['start_url'] | 638 start_url = config['start_url'] |
614 logging.info('Got start URL from config file: `%s`', start_url) | 639 logging.info('Got start URL from config file: `%s`', start_url) |
615 | 640 |
616 # Generate a progressive web app manifest. | 641 # Generate a progressive web app manifest. |
617 pwa_manifest = generate_web_manifest(ca_manifest, start_url) | 642 pwa_manifest = generate_web_manifest(ca_manifest, start_url) |
618 pwa_manifest_path = os.path.join(output_dir, PWA_MANIFEST_FILENAME) | 643 pwa_manifest_path = os.path.join(output_dir, PWA_MANIFEST_FILENAME) |
619 with open(pwa_manifest_path, 'w') as pwa_manifest_file: | 644 with open(pwa_manifest_path, 'w') as pwa_manifest_file: |
620 json.dump(pwa_manifest, pwa_manifest_file, indent=4, sort_keys=True) | 645 json.dump(pwa_manifest, pwa_manifest_file, indent=4, sort_keys=True) |
621 logging.debug('Wrote `%s` to `%s`.', PWA_MANIFEST_FILENAME, pwa_manifest_path) | 646 logging.debug('Wrote `%s` to `%s`.', PWA_MANIFEST_FILENAME, pwa_manifest_path) |
622 | 647 |
623 # Remove unnecessary files from the output web app. This must be done before | 648 # Remove unnecessary files from the output web app. This must be done before |
624 # the service worker is generated, or these files will be cached. | 649 # the service worker is generated, or these files will be cached. |
625 cleanup_output_dir(output_dir) | 650 cleanup_output_dir(output_dir) |
626 | 651 |
627 # Edit the HTML and JS code of the output web app. | 652 # Edit the HTML and JS code of the output web app. |
628 # This is adding TODOs, injecting tags, etc. - anything that involves editing | 653 # This is adding TODOs, injecting tags, etc. - anything that involves editing |
629 # user code directly. This must be done before the static code is copied | 654 # user code directly. This must be done before the static code is copied |
630 # across, or the polyfills will have TODOs added to them. | 655 # across, or the polyfills will have TODOs added to them. |
631 edit_code(output_dir, required_js_paths, ca_manifest, config) | 656 # Order is significant here - always, then dependencies, then polyfills. |
| 657 required_script_paths = (required_always_paths + required_dependency_paths |
| 658 + required_polyfill_paths) |
| 659 edit_code(output_dir, required_script_paths, ca_manifest, config) |
632 | 660 |
633 # We want the static SW file to be copied in too, so we add it here. | 661 # We want the static SW file to be copied in too, so we add it here. |
634 # We have to add it after edit_code or it would be included in the HTML, but | 662 # We have to add it after edit_code or it would be included in the HTML, but |
635 # this is service worker-only code, and shouldn't be included there. | 663 # this is service worker-only code, and shouldn't be included there. |
636 required_js_paths.append(SW_STATIC_SCRIPT_NAME) | 664 required_always_paths.append(SW_STATIC_SCRIPT_NAME) |
637 | 665 |
638 # Copy static code from Caterpillar into the output web app. | 666 # Copy static code from Caterpillar into the output web app. |
639 # This must be done before the service worker is generated, or these files | 667 # This must be done before the service worker is generated, or these files |
640 # will not be cached. | 668 # will not be cached. |
641 copy_static_code(required_js_paths, output_dir, boilerplate_dir) | 669 required_static_paths = required_always_paths + required_polyfill_paths |
| 670 copy_static_code(required_static_paths, output_dir, boilerplate_dir) |
642 | 671 |
643 # Read in the polyfill manifests and install polyfill dependencies. | 672 # Install the polyfill dependencies. This must be done before the service |
644 # This must be done after editing code (or the dependencies will also be | 673 # worker is generated, or the dependencies won't be cached. |
645 # edited) and before the service worker is generated (or the dependencies | |
646 # won't be cached). | |
647 polyfill_manifests = polyfill_manifest.load_many(polyfillable) | |
648 dependencies = [dependency | |
649 for manifest in polyfill_manifests.values() | |
650 for dependency in manifest['dependencies']] | |
651 try: | 674 try: |
652 install_dependencies(dependencies, output_dir) | 675 install_dependencies(dependencies, output_dir) |
653 except ValueError as e: | 676 except ValueError as e: |
654 logging.error(e.message) | 677 logging.error(e.message) |
655 return | 678 return |
656 | 679 |
657 # Generate and write a service worker. | 680 # Generate and write a service worker. |
658 add_service_worker(output_dir, ca_manifest, polyfill_paths(polyfillable), | 681 required_sw_paths = required_dependency_paths + required_polyfill_paths |
| 682 add_service_worker(output_dir, ca_manifest, required_sw_paths, |
659 boilerplate_dir) | 683 boilerplate_dir) |
660 | 684 |
661 logging.info('Conversion complete.') | 685 logging.info('Conversion complete.') |
662 | 686 |
663 | 687 |
664 class Formatter(logging.Formatter): | 688 class Formatter(logging.Formatter): |
665 """Caterpillar logging formatter. | 689 """Caterpillar logging formatter. |
666 | 690 |
667 Adds color to the logged information. | 691 Adds color to the logged information. |
668 """ | 692 """ |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
723 if args.mode == 'config': | 747 if args.mode == 'config': |
724 configuration.generate_and_save(args.output, args.interactive) | 748 configuration.generate_and_save(args.output, args.interactive) |
725 | 749 |
726 elif args.mode == 'convert': | 750 elif args.mode == 'convert': |
727 config = configuration.load(args.config) | 751 config = configuration.load(args.config) |
728 convert_app(args.input, args.output, config, args.force) | 752 convert_app(args.input, args.output, config, args.force) |
729 | 753 |
730 | 754 |
731 if __name__ == '__main__': | 755 if __name__ == '__main__': |
732 sys.exit(main()) | 756 sys.exit(main()) |
OLD | NEW |