wuchale 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -8
- package/package.json +1 -1
- package/preprocess/index.js +17 -41
- package/preprocess/prep.js +155 -109
package/README.md
CHANGED
|
@@ -35,7 +35,10 @@ minimal and constant-time.
|
|
|
35
35
|
### 🧩 Full Nesting Support
|
|
36
36
|
|
|
37
37
|
Handles deeply nested markup and interpolations — mixed conditionals, loops,
|
|
38
|
-
and awaits — by compiling them into nested Svelte snippets.
|
|
38
|
+
and awaits — by compiling them into nested Svelte snippets. That means you can
|
|
39
|
+
go as crazy as
|
|
40
|
+
[this test](https://github.com/K1DV5/wuchale/blob/main/tests/complicated/app.svelte)
|
|
41
|
+
and it will still extract the correct texts.
|
|
39
42
|
|
|
40
43
|
### 📦 No String Parsing at Runtime
|
|
41
44
|
|
|
@@ -56,8 +59,9 @@ optional integration with external tools.
|
|
|
56
59
|
|
|
57
60
|
### 🚀 Tiny Footprint
|
|
58
61
|
|
|
59
|
-
Adds just 2 packages (
|
|
60
|
-
|
|
62
|
+
Adds just 2 packages (itself and `pofile`) to `node_modules`, as the other
|
|
63
|
+
dependency is Svelte itself. No 200 packages and 90MB dependency trees like
|
|
64
|
+
some existing solutions.
|
|
61
65
|
|
|
62
66
|
### ✨ Ready for Svelte 5
|
|
63
67
|
|
|
@@ -79,11 +83,39 @@ Add to your Vite config:
|
|
|
79
83
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
80
84
|
import { wuchale } from 'wuchale'
|
|
81
85
|
|
|
82
|
-
export default {
|
|
86
|
+
export default {
|
|
87
|
+
plugins: [
|
|
88
|
+
wuchale(),
|
|
89
|
+
svelte(),
|
|
90
|
+
]
|
|
91
|
+
// ...your other config
|
|
92
|
+
}
|
|
83
93
|
|
|
84
94
|
```
|
|
85
95
|
|
|
86
|
-
|
|
96
|
+
Create `/locales/` if it doesn't exist, and then set it up in your main
|
|
97
|
+
component. Assuming `/src/App.svelte`:
|
|
98
|
+
|
|
99
|
+
```svelte
|
|
100
|
+
<script>
|
|
101
|
+
import {setTranslations} from 'wuchale/runtime.svelte'
|
|
102
|
+
|
|
103
|
+
let locale = $state('en')
|
|
104
|
+
|
|
105
|
+
$effect.pre(() => {
|
|
106
|
+
import(`../locales/${locale}.json`).then(mod => {
|
|
107
|
+
setTranslations(mod.default)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
</script>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Note that you manage the state of which locale is active and how to download
|
|
114
|
+
the compiled `.json`. This is to allow maximum flexibility, meaning you can use
|
|
115
|
+
lazy loading (like this example) or you can import it directly and it will be
|
|
116
|
+
bundled by Vite. After that, you notify `wuchale` to set it as the current one.
|
|
117
|
+
|
|
118
|
+
Then finally you write your Svelte files naturally:
|
|
87
119
|
|
|
88
120
|
```svelte
|
|
89
121
|
|
|
@@ -98,6 +130,8 @@ import WuchaleTrans, { wuchaleTrans } from 'wuchale/runtime.svelte'
|
|
|
98
130
|
<h1>{wuchaleTrans(0)}</h1> <!-- Extracted "Hello" as index 0 -->
|
|
99
131
|
```
|
|
100
132
|
|
|
133
|
+
Full example below.
|
|
134
|
+
|
|
101
135
|
## 📦 How It Works
|
|
102
136
|
|
|
103
137
|
### Process
|
|
@@ -135,6 +169,8 @@ Input:
|
|
|
135
169
|
|
|
136
170
|
```svelte
|
|
137
171
|
|
|
172
|
+
<p>Hi there!</p>
|
|
173
|
+
|
|
138
174
|
<p>Hello <b>{userName}</b></p>
|
|
139
175
|
|
|
140
176
|
```
|
|
@@ -142,19 +178,62 @@ Input:
|
|
|
142
178
|
Output:
|
|
143
179
|
|
|
144
180
|
```svelte
|
|
181
|
+
<script>
|
|
182
|
+
import WuchaleTrans, { wuchaleTrans } from 'wuchale/runtime.svelte'
|
|
183
|
+
</script>
|
|
145
184
|
|
|
146
|
-
<p>{wuchaleTrans(0
|
|
185
|
+
<p>{wuchaleTrans(0)}</p>
|
|
186
|
+
|
|
187
|
+
<p>
|
|
188
|
+
{#snippet wuchaleSnippet0(ctx)}
|
|
189
|
+
<b>{ctx[1]}</b>
|
|
190
|
+
{/snippet}
|
|
191
|
+
<WuchaleTrans tags={[wuchaleSnippet0]} id={1} args={[userName]} />
|
|
192
|
+
</p>
|
|
147
193
|
|
|
148
194
|
```
|
|
149
195
|
|
|
150
|
-
|
|
196
|
+
Extracted catalog (PO) for `en`:
|
|
151
197
|
|
|
152
198
|
```nginx
|
|
153
199
|
|
|
154
|
-
msgid "
|
|
200
|
+
msgid "Hi there!"
|
|
201
|
+
msgstr "Hi there!"
|
|
202
|
+
|
|
203
|
+
msgid "Hello {0}"
|
|
204
|
+
msgstr "Hello {0}"
|
|
155
205
|
|
|
156
206
|
```
|
|
157
207
|
|
|
208
|
+
Extracted catalog (PO) for `es`, initially empty `msgstr`, but after a translator or Gemini translates it:
|
|
209
|
+
|
|
210
|
+
```nginx
|
|
211
|
+
|
|
212
|
+
msgid "Hi there!"
|
|
213
|
+
msgstr "¡Hola!"
|
|
214
|
+
|
|
215
|
+
msgid "Hello {0}"
|
|
216
|
+
msgstr "Hola {0}"
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Which is then automatically compiled to:
|
|
221
|
+
|
|
222
|
+
```json
|
|
223
|
+
[
|
|
224
|
+
"¡Hola!",
|
|
225
|
+
[
|
|
226
|
+
"Hola ",
|
|
227
|
+
[
|
|
228
|
+
0,
|
|
229
|
+
0
|
|
230
|
+
]
|
|
231
|
+
]
|
|
232
|
+
]
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
This is what you import when you set it up above in `App.svelte`.
|
|
236
|
+
|
|
158
237
|
## Supported syntax
|
|
159
238
|
|
|
160
239
|
Text can be in three places: markup, script and attributes. Script means not
|
|
@@ -196,6 +275,35 @@ necessary to have a separate plurals support because you can do something like:
|
|
|
196
275
|
And they will be extracted separately. You can also make a reusable function
|
|
197
276
|
yourself.
|
|
198
277
|
|
|
278
|
+
## Configuration
|
|
279
|
+
|
|
280
|
+
To configure `wuchale`, you pass an object that looks like the following (the
|
|
281
|
+
default) to `wuchale()` in your `vite.config.js` `vite.config.ts`:
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
export const defaultOptions = {
|
|
285
|
+
sourceLocale: 'en',
|
|
286
|
+
otherLocales: ['am'],
|
|
287
|
+
localesDir: './locales',
|
|
288
|
+
importFrom: 'wuchale/runtime.svelte',
|
|
289
|
+
heuristic: defaultHeuristic,
|
|
290
|
+
geminiAPIKey: 'env',
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
While the others are self explanatory, the `heuristic` is a function that
|
|
295
|
+
decides what text to extract and what not to. The `defaultHeuristic` is the
|
|
296
|
+
implementation of the above rules, but you can roll your own and provide it
|
|
297
|
+
here. The function should receive the following arguments:
|
|
298
|
+
|
|
299
|
+
- `text`: The candidate text
|
|
300
|
+
- `scope`: Where the text is located, i.e. it can be one of `markup`, `script`, and `attribute`
|
|
301
|
+
|
|
302
|
+
And it should return an object with two properties:
|
|
303
|
+
|
|
304
|
+
- `extract` (boolean): Whether to extract it or not
|
|
305
|
+
- `replace` (`string`): The string to replace it with. This is how you specify how to remove parts such as the prefixes above (`+` and `-`).
|
|
306
|
+
|
|
199
307
|
## 🧹 Cleaning
|
|
200
308
|
|
|
201
309
|
Unused keys are marked as obsolete during a production build. Obsoletes are
|
package/package.json
CHANGED
package/preprocess/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Preprocess, { IndexTracker } from "./prep.js"
|
|
1
|
+
import Preprocess, { defaultHeuristic, IndexTracker } from "./prep.js"
|
|
2
2
|
import { parse } from "svelte/compiler"
|
|
3
3
|
import {writeFile} from 'node:fs/promises'
|
|
4
4
|
import compileTranslation from "./compile.js"
|
|
@@ -6,30 +6,6 @@ import setupGemini from "./gemini.js"
|
|
|
6
6
|
import PO from "pofile"
|
|
7
7
|
import { relative } from "node:path"
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
* @param {string} text
|
|
11
|
-
* @param {string} scope
|
|
12
|
-
* @returns {{extract: boolean, replace: string}}
|
|
13
|
-
*/
|
|
14
|
-
export function defaultHeuristic(text, scope = 'markup') {
|
|
15
|
-
if (scope === 'markup') {
|
|
16
|
-
if (text.startsWith('-')) {
|
|
17
|
-
return {extract: false, replace: text.slice(1).trim()}
|
|
18
|
-
}
|
|
19
|
-
return {extract: true, replace: text}
|
|
20
|
-
}
|
|
21
|
-
// script and attribute
|
|
22
|
-
if (text.startsWith('+')) {
|
|
23
|
-
return {extract: true, replace: text.slice(1).trim()}
|
|
24
|
-
}
|
|
25
|
-
const range = 'AZ'
|
|
26
|
-
const startCode = text.charCodeAt(0)
|
|
27
|
-
if (startCode >= range.charCodeAt(0) && startCode <= range.charCodeAt(1)) {
|
|
28
|
-
return {extract: true, replace: text}
|
|
29
|
-
}
|
|
30
|
-
return {extract: false, replace: text}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
9
|
export const defaultOptions = {
|
|
34
10
|
sourceLocale: 'en',
|
|
35
11
|
otherLocales: ['am'],
|
|
@@ -45,24 +21,22 @@ export const defaultOptions = {
|
|
|
45
21
|
async function loadPONoFail(filename) {
|
|
46
22
|
return new Promise((res) => {
|
|
47
23
|
PO.load(filename, (err, po) => {
|
|
48
|
-
if (err) {
|
|
49
|
-
res({})
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
24
|
const translations = {}
|
|
53
25
|
let total = 0
|
|
54
26
|
let obsolete = 0
|
|
55
27
|
let untranslated = 0
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
obsolete
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
28
|
+
if (!err) {
|
|
29
|
+
for (const item of po.items) {
|
|
30
|
+
total++
|
|
31
|
+
if (item.obsolete) {
|
|
32
|
+
obsolete++
|
|
33
|
+
continue
|
|
34
|
+
}
|
|
35
|
+
if (!item.msgstr[0]) {
|
|
36
|
+
untranslated++
|
|
37
|
+
}
|
|
38
|
+
translations[item.msgid] = item
|
|
64
39
|
}
|
|
65
|
-
translations[item.msgid] = item
|
|
66
40
|
}
|
|
67
41
|
res({translations, total, obsolete, untranslated})
|
|
68
42
|
})
|
|
@@ -142,7 +116,7 @@ export default async function wuchale(options = defaultOptions) {
|
|
|
142
116
|
|
|
143
117
|
/**
|
|
144
118
|
* @param {string} content
|
|
145
|
-
* @param {import(
|
|
119
|
+
* @param {import('estree').Program | import("svelte/compiler").AST.Root} ast
|
|
146
120
|
* @param {string} filename
|
|
147
121
|
*/
|
|
148
122
|
async function preprocess(content, ast, filename) {
|
|
@@ -219,6 +193,9 @@ export default async function wuchale(options = defaultOptions) {
|
|
|
219
193
|
* @param {string} id
|
|
220
194
|
*/
|
|
221
195
|
handler: async function(code, id) {
|
|
196
|
+
if (!id.startsWith(projectRoot)) {
|
|
197
|
+
return
|
|
198
|
+
}
|
|
222
199
|
const isModule = id.endsWith('.svelte.js')
|
|
223
200
|
if (!id.endsWith('.svelte') && !isModule) {
|
|
224
201
|
return
|
|
@@ -227,8 +204,7 @@ export default async function wuchale(options = defaultOptions) {
|
|
|
227
204
|
if (isModule) {
|
|
228
205
|
ast = this.parse(code)
|
|
229
206
|
} else {
|
|
230
|
-
ast = parse(code)
|
|
231
|
-
ast.type = 'SvelteComponent'
|
|
207
|
+
ast = parse(code, {modern: true})
|
|
232
208
|
}
|
|
233
209
|
const filename = relative(projectRoot, id)
|
|
234
210
|
const processed = await preprocess(code, ast, filename)
|
package/preprocess/prep.js
CHANGED
|
@@ -7,31 +7,36 @@ const rtComponent = 'WuchaleTrans'
|
|
|
7
7
|
const rtFunc = 'wuchaleTrans'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* @typedef {
|
|
11
|
-
* @
|
|
12
|
-
* @property {number} start
|
|
13
|
-
* @property {number} end
|
|
10
|
+
* @typedef {"script" | "markup" | "attribute"} TxtScope
|
|
11
|
+
* @typedef {(text: string, scope: TxtScope) => {extract: boolean; replace: string}} HeuristicFunc
|
|
14
12
|
*/
|
|
15
13
|
|
|
16
14
|
/**
|
|
17
|
-
* @
|
|
18
|
-
* @typedef {Node & {value: string}} NodeWithVal
|
|
19
|
-
* @typedef {Node & {data: string}} NodeWithData
|
|
20
|
-
* @typedef {Node & {
|
|
21
|
-
* attributes: Attribute[],
|
|
22
|
-
* children: (Element & NodeWithData & { expression: Node })[]
|
|
23
|
-
* inCompoundText: boolean | null,
|
|
24
|
-
* }} Element
|
|
25
|
-
* @typedef {(text: string, scope?: string) => {extract: boolean; replace: string}} HeuristicFunc
|
|
26
|
-
* @typedef {Node & {
|
|
27
|
-
* properties: Node & { key: Node, value: Node }[]
|
|
28
|
-
* }} ObjExpr
|
|
15
|
+
* @type {HeuristicFunc}
|
|
29
16
|
*/
|
|
17
|
+
export function defaultHeuristic(text, scope = 'markup') {
|
|
18
|
+
if (scope === 'markup') {
|
|
19
|
+
if (text.startsWith('-')) {
|
|
20
|
+
return {extract: false, replace: text.slice(1).trim()}
|
|
21
|
+
}
|
|
22
|
+
return {extract: true, replace: text}
|
|
23
|
+
}
|
|
24
|
+
// script and attribute
|
|
25
|
+
if (text.startsWith('+')) {
|
|
26
|
+
return {extract: true, replace: text.slice(1).trim()}
|
|
27
|
+
}
|
|
28
|
+
const range = 'AZ'
|
|
29
|
+
const startCode = text.charCodeAt(0)
|
|
30
|
+
if (startCode >= range.charCodeAt(0) && startCode <= range.charCodeAt(1)) {
|
|
31
|
+
return {extract: true, replace: text}
|
|
32
|
+
}
|
|
33
|
+
return {extract: false, replace: text}
|
|
34
|
+
}
|
|
30
35
|
|
|
31
36
|
class NestText extends String {
|
|
32
37
|
/**
|
|
33
38
|
* @param {string} txt
|
|
34
|
-
* @param {
|
|
39
|
+
* @param {TxtScope} scope
|
|
35
40
|
*/
|
|
36
41
|
constructor(txt, scope) {
|
|
37
42
|
super(txt)
|
|
@@ -80,12 +85,13 @@ export default class Preprocess {
|
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
/**
|
|
83
|
-
* @param {
|
|
88
|
+
* @param {number} start
|
|
89
|
+
* @param {number} end
|
|
84
90
|
* @param {string} text
|
|
85
|
-
* @param {
|
|
91
|
+
* @param {TxtScope} scope
|
|
86
92
|
* @returns {Array<*> & {0: boolean, 1: NestText}}
|
|
87
93
|
*/
|
|
88
|
-
modifyCheck = (
|
|
94
|
+
modifyCheck = (start, end, text, scope) => {
|
|
89
95
|
text = text.replace(/\s+/g, ' ').trim()
|
|
90
96
|
if (text === '') {
|
|
91
97
|
// nothing to ask
|
|
@@ -94,7 +100,7 @@ export default class Preprocess {
|
|
|
94
100
|
let {extract, replace} = this.heuristic(text, scope)
|
|
95
101
|
replace = replace.trim()
|
|
96
102
|
if (!extract && text !== replace) {
|
|
97
|
-
this.mstr.update(
|
|
103
|
+
this.mstr.update(start, end, replace)
|
|
98
104
|
}
|
|
99
105
|
return [extract, new NestText(replace, scope)]
|
|
100
106
|
}
|
|
@@ -104,23 +110,24 @@ export default class Preprocess {
|
|
|
104
110
|
// visitImportDeclaration = () => []
|
|
105
111
|
|
|
106
112
|
/**
|
|
107
|
-
* @param {
|
|
113
|
+
* @param {import('estree').Literal & {start: number, end: number}} node
|
|
108
114
|
* @returns {NestText[]}
|
|
109
115
|
*/
|
|
110
116
|
visitLiteral = node => {
|
|
111
117
|
if (typeof node.value !== 'string') {
|
|
112
118
|
return []
|
|
113
119
|
}
|
|
114
|
-
const
|
|
120
|
+
const { start, end } = node
|
|
121
|
+
const [pass, txt] = this.modifyCheck(start, end, node.value, 'script')
|
|
115
122
|
if (!pass) {
|
|
116
123
|
return []
|
|
117
124
|
}
|
|
118
|
-
this.mstr.update(
|
|
125
|
+
this.mstr.update(start, end, `${rtFunc}(${this.index.get(txt.toString())})`)
|
|
119
126
|
return [txt]
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
/**
|
|
123
|
-
* @param {
|
|
130
|
+
* @param {import('estree').ArrayExpression} node
|
|
124
131
|
* @returns {NestText[]}
|
|
125
132
|
*/
|
|
126
133
|
visitArrayExpression = node => {
|
|
@@ -132,20 +139,22 @@ export default class Preprocess {
|
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
/**
|
|
135
|
-
* @param {
|
|
142
|
+
* @param {import('estree').ObjectExpression} node
|
|
136
143
|
* @returns {NestText[]}
|
|
137
144
|
*/
|
|
138
145
|
visitObjectExpression = node => {
|
|
139
146
|
const txts = []
|
|
140
147
|
for (const prop of node.properties) {
|
|
148
|
+
// @ts-ignore
|
|
141
149
|
txts.push(...this.visit(prop.key))
|
|
150
|
+
// @ts-ignore
|
|
142
151
|
txts.push(...this.visit(prop.value))
|
|
143
152
|
}
|
|
144
153
|
return txts
|
|
145
154
|
}
|
|
146
155
|
|
|
147
156
|
/**
|
|
148
|
-
* @param {
|
|
157
|
+
* @param {import('estree').MemberExpression} node
|
|
149
158
|
* @returns {NestText[]}
|
|
150
159
|
*/
|
|
151
160
|
visitMemberExpression = node => {
|
|
@@ -156,7 +165,7 @@ export default class Preprocess {
|
|
|
156
165
|
}
|
|
157
166
|
|
|
158
167
|
/**
|
|
159
|
-
* @param {
|
|
168
|
+
* @param {import('estree').CallExpression} node
|
|
160
169
|
* @returns {NestText[]}
|
|
161
170
|
*/
|
|
162
171
|
visitCallExpression = node => {
|
|
@@ -168,13 +177,7 @@ export default class Preprocess {
|
|
|
168
177
|
}
|
|
169
178
|
|
|
170
179
|
/**
|
|
171
|
-
* @param {
|
|
172
|
-
* declarations: Node & {
|
|
173
|
-
* init: Node & {
|
|
174
|
-
* callee: Node & { name: string },
|
|
175
|
-
* },
|
|
176
|
-
* }[]
|
|
177
|
-
* }} node
|
|
180
|
+
* @param {import('estree').VariableDeclaration} node
|
|
178
181
|
* @returns {NestText[]}
|
|
179
182
|
*/
|
|
180
183
|
visitVariableDeclaration = node => {
|
|
@@ -197,24 +200,21 @@ export default class Preprocess {
|
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
/**
|
|
200
|
-
* @param {
|
|
203
|
+
* @param {import('estree').ExportDefaultDeclaration} node
|
|
201
204
|
* @returns {NestText[]}
|
|
202
205
|
*/
|
|
203
206
|
visitExportDefaultDeclaration = node => this.visit(node.declaration)
|
|
204
207
|
|
|
205
208
|
/**
|
|
206
|
-
* @param {
|
|
207
|
-
* quasis: (Node & {
|
|
208
|
-
* value: {cooked: string}
|
|
209
|
-
* })[],
|
|
210
|
-
* expressions: Node[]
|
|
211
|
-
* }} node
|
|
209
|
+
* @param {import('estree').TemplateLiteral} node
|
|
212
210
|
* @returns {NestText[]}
|
|
213
211
|
*/
|
|
214
212
|
visitTemplateLiteral = node => {
|
|
215
213
|
const txts = []
|
|
216
214
|
const quasi0 = node.quasis[0]
|
|
217
|
-
|
|
215
|
+
// @ts-ignore
|
|
216
|
+
const {start: start0, end: end0} = quasi0
|
|
217
|
+
const [pass, txt] = this.modifyCheck(start0, end0, quasi0.value.cooked, 'script')
|
|
218
218
|
if (!pass) {
|
|
219
219
|
return txts
|
|
220
220
|
}
|
|
@@ -223,17 +223,20 @@ export default class Preprocess {
|
|
|
223
223
|
txts.push(...this.visit(expr))
|
|
224
224
|
const quasi = node.quasis[i + 1]
|
|
225
225
|
nTxt += `{${i}}${quasi.value.cooked}`
|
|
226
|
-
|
|
226
|
+
// @ts-ignore
|
|
227
|
+
const {start, end} = quasi
|
|
228
|
+
this.mstr.remove(start - 1, end)
|
|
227
229
|
if (i + 1 === node.expressions.length) {
|
|
228
230
|
continue
|
|
229
231
|
}
|
|
230
|
-
this.mstr.update(
|
|
232
|
+
this.mstr.update(end, end + 2, ', ')
|
|
231
233
|
}
|
|
232
234
|
let repl = `${rtFunc}(${this.index.get(txt.toString())}`
|
|
233
235
|
if (node.expressions.length) {
|
|
234
236
|
repl += ', '
|
|
235
237
|
}
|
|
236
|
-
this.mstr.update(
|
|
238
|
+
this.mstr.update(start0 - 1, end0 + 2, repl)
|
|
239
|
+
// @ts-ignore
|
|
237
240
|
this.mstr.update(node.end - 1, node.end, ')')
|
|
238
241
|
txts.push(new NestText(nTxt, 'script'))
|
|
239
242
|
return txts
|
|
@@ -241,19 +244,19 @@ export default class Preprocess {
|
|
|
241
244
|
|
|
242
245
|
|
|
243
246
|
/**
|
|
244
|
-
* @param {
|
|
247
|
+
* @param {import("svelte/compiler").AST.ExpressionTag} node
|
|
245
248
|
* @returns {NestText[]}
|
|
246
249
|
*/
|
|
247
|
-
|
|
250
|
+
visitExpressionTag = node => this.visit(node.expression)
|
|
248
251
|
|
|
249
252
|
/**
|
|
250
|
-
* @param {
|
|
253
|
+
* @param {import("svelte/compiler").AST.ElementLike} node
|
|
251
254
|
* @returns {boolean}
|
|
252
255
|
*/
|
|
253
256
|
checkHasCompoundText = node => {
|
|
254
257
|
let text = false
|
|
255
258
|
let nonText = false
|
|
256
|
-
for (const child of node.
|
|
259
|
+
for (const child of node.fragment.nodes ?? []) {
|
|
257
260
|
if (child.type === 'Text') {
|
|
258
261
|
if (child.data.trim()) {
|
|
259
262
|
text = true
|
|
@@ -266,23 +269,29 @@ export default class Preprocess {
|
|
|
266
269
|
}
|
|
267
270
|
|
|
268
271
|
/**
|
|
269
|
-
* @
|
|
272
|
+
* @typedef {import("svelte/compiler").AST.ElementLike & {inCompoundText: boolean}} ElementNode
|
|
273
|
+
* @param {ElementNode & {
|
|
274
|
+
* fragment: import("svelte/compiler").AST.Fragment & {
|
|
275
|
+
* nodes: ElementNode[]
|
|
276
|
+
* },
|
|
277
|
+
* }} node
|
|
270
278
|
* @returns {NestText[]}
|
|
271
279
|
*/
|
|
272
|
-
|
|
280
|
+
visitRegularElement = node => {
|
|
273
281
|
const txts = []
|
|
274
282
|
for (const attrib of node.attributes) {
|
|
275
|
-
txts.push(...this.
|
|
283
|
+
txts.push(...this.visit(attrib))
|
|
276
284
|
}
|
|
277
|
-
if (node.
|
|
285
|
+
if (node.fragment.nodes.length === 0) {
|
|
278
286
|
return txts
|
|
279
287
|
}
|
|
280
288
|
let hasTextChild = false
|
|
281
289
|
let hasNonTextChild = false
|
|
282
290
|
const textNodesToModify = {}
|
|
283
|
-
for (const [i, child] of node.
|
|
291
|
+
for (const [i, child] of node.fragment.nodes.entries()) {
|
|
284
292
|
if (child.type === 'Text') {
|
|
285
|
-
const
|
|
293
|
+
const { start, end } = child
|
|
294
|
+
const [pass, modify] = this.modifyCheck(start, end, child.data, 'markup')
|
|
286
295
|
if (pass) {
|
|
287
296
|
hasTextChild = true
|
|
288
297
|
textNodesToModify[i] = modify
|
|
@@ -298,8 +307,8 @@ export default class Preprocess {
|
|
|
298
307
|
let txt = ''
|
|
299
308
|
let iArg = 0
|
|
300
309
|
let iTag = 0
|
|
301
|
-
const lastChildEnd = node.
|
|
302
|
-
for (const [i, child] of node.
|
|
310
|
+
const lastChildEnd = node.fragment.nodes.slice(-1)[0].end
|
|
311
|
+
for (const [i, child] of node.fragment.nodes.entries()) {
|
|
303
312
|
if (child.type === 'Comment') {
|
|
304
313
|
continue
|
|
305
314
|
}
|
|
@@ -309,7 +318,7 @@ export default class Preprocess {
|
|
|
309
318
|
continue
|
|
310
319
|
}
|
|
311
320
|
txt += ' ' + modify
|
|
312
|
-
if (node.inCompoundText && node.
|
|
321
|
+
if (node.inCompoundText && node.fragment.nodes.length === 1) {
|
|
313
322
|
this.mstr.update(child.start, child.end, `{ctx[1]}`)
|
|
314
323
|
} else {
|
|
315
324
|
this.mstr.remove(child.start, child.end)
|
|
@@ -320,8 +329,8 @@ export default class Preprocess {
|
|
|
320
329
|
txts.push(...this.visit(child))
|
|
321
330
|
continue
|
|
322
331
|
}
|
|
323
|
-
if (child.type === '
|
|
324
|
-
txts.push(...this.
|
|
332
|
+
if (child.type === 'ExpressionTag') {
|
|
333
|
+
txts.push(...this.visitExpressionTag(child))
|
|
325
334
|
txt += ` {${iArg}}`
|
|
326
335
|
this.mstr.move(child.start + 1, child.end - 1, lastChildEnd)
|
|
327
336
|
if (iArg > 0) {
|
|
@@ -334,16 +343,17 @@ export default class Preprocess {
|
|
|
334
343
|
continue
|
|
335
344
|
}
|
|
336
345
|
// elements and components
|
|
346
|
+
// @ts-ignore
|
|
337
347
|
child.inCompoundText = true
|
|
338
348
|
let chTxt = ''
|
|
339
349
|
for (const txt of this.visit(child)) {
|
|
340
|
-
if (child.type
|
|
350
|
+
if (['RegularElement', 'Component'].includes(child.type) && txt.scope === 'markup') {
|
|
341
351
|
chTxt += txt.toString()
|
|
342
352
|
} else { // attributes, blocks
|
|
343
353
|
txts.push(txt)
|
|
344
354
|
}
|
|
345
355
|
}
|
|
346
|
-
if (child.type
|
|
356
|
+
if (['RegularElement', 'Component'].includes(child.type)) {
|
|
347
357
|
chTxt = `<${iTag}>${chTxt}</${iTag}>`
|
|
348
358
|
} else {
|
|
349
359
|
// InlineComponent
|
|
@@ -392,14 +402,15 @@ export default class Preprocess {
|
|
|
392
402
|
return txts
|
|
393
403
|
}
|
|
394
404
|
|
|
395
|
-
|
|
405
|
+
visitComponent = this.visitRegularElement
|
|
396
406
|
|
|
397
407
|
/**
|
|
398
|
-
* @param {
|
|
408
|
+
* @param {import("svelte/compiler").AST.Text} node
|
|
399
409
|
* @returns {NestText[]}
|
|
400
410
|
*/
|
|
401
411
|
visitText = node => {
|
|
402
|
-
const
|
|
412
|
+
const { start, end } = node
|
|
413
|
+
const [pass, txt] = this.modifyCheck(start, end, node.data, 'markup')
|
|
403
414
|
if (!pass) {
|
|
404
415
|
return []
|
|
405
416
|
}
|
|
@@ -408,29 +419,41 @@ export default class Preprocess {
|
|
|
408
419
|
}
|
|
409
420
|
|
|
410
421
|
/**
|
|
411
|
-
* @param {
|
|
422
|
+
* @param {import("svelte/compiler").AST.SpreadAttribute} node
|
|
423
|
+
* @returns {NestText[]}
|
|
424
|
+
*/
|
|
425
|
+
visitSpreadAttribute = node => {
|
|
426
|
+
return this.visit(node.expression)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* @param {import("svelte/compiler").AST.Attribute} node
|
|
412
431
|
* @returns {NestText[]}
|
|
413
432
|
*/
|
|
414
433
|
visitAttribute = node => {
|
|
415
|
-
if (node.
|
|
416
|
-
return this.visit(node.expression)
|
|
417
|
-
}
|
|
418
|
-
if (node.type !== 'Attribute' || typeof node.value === 'boolean') {
|
|
434
|
+
if (node.value === true) {
|
|
419
435
|
return []
|
|
420
436
|
}
|
|
421
437
|
const txts = []
|
|
422
|
-
|
|
423
|
-
|
|
438
|
+
let values
|
|
439
|
+
if (Array.isArray(node.value)) {
|
|
440
|
+
values = node.value
|
|
441
|
+
} else {
|
|
442
|
+
values = [node.value]
|
|
443
|
+
}
|
|
444
|
+
for (const value of values) {
|
|
445
|
+
if (value.type !== 'Text') { // ExpressionTag
|
|
424
446
|
txts.push(...this.visit(value))
|
|
425
447
|
continue
|
|
426
448
|
}
|
|
427
|
-
|
|
449
|
+
// Text
|
|
450
|
+
const {start, end} = value
|
|
451
|
+
const [pass, txt] = this.modifyCheck(start, end, value.data, 'attribute')
|
|
428
452
|
if (!pass) {
|
|
429
453
|
continue
|
|
430
454
|
}
|
|
431
455
|
txts.push(txt)
|
|
432
456
|
this.mstr.update(value.start, value.end, `{${rtFunc}(${this.index.get(txt.toString())})}`)
|
|
433
|
-
let {start, end} = value
|
|
434
457
|
if (!`'"`.includes(this.content[start - 1])) {
|
|
435
458
|
continue
|
|
436
459
|
}
|
|
@@ -441,60 +464,85 @@ export default class Preprocess {
|
|
|
441
464
|
}
|
|
442
465
|
|
|
443
466
|
/**
|
|
444
|
-
* @param {
|
|
467
|
+
* @param {import("svelte/compiler").AST.Fragment} node
|
|
445
468
|
* @returns {NestText[]}
|
|
446
469
|
*/
|
|
447
470
|
visitFragment = node => {
|
|
448
471
|
const txts = []
|
|
449
|
-
for (const child of node.
|
|
472
|
+
for (const child of node.nodes) {
|
|
450
473
|
txts.push(...this.visit(child))
|
|
451
474
|
}
|
|
452
475
|
return txts
|
|
453
476
|
}
|
|
454
477
|
|
|
455
|
-
|
|
478
|
+
/**
|
|
479
|
+
* @param {import("svelte/compiler").AST.SnippetBlock} node
|
|
480
|
+
* @returns {NestText[]}
|
|
481
|
+
*/
|
|
482
|
+
visitSnippetBlock = node => this.visitFragment(node.body)
|
|
456
483
|
|
|
457
484
|
/**
|
|
458
|
-
* @param {
|
|
485
|
+
* @param {import("svelte/compiler").AST.IfBlock} node
|
|
459
486
|
* @returns {NestText[]}
|
|
460
487
|
*/
|
|
461
488
|
visitIfBlock = node => {
|
|
462
|
-
const txts = this.visit(node.
|
|
463
|
-
|
|
464
|
-
|
|
489
|
+
const txts = this.visit(node.test)
|
|
490
|
+
txts.push(...this.visit(node.consequent))
|
|
491
|
+
if (node.alternate) {
|
|
492
|
+
txts.push(...this.visit(node.alternate))
|
|
465
493
|
}
|
|
466
494
|
return txts
|
|
467
495
|
}
|
|
468
496
|
|
|
469
|
-
|
|
497
|
+
/**
|
|
498
|
+
* @param {import("svelte/compiler").AST.EachBlock} node
|
|
499
|
+
* @returns {NestText[]}
|
|
500
|
+
*/
|
|
501
|
+
visitEachBlock = node => {
|
|
502
|
+
const txts = [
|
|
503
|
+
...this.visit(node.expression),
|
|
504
|
+
...this.visit(node.body),
|
|
505
|
+
]
|
|
506
|
+
if (node.fallback) {
|
|
507
|
+
txts.push(...this.visit(node.fallback),)
|
|
508
|
+
}
|
|
509
|
+
if (node.key) {
|
|
510
|
+
txts.push(...this.visit(node.key),)
|
|
511
|
+
}
|
|
512
|
+
return txts
|
|
513
|
+
}
|
|
470
514
|
|
|
471
|
-
|
|
515
|
+
/**
|
|
516
|
+
* @param {import("svelte/compiler").AST.KeyBlock} node
|
|
517
|
+
* @returns {NestText[]}
|
|
518
|
+
*/
|
|
519
|
+
visitKeyBlock = node => {
|
|
520
|
+
return [
|
|
521
|
+
...this.visit(node.expression),
|
|
522
|
+
...this.visit(node.fragment),
|
|
523
|
+
]
|
|
524
|
+
}
|
|
472
525
|
|
|
473
526
|
/**
|
|
474
|
-
* @
|
|
475
|
-
* @param {Node & {
|
|
476
|
-
* expression: Node
|
|
477
|
-
* value: ObjExpr
|
|
478
|
-
* pending: Block
|
|
479
|
-
* then: Block
|
|
480
|
-
* catch: Block
|
|
481
|
-
* }} node
|
|
527
|
+
* @param {import("svelte/compiler").AST.AwaitBlock} node
|
|
482
528
|
* @returns {NestText[]}
|
|
483
529
|
*/
|
|
484
530
|
visitAwaitBlock = node => {
|
|
485
|
-
const txts =
|
|
486
|
-
|
|
487
|
-
...this.visit(node.value),
|
|
488
|
-
...this.visitFragment(node.pending),
|
|
531
|
+
const txts = [
|
|
532
|
+
...this.visit(node.expression),
|
|
489
533
|
...this.visitFragment(node.then),
|
|
490
|
-
|
|
491
|
-
)
|
|
534
|
+
]
|
|
535
|
+
if (node.pending) {
|
|
536
|
+
txts.push(...this.visitFragment(node.pending),)
|
|
537
|
+
}
|
|
538
|
+
if (node.catch) {
|
|
539
|
+
txts.push(...this.visitFragment(node.catch),)
|
|
540
|
+
}
|
|
492
541
|
return txts
|
|
493
542
|
}
|
|
494
543
|
|
|
495
544
|
/**
|
|
496
|
-
* @
|
|
497
|
-
* @param {Program} node
|
|
545
|
+
* @param {import('estree').Program} node
|
|
498
546
|
* @returns {NestText[]}
|
|
499
547
|
*/
|
|
500
548
|
visitProgram = (node, needImport = true) => {
|
|
@@ -504,20 +552,18 @@ export default class Preprocess {
|
|
|
504
552
|
}
|
|
505
553
|
if (needImport) {
|
|
506
554
|
const importStmt = `import {${rtFunc}} from "${this.importFrom}"\n`
|
|
555
|
+
// @ts-ignore
|
|
507
556
|
this.mstr.appendRight(node.start, importStmt)
|
|
508
557
|
}
|
|
509
558
|
return txts
|
|
510
559
|
}
|
|
511
560
|
|
|
512
561
|
/**
|
|
513
|
-
* @param {
|
|
514
|
-
* html: Node & {children: Node[]}
|
|
515
|
-
* instance: Node & {content: Program}}
|
|
516
|
-
* } node
|
|
562
|
+
* @param {import("svelte/compiler").AST.Root} node
|
|
517
563
|
* @returns {NestText[]}
|
|
518
564
|
*/
|
|
519
|
-
|
|
520
|
-
const txts = this.visitFragment(node.
|
|
565
|
+
visitRoot = node => {
|
|
566
|
+
const txts = this.visitFragment(node.fragment)
|
|
521
567
|
if (node.instance) {
|
|
522
568
|
txts.push(...this.visitProgram(node.instance.content, false))
|
|
523
569
|
}
|
|
@@ -528,8 +574,8 @@ export default class Preprocess {
|
|
|
528
574
|
}
|
|
529
575
|
const importStmt = `import ${rtComponent}, {${rtFunc}} from "${this.importFrom}"\n`
|
|
530
576
|
if (node.instance) {
|
|
577
|
+
// @ts-ignore
|
|
531
578
|
this.mstr.appendRight(node.instance.content.start, importStmt)
|
|
532
|
-
// @ts-ignore
|
|
533
579
|
} else if (node.module) {
|
|
534
580
|
// @ts-ignore
|
|
535
581
|
this.mstr.appendRight(node.module.content.start, importStmt)
|
|
@@ -540,7 +586,7 @@ export default class Preprocess {
|
|
|
540
586
|
}
|
|
541
587
|
|
|
542
588
|
/**
|
|
543
|
-
* @param {
|
|
589
|
+
* @param {import("svelte/compiler").AST.SvelteNode} node
|
|
544
590
|
* @returns {NestText[]}
|
|
545
591
|
*/
|
|
546
592
|
visit = node => {
|
|
@@ -554,7 +600,7 @@ export default class Preprocess {
|
|
|
554
600
|
|
|
555
601
|
/**
|
|
556
602
|
* @param {string} content
|
|
557
|
-
* @param {
|
|
603
|
+
* @param {import('estree').Program | import("svelte/compiler").AST.Root} ast
|
|
558
604
|
* @returns {NestText[]}
|
|
559
605
|
*/
|
|
560
606
|
process = (content, ast) => {
|