ember-repl 3.0.0-beta.0 → 3.0.0-beta.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/README.md +176 -42
- package/dist/browser/compile/formats.d.ts +15 -0
- package/dist/browser/compile/formats.js +169 -0
- package/dist/browser/compile/formats.js.map +1 -0
- package/dist/browser/compile/index.d.ts +32 -0
- package/dist/browser/compile/index.js +90 -0
- package/dist/browser/compile/index.js.map +1 -0
- package/dist/browser/compile/markdown-to-ember.d.ts +18 -0
- package/dist/browser/compile/markdown-to-ember.js +237 -0
- package/dist/browser/compile/markdown-to-ember.js.map +1 -0
- package/dist/browser/compile/types.d.ts +7 -0
- package/dist/browser/compile/types.js +2 -0
- package/dist/browser/compile/types.js.map +1 -0
- package/dist/browser/eti/preprocess.js +0 -1
- package/dist/browser/eti/preprocess.js.map +1 -1
- package/dist/browser/hbs.d.ts +5 -2
- package/dist/browser/hbs.js +2 -5
- package/dist/browser/hbs.js.map +1 -1
- package/dist/browser/index.d.ts +1 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/known-modules.d.ts +0 -2
- package/dist/browser/known-modules.js +0 -2
- package/dist/browser/known-modules.js.map +1 -1
- package/dist/browser/types.d.ts +4 -0
- package/dist/test-support/index.d.ts +2 -0
- package/dist/test-support/index.js +8 -0
- package/dist/test-support/index.js.map +1 -0
- package/package.json +37 -13
- package/src/browser/compile/formats.ts +168 -0
- package/src/browser/compile/index.ts +131 -0
- package/src/browser/compile/markdown-to-ember.ts +318 -0
- package/src/browser/compile/types.ts +7 -0
- package/src/browser/hbs.ts +7 -7
- package/src/browser/index.ts +1 -0
- package/src/browser/known-modules.ts +0 -2
- package/src/browser/types.ts +4 -0
- package/src/test-support/index.ts +5 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import rehypeRaw from 'rehype-raw';
|
|
2
|
+
import rehypeStringify from 'rehype-stringify';
|
|
3
|
+
import remarkParse from 'remark-parse';
|
|
4
|
+
import remarkRehype from 'remark-rehype';
|
|
5
|
+
import { unified } from 'unified';
|
|
6
|
+
import { visit } from 'unist-util-visit';
|
|
7
|
+
|
|
8
|
+
import { invocationOf, nameFor } from '../utils';
|
|
9
|
+
|
|
10
|
+
import type { Node } from 'hast';
|
|
11
|
+
import type { Code, Text } from 'mdast';
|
|
12
|
+
import type { Parent } from 'unist';
|
|
13
|
+
import type { VFile } from 'vfile';
|
|
14
|
+
|
|
15
|
+
export interface ExtractedCode {
|
|
16
|
+
name: string;
|
|
17
|
+
code: string;
|
|
18
|
+
lang: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LiveCodeExtraction {
|
|
22
|
+
templateOnlyGlimdown: string;
|
|
23
|
+
blocks: ExtractedCode[];
|
|
24
|
+
}
|
|
25
|
+
type LiveData = {
|
|
26
|
+
liveCode?: ExtractedCode[];
|
|
27
|
+
};
|
|
28
|
+
type VFileWithMeta = VFile & {
|
|
29
|
+
data: LiveData;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
interface Options {
|
|
33
|
+
snippets?: {
|
|
34
|
+
classList?: string[];
|
|
35
|
+
};
|
|
36
|
+
demo?: {
|
|
37
|
+
classList?: string[];
|
|
38
|
+
};
|
|
39
|
+
copyComponent?: string;
|
|
40
|
+
shadowComponent?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const GLIMDOWN_PREVIEW = Symbol('__GLIMDOWN_PREVIEW__');
|
|
44
|
+
const GLIMDOWN_RENDER = Symbol('__GLIMDOWN_RENDER__');
|
|
45
|
+
const ALLOWED_LANGUAGES = ['gjs', 'hbs'] as const;
|
|
46
|
+
|
|
47
|
+
type AllowedLanguage = (typeof ALLOWED_LANGUAGES)[number];
|
|
48
|
+
type RelevantCode = Omit<Code, 'lang'> & { lang: AllowedLanguage };
|
|
49
|
+
|
|
50
|
+
const escapeCurlies = (node: Text | Parent) => {
|
|
51
|
+
if ('value' in node && node.value) {
|
|
52
|
+
node.value = node.value.replace(/{{/g, '\\{{');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if ('children' in node && node.children) {
|
|
56
|
+
node.children.forEach((child) => escapeCurlies(child as Parent));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!node.data) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if ('hChildren' in node.data && Array.isArray(node.data['hChildren'])) {
|
|
64
|
+
node.data['hChildren'].forEach(escapeCurlies);
|
|
65
|
+
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function isLive(meta: string) {
|
|
71
|
+
return meta.includes('live');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isPreview(meta: string) {
|
|
75
|
+
return meta.includes('preview');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isBelow(meta: string) {
|
|
79
|
+
return meta.includes('below');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// TODO: extract and publish remark plugin
|
|
83
|
+
function liveCodeExtraction(options: Options = {}) {
|
|
84
|
+
let { copyComponent, snippets, demo } = options;
|
|
85
|
+
let { classList: snippetClasses } = snippets || {};
|
|
86
|
+
let { classList: demoClasses } = demo || {};
|
|
87
|
+
|
|
88
|
+
snippetClasses ??= [];
|
|
89
|
+
demoClasses ??= [];
|
|
90
|
+
|
|
91
|
+
function isRelevantCode(node: Code): node is RelevantCode {
|
|
92
|
+
if (node.type !== 'code') return false;
|
|
93
|
+
|
|
94
|
+
let { meta, lang } = node;
|
|
95
|
+
|
|
96
|
+
meta = meta?.trim();
|
|
97
|
+
|
|
98
|
+
if (!meta || !lang) return false;
|
|
99
|
+
|
|
100
|
+
if (!meta.includes('live')) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!(ALLOWED_LANGUAGES as unknown as string[]).includes(lang)) return false;
|
|
105
|
+
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let copyNode = {
|
|
110
|
+
type: 'html',
|
|
111
|
+
value: copyComponent,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
function enhance(code: Code) {
|
|
115
|
+
code.data ??= {};
|
|
116
|
+
code.data['hProperties'] ??= {};
|
|
117
|
+
// This is secret-to-us-only API, so we don't really care about the type
|
|
118
|
+
(code.data['hProperties'] as any)[GLIMDOWN_PREVIEW] = true;
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
data: {
|
|
122
|
+
hProperties: { className: snippetClasses },
|
|
123
|
+
},
|
|
124
|
+
type: 'div',
|
|
125
|
+
hProperties: { className: snippetClasses },
|
|
126
|
+
children: [code, copyNode],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function flatReplaceAt<T>(array: T[], index: number, replacement: T[]) {
|
|
131
|
+
array.splice(index, 1, ...replacement);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// because we mutate the tree as we iterate,
|
|
135
|
+
// we need to make sure we don't loop forever
|
|
136
|
+
const seen = new Set();
|
|
137
|
+
|
|
138
|
+
return function transformer(tree: Parent, file: VFileWithMeta) {
|
|
139
|
+
visit(tree, ['code'], function (node, index, parent) {
|
|
140
|
+
if (parent === null) return;
|
|
141
|
+
if (index === null) return;
|
|
142
|
+
|
|
143
|
+
if (!isRelevantCode(node as Code)) {
|
|
144
|
+
let enhanced = enhance(node as Code);
|
|
145
|
+
|
|
146
|
+
parent.children[index] = enhanced;
|
|
147
|
+
|
|
148
|
+
return 'skip';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (seen.has(node)) return 'skip';
|
|
152
|
+
|
|
153
|
+
seen.add(node);
|
|
154
|
+
|
|
155
|
+
let { meta, lang, value } = node as Code;
|
|
156
|
+
|
|
157
|
+
if (!meta) return 'skip';
|
|
158
|
+
if (!lang) return 'skip';
|
|
159
|
+
|
|
160
|
+
file.data.liveCode ??= [];
|
|
161
|
+
|
|
162
|
+
let code = value.trim();
|
|
163
|
+
let name = nameFor(code);
|
|
164
|
+
let invocation = invocationOf(name);
|
|
165
|
+
|
|
166
|
+
let shadow = options.shadowComponent;
|
|
167
|
+
|
|
168
|
+
let wrapInShadow = shadow && !meta?.includes('no-shadow');
|
|
169
|
+
|
|
170
|
+
if (wrapInShadow) {
|
|
171
|
+
invocation = `<${shadow}>${invocation}</${shadow}>`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let invokeNode = {
|
|
175
|
+
type: 'html',
|
|
176
|
+
data: {
|
|
177
|
+
hProperties: { [GLIMDOWN_RENDER]: true },
|
|
178
|
+
},
|
|
179
|
+
value: `<div class="${demoClasses}">${invocation}</div>`,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
let wrapper = enhance(node as Code);
|
|
183
|
+
|
|
184
|
+
file.data.liveCode.push({
|
|
185
|
+
lang,
|
|
186
|
+
name,
|
|
187
|
+
code,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
let live = isLive(meta);
|
|
191
|
+
let preview = isPreview(meta);
|
|
192
|
+
let below = isBelow(meta);
|
|
193
|
+
|
|
194
|
+
if (live && preview && below) {
|
|
195
|
+
flatReplaceAt(parent.children, index, [wrapper, invokeNode]);
|
|
196
|
+
|
|
197
|
+
return 'skip';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (live && preview) {
|
|
201
|
+
flatReplaceAt(parent.children, index, [invokeNode, wrapper]);
|
|
202
|
+
|
|
203
|
+
return 'skip';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (live) {
|
|
207
|
+
parent.children[index] = invokeNode;
|
|
208
|
+
|
|
209
|
+
return 'skip';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
parent.children[index] = wrapper;
|
|
213
|
+
|
|
214
|
+
return;
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function buildCompiler(options: ParseMarkdownOptions) {
|
|
220
|
+
return (
|
|
221
|
+
unified()
|
|
222
|
+
// .use(markdown)
|
|
223
|
+
.use(remarkParse)
|
|
224
|
+
// TODO: we only want to do this when we have pre > code.
|
|
225
|
+
// code can exist inline.
|
|
226
|
+
.use(liveCodeExtraction, {
|
|
227
|
+
snippets: {
|
|
228
|
+
classList: ['glimdown-snippet', 'relative'],
|
|
229
|
+
},
|
|
230
|
+
demo: {
|
|
231
|
+
classList: ['glimdown-render'],
|
|
232
|
+
},
|
|
233
|
+
copyComponent: options?.CopyComponent,
|
|
234
|
+
shadowComponent: options?.ShadowComponent,
|
|
235
|
+
})
|
|
236
|
+
// .use(() => (tree) => visit(tree, (node) => console.log('i', node)))
|
|
237
|
+
// remark rehype is needed to convert markdown to HTML
|
|
238
|
+
// However, it also changes all the nodes, so we need another pass
|
|
239
|
+
// to make sure our Glimmer-aware nodes are in tact
|
|
240
|
+
.use(remarkRehype, { allowDangerousHtml: true })
|
|
241
|
+
// Convert invocables to raw format, so Glimmer can invoke them
|
|
242
|
+
.use(() => (tree: Node) => {
|
|
243
|
+
visit(tree, function (node) {
|
|
244
|
+
// We rely on an implicit transformation of data.hProperties => properties
|
|
245
|
+
let properties = (node as any).properties;
|
|
246
|
+
|
|
247
|
+
if (properties?.[GLIMDOWN_PREVIEW]) {
|
|
248
|
+
// Have to sanitize anything Glimmer could try to render
|
|
249
|
+
escapeCurlies(node as Parent);
|
|
250
|
+
|
|
251
|
+
return 'skip';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (node.type === 'element' || ('tagName' in node && node.tagName === 'code')) {
|
|
255
|
+
if (properties?.[GLIMDOWN_RENDER]) {
|
|
256
|
+
node.type = 'glimmer_raw';
|
|
257
|
+
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
escapeCurlies(node as Parent);
|
|
262
|
+
|
|
263
|
+
return 'skip';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (node.type === 'text' || node.type === 'raw') {
|
|
267
|
+
// definitively not the better way, but this is supposed to detect "glimmer" nodes
|
|
268
|
+
if (
|
|
269
|
+
'value' in node &&
|
|
270
|
+
typeof node.value === 'string' &&
|
|
271
|
+
node.value.match(/<\/?[_A-Z:0-9].*>/g)
|
|
272
|
+
) {
|
|
273
|
+
node.type = 'glimmer_raw';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
node.type = 'glimmer_raw';
|
|
277
|
+
|
|
278
|
+
return 'skip';
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return;
|
|
282
|
+
});
|
|
283
|
+
})
|
|
284
|
+
.use(rehypeRaw, { passThrough: ['glimmer_raw', 'raw'] })
|
|
285
|
+
.use(() => (tree) => {
|
|
286
|
+
visit(tree, 'glimmer_raw', (node: Node) => {
|
|
287
|
+
node.type = 'raw';
|
|
288
|
+
});
|
|
289
|
+
})
|
|
290
|
+
.use(rehypeStringify, {
|
|
291
|
+
collapseEmptyAttributes: true,
|
|
292
|
+
closeSelfClosing: true,
|
|
293
|
+
allowParseErrors: true,
|
|
294
|
+
allowDangerousCharacters: true,
|
|
295
|
+
allowDangerousHtml: true,
|
|
296
|
+
})
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
interface ParseMarkdownOptions {
|
|
301
|
+
CopyComponent?: string;
|
|
302
|
+
ShadowComponent?: string;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @internal not under semver
|
|
307
|
+
*/
|
|
308
|
+
export async function parseMarkdown(
|
|
309
|
+
input: string,
|
|
310
|
+
options: ParseMarkdownOptions = {}
|
|
311
|
+
): Promise<LiveCodeExtraction> {
|
|
312
|
+
let markdownCompiler = buildCompiler(options);
|
|
313
|
+
let processed = await markdownCompiler.process(input);
|
|
314
|
+
let liveCode = (processed.data as LiveData).liveCode || [];
|
|
315
|
+
let templateOnly = processed.toString();
|
|
316
|
+
|
|
317
|
+
return { templateOnlyGlimdown: templateOnly, blocks: liveCode };
|
|
318
|
+
}
|
package/src/browser/hbs.ts
CHANGED
|
@@ -27,18 +27,15 @@ const { getTemplateLocals } = importSync('@glimmer/syntax') as any;
|
|
|
27
27
|
*
|
|
28
28
|
* (templates alone do not have a way to import / define complex structures)
|
|
29
29
|
*/
|
|
30
|
-
export function compileHBS(
|
|
31
|
-
template: string,
|
|
32
|
-
options: Omit<CompileTemplateOptions, 'moduleName'> = {}
|
|
33
|
-
): CompileResult {
|
|
30
|
+
export function compileHBS(template: string, options: CompileTemplateOptions = {}): CompileResult {
|
|
34
31
|
let name = nameFor(template);
|
|
35
32
|
let component: undefined | ComponentLike;
|
|
36
33
|
let error: undefined | Error;
|
|
37
34
|
|
|
38
35
|
try {
|
|
39
36
|
component = setComponentTemplate(
|
|
40
|
-
compileTemplate(template, { moduleName: name, ...options }),
|
|
41
|
-
templateOnlyComponent(name)
|
|
37
|
+
compileTemplate(template, { moduleName: options.moduleName || name, ...options }),
|
|
38
|
+
templateOnlyComponent(options.moduleName || name)
|
|
42
39
|
) as ComponentLike;
|
|
43
40
|
} catch (e) {
|
|
44
41
|
error = e as Error | undefined;
|
|
@@ -48,7 +45,10 @@ export function compileHBS(
|
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
interface CompileTemplateOptions {
|
|
51
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Used for debug viewing
|
|
50
|
+
*/
|
|
51
|
+
moduleName?: string;
|
|
52
52
|
scope?: Record<string, unknown>;
|
|
53
53
|
}
|
|
54
54
|
|
package/src/browser/index.ts
CHANGED
|
@@ -15,7 +15,6 @@ import * as _modifier from '@ember/modifier';
|
|
|
15
15
|
import * as _object from '@ember/object';
|
|
16
16
|
import * as _runloop from '@ember/runloop';
|
|
17
17
|
import * as _service from '@ember/service';
|
|
18
|
-
import * as _string from '@ember/string';
|
|
19
18
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
20
19
|
// @ts-ignore
|
|
21
20
|
import { createTemplateFactory } from '@ember/template-factory';
|
|
@@ -34,7 +33,6 @@ export const modules = {
|
|
|
34
33
|
'@ember/object': _object,
|
|
35
34
|
'@ember/runloop': _runloop,
|
|
36
35
|
'@ember/service': _service,
|
|
37
|
-
'@ember/string': _string,
|
|
38
36
|
'@ember/template-factory': { createTemplateFactory },
|
|
39
37
|
'@ember/utils': _utils,
|
|
40
38
|
// '@ember/owner': _owner,
|
package/src/browser/types.ts
CHANGED