Skip to content

Commit fb51cbe

Browse files
authored
Performance improvement (#133)
1 parent ca17edf commit fb51cbe

File tree

7 files changed

+497
-85
lines changed

7 files changed

+497
-85
lines changed

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"eslint-fix": "npm run lint -- --fix",
1818
"test": "mocha --require ts-node/register \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
1919
"cover": "nyc --reporter=lcov npm run test",
20-
"debug": "mocha --require ts-node/register/transpile-only \"tests/src/**/*.ts\" --reporter dot",
20+
"debug": "mocha --require ts-node/register/transpile-only \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
2121
"preversion": "npm run lint && npm test",
2222
"update-fixtures": "ts-node --transpile-only ./tools/update-fixtures.ts",
2323
"eslint-playground": "eslint tests/fixtures --ext .svelte --config .eslintrc-for-playground.js --format codeframe",
@@ -52,6 +52,7 @@
5252
"@ota-meshi/eslint-plugin": "^0.10.0",
5353
"@ota-meshi/eslint-plugin-svelte": "^0.22.0",
5454
"@types/benchmark": "^2.1.1",
55+
"@types/chai": "^4.3.0",
5556
"@types/eslint": "^8.0.0",
5657
"@types/eslint-scope": "^3.7.0",
5758
"@types/eslint-visitor-keys": "^1.0.0",
@@ -61,6 +62,7 @@
6162
"@typescript-eslint/eslint-plugin": "^5.4.0",
6263
"@typescript-eslint/parser": "^5.4.0",
6364
"benchmark": "^2.1.4",
65+
"chai": "^4.3.4",
6466
"code-red": "^0.2.3",
6567
"eslint": "^8.2.0",
6668
"eslint-config-prettier": "^8.3.0",
@@ -77,6 +79,7 @@
7779
"locate-character": "^2.0.5",
7880
"magic-string": "^0.25.7",
7981
"mocha": "^9.1.3",
82+
"mocha-chai-jest-snapshot": "^1.1.3",
8083
"nyc": "^15.1.0",
8184
"prettier": "^2.0.5",
8285
"prettier-plugin-svelte": "^2.5.0",

src/context/index.ts

+71-42
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
import fs from "fs"
22
import path from "path"
3-
import type { Comment, Locations, Position, Token } from "../ast"
3+
import type {
4+
Comment,
5+
Locations,
6+
Position,
7+
SvelteScriptElement,
8+
SvelteStyleElement,
9+
Token,
10+
} from "../ast"
411
import type ESTree from "estree"
512
import { ScriptLetContext } from "./script-let"
613
import { LetDirectiveCollections } from "./let-directive-collection"
714
import { getParserName } from "../parser/resolve-parser"
15+
import type { AttributeToken } from "../parser/html"
16+
import { parseAttributes } from "../parser/html"
817

918
export class ScriptsSourceCode {
1019
private raw: string
@@ -87,6 +96,8 @@ export class Context {
8796

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

99+
private readonly blocks: Block[] = []
100+
90101
public constructor(code: string, parserOptions: any) {
91102
this.code = code
92103
this.parserOptions = parserOptions
@@ -96,21 +107,25 @@ export class Context {
96107

97108
let templateCode = ""
98109
let scriptCode = ""
99-
let scriptAttrs: Record<string, string | undefined> = {}
110+
const scriptAttrs: Record<string, string | undefined> = {}
100111

101112
let start = 0
102113
for (const block of extractBlocks(code)) {
114+
this.blocks.push(block)
103115
templateCode +=
104-
code.slice(start, block.codeRange[0]) +
105-
spaces.slice(block.codeRange[0], block.codeRange[1])
116+
code.slice(start, block.contentRange[0]) +
117+
spaces.slice(block.contentRange[0], block.contentRange[1])
106118
if (block.tag === "script") {
107119
scriptCode +=
108-
spaces.slice(start, block.codeRange[0]) + block.code
109-
scriptAttrs = Object.assign(scriptAttrs, block.attrs)
120+
spaces.slice(start, block.contentRange[0]) +
121+
code.slice(...block.contentRange)
122+
for (const attr of block.attrs) {
123+
scriptAttrs[attr.key.name] = attr.value?.value
124+
}
110125
} else {
111-
scriptCode += spaces.slice(start, block.codeRange[1])
126+
scriptCode += spaces.slice(start, block.contentRange[1])
112127
}
113-
start = block.codeRange[1]
128+
start = block.contentRange[1]
114129
}
115130
templateCode += code.slice(start)
116131
scriptCode += spaces.slice(start)
@@ -223,49 +238,63 @@ export class Context {
223238
public stripScriptCode(start: number, end: number): void {
224239
this.sourceCode.scripts.stripCode(start, end)
225240
}
241+
242+
public findBlock(
243+
element: SvelteScriptElement | SvelteStyleElement,
244+
): Block | undefined {
245+
const tag = element.type === "SvelteScriptElement" ? "script" : "style"
246+
return this.blocks.find(
247+
(block) =>
248+
block.tag === tag &&
249+
element.range[0] <= block.contentRange[0] &&
250+
block.contentRange[1] <= element.range[1],
251+
)
252+
}
226253
}
227254

228-
/** Extract <script> blocks */
229-
function* extractBlocks(code: string): IterableIterator<{
230-
code: string
231-
codeRange: [number, number]
232-
attrs: Record<string, string | undefined>
255+
type Block = {
233256
tag: "script" | "style"
234-
}> {
235-
const startTagRe = /<(script|style)(\s[\s\S]*?)?>/giu
236-
const endScriptTagRe = /<\/script(?:\s[\s\S]*?)?>/giu
237-
const endStyleTagRe = /<\/style(?:\s[\s\S]*?)?>/giu
238-
let startTagRes
239-
while ((startTagRes = startTagRe.exec(code))) {
240-
const [startTag, tag, attributes = ""] = startTagRes
241-
const startTagStart = startTagRes.index
242-
const startTagEnd = startTagStart + startTag.length
257+
attrs: AttributeToken[]
258+
contentRange: [number, number]
259+
}
260+
261+
/** Extract <script> blocks */
262+
function* extractBlocks(code: string): IterableIterator<Block> {
263+
const startTagOpenRe = /<(script|style)([\s>])/giu
264+
const endScriptTagRe = /<\/script>/giu
265+
const endStyleTagRe = /<\/style>/giu
266+
let startTagOpenMatch
267+
while ((startTagOpenMatch = startTagOpenRe.exec(code))) {
268+
const [, tag, nextChar] = startTagOpenMatch
269+
let startTagEnd = startTagOpenRe.lastIndex
270+
271+
let attrs: AttributeToken[] = []
272+
if (!nextChar.trim()) {
273+
const attrsData = parseAttributes(code, startTagOpenRe.lastIndex)
274+
attrs = attrsData.attributes
275+
startTagEnd = attrsData.index
276+
if (code[startTagEnd] === "/") {
277+
startTagEnd++
278+
}
279+
if (code[startTagEnd] === ">") {
280+
startTagEnd++
281+
} else {
282+
continue
283+
}
284+
}
243285
const endTagRe =
244286
tag.toLowerCase() === "script" ? endScriptTagRe : endStyleTagRe
245-
endTagRe.lastIndex = startTagRe.lastIndex
246-
const endTagRes = endTagRe.exec(code)
247-
if (endTagRes) {
248-
const endTagStart = endTagRes.index
249-
const codeRange: [number, number] = [startTagEnd, endTagStart]
250-
251-
const attrRe =
252-
/(?<key>[^\s=]+)(?:=(?:"(?<val1>[^"]*)"|'(?<val2>[^"]*)'|(?<val3>[^\s=]+)))?/gu
253-
const attrs: Record<string, string | undefined> = {}
254-
let attrRes
255-
while ((attrRes = attrRe.exec(attributes))) {
256-
attrs[attrRes.groups!.key] =
257-
(attrRes.groups!.val1 ||
258-
attrRes.groups!.val2 ||
259-
attrRes.groups!.val3) ??
260-
null
261-
}
287+
endTagRe.lastIndex = startTagEnd
288+
const endTagMatch = endTagRe.exec(code)
289+
if (endTagMatch) {
290+
const endTagStart = endTagMatch.index
291+
const contentRange: [number, number] = [startTagEnd, endTagStart]
262292
yield {
263-
code: code.slice(...codeRange),
264-
codeRange,
293+
contentRange,
265294
attrs,
266295
tag: tag as "script" | "style",
267296
}
268-
startTagRe.lastIndex = endTagRe.lastIndex
297+
startTagOpenRe.lastIndex = endTagRe.lastIndex
269298
}
270299
}
271300
}

src/parser/converts/root.ts

+40-37
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type * as SvAST from "../svelte-ast-types"
2-
import { parse } from "svelte/compiler"
32
import type {
3+
SvelteAttribute,
44
SvelteName,
55
SvelteProgram,
66
SvelteScriptElement,
@@ -9,7 +9,7 @@ import type {
99
import {} from "./common"
1010
import type { Context } from "../../context"
1111
import { convertChildren, extractElementTags } from "./element"
12-
import { convertAttributes } from "./attr"
12+
import { extractTextTokens } from "./text"
1313

1414
/**
1515
* Convert root
@@ -136,38 +136,6 @@ function extractAttributes(
136136
element: SvelteScriptElement | SvelteStyleElement,
137137
ctx: Context,
138138
) {
139-
const script = element.type === "SvelteScriptElement"
140-
141-
let code = " ".repeat(element.range[0])
142-
143-
const elementCode = ctx.sourceCode.template.slice(...element.range)
144-
const startRegex = script
145-
? /<script(\s[\s\S]*?)?>/giu
146-
: /<style(\s[\s\S]*?)?>/giu
147-
const endTag = script ? "</script>" : "</style>"
148-
let re
149-
let index = 0
150-
while ((re = startRegex.exec(elementCode))) {
151-
const [, attributes] = re
152-
153-
const endTagIndex = elementCode.indexOf(endTag, startRegex.lastIndex)
154-
if (endTagIndex >= 0) {
155-
const contextLength = endTagIndex - startRegex.lastIndex
156-
code += elementCode.slice(index, re.index)
157-
code += `${script ? "<div " : "<div "}${attributes || ""}>`
158-
code += `${" ".repeat(contextLength)}</div>`
159-
startRegex.lastIndex = index = endTagIndex + endTag.length
160-
} else {
161-
break
162-
}
163-
}
164-
code += elementCode.slice(index)
165-
const svelteAst = parse(code) as SvAST.Ast
166-
167-
const fakeElement = svelteAst.html.children.find(
168-
(c) => c.type === "Element",
169-
) as SvAST.Element
170-
171139
element.startTag = {
172140
type: "SvelteStartTag",
173141
attributes: [],
@@ -182,7 +150,42 @@ function extractAttributes(
182150
end: null as any,
183151
},
184152
}
185-
element.startTag.attributes.push(
186-
...convertAttributes(fakeElement.attributes, element.startTag, ctx),
187-
)
153+
const block = ctx.findBlock(element)
154+
if (block) {
155+
for (const attr of block.attrs) {
156+
const attrNode: SvelteAttribute = {
157+
type: "SvelteAttribute",
158+
boolean: false,
159+
key: null as any,
160+
value: [],
161+
parent: element.startTag,
162+
...ctx.getConvertLocation({
163+
start: attr.key.start,
164+
end: attr.value?.end ?? attr.key.end,
165+
}),
166+
}
167+
element.startTag.attributes.push(attrNode)
168+
attrNode.key = {
169+
type: "SvelteName",
170+
name: attr.key.name,
171+
parent: attrNode,
172+
...ctx.getConvertLocation(attr.key),
173+
}
174+
ctx.addToken("HTMLIdentifier", attr.key)
175+
if (attr.value == null) {
176+
attrNode.boolean = true
177+
} else {
178+
const valueLoc = attr.value.quote
179+
? { start: attr.value.start + 1, end: attr.value.end - 1 }
180+
: attr.value
181+
attrNode.value.push({
182+
type: "SvelteLiteral",
183+
value: attr.value.value,
184+
parent: attrNode,
185+
...ctx.getConvertLocation(valueLoc),
186+
})
187+
extractTextTokens(valueLoc, ctx)
188+
}
189+
}
190+
}
188191
}

src/parser/converts/text.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,16 @@ export function convertTemplateLiteralToLiteral(
4646
parent,
4747
...ctx.getConvertLocation(node),
4848
}
49-
extractTextTokens(node, ctx)
49+
extractTextTokens(getWithLoc(node), ctx)
5050
return text
5151
}
5252

5353
/** Extract tokens */
54-
function extractTextTokens(
55-
node: SvAST.Text | ESTree.TemplateLiteral,
54+
export function extractTextTokens(
55+
node: { start: number; end: number },
5656
ctx: Context,
57-
) {
58-
const loc = getWithLoc(node)
57+
): void {
58+
const loc = node
5959
let start = loc.start
6060
let word = false
6161
for (let index = loc.start; index < loc.end; index++) {

0 commit comments

Comments
 (0)