mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
feat: add taglib extensions and type definitions for typescript support (#1885)
* feat: add taglib extensions and type definitions for typescript support * [ci] format --------- Co-authored-by: DylanPiercey <DylanPiercey@users.noreply.github.com>
This commit is contained in:
parent
aaa1a91ccc
commit
f1efd707aa
7
.changeset/gentle-foxes-knock.md
Normal file
7
.changeset/gentle-foxes-knock.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
"@marko/translator-default": patch
|
||||
"@marko/compiler": patch
|
||||
"marko": patch
|
||||
---
|
||||
|
||||
Add taglib extensions and type definitions for typescript support.
|
||||
7
packages/babel-utils/index.d.ts
vendored
7
packages/babel-utils/index.d.ts
vendored
@ -70,8 +70,11 @@ export interface TagDefinition {
|
||||
|
||||
export interface TaglibLookup {
|
||||
getTagsSorted(): TagDefinition[];
|
||||
getTag(tagName: string): TagDefinition;
|
||||
getAttribute(tagName: string, attrName: string): AttributeDefinition;
|
||||
getTag(tagName: string): undefined | TagDefinition;
|
||||
getAttribute(
|
||||
tagName: string,
|
||||
attrName: string
|
||||
): undefined | AttributeDefinition;
|
||||
forEachAttribute(
|
||||
tagName: string,
|
||||
callback: (attr: AttributeDefinition, tag: TagDefinition) => void
|
||||
|
||||
@ -14,6 +14,7 @@ class Tag {
|
||||
this.attributes = {};
|
||||
this.transformers = [];
|
||||
this.patternAttributes = [];
|
||||
this.types = undefined;
|
||||
}
|
||||
|
||||
addAttribute(attr) {
|
||||
|
||||
@ -30,6 +30,7 @@ class Taglib {
|
||||
ok(filePath, '"filePath" expected');
|
||||
this.filePath = this.path /* deprecated */ = this.id = filePath;
|
||||
this.dirname = path.dirname(this.filePath);
|
||||
this.scriptLang = undefined;
|
||||
this.tags = {};
|
||||
this.migrators = [];
|
||||
this.transformers = [];
|
||||
|
||||
@ -322,6 +322,17 @@ class TagLoader {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This property is used by @marko/language-tools (editor tooling)
|
||||
* to override the Marko file used when generating the tags exposed
|
||||
* typescript / jsdoc types.
|
||||
*/
|
||||
types(value) {
|
||||
var tag = this.tag;
|
||||
var dirname = this.dirname;
|
||||
tag.types = nodePath.resolve(dirname, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* An Object where each property maps to an attribute definition.
|
||||
* The property key will be the attribute name and the property value
|
||||
|
||||
@ -210,6 +210,12 @@ class TaglibLoader {
|
||||
}
|
||||
}
|
||||
}
|
||||
scriptLang(lang) {
|
||||
// The "script-lang" property is used to specify the language of embedded scripts (either "js" or "ts").
|
||||
// The language tools will prefer the language specified by the "script-lang" if specified.
|
||||
// If unspecified the language tools will check for a tsconfig, if one is found then "ts", otherwise we use "js".
|
||||
this.taglib.scriptLang = lang;
|
||||
}
|
||||
tagsDir(dir) {
|
||||
// The "tags-dir" property is used to supporting scanning
|
||||
// of a directory to discover custom tags. Scanning a directory
|
||||
|
||||
317
packages/marko/index.d.ts
vendored
Normal file
317
packages/marko/index.d.ts
vendored
Normal file
@ -0,0 +1,317 @@
|
||||
declare module "*.marko" {
|
||||
const template: Marko.Template;
|
||||
export default template;
|
||||
}
|
||||
|
||||
declare namespace NodeJS {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface ReadableStream {}
|
||||
}
|
||||
|
||||
declare namespace Marko {
|
||||
/** A mutable global object for the current render. */
|
||||
export interface Global {
|
||||
serializedGlobals?: Record<string, boolean>;
|
||||
[attr: PropertyKey]: unknown;
|
||||
}
|
||||
|
||||
export type TemplateInput<Input = { [attr: PropertyKey]: any }> = Input & {
|
||||
$global?: Global;
|
||||
};
|
||||
|
||||
export interface Out<Component extends Marko.Component = Marko.Component>
|
||||
extends PromiseLike<RenderResult<Component>> {
|
||||
/** The underlying ReadableStream Marko is writing into. */
|
||||
stream: unknown;
|
||||
/** A mutable global object for the current render. */
|
||||
global: Global;
|
||||
/** Disable all async rendering. Will error if something beings async. */
|
||||
sync(): void;
|
||||
/** Returns true if async rendering is disabled. */
|
||||
isSync(): boolean;
|
||||
/** Write unescaped text at the current stream position. */
|
||||
write(val: string | void): this;
|
||||
/** Write javascript content to be merged with the scripts Marko sends out on the next flush. */
|
||||
script(val: string | void): this;
|
||||
/** Returns the currently rendered html content. */
|
||||
toString(): string;
|
||||
/** Starts a new async/forked stream. */
|
||||
beginAsync(options?: {
|
||||
name?: string;
|
||||
timeout?: number;
|
||||
last?: boolean;
|
||||
}): Out;
|
||||
/** Marks the current stream as complete (async streams may still be executing). */
|
||||
end(val?: string | void): this;
|
||||
emit(eventName: PropertyKey, ...args: any[]): boolean;
|
||||
on(eventName: PropertyKey, listener: (...args: any[]) => any): this;
|
||||
once(eventName: PropertyKey, listener: (...args: any[]) => any): this;
|
||||
prependListener(
|
||||
eventName: PropertyKey,
|
||||
listener: (...args: any[]) => any
|
||||
): this;
|
||||
removeListener(
|
||||
eventName: PropertyKey,
|
||||
listener: (...args: any[]) => any
|
||||
): this;
|
||||
/** Register a callback executed when the last async out has completed. */
|
||||
onLast(listener: (next: () => void) => unknown): this;
|
||||
/** Pipe Marko's stream to another stream. */
|
||||
pipe(stream: unknown): this;
|
||||
/** Emits an error on the stream. */
|
||||
error(e: Error): this;
|
||||
/** Schedules a Marko to flush buffered html to the underlying stream. */
|
||||
flush(): this;
|
||||
/** Creates a detached out stream (used for out of order flushing). */
|
||||
createOut(): Out;
|
||||
/** Write escaped text at the current stream position. */
|
||||
text(val: string | void): void;
|
||||
}
|
||||
|
||||
/** Body content created from by a component, typically held in an object with a renderBody property. */
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface Body<
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
in Params extends readonly any[] = [],
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
out Return = void
|
||||
> {}
|
||||
|
||||
/** Valid data types which can be passed in as a <${dynamic}/> tag name. */
|
||||
export type DynamicTagName =
|
||||
| {
|
||||
renderBody?: Body<any, any> | Template | string | void | false;
|
||||
}
|
||||
| Body<any, any>
|
||||
| Template
|
||||
| string
|
||||
| void
|
||||
| false;
|
||||
|
||||
/** Extract the return tag type from a renderBody. */
|
||||
export type BodyReturnType<B> = B extends Body<any, infer Return>
|
||||
? Return
|
||||
: never;
|
||||
|
||||
/** Extract the tag parameter types received by a renderBody. */
|
||||
export type BodyParamaters<B> = B extends Body<infer Params, any>
|
||||
? Params
|
||||
: never;
|
||||
|
||||
export abstract class Component<
|
||||
Input extends Record<PropertyKey, any> = Record<PropertyKey, any>
|
||||
> implements Emitter
|
||||
{
|
||||
/** A unique id for this instance. */
|
||||
public readonly id: string;
|
||||
/** The top level element rendered by this instance. */
|
||||
public readonly el: Element | void;
|
||||
/** The attributes passed to this instance. */
|
||||
public readonly input: Input;
|
||||
/** @deprecated */
|
||||
public readonly els: Element[];
|
||||
/** Mutable state that when changed causes a rerender. */
|
||||
abstract state: undefined | null | Record<PropertyKey, any>;
|
||||
|
||||
/** Returns the amount of event handlers listening to a specific event. */
|
||||
listenerCount(eventName: PropertyKey): number;
|
||||
/**
|
||||
* Used to wrap an existing event emitted and ensure that all events are
|
||||
* cleaned up once this component is destroyed
|
||||
* */
|
||||
subscribeTo(
|
||||
emitter: unknown
|
||||
): Omit<Emitter, "listenerCount" | "prependListener" | "emit">;
|
||||
/** Emits an event on the component instance. */
|
||||
emit(eventName: PropertyKey, ...args: any[]): boolean;
|
||||
/** Listen to an event on the component instance. */
|
||||
on(eventName: PropertyKey, listener: (...args: any[]) => any): this;
|
||||
/** Listen to an event on the component instance once. */
|
||||
once(eventName: PropertyKey, listener: (...args: any[]) => any): this;
|
||||
/** Listen to an event on the component instance before all other listeners. */
|
||||
prependListener(
|
||||
eventName: PropertyKey,
|
||||
listener: (...args: any[]) => any
|
||||
): this;
|
||||
/** Remove a listener from the component instance. */
|
||||
removeListener(
|
||||
eventName: PropertyKey,
|
||||
listener: (...args: any[]) => any
|
||||
): this;
|
||||
/** Remove all listeners from the component instance. */
|
||||
removeAllListeners(eventName?: PropertyKey): this;
|
||||
/** Removes the component instance from the DOM and cleans up all active event handlers including all children. */
|
||||
destroy(): void;
|
||||
/** Schedule an update (similar to if a state had been changed). */
|
||||
forceUpdate(): void;
|
||||
/** Generates a unique id derived from the current unique instance id (similar to :scoped in the template). */
|
||||
elId(key?: string, index?: number): string;
|
||||
/** @alias elId */
|
||||
getElId(key?: string, index?: number): string;
|
||||
/** Gets an element reference by its `key` attribute in the template. */
|
||||
getEl<T extends Element | void = Element | void>(
|
||||
key?: string,
|
||||
index?: number
|
||||
): T;
|
||||
/** Gets all element references by their `key` attribute in the template. */
|
||||
getEls<T extends Element[] = Element[]>(key: string): T;
|
||||
/** Gets a component reference by its `key` attribute in the template. */
|
||||
getComponent<T extends Component | void = Component | void>(
|
||||
key: string,
|
||||
index?: number
|
||||
): T;
|
||||
/** Gets all component references by their `key` attribute in the template. */
|
||||
getComponents<T extends Component[] = Component[]>(key: string): T;
|
||||
/** True if this instance has been removed from the dom. */
|
||||
/** True if this instance is scheduled to rerender. */
|
||||
isDestroyed(): boolean;
|
||||
/** Replace the entire state object with a new one, removing old properties. */
|
||||
replaceState(state: this["state"]): void;
|
||||
/**
|
||||
* Update a property on this.state (should prefer mutating this.state directly).
|
||||
* When passed an object as the first argument, it will be merged into the state.
|
||||
*/
|
||||
setState<Key extends PropertyKey>(
|
||||
name: Key & keyof this["state"],
|
||||
value: (this["state"] & Record<PropertyKey, unknown>)[Key]
|
||||
): void;
|
||||
setState(value: Partial<this["state"]>): void;
|
||||
|
||||
/** Schedules an update related to a specific state property and optionally updates the value. */
|
||||
setStateDirty<Key extends PropertyKey>(
|
||||
name: Key & keyof this["state"],
|
||||
value?: (this["state"] & Record<PropertyKey, unknown>)[Key]
|
||||
): void;
|
||||
/** Synchronously flush any scheduled updates. */
|
||||
update(): void;
|
||||
/** Appends the dom for the current instance to a parent DOM element. */
|
||||
appendTo(target: ParentNode): this;
|
||||
/** Inserts the dom for the current instance after a sibling DOM element. */
|
||||
insertAfter(target: ChildNode): this;
|
||||
/** Inserts the dom for the current instance before a sibling DOM element. */
|
||||
insertBefore(target: ChildNode): this;
|
||||
/** Prepends the dom for the current instance to a parent DOM element. */
|
||||
prependTo(target: ParentNode): this;
|
||||
/** Replaces an existing DOM element with the dom for the current instance. */
|
||||
replace(target: ChildNode): this;
|
||||
/** Replaces the children of an existing DOM element with the dom for the current instance. */
|
||||
replaceChildrenOf(target: ParentNode): this;
|
||||
/** Called when the component is firsted created. */
|
||||
abstract onCreate?(input: this["input"], out: Marko.Out): void;
|
||||
/** Called every time the component receives input from it's parent. */
|
||||
abstract onInput?(
|
||||
input: this["input"],
|
||||
out: Marko.Out
|
||||
): void | this["input"];
|
||||
/** Called after a component has successfully rendered, but before it's update has been applied to the dom. */
|
||||
abstract onRender?(out: Marko.Out): void;
|
||||
/** Called after the first time the component renders and is attached to the dom. */
|
||||
abstract onMount?(): void;
|
||||
/** Called when a components render has been applied to the DOM (excluding when it is initially mounted). */
|
||||
abstract onUpdate?(): void;
|
||||
/** Called when a component is destroyed and removed from the dom. */
|
||||
abstract onDestroy?(): void;
|
||||
}
|
||||
|
||||
/** The top level api for a Marko Template. */
|
||||
export abstract class Template {
|
||||
/** Creates a Marko compatible output stream. */
|
||||
createOut(): Out;
|
||||
|
||||
/**
|
||||
* The folowing types are processed up by the @marko/language-tools
|
||||
* and inlined into the compiled template.
|
||||
*
|
||||
* This is done to support generics on each of these methods
|
||||
* until TypeScript supports higher kinded types.
|
||||
*
|
||||
* https://github.com/microsoft/TypeScript/issues/1213
|
||||
*/
|
||||
|
||||
/** @marko-overload-start */
|
||||
/** Asynchronously render the template. */
|
||||
abstract render(
|
||||
input: Marko.TemplateInput,
|
||||
stream?: {
|
||||
write: (chunk: string) => void;
|
||||
end: (chunk?: string) => void;
|
||||
}
|
||||
): Marko.Out<Marko.Component>;
|
||||
|
||||
/** Synchronously render the template. */
|
||||
abstract renderSync(
|
||||
input: Marko.TemplateInput
|
||||
): Marko.RenderResult<Marko.Component>;
|
||||
|
||||
/** Synchronously render a template to a string. */
|
||||
abstract renderToString(input: Marko.TemplateInput): string;
|
||||
|
||||
/** Render a template and return a stream.Readable in nodejs or a ReadableStream in a web worker environment. */
|
||||
abstract stream(
|
||||
input: Marko.TemplateInput
|
||||
): ReadableStream<string> & NodeJS.ReadableStream;
|
||||
/** @marko-overload-end */
|
||||
}
|
||||
|
||||
export interface RenderResult<
|
||||
out Component extends Marko.Component = Marko.Component
|
||||
> {
|
||||
/** Returns the component created as a result of rendering the template. */
|
||||
getComponent(): Component;
|
||||
getComponents(selector?: any): any;
|
||||
/** Triggers the mount lifecycle of a component without necessarily attaching it to the DOM. */
|
||||
afterInsert(host?: any): this;
|
||||
/** Gets the DOM node rendered by a template. */
|
||||
getNode(host?: any): Node;
|
||||
/** Gets the HTML output of the rendered template. */
|
||||
toString(): string;
|
||||
/** Appends the dom of the rendered template to a parent DOM element. */
|
||||
appendTo(target: ParentNode): this;
|
||||
/** Inserts the dom of the rendered template after a sibling DOM element. */
|
||||
insertAfter(target: ChildNode): this;
|
||||
/** Inserts the dom of the rendered template before a sibling DOM element. */
|
||||
insertBefore(target: ChildNode): this;
|
||||
/** Prepends the dom of the rendered template to a parent DOM element. */
|
||||
prependTo(target: ParentNode): this;
|
||||
/** Replaces an existing DOM element with the dom of the rendered template. */
|
||||
replace(target: ChildNode): this;
|
||||
/** Replaces the children of an existing DOM element with the dom of the rendered template. */
|
||||
replaceChildrenOf(target: ParentNode): this;
|
||||
out: Out<Component>;
|
||||
/** @deprecated */
|
||||
document: any;
|
||||
/** @deprecated */
|
||||
getOutput(): string;
|
||||
/** @deprecated */
|
||||
html: string;
|
||||
/** @deprecated */
|
||||
context: Out<Component>;
|
||||
}
|
||||
|
||||
export interface Emitter {
|
||||
listenerCount(eventName: PropertyKey): number;
|
||||
emit(eventName: PropertyKey, ...args: any[]): boolean;
|
||||
on(eventName: PropertyKey, listener: (...args: any[]) => any): this;
|
||||
once(eventName: PropertyKey, listener: (...args: any[]) => any): this;
|
||||
prependListener(
|
||||
eventName: PropertyKey,
|
||||
listener: (...args: any[]) => any
|
||||
): this;
|
||||
removeListener(
|
||||
eventName: PropertyKey,
|
||||
listener: (...args: any[]) => any
|
||||
): this;
|
||||
removeAllListeners(eventName?: PropertyKey): this;
|
||||
}
|
||||
|
||||
export type Repeated<T> = [T, T, ...T[]];
|
||||
export type Repeatable<T> = T | Repeated<T>;
|
||||
export type MaybeRepeatable<T> = undefined | Repeatable<T>;
|
||||
|
||||
export interface NativeTags {
|
||||
[name: string]: {
|
||||
input: Record<string, unknown>;
|
||||
return: unknown;
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -71,5 +71,6 @@
|
||||
"index-browser.marko",
|
||||
"index.js",
|
||||
"node-require.js"
|
||||
]
|
||||
],
|
||||
"types": "index.d.ts"
|
||||
}
|
||||
|
||||
13
packages/marko/src/core-tags/core/await/index.marko
Normal file
13
packages/marko/src/core-tags/core/await/index.marko
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{
|
||||
* value?: T;
|
||||
* then?: Marko.Body<[Awaited<T>], void>;
|
||||
* catch?: Marko.Body<[unknown], void>;
|
||||
* placeholder?: Marko.Body<[], void>;
|
||||
* client-reorder?: boolean;
|
||||
* name?: string;
|
||||
* timeout?: number;
|
||||
* show-after?: string;
|
||||
* }} Input
|
||||
*/
|
||||
@ -284,6 +284,7 @@ export default {
|
||||
},
|
||||
"<await>": {
|
||||
renderer: "marko/src/core-tags/core/await/renderer.js",
|
||||
types: "marko/src/core-tags/core/await/index.marko",
|
||||
"code-generator": translateAwait,
|
||||
"@_provider": "expression",
|
||||
"@_name": "string",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user