@abide/abide 0.101.0 → 0.103.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/CHANGELOG.md +26 -0
- package/bin/abide.ts +226 -164
- package/docs/REGISTRY.md +730 -0
- package/docs/RULEBOOK.md +2768 -0
- package/docs/abide.md +13127 -0
- package/package.json +6 -3
- package/scaffold/AGENTS.md +38 -0
- package/scaffold/CLAUDE.md +1 -0
- package/scaffold/src/layout.abide +18 -0
- package/src/compiler/Lowering.ts +78 -9
- package/src/compiler/ShadowMap.ts +2 -2
- package/src/compiler/TypeSession.ts +341 -71
- package/src/compiler/appMap.ts +96 -0
- package/src/compiler/argsSchemaOf.ts +1 -1
- package/src/compiler/checkApp.ts +53 -8
- package/src/compiler/checkedShadow.ts +73 -6
- package/src/compiler/classifyRead.ts +6 -4
- package/src/compiler/classifyTemplate.ts +1 -1
- package/src/compiler/compileApp.ts +598 -200
- package/src/compiler/compiledEntry.ts +48 -40
- package/src/compiler/componentGraph.ts +57 -71
- package/src/compiler/declaredExports.ts +26 -9
- package/src/compiler/emitClient.ts +142 -72
- package/src/compiler/emitComponent.ts +41 -12
- package/src/compiler/emitSource.ts +102 -13
- package/src/compiler/handlersUnder.ts +164 -76
- package/src/compiler/hooksCalled.ts +35 -0
- package/src/compiler/index.ts +7 -0
- package/src/compiler/mountsOf.ts +150 -0
- package/src/compiler/nearest.ts +14 -0
- package/src/compiler/parseAbide.ts +157 -20
- package/src/compiler/projectShadow.ts +250 -19
- package/src/compiler/reactiveTypes.ts +20 -7
- package/src/compiler/readSources.ts +15 -2
- package/src/compiler/readsIn.ts +62 -8
- package/src/compiler/renderMarkup.ts +3 -3
- package/src/compiler/scaffold.ts +37 -15
- package/src/compiler/serveLanguage.ts +101 -34
- package/src/compiler/sharedModuleWrites.ts +2 -2
- package/src/compiler/sourceMapOf.ts +193 -0
- package/src/compiler/splitImports.ts +205 -83
- package/src/compiler/tokenize.ts +14 -0
- package/src/compiler/writeDefaultProject.ts +29 -2
- package/src/server/CLIENT_BUILD.ts +11 -13
- package/src/server/DEFAULT_PORT.ts +1 -1
- package/src/server/MODULE_QUERIES.ts +27 -0
- package/src/server/TABLES.ts +7 -0
- package/src/server/VIRTUAL_DIRECTORY.ts +4 -5
- package/src/server/abideModules.ts +457 -0
- package/src/server/answerFailure.ts +16 -6
- package/src/server/answerRequest.ts +241 -107
- package/src/server/answerRpc.ts +107 -33
- package/src/server/answerSocket.ts +65 -20
- package/src/server/answerValue.ts +22 -9
- package/src/server/appDirectories.ts +1 -1
- package/src/server/appPlugins.ts +1 -1
- package/src/server/appTree.ts +238 -0
- package/src/server/argsFor.ts +2 -2
- package/src/server/assistant.ts +2 -2
- package/src/server/bridgeRelay.ts +6 -8
- package/src/server/claudeCode.ts +0 -6
- package/src/server/clientBuildOptions.ts +8 -3
- package/src/server/coerceArgs.ts +1 -1
- package/src/server/commands.ts +38 -37
- package/src/server/componentModules.d.ts +2 -1
- package/src/server/compressAnswer.ts +4 -4
- package/src/server/config.ts +25 -29
- package/src/server/configFields.ts +31 -25
- package/src/server/createApp.ts +77 -79
- package/src/server/devWorker.ts +118 -181
- package/src/server/executableName.ts +4 -0
- package/src/server/fenceServer.ts +57 -32
- package/src/server/generatedSurfaces.ts +99 -66
- package/src/server/hooks.ts +137 -52
- package/src/server/iconAccent.ts +191 -0
- package/src/server/index.ts +12 -7
- package/src/server/installSourceMaps.ts +93 -0
- package/src/server/launcherOptions.ts +33 -31
- package/src/server/launcherPage.ts +107 -64
- package/src/server/manifestOf.ts +16 -13
- package/src/server/namesUnder.ts +2 -2
- package/src/server/nativeMenu.ts +6 -6
- package/src/server/negotiateEncoding.ts +1 -1
- package/src/server/preload.ts +13 -0
- package/src/server/principal.ts +10 -5
- package/src/server/realOrAsGiven.ts +10 -0
- package/src/server/refuseAddressCollision.ts +21 -14
- package/src/server/remote.ts +49 -52
- package/src/server/render.ts +65 -94
- package/src/server/renderComponent.ts +5 -0
- package/src/server/reportHealth.ts +2 -2
- package/src/server/requestScope.ts +12 -1
- package/src/server/runBuild.ts +24 -0
- package/src/server/runCompiled.ts +5 -5
- package/src/server/runDesktop.ts +48 -28
- package/src/server/serveBuild.ts +43 -52
- package/src/server/serveDev.ts +79 -37
- package/src/server/shell.ts +7 -22
- package/src/server/sourceMappings.ts +139 -0
- package/src/server/surfaceOf.ts +20 -3
- package/src/server/testBuild.ts +1 -19
- package/src/server/withAbideSources.ts +117 -0
- package/src/shared/ADDRESSES.ts +10 -18
- package/src/shared/APP_META.ts +7 -0
- package/src/shared/CopyOptions.ts +1 -1
- package/src/shared/DEBUG_META.ts +1 -1
- package/src/shared/LAUNCHER_FILES.ts +9 -0
- package/src/shared/RESERVED_DIRECTORIES.ts +24 -0
- package/src/shared/cookies.ts +2 -2
- package/src/shared/csp.ts +1 -1
- package/src/shared/escapeText.ts +1 -1
- package/src/shared/filledPath.ts +85 -0
- package/src/shared/framing.ts +79 -6
- package/src/shared/headKeyOf.ts +2 -0
- package/src/shared/index.ts +2 -1
- package/src/shared/log.ts +27 -13
- package/src/shared/matchRoute.ts +23 -4
- package/src/{ui → shared}/menuBridge.ts +2 -2
- package/src/shared/menuItem.ts +4 -4
- package/src/shared/onMenu.ts +58 -0
- package/src/shared/reactive/Keyed.ts +2 -2
- package/src/shared/reactive/NotLanded.ts +1 -1
- package/src/shared/reactive/Reactive.ts +43 -43
- package/src/shared/reactive/argsContract.ts +4 -4
- package/src/shared/reactive/canonicalKey.ts +4 -4
- package/src/shared/reactive/chunksOfStream.ts +6 -0
- package/src/shared/reactive/index.ts +1 -1
- package/src/shared/reactive/recordWrites.ts +103 -5
- package/src/shared/reactive/structural.ts +1 -1
- package/src/shared/reactive/tracking.ts +1 -1
- package/src/shared/reactive/watch.ts +3 -3
- package/src/shared/refuse.ts +1 -1
- package/src/{ui → shared}/reportMenu.ts +8 -10
- package/src/shared/routePattern.ts +122 -48
- package/src/shared/rpc.ts +37 -8
- package/src/shared/rpcRequest.ts +27 -3
- package/src/shared/url.ts +4 -44
- package/src/shared/writePath.ts +1 -1
- package/src/ui/LAYER_NAMES.ts +1 -1
- package/src/ui/List.ts +3 -3
- package/src/ui/MOUNTING_LEVEL.ts +18 -0
- package/src/ui/Mismatch.ts +1 -1
- package/src/ui/RawRun.ts +1 -1
- package/src/ui/Router.ts +37 -36
- package/src/ui/Scope.ts +1 -1
- package/src/ui/TryRegion.ts +2 -2
- package/src/ui/applyHead.ts +33 -0
- package/src/ui/index.ts +1 -1
- package/src/ui/moveNode.ts +1 -1
- package/src/ui/reconcile.ts +2 -2
- package/src/ui/routing.ts +2 -2
- package/src/ui/setAttribute.ts +1 -1
- package/src/ui/setText.ts +1 -1
- package/src/ui/socketWire.ts +9 -7
- package/src/ui/wire.ts +1 -1
- package/scaffold/src/server/app.ts +0 -6
- package/scaffold/src/shared/.gitkeep +0 -0
- package/scaffold/src/ui/app.html +0 -8
- package/src/server/componentImports.ts +0 -56
- /package/scaffold/src/{ui/pages/page.abide → index.abide} +0 -0
- /package/src/{compiler → shared}/SPECIFIERS.ts +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SyntaxKind } from 'typescript/unstable/ast'
|
|
2
|
+
import { COMPONENT_QUERY, TEXT_QUERY } from '#server/MODULE_QUERIES.ts'
|
|
2
3
|
import { isContextualKeyword, type Token, tokenize } from './tokenize.ts'
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -11,13 +12,12 @@ import { isContextualKeyword, type Token, tokenize } from './tokenize.ts'
|
|
|
11
12
|
* WHERE AN IMPORT BEGINS AND ENDS IS READ OFF the one tokenizer, which carries the template
|
|
12
13
|
* re-scan a bare `` ` `` needs, so an import after a template literal is still found.
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
15
|
+
* Every specifier naming a face of a `.abide` is rewritten where it stands, a static import's and an
|
|
16
|
+
* `import('…')`'s alike (`facedSpecifier`) — so the two cannot ask for different faces.
|
|
16
17
|
*/
|
|
17
18
|
export function splitImports(
|
|
18
19
|
script: string,
|
|
19
|
-
lane:
|
|
20
|
-
landed?: ReadonlyMap<string, string>,
|
|
20
|
+
lane: Lane = 'component',
|
|
21
21
|
): {
|
|
22
22
|
imports: string
|
|
23
23
|
body: string
|
|
@@ -29,18 +29,13 @@ export function splitImports(
|
|
|
29
29
|
let body = ''
|
|
30
30
|
let at = 0
|
|
31
31
|
for (const span of spans) {
|
|
32
|
+
const moved = facedSpecifier(span.specifier, lane)
|
|
32
33
|
if (span.dynamic) {
|
|
33
|
-
if (landed === undefined) continue
|
|
34
|
-
const moved = landedSpecifier(span.specifier, lane, landed)
|
|
35
34
|
if (moved === undefined) continue
|
|
36
35
|
body += `${script.slice(at, span.literalStart + 1)}${moved}`
|
|
37
36
|
at = span.end - 1
|
|
38
37
|
continue
|
|
39
38
|
}
|
|
40
|
-
const moved =
|
|
41
|
-
landed === undefined
|
|
42
|
-
? undefined
|
|
43
|
-
: landedSpecifier(span.specifier, lane, landed)
|
|
44
39
|
imports +=
|
|
45
40
|
moved === undefined
|
|
46
41
|
? `${script.slice(span.start, span.end)}\n`
|
|
@@ -50,7 +45,9 @@ export function splitImports(
|
|
|
50
45
|
// a `[` that automatic semicolon insertion does NOT break — it reads as `f()[2]`, a
|
|
51
46
|
// different program with nothing about it saying so. The empty statement left in its
|
|
52
47
|
// place is the separator the import was.
|
|
53
|
-
|
|
48
|
+
// 38.97 — and as many lines as it spanned, so the body stays line for line with the
|
|
49
|
+
// script it came from and a source map can read the one off the other.
|
|
50
|
+
body += `${script.slice(at, span.start)};${'\n'.repeat(newlinesIn(script, span.start, span.end))}`
|
|
54
51
|
at = span.end
|
|
55
52
|
// The `;` an import may end with would otherwise be left behind beside the one above.
|
|
56
53
|
if (script[at] === ';') at += 1
|
|
@@ -58,6 +55,18 @@ export function splitImports(
|
|
|
58
55
|
return { imports, body: body + script.slice(at) }
|
|
59
56
|
}
|
|
60
57
|
|
|
58
|
+
/** How many line breaks `text` holds between `start` and `end`. */
|
|
59
|
+
export function newlinesIn(text: string, start: number, end: number): number {
|
|
60
|
+
let count = 0
|
|
61
|
+
for (
|
|
62
|
+
let at = text.indexOf('\n', start);
|
|
63
|
+
at !== -1 && at < end;
|
|
64
|
+
at = text.indexOf('\n', at + 1)
|
|
65
|
+
)
|
|
66
|
+
count += 1
|
|
67
|
+
return count
|
|
68
|
+
}
|
|
69
|
+
|
|
61
70
|
// What can stand between `import` and its specifier. A token that cannot is a malformed import,
|
|
62
71
|
// and stopping there is what keeps one broken line from swallowing the statements after it.
|
|
63
72
|
//
|
|
@@ -252,7 +261,14 @@ export function abideImports(
|
|
|
252
261
|
const found: { specifier: string; closingQuote: number }[] = []
|
|
253
262
|
if (!script.includes('.abide')) return found
|
|
254
263
|
for (const span of importSpans(script))
|
|
255
|
-
if (
|
|
264
|
+
if (
|
|
265
|
+
span.specifier.endsWith('.abide') &&
|
|
266
|
+
// 34.21 — a `.abide` imported as text is its text, and no component edge.
|
|
267
|
+
(span.dynamic ||
|
|
268
|
+
!IMPORT_ATTRIBUTES.text.test(
|
|
269
|
+
script.slice(span.literalEnd, span.end),
|
|
270
|
+
))
|
|
271
|
+
)
|
|
256
272
|
found.push({
|
|
257
273
|
specifier: span.specifier,
|
|
258
274
|
closingQuote: span.literalEnd - 1,
|
|
@@ -260,40 +276,167 @@ export function abideImports(
|
|
|
260
276
|
return found
|
|
261
277
|
}
|
|
262
278
|
|
|
279
|
+
/** One name an import statement binds. */
|
|
280
|
+
export type ImportBinding = {
|
|
281
|
+
/** `default`, `*`, or the name imported — a string literal's text where one is written. */
|
|
282
|
+
imported: string
|
|
283
|
+
local: string
|
|
284
|
+
/** Under a `type` modifier, on the binding or on the whole statement: no value of its own. */
|
|
285
|
+
typeOnly: boolean
|
|
286
|
+
/** The binding as a statement's clause spells it, its own modifier kept: `type a as b`. */
|
|
287
|
+
written: string
|
|
288
|
+
}
|
|
289
|
+
|
|
263
290
|
/**
|
|
264
|
-
* Every
|
|
265
|
-
* a
|
|
291
|
+
* Every static import statement a script makes: where it stands, what it names, and each binding,
|
|
292
|
+
* read off the one tokenizer — so a comment or a line break inside the clause is trivia, as it is
|
|
293
|
+
* to bun, and never part of a name.
|
|
266
294
|
*
|
|
267
|
-
*
|
|
295
|
+
* `attributes` is the statement's text after its specifier, a `with { … }` or nothing. A statement of
|
|
296
|
+
* no shape this reads answers no bindings.
|
|
268
297
|
*/
|
|
269
|
-
export function
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
298
|
+
export function importStatements(script: string): {
|
|
299
|
+
start: number
|
|
300
|
+
end: number
|
|
301
|
+
specifier: string
|
|
302
|
+
typeOnly: boolean
|
|
303
|
+
attributes: string
|
|
304
|
+
bindings: ImportBinding[]
|
|
305
|
+
}[] {
|
|
306
|
+
const found: ReturnType<typeof importStatements> = []
|
|
307
|
+
if (!script.includes('import')) return found
|
|
308
|
+
for (const span of importSpans(script)) {
|
|
309
|
+
if (span.dynamic || !script.startsWith('import', span.start)) continue
|
|
310
|
+
const tokens = tokenize(script.slice(span.start, span.literalStart))
|
|
311
|
+
const bindings: ImportBinding[] = []
|
|
312
|
+
let at = 1
|
|
313
|
+
// `type` modifies the statement unless it is the default's own name: `import type from`.
|
|
314
|
+
const typeOnly =
|
|
315
|
+
tokens[at]?.text === 'type' &&
|
|
316
|
+
tokens[at + 1] !== undefined &&
|
|
317
|
+
tokens[at + 1]?.kind !== SyntaxKind.FromKeyword &&
|
|
318
|
+
tokens[at + 1]?.kind !== SyntaxKind.CommaToken
|
|
319
|
+
if (typeOnly) at += 1
|
|
320
|
+
const head = tokens[at]
|
|
273
321
|
if (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
322
|
+
head !== undefined &&
|
|
323
|
+
head.kind !== SyntaxKind.OpenBraceToken &&
|
|
324
|
+
head.kind !== SyntaxKind.AsteriskToken &&
|
|
325
|
+
(tokens[at + 1]?.kind === SyntaxKind.FromKeyword ||
|
|
326
|
+
tokens[at + 1]?.kind === SyntaxKind.CommaToken)
|
|
327
|
+
) {
|
|
328
|
+
bindings.push({
|
|
329
|
+
imported: 'default',
|
|
330
|
+
local: head.text,
|
|
331
|
+
typeOnly,
|
|
332
|
+
written: head.text,
|
|
333
|
+
})
|
|
334
|
+
at += tokens[at + 1]?.kind === SyntaxKind.CommaToken ? 2 : 1
|
|
335
|
+
}
|
|
336
|
+
if (tokens[at]?.kind === SyntaxKind.AsteriskToken) {
|
|
337
|
+
const local = tokens[at + 2]?.text ?? ''
|
|
338
|
+
bindings.push({
|
|
339
|
+
imported: '*',
|
|
340
|
+
local,
|
|
341
|
+
typeOnly,
|
|
342
|
+
written: `* as ${local}`,
|
|
343
|
+
})
|
|
344
|
+
} else if (tokens[at]?.kind === SyntaxKind.OpenBraceToken) {
|
|
345
|
+
// Each element between the braces: `[type] imported [as local]`.
|
|
346
|
+
let element: Token[] = []
|
|
347
|
+
for (at += 1; at < tokens.length; at += 1) {
|
|
348
|
+
const token = tokens[at] as Token
|
|
349
|
+
const closed = token.kind === SyntaxKind.CloseBraceToken
|
|
350
|
+
if (closed || token.kind === SyntaxKind.CommaToken) {
|
|
351
|
+
// `type as t` imports `type`; `type x` and `type x as y` modify.
|
|
352
|
+
const modified =
|
|
353
|
+
element[0]?.text === 'type' &&
|
|
354
|
+
(element.length === 2 || element.length === 4)
|
|
355
|
+
const name = element[modified ? 1 : 0]
|
|
356
|
+
if (name !== undefined) {
|
|
357
|
+
const imported =
|
|
358
|
+
name.kind === SyntaxKind.StringLiteral
|
|
359
|
+
? name.text.slice(1, -1)
|
|
360
|
+
: name.text
|
|
361
|
+
let written = ''
|
|
362
|
+
for (const part of element)
|
|
363
|
+
written +=
|
|
364
|
+
written === '' ? part.text : ` ${part.text}`
|
|
365
|
+
bindings.push({
|
|
366
|
+
imported,
|
|
367
|
+
local: (element.at(-1) as Token).text,
|
|
368
|
+
typeOnly: typeOnly || modified,
|
|
369
|
+
written,
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
element = []
|
|
373
|
+
if (closed) break
|
|
374
|
+
} else element.push(token)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
found.push({
|
|
378
|
+
start: span.start,
|
|
379
|
+
end: span.end,
|
|
380
|
+
specifier: span.specifier,
|
|
381
|
+
typeOnly,
|
|
382
|
+
attributes: script.slice(span.literalEnd, span.end).trim(),
|
|
383
|
+
bindings,
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
return found
|
|
280
387
|
}
|
|
281
388
|
|
|
389
|
+
// The attribute a static import is read by: as text (34.21), or as a mount of routes (23.19).
|
|
390
|
+
export const IMPORT_ATTRIBUTES = {
|
|
391
|
+
text: /\btype\s*:\s*['"]text['"]/,
|
|
392
|
+
routes: /\btype\s*:\s*['"]routes['"]/,
|
|
393
|
+
} as const
|
|
394
|
+
|
|
282
395
|
/**
|
|
283
|
-
* Every import
|
|
396
|
+
* Every static import a script makes `with { type }`: the specifier, where the statement and its
|
|
397
|
+
* literal begin, and where the attributes end.
|
|
284
398
|
*
|
|
285
|
-
*
|
|
399
|
+
* 34.21 — a text import's literal-to-end span is what the projection writes over with the specifier
|
|
400
|
+
* the checker types a `string` (`SOURCE_SUFFIX`), and the emitters ask for as text
|
|
401
|
+
* (`facedSpecifier`). 23.19 — a routes import's whole statement is what the projection blanks: a
|
|
402
|
+
* mount is read by the build off the file, and neither bun nor the checker has a module to give it.
|
|
286
403
|
*/
|
|
287
|
-
function
|
|
288
|
-
|
|
289
|
-
|
|
404
|
+
export function attributedImports(
|
|
405
|
+
script: string,
|
|
406
|
+
type: keyof typeof IMPORT_ATTRIBUTES,
|
|
407
|
+
): ImportSpan[] {
|
|
408
|
+
const found: ImportSpan[] = []
|
|
409
|
+
if (!script.includes(type)) return found
|
|
410
|
+
const attribute = IMPORT_ATTRIBUTES[type]
|
|
290
411
|
for (const span of importSpans(script))
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return
|
|
412
|
+
if (
|
|
413
|
+
!span.dynamic &&
|
|
414
|
+
attribute.test(script.slice(span.literalEnd, span.end))
|
|
415
|
+
)
|
|
416
|
+
found.push(span)
|
|
417
|
+
return found
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** What a file imported as text is written as once projected: `./X.abide` → `./X.abide.source`. */
|
|
421
|
+
export const SOURCE_SUFFIX = '.source'
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* `source` with each module specifier — of a declaration, an `export … from` or an `import('…')` —
|
|
425
|
+
* written as `rewrite` answers for it, and left as it is where `rewrite` answers `undefined`.
|
|
426
|
+
*/
|
|
427
|
+
export function respecified(
|
|
428
|
+
source: string,
|
|
429
|
+
rewrite: (specifier: string) => string | undefined,
|
|
430
|
+
): string {
|
|
431
|
+
let out = ''
|
|
432
|
+
let at = 0
|
|
433
|
+
for (const span of importSpans(source)) {
|
|
434
|
+
const written = rewrite(span.specifier)
|
|
435
|
+
if (written === undefined) continue
|
|
436
|
+
out += `${source.slice(at, span.literalStart + 1)}${written}`
|
|
437
|
+
at = span.literalEnd - 1
|
|
438
|
+
}
|
|
439
|
+
return at === 0 ? source : out + source.slice(at)
|
|
297
440
|
}
|
|
298
441
|
|
|
299
442
|
/**
|
|
@@ -305,62 +448,41 @@ export function importedNames(
|
|
|
305
448
|
script: string,
|
|
306
449
|
): { name: string; specifier: string }[] {
|
|
307
450
|
const names: { name: string; specifier: string }[] = []
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
for (let at = 1; at < last; at += 1) {
|
|
316
|
-
const token = tokens[at] as Token
|
|
317
|
-
if (token.kind === SyntaxKind.FromKeyword) break
|
|
318
|
-
if (token.kind === SyntaxKind.AsKeyword) continue
|
|
319
|
-
if (
|
|
320
|
-
token.kind !== SyntaxKind.Identifier &&
|
|
321
|
-
!isContextualKeyword(token.kind)
|
|
322
|
-
)
|
|
323
|
-
continue
|
|
324
|
-
const next = tokens[at + 1] as Token
|
|
325
|
-
// A name followed by `as` is renamed, and a modifier is followed by what it modifies.
|
|
326
|
-
if (next.kind === SyntaxKind.AsKeyword) continue
|
|
327
|
-
if (
|
|
328
|
-
token.text === 'type' &&
|
|
329
|
-
(next.kind === SyntaxKind.Identifier ||
|
|
330
|
-
next.kind === SyntaxKind.OpenBraceToken ||
|
|
331
|
-
next.kind === SyntaxKind.AsteriskToken ||
|
|
332
|
-
isContextualKeyword(next.kind))
|
|
333
|
-
)
|
|
334
|
-
continue
|
|
335
|
-
names.push({ name: token.text, specifier })
|
|
336
|
-
}
|
|
337
|
-
}
|
|
451
|
+
// `import …` alone — an `export … from` binds nothing in the script it stands in.
|
|
452
|
+
for (const declaration of importStatements(script))
|
|
453
|
+
for (const binding of declaration.bindings)
|
|
454
|
+
names.push({
|
|
455
|
+
name: binding.local,
|
|
456
|
+
specifier: declaration.specifier,
|
|
457
|
+
})
|
|
338
458
|
return names
|
|
339
459
|
}
|
|
340
460
|
|
|
341
461
|
/**
|
|
342
|
-
*
|
|
343
|
-
*
|
|
462
|
+
* The face of a `.abide` a specifier asks for, spelled on it (`MODULE_QUERIES`), or `undefined`
|
|
463
|
+
* where the specifier stays as written.
|
|
344
464
|
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
* WHERE that module is, is the build's: `landed` maps a specifier to the path, relative and
|
|
348
|
-
* without an extension, the build resolved it to (`componentGraph`) — and a `.abide` specifier it
|
|
349
|
-
* does not map keeps the path as written.
|
|
465
|
+
* 34.14 — EVERY OTHER SPECIFIER STAYS AS WRITTEN: the compiled module is served at its `.abide`'s
|
|
466
|
+
* own path, so bun resolves a relative, a `#` or a bare specifier from the file that wrote it.
|
|
350
467
|
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
468
|
+
* 32.17 — a component block's `.abide` import is of the module that file compiles to; a server
|
|
469
|
+
* block's stays bare, for the face a module abide did not compile gets (34.16). 34.21 — a file the
|
|
470
|
+
* projection marked as text is asked for as its text.
|
|
353
471
|
*/
|
|
354
|
-
function
|
|
355
|
-
specifier
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
return `${landed.get(specifier) ?? specifier.slice(0, -'.abide'.length)}${lane}.ts`
|
|
361
|
-
return landed.get(specifier)
|
|
472
|
+
function facedSpecifier(specifier: string, lane: Lane): string | undefined {
|
|
473
|
+
if (specifier.endsWith(SOURCE_SUFFIX))
|
|
474
|
+
return `${specifier.slice(0, -SOURCE_SUFFIX.length)}${TEXT_QUERY}`
|
|
475
|
+
if (lane === 'component' && specifier.endsWith('.abide'))
|
|
476
|
+
return `${specifier}${COMPONENT_QUERY}`
|
|
477
|
+
return undefined
|
|
362
478
|
}
|
|
363
479
|
|
|
480
|
+
/**
|
|
481
|
+
* Which face a block's imports are written for: a component's — its `<script>` and
|
|
482
|
+
* `<script module>`, in either lane — or a server block's, whose `.abide` imports stay bare (34.16).
|
|
483
|
+
*/
|
|
484
|
+
export type Lane = 'component' | 'source'
|
|
485
|
+
|
|
364
486
|
/**
|
|
365
487
|
* `imports` with `props` dropped from the abide import that names it.
|
|
366
488
|
*
|
package/src/compiler/tokenize.ts
CHANGED
|
@@ -31,6 +31,20 @@ export function tokenize(source: string): Token[] {
|
|
|
31
31
|
return tokens
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Whether a token of the code scanned from `from` begins at `at` — false where `at` is inside a
|
|
36
|
+
* string, a template, a regex or a comment.
|
|
37
|
+
*/
|
|
38
|
+
export function startsToken(source: string, from: number, at: number): boolean {
|
|
39
|
+
let found = false
|
|
40
|
+
scan(source, from, (_kind, scanner) => {
|
|
41
|
+
const start = scanner.getTokenStart()
|
|
42
|
+
found = start === at
|
|
43
|
+
return start < at
|
|
44
|
+
})
|
|
45
|
+
return found
|
|
46
|
+
}
|
|
47
|
+
|
|
34
48
|
/**
|
|
35
49
|
* How far `kind` moves the bracket depth a top-level separator is found at: a template's
|
|
36
50
|
* substitutions nest as a call's arguments do, so `${a, b}` holds its comma.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// 38.
|
|
1
|
+
// 38.30 — AN APP WITH NO tsconfig.json IS TYPED AGAINST ONE OF ABIDE'S, not read untyped. The checker
|
|
2
2
|
// is what tells the compiler which names are `Reactive`s (31.3), and with no project to ask every
|
|
3
3
|
// template position read as static: the server painted an unlanded read by throwing out of the
|
|
4
4
|
// render (41.4), the client never bound a slot at all, and `props` went unseen — an app that built
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
import { mkdtemp, rm } from 'node:fs/promises' // bun exposes no temporary-directory creation or recursive removal of its own
|
|
12
12
|
import { tmpdir } from 'node:os' // bun exposes no temporary-directory path of its own
|
|
13
|
+
import { dirname, resolve } from 'node:path' // bun exposes no path helpers of its own
|
|
14
|
+
import { nearest } from './nearest.ts'
|
|
13
15
|
|
|
14
16
|
/** What every abide app in the docs and the scaffold is written against. */
|
|
15
17
|
export const COMPILER_OPTIONS = {
|
|
@@ -31,7 +33,7 @@ export async function writeDefaultProject(
|
|
|
31
33
|
await Bun.write(
|
|
32
34
|
tsconfig,
|
|
33
35
|
JSON.stringify({
|
|
34
|
-
compilerOptions: COMPILER_OPTIONS,
|
|
36
|
+
compilerOptions: { ...COMPILER_OPTIONS, paths: await aliasPaths(root) },
|
|
35
37
|
include: [`${root}/**/*`],
|
|
36
38
|
}),
|
|
37
39
|
)
|
|
@@ -40,3 +42,28 @@ export async function writeDefaultProject(
|
|
|
40
42
|
remove: () => rm(directory, { recursive: true, force: true }),
|
|
41
43
|
}
|
|
42
44
|
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The app's `imports` aliases as `paths`, each target absolute.
|
|
48
|
+
*
|
|
49
|
+
* 34.21 — the checker resolves a text import's declaration beside the file a path names, and takes
|
|
50
|
+
* an `imports` target as the exact file, so `#src/x.abide` imported as text is found through these.
|
|
51
|
+
*/
|
|
52
|
+
async function aliasPaths(root: string): Promise<Record<string, string[]>> {
|
|
53
|
+
const paths: Record<string, string[]> = {}
|
|
54
|
+
const manifest = nearest(root, 'package.json')
|
|
55
|
+
if (manifest === undefined) return paths
|
|
56
|
+
const imports = (
|
|
57
|
+
(await Bun.file(manifest).json()) as {
|
|
58
|
+
imports?: Record<string, unknown>
|
|
59
|
+
}
|
|
60
|
+
).imports
|
|
61
|
+
const base = dirname(manifest)
|
|
62
|
+
for (const [key, targets] of Object.entries(imports ?? {})) {
|
|
63
|
+
const resolved: string[] = []
|
|
64
|
+
for (const target of [targets].flat())
|
|
65
|
+
if (typeof target === 'string') resolved.push(resolve(base, target))
|
|
66
|
+
if (resolved.length > 0) paths[key] = resolved
|
|
67
|
+
}
|
|
68
|
+
return paths
|
|
69
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// WHERE THE BROWSER BUNDLE LANDS, named once for the commands that have to agree about it:
|
|
2
2
|
// `abide build` writes it, `abide start` answers it, and `abide dev` builds it into memory
|
|
3
|
-
// (38.
|
|
3
|
+
// (38.16, 38.20). A leaf, with no imports of its own, for the reason every constant crossing a
|
|
4
4
|
// seam is one — the build is in `bin/` and the server is in `src/server/`, and a string spelled out
|
|
5
5
|
// in both is a build that silently serves nothing. The addresses it is answered at are
|
|
6
6
|
// `#shared/ADDRESSES.ts`.
|
|
@@ -24,10 +24,16 @@ export const CLIENT_ENTRY = 'entry'
|
|
|
24
24
|
*
|
|
25
25
|
* 38.5 — the manifest `manifestOf` reads off the bundle, below the build's own output directory
|
|
26
26
|
* and BESIDE the client directory rather than in it: everything in there is answered under
|
|
27
|
-
* `CLIENT_ADDRESS` (38.
|
|
27
|
+
* `CLIENT_ADDRESS` (38.20), and the manifest is the server's, not the browser's.
|
|
28
28
|
*/
|
|
29
29
|
export const MANIFEST = 'manifest.json'
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* The compilation `abide build` keeps beside the client directory: every `.abide` compiled, and
|
|
33
|
+
* the build's own tables (`Compilation`), which `abide start` serves without the compiler.
|
|
34
|
+
*/
|
|
35
|
+
export const COMPILATION = 'compilation.json'
|
|
36
|
+
|
|
31
37
|
/**
|
|
32
38
|
* The encodings a response can be compressed in, each with its precompressed file's suffix.
|
|
33
39
|
*
|
|
@@ -39,18 +45,10 @@ export const MANIFEST = 'manifest.json'
|
|
|
39
45
|
export const ENCODING_SUFFIX = { br: '.br', gzip: '.gz' } as const
|
|
40
46
|
|
|
41
47
|
/**
|
|
42
|
-
* Where `abide build` copies `
|
|
48
|
+
* Where `abide build` copies every `public` directory, each file at its address, below its own
|
|
49
|
+
* output directory. The source's own name for one is `#shared/RESERVED_DIRECTORIES.ts`'s.
|
|
43
50
|
*
|
|
44
|
-
* 38.
|
|
51
|
+
* 38.18 — a directory rather than the source tree: `abide start` boots what the build produced and
|
|
45
52
|
* reaches for nothing an app might have moved since.
|
|
46
53
|
*/
|
|
47
54
|
export const PUBLIC_DIRECTORY = 'public'
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Where the build puts the module a browser gets in place of each handler module.
|
|
51
|
-
*
|
|
52
|
-
* 16.28 — below the build's own output: the source's path, with this in front. One lookup for the
|
|
53
|
-
* client bundle to make (`fenceServer`), and a place a reader can open to see exactly what the
|
|
54
|
-
* browser was handed.
|
|
55
|
-
*/
|
|
56
|
-
export const WIRE_DIRECTORY = 'wire'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The port an app listens on where neither `--port` nor `PORT` names one: `Bun.serve`'s own default,
|
|
3
|
-
* so `abide start`'s, the first `abide dev` tries (38.
|
|
3
|
+
* so `abide start`'s, the first `abide dev` tries (38.63), and the one a command reaching the local
|
|
4
4
|
* app calls.
|
|
5
5
|
*/
|
|
6
6
|
export const DEFAULT_PORT = 3000
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// WHAT AN IMPORT OF A `.abide` OR A HANDLER FILE ASKS FOR, spelled on the specifier: bun hands a
|
|
2
|
+
// plugin no import attributes, so the compiler writes the face it wants where the plugin can read it
|
|
3
|
+
// (`abideModules`). A `.abide` named with none is the face a module abide did not compile gets.
|
|
4
|
+
|
|
5
|
+
/** The module the file compiles to, as a compiled module imports it. 32.17. */
|
|
6
|
+
export const COMPONENT_QUERY = '?component'
|
|
7
|
+
|
|
8
|
+
/** The file's scoped stylesheet, which its browser module imports. 34.4. */
|
|
9
|
+
export const STYLE_QUERY = '?style'
|
|
10
|
+
|
|
11
|
+
/** The file's text. 34.21. */
|
|
12
|
+
export const TEXT_QUERY = '?text'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The file's browser module, asked for outside a browser bundle — a component's hydration tested
|
|
16
|
+
* under a DOM in `bun test`. Its own `.abide` imports are browser modules too.
|
|
17
|
+
*/
|
|
18
|
+
export const CLIENT_QUERY = '?client'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* What a handler file is imported as: its own path with this after it, so the plugin can serve it
|
|
22
|
+
* between the calls that file its hooks (37.1) and a browser its wrapper (16.74). A stack frame names
|
|
23
|
+
* the file without it (38.97).
|
|
24
|
+
*/
|
|
25
|
+
export const HANDLER_SUFFIX = '.abide-handler'
|
|
26
|
+
/** A path that is a handler file's face. */
|
|
27
|
+
export const HANDLER_FACE = new RegExp(`${RegExp.escape(HANDLER_SUFFIX)}$`)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The build's own modules below `__abide` (`abideModules`), beside the client entry (`CLIENT_ENTRY`).
|
|
2
|
+
|
|
3
|
+
/** The server's tables: routes, error pages, server blocks and handlers. */
|
|
4
|
+
export const SERVER_TABLE = 'server.ts'
|
|
5
|
+
|
|
6
|
+
/** The client router's table, and the `start` the entry calls. */
|
|
7
|
+
export const ROUTES_TABLE = 'routes.ts'
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// Where
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// for the reason the addresses are: no app writes one.
|
|
1
|
+
// Where the build's own modules — the server's tables, the router's and the client entry — are served
|
|
2
|
+
// from, below the app's root (`abideModules`). Under the root so a bare specifier in them resolves as
|
|
3
|
+
// one in the app does, and under a `__abide` directory for the reason the addresses are: no app writes
|
|
4
|
+
// one.
|
|
6
5
|
export const VIRTUAL_DIRECTORY = '__abide'
|