babel-plugin-transform-vue-jsx icon indicating copy to clipboard operation
babel-plugin-transform-vue-jsx copied to clipboard

h is not defined in .tsx functions

Open gettinToasty opened this issue 6 years ago • 13 comments

May be a duplicate of https://github.com/vuejs/babel-plugin-transform-vue-jsx/issues/93

After configuring our TsxComponent class to properly allow babel to handle h auto-injection it appears that there is no actual injection happening in .tsx files:

TsxComponent

interface ITsx {
  render(): JSX.Element;
}

export default abstract class TsxComponent<T> extends Vue implements ITsx {
  private vueTsxProps: Readonly<{}> & Readonly<T>;

  render() {
    return <div />;
  }
}

Implementation

export default class Foo extends TsxComponent<{}> {
  render() {
    return <h1>Hello World</h1>
  }
}

Error

Vue warn]: Error in render: "ReferenceError: h is not defined"

gettinToasty avatar Feb 21 '19 18:02 gettinToasty

var h = this.$createElement isn't added in transpiled functions automatically.

However in case of render I can just write render(h) and it works. But in other functions I have to add this string manually to make it work.

Posted a question on stackoverflow as well.

maksnester avatar Feb 28 '19 13:02 maksnester

@Alendorff not according to the features of this library:

Starting with version 3.4.0 we automatically inject const h = this.$createElement in any method and getter (not functions or arrow functions) declared in ES2015 syntax that has JSX so you can drop the (h) parameter.

gettinToasty avatar Feb 28 '19 17:02 gettinToasty

@gettinToasty yes, sure, I expected this behavior, but that's not happened in my case. 🤷‍♂️ JSX works fine, TSX - not.

maksnester avatar Mar 01 '19 07:03 maksnester

I would reccomend to look at https://github.com/kaorun343/vue-property-decorator and https://github.com/vuejs/vue-class-component, if you want to have component classes in TSX. At least check how they handle that.

healqq avatar Mar 01 '19 19:03 healqq

We use vue-property-decorator for our component classes, I don't think that's part of the issue here.

gettinToasty avatar Mar 01 '19 19:03 gettinToasty

@healqq I didn't catch your point. At what exactly I should look? I use vue-property-decorator with TSX of course. It's quite hard to effectively utilize typescript in vue project without it.

maksnester avatar Mar 01 '19 19:03 maksnester

@healqq by the way even in their example they added h manually as an argument

maksnester avatar Mar 01 '19 19:03 maksnester

@Alendorff that is just a bad(old) example, we’Re using it for our project and h is autoinjected. Other option would be to wait for vue3 release, but it’ll take some time

healqq avatar Mar 01 '19 23:03 healqq

Can you post your tsconfig? Maybe the issue is there?

healqq avatar Mar 01 '19 23:03 healqq

we have jsx: preserve in our tsconfig so ts-loader shouldnt even touch it during compile

gettinToasty avatar Mar 01 '19 23:03 gettinToasty

That's correct, yes. Maybe you can do minimal repo that replicates issue?

healqq avatar Mar 02 '19 07:03 healqq

Hi,

this is a minimal example of how I resolve this issue. there are 4 approaches.

  1. Separate template from logic
  2. typescript static type check in the template
  3. vuejs + tsx
  4. Hot reload

Test1.vue.ts

import { Vue, Component } from 'vue-property-decorator'
import { CreateElement, VNode } from 'vue';
import { render } from './Test1.vui';

@Component({})
export class Test1 extends Vue {
    public message:string = 'hello world';
    public render(h: CreateElement): VNode {
        return render.bind(this)(h);
    }
}

export default Test1;

Test1.vui.tsx

import { CreateElement, VNode } from 'vue';
import { Test1 } from './Test1.vue';

export function render(this: Test1, h: CreateElement): VNode {
    return (
        <div>
            <h1>Title</h1>
            {this.message}
        </div>
    );
}

add this rule to webpack


{
    test: /\.tsx$/,
    use: ['babel-loader', 'vue-jsx-hot-loader', 'ts-loader'],
    exclude: /node_modules/
}

tsconfig.json


{
    "compilerOptions": {
        "lib": [
            "dom",
            "es6",
            "es2015.promise"
        ],
        "sourceMap": true,
        "strict": true,
        "module": "esnext",
        "moduleResolution": "node",
        "target": "es6",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "allowSyntheticDefaultImports": true,
        "jsx": "preserve",
        "jsxFactory": "h",
        "typeRoots": ["./node_modules/@types", "./types"]
    }
}

I hope it helps someone.

juanfernandoe avatar Mar 07 '19 04:03 juanfernandoe

I got the same thing with rollup+tsx. If i use .vue all files works but with tsx h is not defined. Tried loy of babel presets but always got the same thing. BRRRRRRR....

// rollup config

import { RollupOptions, rollup } from 'rollup';
/* eslint-disable sort-imports-es6/sort-imports-es6 */
/* eslint-disable @typescript-eslint/no-var-requires */
import { join, resolve } from 'path';

import { BuildPackage } from '../build-package';
import { isObject } from '../utils';
import { terser } from 'rollup-plugin-terser';

