@fluffjs/cli 0.0.8 → 0.1.1
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/BabelHelpers.d.ts +26 -0
- package/BabelHelpers.js +65 -0
- package/Cli.d.ts +7 -10
- package/Cli.js +139 -52
- package/CodeGenerator.d.ts +53 -39
- package/CodeGenerator.js +330 -725
- package/ComponentCompiler.d.ts +14 -16
- package/ComponentCompiler.js +193 -257
- package/DomPreProcessor.d.ts +36 -0
- package/DomPreProcessor.js +645 -0
- package/ErrorHelpers.d.ts +5 -0
- package/ErrorHelpers.js +8 -0
- package/ExpressionTransformer.d.ts +38 -28
- package/ExpressionTransformer.js +558 -230
- package/Generator.d.ts +1 -5
- package/Generator.js +128 -67
- package/GetterDependencyExtractor.d.ts +4 -0
- package/GetterDependencyExtractor.js +73 -0
- package/IndexHtmlTransformer.d.ts +6 -7
- package/IndexHtmlTransformer.js +82 -88
- package/Parse5Helpers.d.ts +17 -0
- package/Parse5Helpers.js +95 -0
- package/TemplateParser.d.ts +39 -21
- package/TemplateParser.js +462 -268
- package/Typeguards.d.ts +24 -0
- package/Typeguards.js +30 -0
- package/babel-plugin-class-transform.d.ts +3 -18
- package/babel-plugin-class-transform.js +3 -11
- package/babel-plugin-component.d.ts +4 -13
- package/babel-plugin-component.js +7 -0
- package/babel-plugin-imports.d.ts +3 -11
- package/babel-plugin-imports.js +5 -31
- package/babel-plugin-reactive.d.ts +2 -19
- package/babel-plugin-reactive.js +21 -76
- package/bin.js +2 -2
- package/fluff-esbuild-plugin.d.ts +2 -5
- package/fluff-esbuild-plugin.js +4 -1
- package/index.d.ts +6 -2
- package/index.js +1 -1
- package/interfaces/BabelPluginClassTransformState.d.ts +5 -0
- package/interfaces/BabelPluginComponentState.d.ts +4 -0
- package/interfaces/BabelPluginComponentState.js +1 -0
- package/interfaces/BabelPluginImportsState.d.ts +5 -0
- package/interfaces/BabelPluginImportsState.js +1 -0
- package/interfaces/BabelPluginReactiveState.d.ts +13 -0
- package/interfaces/BabelPluginReactiveState.js +1 -0
- package/interfaces/BabelPluginReactiveWatchCallInfo.d.ts +7 -0
- package/interfaces/BabelPluginReactiveWatchCallInfo.js +1 -0
- package/interfaces/BabelPluginReactiveWatchInfo.d.ts +5 -0
- package/interfaces/BabelPluginReactiveWatchInfo.js +1 -0
- package/interfaces/BabelToken.d.ts +8 -0
- package/interfaces/BabelToken.js +1 -0
- package/interfaces/BindingInfo.d.ts +12 -0
- package/interfaces/BindingInfo.js +1 -0
- package/interfaces/BreakMarkerConfig.d.ts +4 -0
- package/interfaces/BreakMarkerConfig.js +1 -0
- package/interfaces/BreakNode.d.ts +4 -0
- package/interfaces/BreakNode.js +1 -0
- package/interfaces/BundleOptions.d.ts +9 -0
- package/interfaces/BundleOptions.js +1 -0
- package/interfaces/ClassTransformOptions.d.ts +10 -0
- package/interfaces/ClassTransformOptions.js +1 -0
- package/interfaces/CliOptions.d.ts +8 -0
- package/interfaces/CliOptions.js +1 -0
- package/interfaces/CommentNode.d.ts +5 -0
- package/interfaces/CommentNode.js +1 -0
- package/interfaces/CompileResult.d.ts +6 -0
- package/interfaces/CompileResult.js +1 -0
- package/interfaces/CompilerOptions.d.ts +6 -0
- package/interfaces/CompilerOptions.js +1 -0
- package/interfaces/ComponentInfo.d.ts +8 -0
- package/interfaces/ComponentInfo.js +1 -0
- package/interfaces/ComponentMetadata.d.ts +9 -0
- package/interfaces/ComponentMetadata.js +1 -0
- package/interfaces/ControlFlow.d.ts +19 -0
- package/interfaces/ControlFlow.js +1 -0
- package/interfaces/ControlFlowNode.d.ts +6 -0
- package/interfaces/ControlFlowNode.js +1 -0
- package/interfaces/ControlFlowParseResult.d.ts +10 -0
- package/interfaces/ControlFlowParseResult.js +1 -0
- package/interfaces/ElementNode.d.ts +11 -0
- package/interfaces/ElementNode.js +1 -0
- package/interfaces/FluffConfigInterface.d.ts +7 -0
- package/interfaces/FluffConfigInterface.js +1 -0
- package/interfaces/FluffPluginOptions.d.ts +9 -0
- package/interfaces/FluffPluginOptions.js +1 -0
- package/interfaces/FluffTarget.d.ts +15 -0
- package/interfaces/FluffTarget.js +1 -0
- package/interfaces/ForMarkerConfig.d.ts +9 -0
- package/interfaces/ForMarkerConfig.js +1 -0
- package/interfaces/ForNode.d.ts +13 -0
- package/interfaces/ForNode.js +1 -0
- package/interfaces/GeneratorOptions.d.ts +5 -0
- package/interfaces/GeneratorOptions.js +1 -0
- package/interfaces/HtmlTransformOptions.d.ts +10 -0
- package/interfaces/HtmlTransformOptions.js +1 -0
- package/interfaces/IfBranch.d.ts +8 -0
- package/interfaces/IfBranch.js +1 -0
- package/interfaces/IfMarkerConfig.d.ts +8 -0
- package/interfaces/IfMarkerConfig.js +1 -0
- package/interfaces/IfNode.d.ts +7 -0
- package/interfaces/IfNode.js +1 -0
- package/interfaces/ImportTransformOptions.d.ts +7 -0
- package/interfaces/ImportTransformOptions.js +1 -0
- package/interfaces/InterpolationNode.d.ts +12 -0
- package/interfaces/InterpolationNode.js +1 -0
- package/interfaces/ParsedTemplate.d.ts +6 -0
- package/interfaces/ParsedTemplate.js +1 -0
- package/interfaces/ParsedTemplateOld.d.ts +9 -0
- package/interfaces/ParsedTemplateOld.js +1 -0
- package/interfaces/PropertyChain.d.ts +2 -0
- package/interfaces/PropertyChain.js +1 -0
- package/interfaces/Scope.d.ts +5 -0
- package/interfaces/Scope.js +1 -0
- package/interfaces/ServeOptions.d.ts +5 -0
- package/interfaces/ServeOptions.js +1 -0
- package/interfaces/SwitchCase.d.ts +8 -0
- package/interfaces/SwitchCase.js +1 -0
- package/interfaces/SwitchMarkerConfig.d.ts +11 -0
- package/interfaces/SwitchMarkerConfig.js +1 -0
- package/interfaces/SwitchNode.d.ts +10 -0
- package/interfaces/SwitchNode.js +1 -0
- package/interfaces/TemplateBinding.d.ts +10 -0
- package/interfaces/TemplateBinding.js +1 -0
- package/interfaces/TemplateNode.d.ts +7 -0
- package/interfaces/TemplateNode.js +1 -0
- package/interfaces/TextMarkerConfig.d.ts +10 -0
- package/interfaces/TextMarkerConfig.js +1 -0
- package/interfaces/TextNode.d.ts +5 -0
- package/interfaces/TextNode.js +1 -0
- package/interfaces/TokenizeResult.d.ts +6 -0
- package/interfaces/TokenizeResult.js +1 -0
- package/interfaces/TransformOptions.d.ts +11 -0
- package/interfaces/TransformOptions.js +1 -0
- package/interfaces/index.d.ts +34 -0
- package/interfaces/index.js +1 -0
- package/package.json +9 -1
- package/types/FluffConfig.d.ts +5 -27
- package/ControlFlowParser.d.ts +0 -55
- package/ControlFlowParser.js +0 -279
- package/types.d.ts +0 -46
- /package/{types.js → interfaces/BabelPluginClassTransformState.js} +0 -0
package/Generator.d.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
appName: string;
|
|
3
|
-
outputDir: string;
|
|
4
|
-
}
|
|
1
|
+
import type { GeneratorOptions } from './interfaces/GeneratorOptions.js';
|
|
5
2
|
export declare class Generator {
|
|
6
3
|
generate(options: GeneratorOptions): void;
|
|
7
4
|
private writeFile;
|
|
@@ -18,5 +15,4 @@ export declare class Generator {
|
|
|
18
15
|
private getComponentHtml;
|
|
19
16
|
private getComponentCss;
|
|
20
17
|
}
|
|
21
|
-
export {};
|
|
22
18
|
//# sourceMappingURL=Generator.d.ts.map
|
package/Generator.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import * as t from '@babel/types';
|
|
1
2
|
import * as fs from 'fs';
|
|
3
|
+
import * as parse5 from 'parse5';
|
|
2
4
|
import * as path from 'path';
|
|
3
5
|
import { fileURLToPath } from 'url';
|
|
6
|
+
import { generate } from './BabelHelpers.js';
|
|
7
|
+
import { Parse5Helpers } from './Parse5Helpers.js';
|
|
4
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
9
|
export class Generator {
|
|
6
10
|
generate(options) {
|
|
@@ -54,13 +58,17 @@ export class Generator {
|
|
|
54
58
|
toPascalCase(str) {
|
|
55
59
|
return str
|
|
56
60
|
.split(/[-_\s]+/)
|
|
57
|
-
.map(word => word.charAt(0)
|
|
61
|
+
.map(word => word.charAt(0)
|
|
62
|
+
.toUpperCase() + word.slice(1)
|
|
63
|
+
.toLowerCase())
|
|
58
64
|
.join('');
|
|
59
65
|
}
|
|
60
66
|
toTitleCase(str) {
|
|
61
67
|
return str
|
|
62
68
|
.split(/[-_\s]+/)
|
|
63
|
-
.map(word => word.charAt(0)
|
|
69
|
+
.map(word => word.charAt(0)
|
|
70
|
+
.toUpperCase() + word.slice(1)
|
|
71
|
+
.toLowerCase())
|
|
64
72
|
.join(' ');
|
|
65
73
|
}
|
|
66
74
|
getPackageJson(name) {
|
|
@@ -138,13 +146,19 @@ export class Generator {
|
|
|
138
146
|
}, null, 2) + '\n';
|
|
139
147
|
}
|
|
140
148
|
getIndexHtml(kebabName, titleName) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
149
|
+
const doc = Parse5Helpers.createDocument();
|
|
150
|
+
const html = Parse5Helpers.createElement('html', [{ name: 'lang', value: 'en' }]);
|
|
151
|
+
const head = Parse5Helpers.createElement('head', []);
|
|
152
|
+
const body = Parse5Helpers.createElement('body', []);
|
|
153
|
+
const charset = Parse5Helpers.createElement('meta', [{ name: 'charset', value: 'UTF-8' }]);
|
|
154
|
+
const viewport = Parse5Helpers.createElement('meta', [
|
|
155
|
+
{ name: 'name', value: 'viewport' },
|
|
156
|
+
{ name: 'content', value: 'width=device-width, initial-scale=1.0' }
|
|
157
|
+
]);
|
|
158
|
+
const title = Parse5Helpers.createElement('title', []);
|
|
159
|
+
Parse5Helpers.appendText(title, `${titleName} - Fluff.js`);
|
|
160
|
+
const style = Parse5Helpers.createElement('style', []);
|
|
161
|
+
Parse5Helpers.appendText(style, `
|
|
148
162
|
* {
|
|
149
163
|
margin: 0;
|
|
150
164
|
padding: 0;
|
|
@@ -154,70 +168,117 @@ export class Generator {
|
|
|
154
168
|
body {
|
|
155
169
|
min-height: 100vh;
|
|
156
170
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
171
|
+
`);
|
|
172
|
+
Parse5Helpers.appendChild(head, charset);
|
|
173
|
+
Parse5Helpers.appendChild(head, viewport);
|
|
174
|
+
Parse5Helpers.appendChild(head, title);
|
|
175
|
+
Parse5Helpers.appendChild(head, style);
|
|
176
|
+
const appElement = Parse5Helpers.createElement(kebabName, []);
|
|
177
|
+
const script = Parse5Helpers.createElement('script', [
|
|
178
|
+
{ name: 'type', value: 'module' },
|
|
179
|
+
{ name: 'src', value: './main.js' }
|
|
180
|
+
]);
|
|
181
|
+
Parse5Helpers.appendChild(body, appElement);
|
|
182
|
+
Parse5Helpers.appendChild(body, script);
|
|
183
|
+
Parse5Helpers.appendChild(html, head);
|
|
184
|
+
Parse5Helpers.appendChild(html, body);
|
|
185
|
+
doc.childNodes.push(html);
|
|
186
|
+
html.parentNode = doc;
|
|
187
|
+
return '<!DOCTYPE html>\n' + parse5.serialize(doc);
|
|
165
188
|
}
|
|
166
189
|
getMainTs(kebabName, pascalName) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
190
|
+
const componentName = `${pascalName}Component`;
|
|
191
|
+
const importPath = `./app/${kebabName}.component.js`;
|
|
192
|
+
const importDecl = t.importDeclaration([t.importSpecifier(t.identifier(componentName), t.identifier(componentName))], t.stringLiteral(importPath));
|
|
193
|
+
const defineCall = t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('customElements'), t.identifier('define')), [t.stringLiteral(kebabName), t.identifier(componentName)]));
|
|
194
|
+
const program = t.program([importDecl, defineCall]);
|
|
195
|
+
return generate(program, { compact: false }).code + '\n';
|
|
171
196
|
}
|
|
172
197
|
getComponentTs(kebabName, pascalName) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
{
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
198
|
+
const componentName = `${pascalName}Component`;
|
|
199
|
+
const importDecl = t.importDeclaration([
|
|
200
|
+
t.importSpecifier(t.identifier('Component'), t.identifier('Component')),
|
|
201
|
+
t.importSpecifier(t.identifier('Reactive'), t.identifier('Reactive'))
|
|
202
|
+
], t.stringLiteral('@fluffjs/fluff'));
|
|
203
|
+
const componentDecorator = t.decorator(t.callExpression(t.identifier('Component'), [
|
|
204
|
+
t.objectExpression([
|
|
205
|
+
t.objectProperty(t.identifier('selector'), t.stringLiteral(kebabName)),
|
|
206
|
+
t.objectProperty(t.identifier('templateUrl'), t.stringLiteral(`./${kebabName}.component.html`)),
|
|
207
|
+
t.objectProperty(t.identifier('styleUrl'), t.stringLiteral(`./${kebabName}.component.css`))
|
|
208
|
+
])
|
|
209
|
+
]));
|
|
210
|
+
const reactiveDecorator = t.decorator(t.callExpression(t.identifier('Reactive'), []));
|
|
211
|
+
const nameProperty = t.classProperty(t.identifier('name'), t.stringLiteral('World'));
|
|
212
|
+
nameProperty.decorators = [reactiveDecorator];
|
|
213
|
+
const reactiveDecorator2 = t.decorator(t.callExpression(t.identifier('Reactive'), []));
|
|
214
|
+
const countProperty = t.classProperty(t.identifier('count'), t.numericLiteral(0));
|
|
215
|
+
countProperty.decorators = [reactiveDecorator2];
|
|
216
|
+
const incrementMethod = t.classMethod('method', t.identifier('increment'), [], t.blockStatement([
|
|
217
|
+
t.expressionStatement(t.updateExpression('++', t.memberExpression(t.thisExpression(), t.identifier('count'))))
|
|
218
|
+
]));
|
|
219
|
+
const classDecl = t.classDeclaration(t.identifier(componentName), t.identifier('HTMLElement'), t.classBody([nameProperty, countProperty, incrementMethod]));
|
|
220
|
+
classDecl.decorators = [componentDecorator];
|
|
221
|
+
const exportDecl = t.exportNamedDeclaration(classDecl, []);
|
|
222
|
+
const program = t.program([importDecl, exportDecl]);
|
|
223
|
+
return generate(program, { compact: false }).code + '\n';
|
|
191
224
|
}
|
|
192
225
|
getComponentHtml() {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
226
|
+
const fragment = parse5.parseFragment('');
|
|
227
|
+
const container = Parse5Helpers.createElement('div', [{ name: 'class', value: 'container' }]);
|
|
228
|
+
const card = Parse5Helpers.createElement('div', [{ name: 'class', value: 'card' }]);
|
|
229
|
+
const logo = Parse5Helpers.createElement('div', [{ name: 'class', value: 'logo' }]);
|
|
230
|
+
Parse5Helpers.appendText(logo, '🧸');
|
|
231
|
+
const h1 = Parse5Helpers.createElement('h1', []);
|
|
232
|
+
Parse5Helpers.appendText(h1, 'Hello, ');
|
|
233
|
+
const highlight = Parse5Helpers.createElement('span', [{ name: 'class', value: 'highlight' }]);
|
|
234
|
+
Parse5Helpers.appendText(highlight, '{{ name }}');
|
|
235
|
+
Parse5Helpers.appendChild(h1, highlight);
|
|
236
|
+
Parse5Helpers.appendText(h1, '!');
|
|
237
|
+
const tagline = Parse5Helpers.createElement('p', [{ name: 'class', value: 'tagline' }]);
|
|
238
|
+
Parse5Helpers.appendText(tagline, 'Welcome to Fluff.js');
|
|
239
|
+
const inputGroup = Parse5Helpers.createElement('div', [{ name: 'class', value: 'input-group' }]);
|
|
240
|
+
const label = Parse5Helpers.createElement('label', [{ name: 'for', value: 'name-input' }]);
|
|
241
|
+
Parse5Helpers.appendText(label, 'What\'s your name?');
|
|
242
|
+
const input = Parse5Helpers.createElement('input', [
|
|
243
|
+
{ name: 'id', value: 'name-input' },
|
|
244
|
+
{ name: 'type', value: 'text' },
|
|
245
|
+
{ name: '[value]', value: 'name' },
|
|
246
|
+
{ name: '(input)', value: 'name = $event.target.value' },
|
|
247
|
+
{ name: 'placeholder', value: 'Enter your name...' }
|
|
248
|
+
]);
|
|
249
|
+
Parse5Helpers.appendChild(inputGroup, label);
|
|
250
|
+
Parse5Helpers.appendChild(inputGroup, input);
|
|
251
|
+
const counterSection = Parse5Helpers.createElement('div', [{ name: 'class', value: 'counter-section' }]);
|
|
252
|
+
const counterP = Parse5Helpers.createElement('p', []);
|
|
253
|
+
Parse5Helpers.appendText(counterP, 'You\'ve clicked ');
|
|
254
|
+
const strong = Parse5Helpers.createElement('strong', []);
|
|
255
|
+
Parse5Helpers.appendText(strong, '{{ count }}');
|
|
256
|
+
Parse5Helpers.appendChild(counterP, strong);
|
|
257
|
+
Parse5Helpers.appendText(counterP, ' times');
|
|
258
|
+
const button = Parse5Helpers.createElement('button', [
|
|
259
|
+
{ name: 'class', value: 'btn' },
|
|
260
|
+
{ name: '(click)', value: 'increment()' }
|
|
261
|
+
]);
|
|
262
|
+
Parse5Helpers.appendText(button, 'Click me! 🎉');
|
|
263
|
+
Parse5Helpers.appendChild(counterSection, counterP);
|
|
264
|
+
Parse5Helpers.appendChild(counterSection, button);
|
|
265
|
+
const footer = Parse5Helpers.createElement('footer', []);
|
|
266
|
+
const footerP = Parse5Helpers.createElement('p', []);
|
|
267
|
+
Parse5Helpers.appendText(footerP, 'Built with Fluff.js — ');
|
|
268
|
+
const em = Parse5Helpers.createElement('em', []);
|
|
269
|
+
Parse5Helpers.appendText(em, '"Just the good stuff"');
|
|
270
|
+
Parse5Helpers.appendChild(footerP, em);
|
|
271
|
+
Parse5Helpers.appendChild(footer, footerP);
|
|
272
|
+
Parse5Helpers.appendChild(card, logo);
|
|
273
|
+
Parse5Helpers.appendChild(card, h1);
|
|
274
|
+
Parse5Helpers.appendChild(card, tagline);
|
|
275
|
+
Parse5Helpers.appendChild(card, inputGroup);
|
|
276
|
+
Parse5Helpers.appendChild(card, counterSection);
|
|
277
|
+
Parse5Helpers.appendChild(card, footer);
|
|
278
|
+
Parse5Helpers.appendChild(container, card);
|
|
279
|
+
fragment.childNodes.push(container);
|
|
280
|
+
container.parentNode = fragment;
|
|
281
|
+
return parse5.serialize(fragment) + '\n';
|
|
221
282
|
}
|
|
222
283
|
getComponentCss() {
|
|
223
284
|
return `:host {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
import { traverse } from './BabelHelpers.js';
|
|
4
|
+
export class GetterDependencyExtractor {
|
|
5
|
+
static extractGetterDependencyMap(code, reactiveProps) {
|
|
6
|
+
const getterDeps = new Map();
|
|
7
|
+
const backingFieldToProp = new Map();
|
|
8
|
+
for (const prop of reactiveProps) {
|
|
9
|
+
backingFieldToProp.set(`__${prop}`, prop);
|
|
10
|
+
}
|
|
11
|
+
const ast = parse(code, {
|
|
12
|
+
sourceType: 'module',
|
|
13
|
+
plugins: ['typescript', 'decorators']
|
|
14
|
+
});
|
|
15
|
+
const getterNames = new Set();
|
|
16
|
+
traverse(ast, {
|
|
17
|
+
ClassMethod(path) {
|
|
18
|
+
const { node } = path;
|
|
19
|
+
if (node.kind !== 'get') {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (t.isIdentifier(node.key)) {
|
|
23
|
+
getterNames.add(node.key.name);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
traverse(ast, {
|
|
28
|
+
ClassMethod(path) {
|
|
29
|
+
const { node } = path;
|
|
30
|
+
if (node.kind !== 'get') {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!t.isIdentifier(node.key)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const getterName = node.key.name;
|
|
37
|
+
const deps = new Set();
|
|
38
|
+
path.traverse({
|
|
39
|
+
MemberExpression(memberPath) {
|
|
40
|
+
const { node: memberNode } = memberPath;
|
|
41
|
+
const { object, property, computed } = memberNode;
|
|
42
|
+
if (!t.isThisExpression(object)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (computed) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!t.isIdentifier(property)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const propName = property.name;
|
|
52
|
+
const publicPropName = backingFieldToProp.get(propName) ?? propName;
|
|
53
|
+
const { parent } = memberPath;
|
|
54
|
+
if (t.isCallExpression(parent) && parent.callee === memberNode) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (reactiveProps.has(publicPropName)) {
|
|
58
|
+
deps.add(publicPropName);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (getterNames.has(publicPropName)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
if (deps.size > 0) {
|
|
67
|
+
getterDeps.set(getterName, Array.from(deps));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return getterDeps;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import type { HtmlTransformOptions } from './interfaces/HtmlTransformOptions.js';
|
|
2
|
+
export type { HtmlTransformOptions } from './interfaces/HtmlTransformOptions.js';
|
|
3
|
+
export declare class IndexHtmlTransformer {
|
|
4
|
+
private static readonly LIVE_RELOAD_SCRIPT;
|
|
5
|
+
static transform(html: string, options: HtmlTransformOptions): Promise<string>;
|
|
6
|
+
private static decodeScriptTextNodes;
|
|
7
7
|
}
|
|
8
|
-
export declare function transformIndexHtml(html: string, options: TransformOptions): Promise<string>;
|
|
9
8
|
//# sourceMappingURL=IndexHtmlTransformer.d.ts.map
|
package/IndexHtmlTransformer.js
CHANGED
|
@@ -1,97 +1,91 @@
|
|
|
1
|
+
import he from 'he';
|
|
1
2
|
import { minify } from 'html-minifier-terser';
|
|
2
3
|
import * as parse5 from 'parse5';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const found = findElement(child, tagName);
|
|
14
|
-
if (found)
|
|
15
|
-
return found;
|
|
4
|
+
import { Parse5Helpers } from './Parse5Helpers.js';
|
|
5
|
+
import { Typeguards } from './Typeguards.js';
|
|
6
|
+
export class IndexHtmlTransformer {
|
|
7
|
+
static LIVE_RELOAD_SCRIPT = `(function() {
|
|
8
|
+
let firstEvent = true;
|
|
9
|
+
const es = new EventSource('/esbuild');
|
|
10
|
+
es.addEventListener('change', e => {
|
|
11
|
+
if (firstEvent) {
|
|
12
|
+
firstEvent = false;
|
|
13
|
+
return;
|
|
16
14
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
attrs: [
|
|
29
|
-
{ name: 'rel', value: 'stylesheet' },
|
|
30
|
-
{ name: 'href', value: href }
|
|
31
|
-
],
|
|
32
|
-
childNodes: [],
|
|
33
|
-
namespaceURI: parse5Html.NS.HTML,
|
|
34
|
-
parentNode: null
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
function createScriptElement(src) {
|
|
38
|
-
return {
|
|
39
|
-
nodeName: 'script',
|
|
40
|
-
tagName: 'script',
|
|
41
|
-
attrs: [
|
|
42
|
-
{ name: 'type', value: 'module' },
|
|
43
|
-
{ name: 'src', value: src }
|
|
44
|
-
],
|
|
45
|
-
childNodes: [],
|
|
46
|
-
namespaceURI: parse5Html.NS.HTML,
|
|
47
|
-
parentNode: null
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function getLiveReloadScript() {
|
|
51
|
-
return `<script>new EventSource('/esbuild').addEventListener('change', e => {
|
|
52
|
-
const { added, removed, updated } = JSON.parse(e.data);
|
|
53
|
-
if (!added.length && !removed.length && updated.length === 1) {
|
|
54
|
-
for (const link of document.getElementsByTagName("link")) {
|
|
55
|
-
const url = new URL(link.href);
|
|
56
|
-
if (url.host === location.host && url.pathname === updated[0]) {
|
|
57
|
-
const next = link.cloneNode();
|
|
58
|
-
next.href = updated[0] + '?' + Math.random().toString(36).slice(2);
|
|
59
|
-
next.onload = () => link.remove();
|
|
60
|
-
link.parentNode.insertBefore(next, link.nextSibling);
|
|
61
|
-
return;
|
|
15
|
+
const { added, removed, updated } = JSON.parse(e.data);
|
|
16
|
+
if (!added.length && !removed.length && updated.length === 1) {
|
|
17
|
+
for (const link of document.getElementsByTagName("link")) {
|
|
18
|
+
const url = new URL(link.href);
|
|
19
|
+
if (url.host === location.host && url.pathname === updated[0]) {
|
|
20
|
+
const next = link.cloneNode();
|
|
21
|
+
next.href = updated[0] + '?' + Math.random().toString(36).slice(2);
|
|
22
|
+
next.onload = () => link.remove();
|
|
23
|
+
link.parentNode.insertBefore(next, link.nextSibling);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
62
26
|
}
|
|
63
27
|
}
|
|
28
|
+
location.reload();
|
|
29
|
+
});
|
|
30
|
+
})();`;
|
|
31
|
+
static async transform(html, options) {
|
|
32
|
+
const doc = parse5.parse(html);
|
|
33
|
+
const jsSrc = options.gzScriptTag ? `${options.jsBundle}.gz` : options.jsBundle;
|
|
34
|
+
const cssSrc = options.cssBundle
|
|
35
|
+
? (options.gzScriptTag ? `${options.cssBundle}.gz` : options.cssBundle)
|
|
36
|
+
: null;
|
|
37
|
+
const head = Parse5Helpers.findElement(doc, 'head');
|
|
38
|
+
const body = Parse5Helpers.findElement(doc, 'body');
|
|
39
|
+
if (head && options.inlineStyles) {
|
|
40
|
+
const styleEl = Parse5Helpers.createElement('style', []);
|
|
41
|
+
Parse5Helpers.appendText(styleEl, options.inlineStyles);
|
|
42
|
+
Parse5Helpers.appendChild(head, styleEl);
|
|
43
|
+
}
|
|
44
|
+
if (head && cssSrc) {
|
|
45
|
+
const linkEl = Parse5Helpers.createElement('link', [
|
|
46
|
+
{ name: 'rel', value: 'stylesheet' },
|
|
47
|
+
{ name: 'href', value: cssSrc }
|
|
48
|
+
]);
|
|
49
|
+
Parse5Helpers.appendChild(head, linkEl);
|
|
50
|
+
}
|
|
51
|
+
if (body) {
|
|
52
|
+
const scriptEl = Parse5Helpers.createElement('script', [
|
|
53
|
+
{ name: 'type', value: 'module' },
|
|
54
|
+
{ name: 'src', value: jsSrc }
|
|
55
|
+
]);
|
|
56
|
+
Parse5Helpers.appendChild(body, scriptEl);
|
|
57
|
+
}
|
|
58
|
+
if (body && options.liveReload) {
|
|
59
|
+
const inlineScriptEl = Parse5Helpers.createElement('script', []);
|
|
60
|
+
Parse5Helpers.appendText(inlineScriptEl, IndexHtmlTransformer.LIVE_RELOAD_SCRIPT);
|
|
61
|
+
Parse5Helpers.appendChild(body, inlineScriptEl);
|
|
62
|
+
}
|
|
63
|
+
if (options.liveReload) {
|
|
64
|
+
IndexHtmlTransformer.decodeScriptTextNodes(doc);
|
|
65
|
+
}
|
|
66
|
+
let result = parse5.serialize(doc);
|
|
67
|
+
if (options.minify) {
|
|
68
|
+
result = await minify(result, {
|
|
69
|
+
collapseWhitespace: true,
|
|
70
|
+
removeComments: true,
|
|
71
|
+
removeRedundantAttributes: true,
|
|
72
|
+
removeEmptyAttributes: true,
|
|
73
|
+
minifyCSS: true,
|
|
74
|
+
minifyJS: true
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
64
78
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const body = findElement(doc, 'body');
|
|
76
|
-
if (head && cssSrc) {
|
|
77
|
-
appendChild(head, createLinkElement(cssSrc));
|
|
78
|
-
}
|
|
79
|
-
if (body) {
|
|
80
|
-
appendChild(body, createScriptElement(jsSrc));
|
|
81
|
-
}
|
|
82
|
-
let result = parse5.serialize(doc);
|
|
83
|
-
if (options.liveReload) {
|
|
84
|
-
result = result.replace('</body>', getLiveReloadScript() + '</body>');
|
|
85
|
-
}
|
|
86
|
-
if (options.minify) {
|
|
87
|
-
result = await minify(result, {
|
|
88
|
-
collapseWhitespace: true,
|
|
89
|
-
removeComments: true,
|
|
90
|
-
removeRedundantAttributes: true,
|
|
91
|
-
removeEmptyAttributes: true,
|
|
92
|
-
minifyCSS: true,
|
|
93
|
-
minifyJS: true
|
|
79
|
+
static decodeScriptTextNodes(node) {
|
|
80
|
+
Parse5Helpers.walkNodes(node, (n) => {
|
|
81
|
+
if (Typeguards.isElement(n) && n.tagName === 'script') {
|
|
82
|
+
for (const child of n.childNodes) {
|
|
83
|
+
if ('value' in child && typeof child.value === 'string') {
|
|
84
|
+
child.value = he.decode(child.value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
94
89
|
});
|
|
95
90
|
}
|
|
96
|
-
return result;
|
|
97
91
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Parse5ChildNode, Parse5Document, Parse5DocumentFragment, Parse5Element, Parse5Node, Parse5ParentNode } from './Typeguards.js';
|
|
2
|
+
export type Parse5NodeVisitor = (node: Parse5Node) => boolean | undefined;
|
|
3
|
+
export declare class Parse5Helpers {
|
|
4
|
+
static createElement(tagName: string, attrs: {
|
|
5
|
+
name: string;
|
|
6
|
+
value: string;
|
|
7
|
+
}[]): Parse5Element;
|
|
8
|
+
static appendChild(parent: Parse5ParentNode, child: Parse5ChildNode): void;
|
|
9
|
+
static appendText(parent: Parse5ParentNode, value: string): void;
|
|
10
|
+
static appendComment(parent: Parse5ParentNode, data: string): void;
|
|
11
|
+
static getTemplateContent(tpl: Parse5Element): Parse5DocumentFragment;
|
|
12
|
+
static createDocument(): Parse5Document;
|
|
13
|
+
static walkNodes(node: Parse5Node, visitor: Parse5NodeVisitor): void;
|
|
14
|
+
static findElement(node: Parse5Node, tagName: string): Parse5Element | null;
|
|
15
|
+
static removeNonMarkerComments(node: Parse5Node): void;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=Parse5Helpers.d.ts.map
|
package/Parse5Helpers.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { html as parse5Html } from 'parse5';
|
|
2
|
+
import { Typeguards } from './Typeguards.js';
|
|
3
|
+
export class Parse5Helpers {
|
|
4
|
+
static createElement(tagName, attrs) {
|
|
5
|
+
const el = {
|
|
6
|
+
nodeName: tagName,
|
|
7
|
+
tagName,
|
|
8
|
+
attrs,
|
|
9
|
+
childNodes: [],
|
|
10
|
+
namespaceURI: parse5Html.NS.HTML,
|
|
11
|
+
parentNode: null
|
|
12
|
+
};
|
|
13
|
+
if (tagName === 'template') {
|
|
14
|
+
const content = {
|
|
15
|
+
nodeName: '#document-fragment',
|
|
16
|
+
childNodes: []
|
|
17
|
+
};
|
|
18
|
+
Reflect.set(el, 'content', content);
|
|
19
|
+
}
|
|
20
|
+
return el;
|
|
21
|
+
}
|
|
22
|
+
static appendChild(parent, child) {
|
|
23
|
+
child.parentNode = parent;
|
|
24
|
+
parent.childNodes.push(child);
|
|
25
|
+
}
|
|
26
|
+
static appendText(parent, value) {
|
|
27
|
+
if (value.length === 0)
|
|
28
|
+
return;
|
|
29
|
+
const textNode = {
|
|
30
|
+
nodeName: '#text',
|
|
31
|
+
value,
|
|
32
|
+
parentNode: parent
|
|
33
|
+
};
|
|
34
|
+
parent.childNodes.push(textNode);
|
|
35
|
+
}
|
|
36
|
+
static appendComment(parent, data) {
|
|
37
|
+
const commentNode = {
|
|
38
|
+
nodeName: '#comment',
|
|
39
|
+
data,
|
|
40
|
+
parentNode: parent
|
|
41
|
+
};
|
|
42
|
+
parent.childNodes.push(commentNode);
|
|
43
|
+
}
|
|
44
|
+
static getTemplateContent(tpl) {
|
|
45
|
+
const content = Reflect.get(tpl, 'content');
|
|
46
|
+
if (!Typeguards.isDocumentFragmentLike(content)) {
|
|
47
|
+
throw new Error('Template element missing content property');
|
|
48
|
+
}
|
|
49
|
+
return content;
|
|
50
|
+
}
|
|
51
|
+
static createDocument() {
|
|
52
|
+
return {
|
|
53
|
+
nodeName: '#document',
|
|
54
|
+
mode: parse5Html.DOCUMENT_MODE.NO_QUIRKS,
|
|
55
|
+
childNodes: []
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
static walkNodes(node, visitor) {
|
|
59
|
+
const shouldStop = visitor(node);
|
|
60
|
+
if (shouldStop === true)
|
|
61
|
+
return;
|
|
62
|
+
if ('childNodes' in node) {
|
|
63
|
+
for (const child of node.childNodes) {
|
|
64
|
+
Parse5Helpers.walkNodes(child, visitor);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
static findElement(node, tagName) {
|
|
69
|
+
if (Typeguards.isElement(node) && node.tagName === tagName) {
|
|
70
|
+
return node;
|
|
71
|
+
}
|
|
72
|
+
if ('childNodes' in node) {
|
|
73
|
+
for (const child of node.childNodes) {
|
|
74
|
+
const found = Parse5Helpers.findElement(child, tagName);
|
|
75
|
+
if (found)
|
|
76
|
+
return found;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
static removeNonMarkerComments(node) {
|
|
82
|
+
if (!('childNodes' in node))
|
|
83
|
+
return;
|
|
84
|
+
const markerPattern = /^\/?(fluff:(if|for|switch|text|break):\d+)$/;
|
|
85
|
+
node.childNodes = node.childNodes.filter(child => {
|
|
86
|
+
if (Typeguards.isCommentNode(child)) {
|
|
87
|
+
return markerPattern.test(child.data);
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
});
|
|
91
|
+
for (const child of node.childNodes) {
|
|
92
|
+
Parse5Helpers.removeNonMarkerComments(child);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|