Skip to content

Performance improvement #133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"eslint-fix": "npm run lint -- --fix",
"test": "mocha --require ts-node/register \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
"cover": "nyc --reporter=lcov npm run test",
"debug": "mocha --require ts-node/register/transpile-only \"tests/src/**/*.ts\" --reporter dot",
"debug": "mocha --require ts-node/register/transpile-only \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
"preversion": "npm run lint && npm test",
"update-fixtures": "ts-node --transpile-only ./tools/update-fixtures.ts",
"eslint-playground": "eslint tests/fixtures --ext .svelte --config .eslintrc-for-playground.js --format codeframe",
Expand Down Expand Up @@ -52,6 +52,7 @@
"@ota-meshi/eslint-plugin": "^0.10.0",
"@ota-meshi/eslint-plugin-svelte": "^0.22.0",
"@types/benchmark": "^2.1.1",
"@types/chai": "^4.3.0",
"@types/eslint": "^8.0.0",
"@types/eslint-scope": "^3.7.0",
"@types/eslint-visitor-keys": "^1.0.0",
Expand All @@ -61,6 +62,7 @@
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"benchmark": "^2.1.4",
"chai": "^4.3.4",
"code-red": "^0.2.3",
"eslint": "^8.2.0",
"eslint-config-prettier": "^8.3.0",
Expand All @@ -77,6 +79,7 @@
"locate-character": "^2.0.5",
"magic-string": "^0.25.7",
"mocha": "^9.1.3",
"mocha-chai-jest-snapshot": "^1.1.3",
"nyc": "^15.1.0",
"prettier": "^2.0.5",
"prettier-plugin-svelte": "^2.5.0",
Expand Down
113 changes: 71 additions & 42 deletions src/context/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import fs from "fs"
import path from "path"
import type { Comment, Locations, Position, Token } from "../ast"
import type {
Comment,
Locations,
Position,
SvelteScriptElement,
SvelteStyleElement,
Token,
} from "../ast"
import type ESTree from "estree"
import { ScriptLetContext } from "./script-let"
import { LetDirectiveCollections } from "./let-directive-collection"
import { getParserName } from "../parser/resolve-parser"
import type { AttributeToken } from "../parser/html"
import { parseAttributes } from "../parser/html"