const autoprefixer = require('autoprefixer');
const nodeResolve = require('rollup-plugin-node-resolve');
const vue = require('rollup-plugin-vue');
const babel = require('rollup-plugin-babel');
const cjs = require('rollup-plugin-commonjs');
const rollString = require('rollup-plugin-string');
const ts = require('rollup-plugin-typescript2');
const requireContext = require('rollup-plugin-require-context');

const { assign, keys } = Object;

const baseConfig = (settings: BuildPackage) => {
  const {
    options: { entryFile },
    sourceDir
  } = settings;
  const pkg = require(join(sourceDir, 'package.json'));
  /*const input = Array.isArray(entryFile)
    ? [...entryFile].map(file => join(sourceDir, file))
    : join(sourceDir, entryFile);
  */
  return {
    external: [...keys(pkg.dependencies), ...keys(pkg.peerDependencies), ...['path', 'url']],
    // inlineDynamicImports: true,
    input: entryFile,
    plugins: [
      nodeResolve({
        mainFields: ['main', 'module', 'browser'],
        extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue']
      }),
      cjs(),
      requireContext(),
      rollString.string({
        include: '**/*.svg'
      }),
      vue({
        css: false,
        style: {
          postcssPlugins: [autoprefixer]
        },
        compileTemplate: true
      }),
      ts({
        cacheRoot: resolve(sourceDir, 'node_modules/.rts2_cache'),
        tsconfig: join(sourceDir, 'tsconfig.build.json'),
        tsconfigOverride: {
          compilerOptions: {
            declaration: true,
            declarationDir: join(settings.outputDir, '@types'),
            moduleResolution: 'node',
            rootDir: settings.sourceDir
          }
        },
        useTsconfigDeclarationDir: true
      }),
      babel({
        // babelrc: false,
        exclude: 'node_modules/**',
        extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue'],
        presets: [
          [
            '@babel/preset-env',
            {
              modules: false
            }
          ],
          //'@vue/babel-preset-jsx'
          //'@vue/babel-preset-app'
          '@vue/app'
        ],
        include: [join(settings.sourceDir, '**/*')],
        //plugins: ["transform-vue-jsx"],
        /*plugins: [
          "@babel/plugin-syntax-jsx",
          ["@babel/plugin-transform-react-jsx", { pragma : 'dom' }]
        ],*/
        //externalHelpers: true,
        runtimeHelpers: true
      })
    ]
  };
};

// compiled es format

import { __extends, __decorate, __metadata } from 'tslib';
import Vue from 'vue';
import { Component } from 'vue-property-decorator';

var UIButton =
/** @class */
function (_super) {
  __extends(UIButton, _super);

  function UIButton(props) {
    return _super.call(this, props) || this;
  }

  UIButton.prototype.render = function (_h) {
    return h("div", {
      "attrs": {
        "className": 'ui__button'
      }
    }, [h("button", ["Ok Button"])]);
  };

  UIButton = __decorate([Component({
    name: 'ui-button'
  }), __metadata("design:paramtypes", [Object])], UIButton);
  return UIButton;
}(Vue);

export default UIButton;

// compiled umd format

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tslib'), require('vue'), require('vue-property-decorator')) :
  typeof define === 'function' && define.amd ? define(['exports', 'tslib', 'vue', 'vue-property-decorator'], factory) :
  (global = global || self, factory(global.ui = {}, global.tslib, global.Vue, global.vuePropertyDecorator));
}(this, (function (exports, tslib, Vue, vuePropertyDecorator) { 'use strict';

  Vue = Vue && Object.prototype.hasOwnProperty.call(Vue, 'default') ? Vue['default'] : Vue;

  var UIButton =
  /** @class */
  function (_super) {
    tslib.__extends(UIButton, _super);

    function UIButton(props) {
      return _super.call(this, props) || this;
    }

    UIButton.prototype.render = function (_h) {
      return h("div", {
        "attrs": {
          "className": 'ui__button'
        }
      }, [h("button", ["Ok Button"])]);
    };

    UIButton = tslib.__decorate([vuePropertyDecorator.Component({
      name: 'ui-button'
    }), tslib.__metadata("design:paramtypes", [Object])], UIButton);
    return UIButton;
  }(Vue);

  // eslint-disable-next-line sort-imports-es6/sort-imports-es6
  /**
   * Plugin install function
   *
   * @param vue - Vue Instance
   * @param _options - Plubin Options
   *
   * @internal
   */
  // eslint-disable-next-line id-match, @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any

  var install = function install(Vue, _options) {
    Vue.component('ui-button', UIButton);
  };
  /**
   * @public
   */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any


  var plugin = {
    install: install,
    version: '1.0.0'
  };
  var GlobalVue = null;

  if (typeof window !== 'undefined') {
    GlobalVue = window.Vue;
  } else if (typeof global !== 'undefined') {
    GlobalVue = global.Vue;
  }

  if (GlobalVue) {
    GlobalVue.use(plugin);
  }

  exports.UIButton = UIButton;
  exports.plugin = plugin;

  Object.defineProperty(exports, '__esModule', { value: true });

})));

When imported to a vue app created by cli always complain about h function. Any clues for Rollup+tsx??

miguelramos avatar Jun 25 '20 17:06 miguelramos