From edd74df0a6fc6195139c5b7cdf5c9ef0e7f99c7d Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Mon, 8 Aug 2022 07:22:34 +0200 Subject: [PATCH] optimize `.replace() calls --- src/compiler/compile/render_dom/Block.ts | 4 ++- src/compiler/compile/render_dom/index.ts | 4 ++- .../render_dom/wrappers/Element/Attribute.ts | 6 ++-- .../wrappers/Element/StyleAttribute.ts | 6 ++-- .../render_dom/wrappers/Element/index.ts | 34 +++++++++++-------- .../wrappers/InlineComponent/index.ts | 4 ++- .../render_dom/wrappers/shared/add_actions.ts | 4 ++- .../shared/create_debugging_comment.ts | 4 ++- .../handlers/shared/get_attribute_value.ts | 4 ++- .../compile/utils/get_name_from_filename.ts | 19 +++++++---- src/compiler/compile/utils/hash.ts | 5 ++- src/compiler/compile/utils/stringify.ts | 8 +++-- src/compiler/parse/index.ts | 4 ++- src/compiler/parse/read/context.ts | 4 ++- src/compiler/parse/read/script.ts | 9 +++-- 15 files changed, 81 insertions(+), 38 deletions(-) diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index 34c4774804..df530a148a 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -23,6 +23,8 @@ export interface BlockOptions { dependencies?: Set; } +const regex_double_quotes = /"/g; + export default class Block { parent?: Block; renderer: Renderer; @@ -415,7 +417,7 @@ export default class Block { block: ${block}, id: ${this.name || 'create_fragment'}.name, type: "${this.type}", - source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}", + source: "${this.comment ? this.comment.replace(regex_double_quotes, '\\"') : ''}", ctx: #ctx }); return ${block};` diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 173d93d5dd..5152775ed8 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -13,6 +13,8 @@ import { flatten } from '../../utils/flatten'; import check_enable_sourcemap from '../utils/check_enable_sourcemap'; import { push_array } from '../../utils/push_array'; +const regex_backslash = /\\/g; + export default function dom( component: Component, options: CompileOptions @@ -530,7 +532,7 @@ export default function dom( constructor(options) { super(); - ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} + ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} @init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, null, ${dirty}); diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index aa003601c9..48cda4cb93 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts @@ -46,6 +46,8 @@ export class BaseAttributeWrapper { } const regex_minus_sign = /-/; +const regex_invalid_variable_identifier_characters = /[^a-zA-Z_$]/g; // is 0-9 missing? +const regex_double_quotes = /"/g; export default class AttributeWrapper extends BaseAttributeWrapper { node: Attribute; @@ -198,7 +200,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper { get_init(block: Block, value) { this.last = this.should_cache && block.get_unique_name( - `${this.parent.var.name}_${this.name.replace(/[^a-zA-Z_$]/g, '_')}_value` + `${this.parent.var.name}_${this.name.replace(regex_invalid_variable_identifier_characters, '_')}_value` ); if (this.should_cache) block.add_variable(this.last); @@ -315,7 +317,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper { return `="${value.map(chunk => { return chunk.type === 'Text' - ? chunk.data.replace(/"/g, '\\"') + ? chunk.data.replace(regex_double_quotes, '\\"') : `\${${chunk.manipulate()}}`; }).join('')}"`; } diff --git a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts index 6ffa37fb71..4cc895848c 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts @@ -108,6 +108,8 @@ function optimize_style(value: Array) { return props; } +const regex_important_flag = /\s*!important\s*$/; + function get_style_value(chunks: Array) { const value: Array = []; @@ -174,9 +176,9 @@ function get_style_value(chunks: Array) { let important = false; const last_chunk = value[value.length - 1]; - if (last_chunk && last_chunk.type === 'Text' && /\s*!important\s*$/.test(last_chunk.data)) { + if (last_chunk && last_chunk.type === 'Text' && regex_important_flag.test(last_chunk.data)) { important = true; - last_chunk.data = last_chunk.data.replace(/\s*!important\s*$/, ''); + last_chunk.data = last_chunk.data.replace(regex_important_flag, ''); if (!last_chunk.data) value.pop(); } diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 4f0b49729c..b81c48957f 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -136,6 +136,8 @@ const events = [ ]; const CHILD_DYNAMIC_ELEMENT_BLOCK = 'child_dynamic_element'; +const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g; +const regex_minus_signs = /-/g; export default class ElementWrapper extends Wrapper { node: Element; @@ -182,7 +184,7 @@ export default class ElementWrapper extends Wrapper { this.var = { type: 'Identifier', - name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') + name: node.name.replace(regex_invalid_variable_identifier_characters, '_') }; this.void = is_void(node.name); @@ -320,19 +322,19 @@ export default class ElementWrapper extends Wrapper { } } else if (${previous_tag}) { ${ - has_transitions - ? b` + has_transitions + ? b` @group_outros(); @transition_out(${this.var}, 1, 1, () => { ${this.var} = null; }); @check_outros(); ` - : b` + : b` ${this.var}.d(1); ${this.var} = null; ` - } + } } ${previous_tag} = ${tag}; `); @@ -547,7 +549,7 @@ export default class ElementWrapper extends Wrapper { } } - add_directives_in_order (block: Block) { + add_directives_in_order(block: Block) { type OrderedAttribute = EventHandler | BindingGroup | Binding | Action; const binding_groups = events @@ -561,7 +563,7 @@ export default class ElementWrapper extends Wrapper { const this_binding = this.bindings.find(b => b.node.name === 'this'); - function getOrder (item: OrderedAttribute) { + function getOrder(item: OrderedAttribute) { if (item instanceof EventHandler) { return item.node.start; } else if (item instanceof Binding) { @@ -674,9 +676,9 @@ export default class ElementWrapper extends Wrapper { function ${handler}(${params}) { ${binding_group.bindings.map(b => b.handler.mutation)} ${Array.from(dependencies) - .filter(dep => dep[0] !== '$') - .filter(dep => !contextual_dependencies.has(dep)) - .map(dep => b`${this.renderer.invalidate(dep)};`)} + .filter(dep => dep[0] !== '$') + .filter(dep => !contextual_dependencies.has(dep)) + .map(dep => b`${this.renderer.invalidate(dep)};`)} } `); @@ -1100,7 +1102,7 @@ export default class ElementWrapper extends Wrapper { const snippet = expression.manipulate(block); let cached_snippet; if (should_cache) { - cached_snippet = block.get_unique_name(`style_${name.replace(/-/g, '_')}`); + cached_snippet = block.get_unique_name(`style_${name.replace(regex_minus_signs, '_')}`); block.add_variable(cached_snippet, snippet); } @@ -1154,10 +1156,14 @@ function to_html(wrappers: Array = new Map(); @@ -596,7 +598,7 @@ export default class InlineComponentWrapper extends Wrapper { this.node.css_custom_properties.forEach((attr) => { const dependencies = attr.get_dependencies(); const should_cache = attr.should_cache(); - const last = should_cache && block.get_unique_name(`${attr.name.replace(/[^a-zA-Z_$]/g, '_')}_last`); + const last = should_cache && block.get_unique_name(`${attr.name.replace(regex_invalid_variable_identifier_characters, '_')}_last`); if (should_cache) block.add_variable(last); const value = attr.get_value(block); const init = should_cache ? x`${last} = ${value}` : value; diff --git a/src/compiler/compile/render_dom/wrappers/shared/add_actions.ts b/src/compiler/compile/render_dom/wrappers/shared/add_actions.ts index 6429f18928..3b39494994 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/add_actions.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/add_actions.ts @@ -12,6 +12,8 @@ export default function add_actions( actions.forEach(action => add_action(block, target, action)); } +const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g; + export function add_action(block: Block, target: string | Expression, action: Action) { const { expression, template_scope } = action; let snippet; @@ -23,7 +25,7 @@ export function add_action(block: Block, target: string | Expression, action: Ac } const id = block.get_unique_name( - `${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action` + `${action.name.replace(regex_invalid_variable_identifier_characters, '_')}_action` ); block.add_variable(id); diff --git a/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts b/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts index 8e9f7fab37..17a99b1426 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts @@ -1,6 +1,8 @@ import Component from '../../../Component'; import { INode } from '../../../nodes/interfaces'; +const regex_whitespace_characters = /\s/g; + export default function create_debugging_comment( node: INode, component: Component @@ -36,5 +38,5 @@ export default function create_debugging_comment( const start = locate(c); const loc = `(${start.line}:${start.column})`; - return `${loc} ${source.slice(c, d)}`.replace(/\s/g, ' '); + return `${loc} ${source.slice(c, d)}`.replace(regex_whitespace_characters, ' '); } diff --git a/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts b/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts index 20d4eddbc5..5ce1aa3ecc 100644 --- a/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts +++ b/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts @@ -15,13 +15,15 @@ export function get_class_attribute_value(attribute: Attribute): ESTreeExpressio return get_attribute_value(attribute); } +const regex_double_quotes = /"/g; + export function get_attribute_value(attribute: Attribute): ESTreeExpression { if (attribute.chunks.length === 0) return x`""`; return attribute.chunks .map((chunk) => { return chunk.type === 'Text' - ? string_literal(chunk.data.replace(/"/g, '"')) as ESTreeExpression + ? string_literal(chunk.data.replace(regex_double_quotes, '"')) as ESTreeExpression : x`@escape(${chunk.node}, true)`; }) .reduce((lhs, rhs) => x`${lhs} + ${rhs}`); diff --git a/src/compiler/compile/utils/get_name_from_filename.ts b/src/compiler/compile/utils/get_name_from_filename.ts index b05605b5ee..9d045c30df 100644 --- a/src/compiler/compile/utils/get_name_from_filename.ts +++ b/src/compiler/compile/utils/get_name_from_filename.ts @@ -1,3 +1,10 @@ +const regex_percentage_characters = /%/g; +const regex_file_ending = /\.[^.]+$/; +const regex_repeated_invalid_variable_identifier_characters = /[^a-zA-Z_$0-9]+/g; +const regex_starts_with_underscore = /^_/; +const regex_ends_with_underscore = /_$/; +const regex_starts_with_digit = /^(\d)/; + export default function get_name_from_filename(filename: string) { if (!filename) return null; @@ -12,12 +19,12 @@ export default function get_name_from_filename(filename: string) { } const base = parts.pop() - .replace(/%/g, 'u') - .replace(/\.[^.]+$/, '') - .replace(/[^a-zA-Z_$0-9]+/g, '_') - .replace(/^_/, '') - .replace(/_$/, '') - .replace(/^(\d)/, '_$1'); + .replace(regex_percentage_characters, 'u') + .replace(regex_file_ending, '') + .replace(regex_repeated_invalid_variable_identifier_characters, '_') + .replace(regex_starts_with_underscore, '') + .replace(regex_ends_with_underscore, '') + .replace(regex_starts_with_digit, '_$1'); if (!base) { throw new Error(`Could not derive component name from file ${filename}`); diff --git a/src/compiler/compile/utils/hash.ts b/src/compiler/compile/utils/hash.ts index c0ea2b3d88..2896926f9d 100644 --- a/src/compiler/compile/utils/hash.ts +++ b/src/compiler/compile/utils/hash.ts @@ -1,6 +1,9 @@ // https://github.com/darkskyapp/string-hash/blob/master/index.js + +const const_return_characters = /\r/g; + export default function hash(str: string): string { - str = str.replace(/\r/g, ''); + str = str.replace(const_return_characters, ''); let hash = 5381; let i = str.length; diff --git a/src/compiler/compile/utils/stringify.ts b/src/compiler/compile/utils/stringify.ts index aa1c42c512..84073c6469 100644 --- a/src/compiler/compile/utils/stringify.ts +++ b/src/compiler/compile/utils/stringify.ts @@ -19,10 +19,14 @@ const escaped = { '>': '>' }; +const regex_html_characters_to_escape = /["'&<>]/g; + export function escape_html(html) { - return String(html).replace(/["'&<>]/g, match => escaped[match]); + return String(html).replace(regex_html_characters_to_escape, match => escaped[match]); } +const regex_template_characters_to_escape = /(\${|`|\\)/g; + export function escape_template(str) { - return str.replace(/(\${|`|\\)/g, '\\$1'); + return str.replace(regex_template_characters_to_escape, '\\$1'); } diff --git a/src/compiler/parse/index.ts b/src/compiler/parse/index.ts index e5e2260bc0..96ec70fa42 100644 --- a/src/compiler/parse/index.ts +++ b/src/compiler/parse/index.ts @@ -15,6 +15,8 @@ interface LastAutoClosedTag { depth: number; } +const regex_position_indicator = / \(\d+:\d+\)$/; + export class Parser { readonly template: string; readonly filename?: string; @@ -93,7 +95,7 @@ export class Parser { acorn_error(err: any) { this.error({ code: 'parse-error', - message: err.message.replace(/ \(\d+:\d+\)$/, '') + message: err.message.replace(regex_position_indicator, '') }, err.pos); } diff --git a/src/compiler/parse/read/context.ts b/src/compiler/parse/read/context.ts index 1e97baf8a7..a63cee4ef2 100644 --- a/src/compiler/parse/read/context.ts +++ b/src/compiler/parse/read/context.ts @@ -11,6 +11,8 @@ import { parse_expression_at } from '../acorn'; import { Pattern } from 'estree'; import parser_errors from '../errors'; +const regex_not_newline_characters = /[^\n]/g; + export default function read_context( parser: Parser ): Pattern & { start: number; end: number } { @@ -65,7 +67,7 @@ export default function read_context( // so we offset it by removing 1 character in the `space_with_newline` // to achieve that, we remove the 1st space encountered, // so it will not affect the `column` of the node - let space_with_newline = parser.template.slice(0, start).replace(/[^\n]/g, ' '); + let space_with_newline = parser.template.slice(0, start).replace(regex_not_newline_characters, ' '); const first_space = space_with_newline.indexOf(' '); space_with_newline = space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1); diff --git a/src/compiler/parse/read/script.ts b/src/compiler/parse/read/script.ts index c9ac4ac344..c4cb150060 100644 --- a/src/compiler/parse/read/script.ts +++ b/src/compiler/parse/read/script.ts @@ -4,6 +4,9 @@ import { Script } from '../../interfaces'; import { Node, Program } from 'estree'; import parser_errors from '../errors'; +const regex_not_newline_characters = /[^\n]/g; +const regex_closing_script_tag = /<\/script\s*>/; + function get_context(parser: Parser, attributes: any[], start: number): string { const context = attributes.find(attribute => attribute.name === 'context'); if (!context) return 'default'; @@ -23,13 +26,13 @@ function get_context(parser: Parser, attributes: any[], start: number): string { export default function read_script(parser: Parser, start: number, attributes: Node[]): Script { const script_start = parser.index; - const data = parser.read_until(/<\/script\s*>/, parser_errors.unclosed_script); + const data = parser.read_until(regex_closing_script_tag, parser_errors.unclosed_script); if (parser.index >= parser.template.length) { parser.error(parser_errors.unclosed_script); } - const source = parser.template.slice(0, script_start).replace(/[^\n]/g, ' ') + data; - parser.read(/<\/script\s*>/); + const source = parser.template.slice(0, script_start).replace(regex_not_newline_characters, ' ') + data; + parser.read(regex_closing_script_tag); let ast: Program;