Search code examples
javascripttypescriptjsxtsxsolid-js

Custom directive in SolidJS is undefined and gets formatted away


I am using SolidJS with Vite. I want to add a custom directive to a div. The somewhat okay-ish documentation can be found here. My problem arises from the simple fact that in my .tsx files the use:__ directive is separated by the colon(:) when saving (and auto-formatting on save).

So this

 <div use:model={...}/>

turns into this

 <div use: model={...}/>

And it produces an error of course.

My second issue on top is that the directive seems to be undefined. My code structure is similiar to this:

declare module 'solid-js' {
  namespace JSX {
    interface Directives {
      model: [() => any, (v: any) => any];
    }
  }
}

export default () => {
  return <div use: model={[() => {}, () => {}]}/>
}

Edit:

My eslint configuration, I am using eslint btw :), is:

{
    "env": {
        "browser": true,
        "commonjs": true
    },
    "parser": "@typescript-eslint/parser",
    "plugins": [
        "solid"
    ],
    "extends": [
        "airbnb-base",
        "airbnb-typescript/base",
        "plugin:solid/typescript"
    ],
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly"
    },
    "parserOptions": {
        "ecmaVersion": "latest",
        "project": "./tsconfig.json"
    },
    "rules": {
        "class-methods-use-this": "off",
        "no-param-reassign": "off",
        "global-require": "off",
        "consistent-return": "off",
        "max-len": "off",
        "no-console": "off",
        "import/no-dynamic-require": "off",
        "linebreak-style": "off",
        "no-new": "off",
        "no-inner-declarations": "off",
        "solid/reactivity": "off",
        "import/no-extraneous-dependencies": [
            "error",
            {
                "devDependencies": true,
                "optionalDependencies": false,
                "peerDependencies": false
            }
        ],
        "@typescript-eslint/naming-convention": [
            "error",
            {
                "selector": "variableLike",
                "leadingUnderscore": "allow",
                "trailingUnderscore": "allow",
                "format": [
                    "camelCase",
                    "PascalCase",
                    "UPPER_CASE"
                ]
            }
        ],
        "import/extensions": [
            "error",
            "ignorePackages",
            {
                "": "never",
                "js": "never",
                "jsx": "never",
                "ts": "never",
                "tsx": "never"
            }
        ]
    }
}

Solution

  • Custom directives are compiler tricks, where directive code will turn into a function call that receives the element and the value as argument:

    directiveFn(el, () => value) {
      // directive definition goes here
    }
    

    The compiler will produce a code that calls the directive during rendering. Value is passed as a function call to preserve reactivity.

    However, the difference between the code you write into the editor and the code that is produced by the compiler causes issues like the ones you experienced.

    The most common issue is typescript tree-shakes the directive function thinking it is not used. Actually it is combination of few other factors, but in the end the code gets tree-shaken. To prevent it, you need use the function in an &&:

    true && directiveFn
    

    This is the first time someone reported an issue related to formatting. Probably the formatting tool treats the colon (:) in the directive's name as a : for a property assignment or type type definition.

    You need to skip formatting the line that has the directive or provide a proper formatting rule.

    If the culprit is eslint, you can use catch-all directive, ignoring any issue with the line or you can find out the exact reason and ignore specific rules pertaining that rule via an inline directive.