export class ScriptsSourceCode {
private raw: string
Expand Down Expand Up @@ -87,6 +96,8 @@ export class Context {

private state: { isTypeScript?: boolean } = {}

private readonly blocks: Block[] = []

public constructor(code: string, parserOptions: any) {
this.code = code
this.parserOptions = parserOptions
Expand All @@ -96,21 +107,25 @@ export class Context {

let templateCode = ""
let scriptCode = ""
let scriptAttrs: Record<string, string | undefined> = {}
const scriptAttrs: Record<string, string | undefined> = {}

let start = 0
for (const block of extractBlocks(code)) {
this.blocks.push(block)
templateCode +=
code.slice(start, block.codeRange[0]) +
spaces.slice(block.codeRange[0], block.codeRange[1])
code.slice(start, block.contentRange[0]) +
spaces.slice(block.contentRange[0], block.contentRange[1])
if (block.tag === "script") {
scriptCode +=
spaces.slice(start, block.codeRange[0]) + block.code
scriptAttrs = Object.assign(scriptAttrs, block.attrs)
spaces.slice(start, block.contentRange[0]) +
code.slice(...block.contentRange)
for (const attr of block.attrs) {
scriptAttrs[attr.key.name] = attr.value?.value
}
} else {
scriptCode += spaces.slice(start, block.codeRange[1])
scriptCode += spaces.slice(start, block.contentRange[1])
}
start = block.codeRange[1]
start = block.contentRange[1]
}
templateCode += code.slice(start)
scriptCode += spaces.slice(start)
Expand Down Expand Up @@ -223,49 +238,63 @@ export class Context {
public stripScriptCode(start: number, end: number): void {
this.sourceCode.scripts.stripCode(start, end)
}

public findBlock(
element: SvelteScriptElement | SvelteStyleElement,
): Block | undefined {
const tag = element.type === "SvelteScriptElement" ? "script" : "style"
return this.blocks.find(
(block) =>
block.tag === tag &&
element.range[0] <= block.contentRange[0] &&
block.contentRange[1] <= element.range[1],
)
}
}

/** Extract <script> blocks */
function* extractBlocks(code: string): IterableIterator<{
code: string
codeRange: [number, number]
attrs: Record<string, string | undefined>
type Block = {
tag: "script" | "style"
}> {
const startTagRe = /<(script|style)(\s[\s\S]*?)?>/giu
const endScriptTagRe = /<\/script(?:\s[\s\S]*?)?>/giu
const endStyleTagRe = /<\/style(?:\s[\s\S]*?)?>/giu
let startTagRes
while ((startTagRes = startTagRe.exec(code))) {
const [startTag, tag, attributes = ""] = startTagRes
const startTagStart = startTagRes.index
const startTagEnd = startTagStart + startTag.length
attrs: AttributeToken[]
contentRange: [number, number]
}

/** Extract <script> blocks */
function* extractBlocks(code: string): IterableIterator<Block> {
const startTagOpenRe = /<(script|style)([\s>])/giu
const endScriptTagRe = /<\/script>/giu
const endStyleTagRe = /<\/style>/giu
let startTagOpenMatch
while ((startTagOpenMatch = startTagOpenRe.exec(code))) {
const [, tag, nextChar] = startTagOpenMatch
let startTagEnd = startTagOpenRe.lastIndex

let attrs: AttributeToken[] = []
if (!nextChar.trim()) {
const attrsData = parseAttributes(code, startTagOpenRe.lastIndex)
attrs = attrsData.attributes
startTagEnd = attrsData.index
if (code[startTagEnd] === "/") {
startTagEnd++
}
if (code[startTagEnd] === ">") {
startTagEnd++
} else {
continue
}
}
const endTagRe =
tag.toLowerCase() === "script" ? endScriptTagRe : endStyleTagRe
endTagRe.lastIndex = startTagRe.lastIndex
const endTagRes = endTagRe.exec(code)
if (endTagRes) {
const endTagStart = endTagRes.index
const codeRange: [number, number] = [startTagEnd, endTagStart]

const attrRe =
/(?<key>[^\s=]+)(?:=(?:"(?<val1>[^"]*)"|'(?<val2>[^"]*)'|(?<val3>[^\s=]+)))?/gu
const attrs: Record<string, string | undefined> = {}
let attrRes
while ((attrRes = attrRe.exec(attributes))) {
attrs[attrRes.groups!.key] =
(attrRes.groups!.val1 ||
attrRes.groups!.val2 ||
attrRes.groups!.val3) ??
null
}
endTagRe.lastIndex = startTagEnd
const endTagMatch = endTagRe.exec(code)
if (endTagMatch) {
const endTagStart = endTagMatch.index
const contentRange: [number, number] = [startTagEnd, endTagStart]
yield {
code: code.slice(...codeRange),
codeRange,
contentRange,
attrs,
tag: tag as "script" | "style",
}
startTagRe.lastIndex = endTagRe.lastIndex
startTagOpenRe.lastIndex = endTagRe.lastIndex
}
}
}
Expand Down
77 changes: 40 additions & 37 deletions src/parser/converts/root.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type * as SvAST from "../svelte-ast-types"
import { parse } from "svelte/compiler"
import type {
SvelteAttribute,
SvelteName,
SvelteProgram,
SvelteScriptElement,
Expand All @@ -9,7 +9,7 @@ import type {
import {} from "./common"
import type { Context } from "../../context"
import { convertChildren, extractElementTags } from "./element"
import { convertAttributes } from "./attr"
import { extractTextTokens } from "./text"

/**
* Convert root
Expand Down Expand Up @@ -136,38 +136,6 @@ function extractAttributes(
element: SvelteScriptElement | SvelteStyleElement,
ctx: Context,
) {
const script = element.type === "SvelteScriptElement"

let code = " ".repeat(element.range[0])

const elementCode = ctx.sourceCode.template.slice(...element.range)
const startRegex = script
? /<script(\s[\s\S]*?)?>/giu
: /<style(\s[\s\S]*?)?>/giu
const endTag = script ? "</script>" : "</style>"
let re
let index = 0
while ((re = startRegex.exec(elementCode))) {
const [, attributes] = re

const endTagIndex = elementCode.indexOf(endTag, startRegex.lastIndex)
if (endTagIndex >= 0) {
const contextLength = endTagIndex - startRegex.lastIndex
code += elementCode.slice(index, re.index)
code += `${script ? "<div " : "<div "}${attributes || ""}>`
code += `${" ".repeat(contextLength)}</div>`
startRegex.lastIndex = index = endTagIndex + endTag.length
} else {
break
}
}
code += elementCode.slice(index)
const svelteAst = parse(code) as SvAST.Ast

const fakeElement = svelteAst.html.children.find(
(c) => c.type === "Element",
) as SvAST.Element

element.startTag = {
type: "SvelteStartTag",
attributes: [],
Expand All @@ -182,7 +150,42 @@ function extractAttributes(
end: null as any,
},
}
element.startTag.attributes.push(
...convertAttributes(fakeElement.attributes, element.startTag, ctx),
)
const block = ctx.findBlock(element)
if (block) {
for (const attr of block.attrs) {
const attrNode: SvelteAttribute = {
type: "SvelteAttribute",
boolean: false,
key: null as any,
value: [],
parent: element.startTag,
...ctx.getConvertLocation({
start: attr.key.start,
end: attr.value?.end ?? attr.key.end,
}),
}
element.startTag.attributes.push(attrNode)
attrNode.key = {
type: "SvelteName",
name: attr.key.name,
parent: attrNode,
...ctx.getConvertLocation(attr.key),
}
ctx.addToken("HTMLIdentifier", attr.key)
if (attr.value == null) {
attrNode.boolean = true
} else {
const valueLoc = attr.value.quote
? { start: attr.value.start + 1, end: attr.value.end - 1 }
: attr.value
attrNode.value.push({
type: "SvelteLiteral",
value: attr.value.value,
parent: attrNode,
...ctx.getConvertLocation(valueLoc),
})
extractTextTokens(valueLoc, ctx)
}
}
}
}
10 changes: 5 additions & 5 deletions src/parser/converts/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,16 @@ export function convertTemplateLiteralToLiteral(
parent,
...ctx.getConvertLocation(node),
}
extractTextTokens(node, ctx)
extractTextTokens(getWithLoc(node), ctx)
return text
}

/** Extract tokens */
function extractTextTokens(
node: SvAST.Text | ESTree.TemplateLiteral,
export function extractTextTokens(
node: { start: number; end: number },
ctx: Context,
) {
const loc = getWithLoc(node)
): void {
const loc = node
let start = loc.start
let word = false
for (let index = loc.start; index < loc.end; index++) {
Expand Down
Loading