1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
|
# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Genaretes a wrapper TS file around a source HTML file holding either
# 1) a Polymer element template or
# 2) an <iron-iconset-svg> definitions
#
# Note: The HTML file must be named either 'icons.html' or be suffixed with
# '_icons.html' for this tool to treat them as #2. Consequently, files holding
# Polymer element templates should not use such naming to be treated as #1.
#
# In case #1 the wrapper exports a getTemplate() function that can be used at
# runtime to import the template. This is useful for implementing Web Components
# using JS modules, where all the HTML needs to reside in a JS file (no more
# HTML imports).
#
# In case #2 the wrapper adds the <iron-iconset-svg> element to <head>, so that
# it can be used by <iron-icon> instances.
import argparse
import io
import re
import shutil
import sys
import tempfile
from os import path, getcwd, makedirs
_HERE_PATH = path.dirname(__file__)
_SRC_PATH = path.normpath(path.join(_HERE_PATH, '..', '..'))
_CWD = getcwd()
sys.path.append(path.join(_SRC_PATH, 'third_party', 'node'))
import node
# Template for native web component HTML templates.
_NATIVE_ELEMENT_TEMPLATE = """import {getTrustedHTML} from '%(scheme)s//resources/js/static_types.js';
export function getTemplate() {
return getTrustedHTML`<!--_html_template_start_-->%(content)s<!--_html_template_end_-->`;
}"""
# Template for Polymer web component HTML templates.
_POLYMER_ELEMENT_TEMPLATE = """import {html} from '%(scheme)s//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
export function getTemplate() {
return html`<!--_html_template_start_-->%(content)s<!--_html_template_end_-->`;
}"""
# Template for Lit component HTML templates.
_LIT_ELEMENT_TEMPLATE = """import {html} from '%(scheme)s//resources/lit/v3_0/lit.rollup.js';
import type {%(class_name)s} from './%(file_name)s.js';
%(imports)s
export function getHtml(this: %(class_name)s) {
return html`<!--_html_template_start_-->%(content)s<!--_html_template_end_-->`;
}"""
# Template for Polymer icon HTML files.
_POLYMER_ICONS_TEMPLATE = """import '%(scheme)s//resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
import {html} from '%(scheme)s//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
const template = html`%(content)s`;
document.head.appendChild(template.content);
"""
# Template for Lit icon HTML files.
_LIT_ICONS_TEMPLATE = """import '%(scheme)s//resources/cr_elements/cr_icon/cr_iconset.js';
import {getTrustedHTML} from '%(scheme)s//resources/js/static_types.js';
const div = document.createElement('div');
div.innerHTML = getTrustedHTML`%(content)s`;
const iconsets = div.querySelectorAll('cr-iconset');
for (const iconset of iconsets) {
document.head.appendChild(iconset);
}
"""
# Tokens used to detect whether the underlying custom element is based on
# Polymer or Lit.
POLYMER_TOKEN = '//resources/polymer/v3_0/polymer/polymer_bundled.min.js'
LIT_TOKEN = '//resources/lit/v3_0/lit.rollup.js'
# Map holding all the different types of HTML files to generate wrappers for.
TEMPLATE_MAP = {
'lit': _LIT_ELEMENT_TEMPLATE,
'lit_icons': _LIT_ICONS_TEMPLATE,
'native': _NATIVE_ELEMENT_TEMPLATE,
'polymer_icons': _POLYMER_ICONS_TEMPLATE,
'polymer': _POLYMER_ELEMENT_TEMPLATE,
}
def detect_template_type(definition_file):
with io.open(definition_file, encoding='utf-8', mode='r') as f:
content = f.read()
if POLYMER_TOKEN in content:
return 'polymer'
elif LIT_TOKEN in content:
return 'lit'
return 'native'
def detect_icon_template_type(icons_file):
with io.open(icons_file, encoding='utf-8', mode='r') as f:
content = f.read()
if 'iron-iconset-svg' in content:
return 'polymer_icons'
assert 'cr-iconset' in content, \
'icons files must include iron-iconset-svg or cr-iconset'
return 'lit_icons'
_IMPORTS_START_REGEX = '^<!-- #html_wrapper_imports_start$'
_IMPORTS_END_REGEX = '^#html_wrapper_imports_end -->$'
# Extract additional imports to carry over to the HTML wrapper file.
def _extract_import_metadata(file, minify):
start_line = -1
end_line = -1
with io.open(file, encoding='utf-8', mode='r') as f:
lines = f.read().splitlines()
for i, line in enumerate(lines):
if start_line == -1:
if re.search(_IMPORTS_START_REGEX, line):
assert end_line == -1
start_line = i
else:
assert end_line == -1
if re.search(_IMPORTS_END_REGEX, line):
assert start_line > -1
end_line = i
break
if start_line == -1 or end_line == -1:
assert start_line == -1
assert end_line == -1
return None
return {
# Strip metadata from content, unless minification is used, which will
# strip any HTML comments anyway.
'content': None if minify else '\n'.join(lines[end_line + 1:]),
'imports': '\n'.join(lines[start_line + 1:end_line]) + '\n',
}
def main(argv):
parser = argparse.ArgumentParser()
parser.add_argument('--in_folder', required=True)
parser.add_argument('--out_folder', required=True)
parser.add_argument('--in_files', required=True, nargs="*")
parser.add_argument('--minify', action='store_true')
parser.add_argument('--use_js', action='store_true')
parser.add_argument('--template',
choices=['polymer', 'lit', 'native', 'detect'],
default='polymer')
parser.add_argument('--scheme',
choices=['chrome', 'relative'],
default='relative')
args = parser.parse_args(argv)
in_folder = path.normpath(path.join(_CWD, args.in_folder))
out_folder = path.normpath(path.join(_CWD, args.out_folder))
extension = '.js' if args.use_js else '.ts'
results = []
# The folder to be used to read the HTML files to be wrapped.
wrapper_in_folder = in_folder
if args.minify:
# Minify the HTML files with html-minifier before generating the wrapper
# .ts files.
# Note: Passing all HTML files to html-minifier all at once because
# passing them individually takes a lot longer.
# Storing the output in a temporary folder, which is used further below when
# creating the final wrapper files.
tmp_out_dir = tempfile.mkdtemp(dir=out_folder)
try:
wrapper_in_folder = tmp_out_dir
# Using the programmatic Node API to invoke html-minifier, because the
# built-in command line API does not support explicitly specifying
# multiple files to be processed, and only supports specifying an input
# folder, which would lead to potentially processing unnecessary HTML
# files that are not part of the build (stale), or handled by other
# html_to_wrapper targets.
node.RunNode(
[path.join(_HERE_PATH, 'html_minifier.js'), in_folder, tmp_out_dir] +
args.in_files)
except RuntimeError as err:
shutil.rmtree(tmp_out_dir)
raise err
out_files = []
# Wrap the input files (minified or not) with an enclosing .ts file.
for in_file in args.in_files:
wrapper_in_file = path.join(wrapper_in_folder, in_file)
template = None
template_type = args.template
filename = path.basename(in_file)
effective_in_file = wrapper_in_file
if filename == 'icons.html' or filename.endswith('_icons.html'):
if args.template == 'polymer':
template_type = 'polymer_icons'
elif args.template == 'lit':
template_type = 'lit_icons'
else:
assert args.template == 'detect', (
r'Polymer/Lit icons files not supported with template="%s"' %
args.template)
template_type = detect_icon_template_type(wrapper_in_file)
elif filename.endswith('icons_lit.html'):
assert args.template == 'lit' or args.template == 'detect', (
r'Lit icons files not supported with template="%s"' % args.template)
# Grab the content from the equivalent Polymer file, and substitute
# cr-iconset for iron-iconset-svg.
polymer_file = path.join(wrapper_in_folder,
in_file.replace('icons_lit', 'icons'))
effective_in_file = polymer_file
template_type = 'lit_icons'
elif template_type == 'detect':
# Locate the file that holds the web component's definition. Assumed to
# be in the same folder as input HTML template file.
definition_file = path.splitext(path.join(in_folder,
in_file))[0] + extension
template_type = detect_template_type(definition_file)
with io.open(effective_in_file, encoding='utf-8', mode='r') as f:
html_content = f.read()
substitutions = {
'content': html_content,
'scheme': 'chrome:' if args.scheme == 'chrome' else '',
}
if template_type == 'lit_icons':
# Replace iron-iconset-svg for the case of Lit icons files generated
# from a Polymer icons file.
if 'iron-iconset-svg' in html_content:
html_content = html_content.replace('iron-iconset-svg', 'cr-iconset')
substitutions['content'] = html_content
elif template_type == 'lit':
# Add Lit specific substitutions.
basename = path.splitext(path.basename(in_file))[0]
# Derive class name from file name. For example
# foo_bar.html -> FooBarElement.
class_name = ''.join(map(str.title, basename.split('_'))) + 'Element'
substitutions['class_name'] = class_name
substitutions['file_name'] = basename
# Extracting import metadata from original non-minified template.
import_metadata = _extract_import_metadata(
path.join(args.in_folder, in_file), args.minify)
substitutions['imports'] = \
'' if import_metadata is None else import_metadata['imports']
if import_metadata is not None and not args.minify:
# Remove metadata lines from content.
substitutions['content'] = import_metadata['content']
wrapper = TEMPLATE_MAP[template_type] % substitutions
out_folder_for_file = path.join(out_folder, path.dirname(in_file))
makedirs(out_folder_for_file, exist_ok=True)
out_file = path.join(out_folder, in_file) + extension
out_files.append(out_file)
with io.open(out_file, mode='wb') as f:
f.write(wrapper.encode('utf-8'))
if args.minify:
# Delete the temporary folder that was holding minified HTML files, no
# longer needed.
shutil.rmtree(tmp_out_dir)
return
if __name__ == '__main__':
main(sys.argv[1:])
|