Skip to content

Hooks to manipulate the Svelte compiler #11611

@adiguba

Description

@adiguba

Describe the problem

As Svelte is a compiler, it would be nice to allow developper to enhance it by providing some hooks.
This will allow to add some specific requirement at compile time, and specific warning/error.

For exemple, I wrote a component MyComp and I want to handle some specific warning/error :

<MyComp format="small" data={data} /> <!-- Warning : 'data' is deprecated, use 'value' instead -->

<MyComp format="mini" /> <!-- Warning : Bad format.  -->

<MyComp data={data} /> <!-- Error : format is required -->

Or another FancyList component that should only accept <li> or <FancyItem> :

<FancyList>
    <li>One item</li>
    <FancyItem>Another Item</FancyItem>
</FancyList> <!-- OK -->

<FancyList>
    One text <!-- ERROR : text is not allowed here -->
    <li>One item</li>
    <div> <!--ERROR : <div> is not allowed here -->
    <FancyItem>Another Item</FancyItem>
    <FancyList>Content</FancyList> <!-- ERROR : <FancyList> is not allowed here -->
</FancyList> <!-- OK -->

I would be nice if Svelte (6 ?) allow us to add these sort of hooks into the compiler.

I know that some checks can be done via TypeScript, but this could allow more control.

Describe the proposed solution

We could use a <script context="compiler"> for that (or a specific method in context="module").

The code inside this tag will only be used at compile time by the compiler, and will not be included in the generated code (client or server).

A special variable $$tag will allow us to manipulate the compiler, in order to display messages/errors :

// MyComp.svelte
<script context="compiler">

	$$tag.error("Display an error message on the entire component, and make the compilation fail");
	$$tag.warn("Display an warning message on the entire component");
	$$tag.info("Display an info message on the entire component");

	$$tag.error("Display an error message on the prop 'data', and make the compilation fail", 'data');
	$$tag.warn("Display an warning message on the prop 'data'", 'data');
	$$tag.info("Display an info message on the prop 'data'", 'data');

</script>

The $$tag should also contain utility methods to check the status of the props :

// MyComp.svelte
<script context="compiler" lang="ts">

	// Get the name of all the props passed
	const propsNames : string[] = $$tags.props();

	if ($$tag.is_present('data')) {
		// true if the prop 'data' is present on the component's tag
		// Ex:
		// 	<MyComp data="data" />
		// 	<MyComp data={data} />
		// 	<MyComp {data} />
		// 	<MyComp data />
		// 	<MyComp data="data" {...props}/>
		// 	<MyComp {...props} data="data" />
	}

	if ($$tag.is_missing('data')) {
		// true if the prop 'data' is not directly present on the component's tag
		// Ex:
		// 	<MyComp />
		// 	<MyComp {...props} />
	}

	if ($$tag.has_spread()) {
		// true if the component's tag has at least one spread
		// Ex:
		//	<MyComp {...props} />
	}

	if ($$tag.is_spreadable('data')) {
		// true if the component's tag has at least on spread,
		// and the prop 'data' can be set by the spread
		// Ex:
		//	<MyComp {...props} />
		//	<MyComp data="data" {...props} />
		//
		// But false for :
		//	<MyComp {...props} data="data" />
	}

	if ($$tag.is_constant('data')) {
		// true if the prop 'data' is present and has a constant value
		// Ex:
		// 	<MyComp data="data" />
		// 	<MyComp data={false} />
		// 	<MyComp data={true} />
		// 	<MyComp data={15} />
		// 	<MyComp data={null} />
		// 	<MyComp data={undefined} />
		// 	<MyComp data={"data"} />
		//
		// But false for :
		//	<MyComp data={data} />
		//	<MyComp bind:data={data} />
		//	<MyComp data="text with {data}" />
	}


	if ($$tag.is_constant('data', ['a', 'b', 'c'])) {
		// true if the prop 'data' is present and has a constant value
		// which corresponds to one of the values
		// Ex:
		// 	<MyComp data="a" />
		// 	<MyComp data="b" />
		// 	<MyComp data="c" />
		//
		// But false for :
		//	<MyComp data="d" />
		//	<MyComp data={data} />
	}

	if ($$tag.is_bind('data')) {
		// true if the prop 'data' is binded
		// Ex:
		// 	<MyComp bind:data={data} />
		// 	<MyComp bind:data />
	}

	if ($$tag.is_snippet('data')) {
		// true if the prop 'data' is a snippet
		// Ex:
		// 	<MyComp> {#snippet data()} XXX {/snippet} </MyComp>
	}

</script>

It should also include some function that make basic and classic checks :

// MyComp.svelte
<script context="compiler">

	/** Show an error if the props is missng */
	$$tag.require('format', 'The props "format" is required');

	/** If the props is a constant, verify that his value match one of the provideds values */
	$$tag.verify('format', ['full', 'medium', 'small'], "Bad format");

	/** Same thing, but with a regexp : */
	$$tag.verify('format', /full|medium|small/g, "Bad format");

	/** Or using a callback to verify the value : */
	$$tag.verify('format', (v) => {
		return v === 'full' || v === 'medium' || v === 'small'
	}, "Bad format");

	/** Same thing, but using an Error as message */
	$$tag.verify('pattern', (v) => {
		return new Regexp(v, "g"); // the catched error will be used as error message
	});

</script>

Snippets

And a verify_snippet() to check the format of the snippets.

Some examples :

// UList.svelte
<script context="compiler">

	$$tags.verify_snippet('children', {
		text: false,		// disallow text on the root of the snippet
		html: false,		// disallow {@html} on the root of the snippet
		allows: [ 'li', Li ],	// Accept allow only tag <li> or component <Li>
	});

<script>
// Dialog.svelte
<script context="compiler">

	$$tags.verify_snippet('children', {
		// snippet must have exactly one Header, one Content and one Footer :
		match: [ Header, Content, Footer ]
	});

<script>
// Table.svelte
<script context="compiler">

	$$tags.verify_snippet('children', {
		// snippet must have : 
		match: [ 
			[ THead, 0, 1],	// zero or one THead
			[ TBody, 1, 1],	// one TBody
			[ TFoot, 0, 1]  // zero or one TFoot
		]
	});

<script>

Importance

nice to have

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions