# Copyright 2019 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # Generates Polymer3 UI elements (using JS modules) from existing Polymer2 # elements (using HTML imports). This is useful for avoiding code duplication # while Polymer2 to Polymer3 migration is in progress. # # Variables: # html_file: # The input Polymer2 HTML file to be processed. # # js_file: # The input Polymer2 JS file to be processed, or the name of the output JS # file when no input JS file exists (see |html_type| below). # # in_folder: # The folder where |html_file| and |js_file| (when it exists) reside. # # out_folder: # The output folder for the generated Polymer JS file. # # html_type: # Specifies the type of the |html_file| such that the script knows how to # process the |html_file|. Available values are: # dom-module: A file holding a for a UI element (this is # the majority case). Note: having multiple s # within a single HTML file is not currently supported # style-module: A file holding a shared style # (no corresponding Polymer2 JS file exists) # custom-style: A file holding a (usually a *_vars_css.html # file, no corresponding Polymer2 JS file exists) # iron-iconset: A file holding one or more instances # (no corresponding Polymer2 JS file exists) # v3-ready: A file holding HTML that is already written for Polymer3. A # Polymer3 JS file already exists for such cases. In this mode # HTML content is simply pasted within the JS file. This mode # will be the only supported mode after migration finishes. # # namespace_rewrites: # A list of string replacements for replacing global namespaced references # with explicitly imported dependencies in the generated JS module. # For example "cr.foo.Bar|Bar" will replace all occurrences of "cr.foo.Bar" # with "Bar". # # auto_imports: # A list of of auto-imports, to inform the script on which variables to # import from a JS module. For example "ui/webui/foo/bar/baz.html|Foo,Bar" # will result in something like "import {Foo, Bar} from ...;" when # encountering any dependency to that file. import argparse import io import os import re import sys _CWD = os.getcwd() _HERE_PATH = os.path.dirname(__file__) _ROOT = os.path.normpath(os.path.join(_HERE_PATH, '..', '..')) POLYMER_V1_DIR = 'third_party/polymer/v1_0/components-chromium/' POLYMER_V3_DIR = 'third_party/polymer/v3_0/components-chromium/' # Rewrite rules for replacing global namespace references like "cr.ui.Foo", to # "Foo" within a generated JS module. Populated from command line arguments. _namespace_rewrites = {} # Auto-imports map, populated from command line arguments. Specifies which # variables to import from a given dependency. For example this is used to # import |FocusOutlineManager| whenever a dependency to # ui/webui/resources/html/cr/ui/focus_outline_manager.html is encountered. _auto_imports = {} # Populated from command line arguments. Specifies a list of HTML imports to # ignore when converting HTML imports to JS modules. _ignore_imports = [] _migrated_imports = [] _chrome_redirects = { 'chrome://resources/polymer/v1_0/': POLYMER_V1_DIR, 'chrome://resources/html/': 'ui/webui/resources/html/', 'chrome://resources/cr_elements/': 'ui/webui/resources/cr_elements/', '//resources/polymer/v1_0/': POLYMER_V1_DIR, '//resources/html/': 'ui/webui/resources/html/', '//resources/cr_elements/': 'ui/webui/resources/cr_elements/', } _chrome_reverse_redirects = { POLYMER_V3_DIR: '//resources/polymer/v3_0/', 'ui/webui/resources/': '//resources/', } # Helper class for converting dependencies expressed in HTML imports, to JS # imports. |to_js_import()| is the only public method exposed by this class. # Internally an HTML import path is # # 1) normalized, meaning converted from a chrome or relative URL to to an # absolute path starting at the repo's root # 2) converted to an equivalent JS normalized path # 3) de-normalized, meaning converted back to a relative or chrome URL # 4) converted to a JS import statement class Dependency: def __init__(self, src, dst): self.html_file = src self.html_path = dst self.input_format = ('chrome' if self.html_path.startswith('chrome://') or self.html_path.startswith('//') else 'relative') self.output_format = self.input_format self.html_path_normalized = self._to_html_normalized() self.js_path_normalized = self._to_js_normalized() self.js_path = self._to_js() def _to_html_normalized(self): if self.input_format == 'chrome': self.html_path_normalized = self.html_path for r in _chrome_redirects: if self.html_path.startswith(r): self.html_path_normalized = ( self.html_path.replace(r, _chrome_redirects[r])) break return self.html_path_normalized input_dir = os.path.relpath(os.path.dirname(self.html_file), _ROOT) return os.path.normpath( os.path.join(input_dir, self.html_path)).replace("\\", "/") def _to_js_normalized(self): if re.match(POLYMER_V1_DIR, self.html_path_normalized): return (self.html_path_normalized .replace(POLYMER_V1_DIR, POLYMER_V3_DIR) .replace(r'.html', '.js')) if self.html_path_normalized == 'ui/webui/resources/html/polymer.html': self.output_format = 'chrome' return POLYMER_V3_DIR + 'polymer/polymer_bundled.min.js' if re.match(r'ui/webui/resources/html/', self.html_path_normalized): return (self.html_path_normalized .replace(r'ui/webui/resources/html/', 'ui/webui/resources/js/') .replace(r'.html', '.m.js')) extension = ( '.js' if self.html_path_normalized in _migrated_imports else '.m.js') return self.html_path_normalized.replace(r'.html', extension) def _to_js(self): js_path = self.js_path_normalized if self.output_format == 'chrome': for r in _chrome_reverse_redirects: if self.js_path_normalized.startswith(r): js_path = self.js_path_normalized.replace( r, _chrome_reverse_redirects[r]) break return js_path input_dir = os.path.relpath(os.path.dirname(self.html_file), _ROOT) relpath = os.path.relpath( self.js_path_normalized, input_dir).replace("\\", "/") # Prepend "./" if |relpath| refers to a relative subpath, that is not "../". # This prefix is required for JS Modules paths. if not relpath.startswith('.'): relpath = './' + relpath return relpath def to_js_import(self, auto_imports): if self.html_path_normalized in auto_imports: imports = auto_imports[self.html_path_normalized] return 'import {%s} from \'%s\';' % (', '.join(imports), self.js_path) return 'import \'%s\';' % self.js_path def _generate_js_imports(html_file): output = [] imports_end_index = -1 imports_found = False with io.open(html_file, encoding='utf-8', mode='r') as f: lines = f.readlines() deps = [] for i, line in enumerate(lines): match = re.search(r'\s* tag. if (i > 0): previous_line = lines[i - 1] if re.search(r'^\s* tag. if re.search(r'^// \s*', output[imports_end_index + 1]): imports_end_index += 1 return output[0:imports_end_index + 1] def _extract_dom_module_id(html_file): with io.open(html_file, encoding='utf-8', mode='r') as f: contents = f.read() match = re.search(r'\s*%s' % \ html_template; def _extract_template(html_file, html_type): if html_type == 'v3-ready': with io.open(html_file, encoding='utf-8', mode='r') as f: template = f.read() return _add_template_markers('\n' + template) if html_type == 'dom-module': with io.open(html_file, encoding='utf-8', mode='r') as f: lines = f.readlines() start_line = -1 end_line = -1 for i, line in enumerate(lines): if re.match(r'\s*', line): assert start_line != -1 assert end_line == -1 assert re.match(r'\s*', lines[i - 2]) assert re.match(r'\s*