@eventra_dev/eventra-cli 0.1.25 → 0.1.27
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 +62 -50
- package/dist/commands/watch.js +41 -23
- package/dist/engine/callResolver.js +52 -4
- package/dist/engine/engine.js +63 -31
- package/dist/engine/exportResolver.js +16 -4
- package/dist/engine/functionAnalyzer.js +66 -13
- package/dist/engine/graph.js +40 -4
- package/dist/engine/languageService.js +83 -20
- package/dist/engine/resolver.js +29 -21
- package/dist/engine/scanner.js +57 -46
- package/dist/engine/trackingDetector.js +68 -0
- package/dist/utils/processFile.js +0 -9
- package/dist/utils/universal.js +25 -87
- package/package.json +1 -1
- package/src/commands/watch.ts +45 -25
- package/src/engine/callResolver.ts +77 -6
- package/src/engine/engine.ts +77 -32
- package/src/engine/exportResolver.ts +22 -5
- package/src/engine/functionAnalyzer.ts +76 -16
- package/src/engine/graph.ts +45 -6
- package/src/engine/languageService.ts +100 -29
- package/src/engine/resolver.ts +36 -27
- package/src/engine/scanner.ts +72 -50
- package/src/engine/trackingDetector.ts +94 -0
- package/src/types.ts +0 -3
- package/src/utils/processFile.ts +0 -9
- package/src/utils/universal.ts +26 -102
package/dist/engine/scanner.js
CHANGED
|
@@ -10,7 +10,7 @@ const callResolver_1 = require("./callResolver");
|
|
|
10
10
|
const exportResolver_1 = require("./exportResolver");
|
|
11
11
|
const functionAnalyzer_1 = require("./functionAnalyzer");
|
|
12
12
|
const boundary_1 = require("./boundary");
|
|
13
|
-
const
|
|
13
|
+
const trackingDetector_1 = require("./trackingDetector");
|
|
14
14
|
function getCallName(expr) {
|
|
15
15
|
if (typescript_1.default.isIdentifier(expr))
|
|
16
16
|
return expr.text;
|
|
@@ -25,6 +25,16 @@ function getJsxAttrName(name) {
|
|
|
25
25
|
return name.name.text;
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
|
+
// unwrap
|
|
29
|
+
function unwrap(expr) {
|
|
30
|
+
while (typescript_1.default.isAsExpression(expr) ||
|
|
31
|
+
typescript_1.default.isParenthesizedExpression(expr) ||
|
|
32
|
+
typescript_1.default.isTypeAssertionExpression(expr) ||
|
|
33
|
+
typescript_1.default.isNonNullExpression(expr)) {
|
|
34
|
+
expr = expr.expression;
|
|
35
|
+
}
|
|
36
|
+
return expr;
|
|
37
|
+
}
|
|
28
38
|
function isValidEvent(v) {
|
|
29
39
|
return (v &&
|
|
30
40
|
v.length > 1 &&
|
|
@@ -32,70 +42,75 @@ function isValidEvent(v) {
|
|
|
32
42
|
!v.includes(" ") &&
|
|
33
43
|
/^[a-zA-Z0-9._:-]+$/.test(v));
|
|
34
44
|
}
|
|
35
|
-
function isPropUsedInTracking(fn, prop) {
|
|
45
|
+
function isPropUsedInTracking(fn, checker, prop) {
|
|
36
46
|
let found = false;
|
|
37
47
|
function visit(node) {
|
|
38
48
|
if (typescript_1.default.isCallExpression(node)) {
|
|
39
|
-
|
|
40
|
-
if (!TRACKING_NAMES.has(name)) {
|
|
49
|
+
if (!(0, trackingDetector_1.isTrackingCall)(node, checker)) {
|
|
41
50
|
typescript_1.default.forEachChild(node, visit);
|
|
42
51
|
return;
|
|
43
52
|
}
|
|
44
53
|
for (const arg of node.arguments) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
const a = unwrap(arg);
|
|
55
|
+
if ((typescript_1.default.isIdentifier(a) && a.text === prop) ||
|
|
56
|
+
(typescript_1.default.isPropertyAccessExpression(a) &&
|
|
57
|
+
a.name.text === prop)) {
|
|
48
58
|
found = true;
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
62
|
typescript_1.default.forEachChild(node, visit);
|
|
53
63
|
}
|
|
54
|
-
|
|
64
|
+
if (fn.body) {
|
|
65
|
+
visit(fn.body);
|
|
66
|
+
}
|
|
55
67
|
return found;
|
|
56
68
|
}
|
|
57
69
|
function scanSource(source, checker, config, visited = new Set(), depth = 0, inheritedParamMap) {
|
|
58
70
|
const events = new Set();
|
|
59
71
|
const detectedFunctionWrappers = new Set();
|
|
60
72
|
const detectedComponentWrappers = new Map();
|
|
61
|
-
if (
|
|
73
|
+
if (visited.has(source.fileName) ||
|
|
74
|
+
depth > 10 ||
|
|
75
|
+
visited.size > 200) {
|
|
62
76
|
return { events, detectedFunctionWrappers, detectedComponentWrappers };
|
|
63
77
|
}
|
|
64
78
|
visited.add(source.fileName);
|
|
65
79
|
function visit(node) {
|
|
66
80
|
// CALL EXPRESSIONS
|
|
67
81
|
if (typescript_1.default.isCallExpression(node)) {
|
|
68
|
-
const
|
|
82
|
+
const expr = unwrap(node.expression);
|
|
83
|
+
const name = getCallName(expr);
|
|
69
84
|
const isKnownWrapper = config.functionWrappers.some(w => w.name === name);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (!resolvedFn && typescript_1.default.isPropertyAccessExpression(node.expression)) {
|
|
73
|
-
const symbol = checker.getSymbolAtLocation(node.expression.name);
|
|
74
|
-
if (symbol) {
|
|
75
|
-
const decls = symbol.getDeclarations();
|
|
76
|
-
if (decls?.length) {
|
|
77
|
-
const d = decls[0];
|
|
78
|
-
if (typescript_1.default.isMethodDeclaration(d) ||
|
|
79
|
-
typescript_1.default.isFunctionDeclaration(d) ||
|
|
80
|
-
typescript_1.default.isFunctionExpression(d) ||
|
|
81
|
-
typescript_1.default.isArrowFunction(d)) {
|
|
82
|
-
resolvedFn = d;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
85
|
+
const isTrackingByType = (0, trackingDetector_1.isTrackingCall)(node, checker);
|
|
86
|
+
let resolvedFn = (0, callResolver_1.resolveFunctionFromCall)(expr, checker);
|
|
87
87
|
const isAutoWrapper = resolvedFn && (0, functionAnalyzer_1.findTrackParamIndex)(resolvedFn) !== null;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
name.startsWith("$") ||
|
|
88
|
+
let isTracking = isKnownWrapper ||
|
|
89
|
+
isTrackingByType ||
|
|
90
|
+
name.startsWith("$") ||
|
|
91
91
|
(isAutoWrapper &&
|
|
92
92
|
resolvedFn &&
|
|
93
93
|
!(0, boundary_1.isExternalFile)(resolvedFn.getSourceFile().fileName));
|
|
94
|
+
// fallback
|
|
95
|
+
if (!isTracking) {
|
|
96
|
+
if (typescript_1.default.isPropertyAccessExpression(expr) &&
|
|
97
|
+
expr.name.text === "track") {
|
|
98
|
+
isTracking = true;
|
|
99
|
+
}
|
|
100
|
+
else if (typescript_1.default.isIdentifier(expr) &&
|
|
101
|
+
expr.text === "track") {
|
|
102
|
+
isTracking = true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
94
105
|
if (!isTracking) {
|
|
95
106
|
typescript_1.default.forEachChild(node, visit);
|
|
96
107
|
return;
|
|
97
108
|
}
|
|
98
|
-
|
|
109
|
+
// detect wrapper
|
|
110
|
+
if (!isKnownWrapper &&
|
|
111
|
+
isAutoWrapper &&
|
|
112
|
+
!isTrackingByType &&
|
|
113
|
+
!name.startsWith("track")) {
|
|
99
114
|
detectedFunctionWrappers.add(name);
|
|
100
115
|
}
|
|
101
116
|
const paramMap = new Map(inheritedParamMap ?? []);
|
|
@@ -114,26 +129,22 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
|
|
|
114
129
|
if (resolvedFn) {
|
|
115
130
|
const idx = (0, functionAnalyzer_1.findTrackParamIndex)(resolvedFn);
|
|
116
131
|
if (idx !== null && node.arguments[idx]) {
|
|
117
|
-
const res = (0, resolver_1.resolveNodeValue)(node.arguments[idx], checker, paramMap);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
});
|
|
123
|
-
}
|
|
132
|
+
const res = (0, resolver_1.resolveNodeValue)(unwrap(node.arguments[idx]), checker, paramMap);
|
|
133
|
+
res.values.forEach(v => {
|
|
134
|
+
if (isValidEvent(v))
|
|
135
|
+
events.add(v);
|
|
136
|
+
});
|
|
124
137
|
handled = true;
|
|
125
138
|
}
|
|
126
139
|
}
|
|
127
140
|
// FALLBACK
|
|
128
141
|
if (!handled) {
|
|
129
142
|
for (const arg of node.arguments) {
|
|
130
|
-
const res = (0, resolver_1.resolveNodeValue)(arg, checker, paramMap);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
136
|
-
}
|
|
143
|
+
const res = (0, resolver_1.resolveNodeValue)(unwrap(arg), checker, paramMap);
|
|
144
|
+
res.values.forEach(v => {
|
|
145
|
+
if (isValidEvent(v))
|
|
146
|
+
events.add(v);
|
|
147
|
+
});
|
|
137
148
|
}
|
|
138
149
|
}
|
|
139
150
|
// CROSS FILE
|
|
@@ -183,7 +194,7 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
|
|
|
183
194
|
continue;
|
|
184
195
|
for (const el of param.name.elements) {
|
|
185
196
|
const prop = el.name.getText();
|
|
186
|
-
const isEvent = isPropUsedInTracking(fn, prop) ||
|
|
197
|
+
const isEvent = isPropUsedInTracking(fn, checker, prop) ||
|
|
187
198
|
config.wrappers.some(w => w.name === tagName && w.prop === prop);
|
|
188
199
|
if (!isEvent)
|
|
189
200
|
continue;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isTrackingCall = isTrackingCall;
|
|
7
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
function isStringLike(type) {
|
|
9
|
+
return ((type.flags & typescript_1.default.TypeFlags.StringLike) !== 0 ||
|
|
10
|
+
(type.flags & typescript_1.default.TypeFlags.StringLiteral) !== 0);
|
|
11
|
+
}
|
|
12
|
+
function hasTrackLikeSignature(type, checker) {
|
|
13
|
+
const sigs = type.getCallSignatures();
|
|
14
|
+
for (const sig of sigs) {
|
|
15
|
+
const params = sig.getParameters();
|
|
16
|
+
if (!params.length)
|
|
17
|
+
continue;
|
|
18
|
+
const t = checker.getTypeOfSymbolAtLocation(params[0], params[0].valueDeclaration);
|
|
19
|
+
if (isStringLike(t)) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function isEventraLikeType(type, checker) {
|
|
26
|
+
const props = type.getProperties();
|
|
27
|
+
for (const prop of props) {
|
|
28
|
+
const decl = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
29
|
+
if (!decl)
|
|
30
|
+
continue;
|
|
31
|
+
const t = checker.getTypeOfSymbolAtLocation(prop, decl);
|
|
32
|
+
if (hasTrackLikeSignature(t, checker)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function isTrackingCall(node, checker) {
|
|
39
|
+
const expr = node.expression;
|
|
40
|
+
// fn(...)
|
|
41
|
+
if (typescript_1.default.isIdentifier(expr)) {
|
|
42
|
+
const type = checker.getTypeAtLocation(expr);
|
|
43
|
+
if (hasTrackLikeSignature(type, checker)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// obj.method(...)
|
|
48
|
+
if (typescript_1.default.isPropertyAccessExpression(expr)) {
|
|
49
|
+
const objType = checker.getTypeAtLocation(expr.expression);
|
|
50
|
+
// объект похож на tracker
|
|
51
|
+
if (isEventraLikeType(objType, checker)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
// fallback
|
|
55
|
+
const methodType = checker.getTypeAtLocation(expr.name);
|
|
56
|
+
if (hasTrackLikeSignature(methodType, checker)) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// obj["track"](...)
|
|
61
|
+
if (typescript_1.default.isElementAccessExpression(expr)) {
|
|
62
|
+
const objType = checker.getTypeAtLocation(expr.expression);
|
|
63
|
+
if (isEventraLikeType(objType, checker)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.processFile = processFile;
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
4
|
const universal_1 = require("./universal");
|
|
9
5
|
function processFile(file, content) {
|
|
10
6
|
const { code, deps } = (0, universal_1.parseUniversal)(content, file);
|
|
11
|
-
const ext = path_1.default.extname(file);
|
|
12
|
-
const virtualFile = /\.(vue|svelte|astro|html)$/i.test(file)
|
|
13
|
-
? file.replace(ext, "") + ".tsx"
|
|
14
|
-
: file;
|
|
15
7
|
return {
|
|
16
8
|
content: code || "",
|
|
17
|
-
virtualFile,
|
|
18
9
|
deps: [...new Set(deps.filter(Boolean))],
|
|
19
10
|
};
|
|
20
11
|
}
|
package/dist/utils/universal.js
CHANGED
|
@@ -15,7 +15,7 @@ function stripComments(input) {
|
|
|
15
15
|
.replace(/\/\/.*$/gm, "");
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Extract JS expressions
|
|
18
|
+
* Extract JS expressions
|
|
19
19
|
*/
|
|
20
20
|
function extractJS(input) {
|
|
21
21
|
const result = [];
|
|
@@ -25,7 +25,6 @@ function extractJS(input) {
|
|
|
25
25
|
let escape = false;
|
|
26
26
|
while (i < len) {
|
|
27
27
|
const ch = input[i];
|
|
28
|
-
// STRING
|
|
29
28
|
if (inString) {
|
|
30
29
|
if (escape)
|
|
31
30
|
escape = false;
|
|
@@ -41,95 +40,31 @@ function extractJS(input) {
|
|
|
41
40
|
i++;
|
|
42
41
|
continue;
|
|
43
42
|
}
|
|
44
|
-
// { ... }
|
|
45
43
|
if (ch === "{") {
|
|
46
44
|
let depth = 1;
|
|
47
45
|
let j = i + 1;
|
|
48
|
-
let innerStr = null;
|
|
49
|
-
let innerEsc = false;
|
|
50
46
|
while (j < len && depth > 0) {
|
|
51
|
-
|
|
52
|
-
if (innerStr) {
|
|
53
|
-
if (innerEsc)
|
|
54
|
-
innerEsc = false;
|
|
55
|
-
else if (c === "\\")
|
|
56
|
-
innerEsc = true;
|
|
57
|
-
else if (c === innerStr)
|
|
58
|
-
innerStr = null;
|
|
59
|
-
j++;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if (c === '"' || c === "'" || c === "`") {
|
|
63
|
-
innerStr = c;
|
|
64
|
-
j++;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (c === "{")
|
|
47
|
+
if (input[j] === "{")
|
|
68
48
|
depth++;
|
|
69
|
-
else if (
|
|
49
|
+
else if (input[j] === "}")
|
|
70
50
|
depth--;
|
|
71
51
|
j++;
|
|
72
52
|
}
|
|
73
53
|
if (depth === 0) {
|
|
74
54
|
const expr = input.slice(i + 1, j - 1).trim();
|
|
75
|
-
if (expr &&
|
|
76
|
-
(expr.includes("(") ||
|
|
77
|
-
expr.includes("=") ||
|
|
78
|
-
expr.includes(".") ||
|
|
79
|
-
expr.includes("track"))) {
|
|
55
|
+
if (expr && /[a-zA-Z0-9_$]\s*\(/.test(expr)) {
|
|
80
56
|
result.push(expr);
|
|
81
57
|
}
|
|
82
58
|
i = j;
|
|
83
59
|
continue;
|
|
84
60
|
}
|
|
85
61
|
}
|
|
86
|
-
// attr="..."
|
|
87
|
-
if (ch === "=" && (input[i + 1] === '"' || input[i + 1] === "'")) {
|
|
88
|
-
const quote = input[i + 1];
|
|
89
|
-
let j = i + 2;
|
|
90
|
-
let val = "";
|
|
91
|
-
while (j < len) {
|
|
92
|
-
const c = input[j];
|
|
93
|
-
if (c === "\\" && input[j + 1]) {
|
|
94
|
-
val += c + input[j + 1];
|
|
95
|
-
j += 2;
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if (c === quote)
|
|
99
|
-
break;
|
|
100
|
-
val += c;
|
|
101
|
-
j++;
|
|
102
|
-
}
|
|
103
|
-
if (val.trim() &&
|
|
104
|
-
(val.includes("(") ||
|
|
105
|
-
val.includes("=>") ||
|
|
106
|
-
val.includes("track"))) {
|
|
107
|
-
result.push(val.trim());
|
|
108
|
-
}
|
|
109
|
-
i = j + 1;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
62
|
i++;
|
|
113
63
|
}
|
|
114
|
-
// Vue / Svelte / JSX handlers
|
|
115
|
-
const extra = [
|
|
116
|
-
/@[\w-]+\s*=\s*["']([\s\S]*?)["']/g,
|
|
117
|
-
/v-on:[\w-]+\s*=\s*["']([\s\S]*?)["']/g,
|
|
118
|
-
/on:[\w-]+\s*=\s*{([\s\S]*?)}/g,
|
|
119
|
-
/on\w+\s*=\s*{([\s\S]*?)}/g,
|
|
120
|
-
];
|
|
121
|
-
for (const r of extra) {
|
|
122
|
-
const m = input.matchAll(r);
|
|
123
|
-
for (const x of m) {
|
|
124
|
-
const v = x[1]?.trim();
|
|
125
|
-
if (v)
|
|
126
|
-
result.push(v);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
64
|
return result;
|
|
130
65
|
}
|
|
131
66
|
/**
|
|
132
|
-
*
|
|
67
|
+
* Loose JS extractor
|
|
133
68
|
*/
|
|
134
69
|
function extractLooseJS(input) {
|
|
135
70
|
const result = [];
|
|
@@ -140,28 +75,31 @@ function extractLooseJS(input) {
|
|
|
140
75
|
continue;
|
|
141
76
|
if (l.startsWith("<"))
|
|
142
77
|
continue;
|
|
143
|
-
|
|
144
|
-
if (l.includes("track") ||
|
|
145
|
-
l.includes(".track(") ||
|
|
146
|
-
l.includes("=>") ||
|
|
147
|
-
l.startsWith("function") ||
|
|
148
|
-
l.includes("(")) {
|
|
78
|
+
if (/[a-zA-Z0-9_$]\s*\(/.test(l)) {
|
|
149
79
|
result.push(l);
|
|
150
80
|
}
|
|
151
81
|
}
|
|
152
82
|
return result;
|
|
153
83
|
}
|
|
154
84
|
/**
|
|
155
|
-
*
|
|
85
|
+
* UNIVERSAL PARSER
|
|
86
|
+
* Support:
|
|
87
|
+
* - React / JSX
|
|
88
|
+
* - Vue
|
|
89
|
+
* - Svelte
|
|
90
|
+
* - Astro
|
|
91
|
+
* - HTML + JS
|
|
92
|
+
* - SSR
|
|
156
93
|
*/
|
|
157
94
|
function parseUniversal(content, file) {
|
|
158
95
|
const parts = [];
|
|
159
96
|
const deps = [];
|
|
160
97
|
const clean = stripComments(content);
|
|
161
|
-
//
|
|
98
|
+
// ASTRO FRONTMATTER
|
|
162
99
|
const fm = clean.match(/^---([\s\S]*?)---/);
|
|
163
|
-
if (fm?.[1])
|
|
100
|
+
if (fm?.[1]) {
|
|
164
101
|
parts.push(fm[1]);
|
|
102
|
+
}
|
|
165
103
|
// <script src="">
|
|
166
104
|
const scriptSrc = clean.matchAll(/<script\b[^>]*?\bsrc=["'](.+?)["'][^>]*>/g);
|
|
167
105
|
for (const s of scriptSrc) {
|
|
@@ -171,29 +109,29 @@ function parseUniversal(content, file) {
|
|
|
171
109
|
: path_1.default.resolve(path_1.default.dirname(file), src);
|
|
172
110
|
deps.push(resolved);
|
|
173
111
|
}
|
|
174
|
-
//
|
|
112
|
+
// INLINE <script>
|
|
175
113
|
const scripts = clean.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/g);
|
|
176
114
|
for (const m of scripts) {
|
|
177
115
|
const attrs = m[1];
|
|
178
|
-
// skip JSON
|
|
116
|
+
// skip JSON
|
|
179
117
|
if (/type=["']application\/json/.test(attrs))
|
|
180
118
|
continue;
|
|
181
119
|
if (m[2].trim()) {
|
|
182
120
|
parts.push(m[2]);
|
|
183
121
|
}
|
|
184
122
|
}
|
|
185
|
-
//
|
|
123
|
+
// REMOVE SCRIPTS → TEMPLATE PART
|
|
186
124
|
const noScripts = clean.replace(/<script[\s\S]*?<\/script>/g, "");
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// expressions
|
|
125
|
+
// LOOSE JS
|
|
126
|
+
parts.push(...extractLooseJS(noScripts));
|
|
127
|
+
// EXPRESSIONS
|
|
191
128
|
const extracted = extractJS(clean);
|
|
192
129
|
for (const e of extracted) {
|
|
193
130
|
parts.push(e + ";");
|
|
194
131
|
}
|
|
132
|
+
// RESULT
|
|
195
133
|
return {
|
|
196
134
|
code: [...new Set(parts)].join("\n"),
|
|
197
|
-
deps,
|
|
135
|
+
deps: [...new Set(deps.filter(Boolean))],
|
|
198
136
|
};
|
|
199
137
|
}
|
package/package.json
CHANGED
package/src/commands/watch.ts
CHANGED
|
@@ -14,6 +14,10 @@ type QueueItem = {
|
|
|
14
14
|
virtual: string;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
function normalize(file: string) {
|
|
18
|
+
return path.resolve(file).replace(/\\/g, "/");
|
|
19
|
+
}
|
|
20
|
+
|
|
17
21
|
export async function watch() {
|
|
18
22
|
const config = await loadConfig();
|
|
19
23
|
if (!config) return;
|
|
@@ -27,6 +31,7 @@ export async function watch() {
|
|
|
27
31
|
|
|
28
32
|
const engine = new EventraEngine(process.cwd());
|
|
29
33
|
const watchedDeps = new Set<string>();
|
|
34
|
+
|
|
30
35
|
const cache = new Map<
|
|
31
36
|
string,
|
|
32
37
|
{ content: string; deps: string[] }
|
|
@@ -37,18 +42,18 @@ export async function watch() {
|
|
|
37
42
|
// COLLECT + CACHE
|
|
38
43
|
for (const file of files) {
|
|
39
44
|
try {
|
|
40
|
-
const
|
|
45
|
+
const abs = normalize(file);
|
|
46
|
+
const raw = await fs.readFile(abs, "utf-8");
|
|
41
47
|
|
|
42
|
-
if (raw.length > 2_000_000) continue;
|
|
48
|
+
if (raw.length > 2_000_000) continue;
|
|
43
49
|
|
|
44
|
-
const parsed = processFile(
|
|
50
|
+
const parsed = processFile(abs, raw);
|
|
45
51
|
|
|
46
|
-
cache.set(
|
|
47
|
-
toScan.add(
|
|
52
|
+
cache.set(abs, parsed);
|
|
53
|
+
toScan.add(abs);
|
|
48
54
|
|
|
49
55
|
for (const dep of parsed.deps) {
|
|
50
|
-
|
|
51
|
-
toScan.add(absDep);
|
|
56
|
+
toScan.add(normalize(dep));
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
} catch {
|
|
@@ -56,7 +61,7 @@ export async function watch() {
|
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
|
|
59
|
-
// PRELOAD
|
|
64
|
+
// PRELOAD
|
|
60
65
|
for (const file of toScan) {
|
|
61
66
|
try {
|
|
62
67
|
let parsed = cache.get(file);
|
|
@@ -64,10 +69,10 @@ export async function watch() {
|
|
|
64
69
|
if (!parsed) {
|
|
65
70
|
const raw = await fs.readFile(file, "utf-8");
|
|
66
71
|
parsed = processFile(file, raw);
|
|
72
|
+
cache.set(file, parsed);
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
const virtual = getVirtualFile(file);
|
|
70
|
-
|
|
71
76
|
engine.preloadFile?.(virtual, parsed.content);
|
|
72
77
|
|
|
73
78
|
} catch {}
|
|
@@ -88,7 +93,7 @@ export async function watch() {
|
|
|
88
93
|
engine.scanFile(virtual, parsed.content, config);
|
|
89
94
|
|
|
90
95
|
for (const dep of parsed.deps) {
|
|
91
|
-
watchedDeps.add(
|
|
96
|
+
watchedDeps.add(normalize(dep));
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
} catch {}
|
|
@@ -120,29 +125,44 @@ export async function watch() {
|
|
|
120
125
|
if (raw.length > 2_000_000) continue;
|
|
121
126
|
|
|
122
127
|
const parsed = processFile(item.real, raw);
|
|
123
|
-
|
|
124
128
|
cache.set(item.real, parsed);
|
|
125
129
|
|
|
126
130
|
engine.updateFile(item.virtual, parsed.content, config);
|
|
127
131
|
|
|
128
|
-
//
|
|
132
|
+
// DEPENDENCY DIFF
|
|
133
|
+
const nextDeps = new Set<string>();
|
|
134
|
+
|
|
129
135
|
for (const dep of parsed.deps) {
|
|
130
|
-
|
|
136
|
+
nextDeps.add(normalize(dep));
|
|
137
|
+
}
|
|
131
138
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
// add new deps
|
|
140
|
+
for (const dep of nextDeps) {
|
|
141
|
+
if (!watchedDeps.has(dep)) {
|
|
142
|
+
watcher.add(dep);
|
|
143
|
+
watchedDeps.add(dep);
|
|
135
144
|
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// remove old deps
|
|
148
|
+
for (const dep of [...watchedDeps]) {
|
|
149
|
+
if (!nextDeps.has(dep)) {
|
|
150
|
+
watcher.unwatch(dep);
|
|
151
|
+
watchedDeps.delete(dep);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
136
154
|
|
|
155
|
+
// preload deps
|
|
156
|
+
for (const dep of nextDeps) {
|
|
137
157
|
try {
|
|
138
|
-
const
|
|
139
|
-
const
|
|
158
|
+
const raw = await fs.readFile(dep, "utf-8");
|
|
159
|
+
const parsedDep = processFile(dep, raw);
|
|
140
160
|
|
|
141
|
-
cache.set(
|
|
161
|
+
cache.set(dep, parsedDep);
|
|
142
162
|
|
|
143
|
-
const
|
|
163
|
+
const virtual = getVirtualFile(dep);
|
|
144
164
|
|
|
145
|
-
engine.updateFile(
|
|
165
|
+
engine.updateFile(virtual, parsedDep.content, config);
|
|
146
166
|
} catch {}
|
|
147
167
|
}
|
|
148
168
|
|
|
@@ -174,7 +194,7 @@ export async function watch() {
|
|
|
174
194
|
|
|
175
195
|
// SCHEDULER
|
|
176
196
|
const schedule = (file: string) => {
|
|
177
|
-
const abs =
|
|
197
|
+
const abs = normalize(file);
|
|
178
198
|
const virtual = getVirtualFile(abs);
|
|
179
199
|
|
|
180
200
|
console.log(chalk.gray(`change: ${file}`));
|
|
@@ -182,7 +202,7 @@ export async function watch() {
|
|
|
182
202
|
queued.set(abs, { real: abs, virtual });
|
|
183
203
|
|
|
184
204
|
if (timer) clearTimeout(timer);
|
|
185
|
-
timer = setTimeout(run,
|
|
205
|
+
timer = setTimeout(run, 200);
|
|
186
206
|
};
|
|
187
207
|
|
|
188
208
|
// WATCHER
|
|
@@ -196,7 +216,7 @@ export async function watch() {
|
|
|
196
216
|
.on("add", schedule)
|
|
197
217
|
.on("change", schedule)
|
|
198
218
|
.on("unlink", async (file) => {
|
|
199
|
-
const abs =
|
|
219
|
+
const abs = normalize(file);
|
|
200
220
|
|
|
201
221
|
console.log(chalk.gray(`remove: ${file}`));
|
|
202
222
|
|
|
@@ -220,7 +240,7 @@ export async function watch() {
|
|
|
220
240
|
console.error(chalk.red("Watcher error:"), err);
|
|
221
241
|
});
|
|
222
242
|
|
|
223
|
-
// WATCH DEPS
|
|
243
|
+
// WATCH DEPS INIT
|
|
224
244
|
for (const dep of watchedDeps) {
|
|
225
245
|
watcher.add(dep);
|
|
226
246
|
}
|