Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance oriented .babelrc for TS users #884

Closed
theKashey opened this issue Mar 6, 2018 · 33 comments
Closed

Performance oriented .babelrc for TS users #884

theKashey opened this issue Mar 6, 2018 · 33 comments
Assignees

Comments

@theKashey
Copy link
Collaborator

We have to include example with babel configuration for TS users.
The key ideas:

  1. Add syntax parsers
  2. Add react-hot-loader transformation plugin
  3. Do not add any other transformation plugins to maintain build speed.
@vilicvane
Copy link

I would suggest to use tsconfig.json with module option set to esnext, then have babel to transform modules with transform-es2015-modules-commonjs. Thus the user can take the advantages of allowSyntheticDefaultImports and babel interop require, so that they don't have to use import wildcard everywhere.

@PetrShypila
Copy link

Does anyone know if I can target TS to ES5 directly with react-hot-loader? I see in examples TS targets ES2015 which then will be transformed to ES5 with babel-loader.

@theKashey
Copy link
Collaborator Author

No. In this case RHL will not see any Components, ie classes, as long they will be transpile, and you will lose ability to change like a anything except ‘render’ method.

@johnnyreilly
Copy link
Contributor

johnnyreilly commented Mar 17, 2018

Hey all!

Maintainer of ts-loader here. I've just been checking out v4 of this mighty fine project and discovered that with v4 I need to start using babel:

When using TypeScript, Babel is not required, but React Hot Loader will not work without it.

With v3 I didn't need to which I liked. Would you be able to clarify why babel is needed with v4 please? If it's the only game in town that's cool - but if there's a way I can stick with just my TypeScript + core-js flow I'd love to.

If babel has to be in the mix I want to keep it as targeted as possible. I maintain a react-hot-loader example in the ts-loader repo so I'd like to provide a good boilerplate for people wondering how to use ts-loader with react-hot-loader.

I'd love to understand the limitation if you're up for explaining! 🌻

@theKashey
Copy link
Collaborator Author

@johnnyreilly - so here is a fairy tale.
v4 was started to support arrow functions as class members, for the projects which does not use es5 transpilation in dev mode. Actually, result is a bit more debuggable, as long it is almost the "source" code.
So, if your component got an arrow-function member, that member will "lock" this, and we could not use proxy as we do in v3 to do RHL's work - there is no way to fake this for arrow functions.

As result - RHL will search for classes, and inject magic method, to EVAL something in the class scope and class context

And later we drop thousand lines from proxy, and just rely on this 3 lines long method.

If you will transpile TS to ES5 - RHL will not found any "class" it is looking for.
If you will not apply babel-plugin - RHL will be unable to update non-prototype-based method. Ie "onClick" handler.

This means - if you will transpile TS to ES5 or dont apply babel - RHL will still work (dont forget to import it before react), but just will lose some abilities. Not the main ones.

@johnnyreilly
Copy link
Contributor

johnnyreilly commented Mar 17, 2018

Hey @theKashey!

Thanks for that; super interesting context. I've a bunch of questions off the back of what you've said:

  1. So I can live without Babel, but from what you've said it sounds like I'll lose the ability to debug arrow functions in components. eg. onClick etc. That's a bit of a bind (see what I did? JavaScript jokes 😄 ) as I debug those a lot.

  2. If I was emitting classes from my TypeScript (say having an emit target of es2015 instead of es6) would I be fine without Babel? I'm not in that position now because of IE 11 but that day will one day come.

  3. What's the minimum Babel usage I have to have in place? My guess is that I need to have TypeScript emitting es2015 and that's me done. Is that right?

  4. I didn't realize import order was significant when it came to RHL. Can you tell me more about this please?

  5. As far as I can tell RHL v3 allows me to successfully hot module reload without babel in the mix. Debugging arrow functions seems fine as well. That being the case, what's the advantage of v4 over v3? Is it essentially down to this:

And later we drop thousand lines from proxy, and just rely on this 3 lines long method.

Trying to do a cost / benefit on:

  • living with v3 and keeping Babel out of the pipeline

vs

  • latest and greatest RHL but with Babel so slower build.

@theKashey
Copy link
Collaborator Author

So I can live without Babel, but from what you've said it sounds like I'll lose the ability to debug arrow functions in components. eg. onClick etc. That's a bit of a bind (see what I did? JavaScript jokes 😄 ) as I debug those a lot.

Not debugging. Replacing. RHL will lose ability to repeat changes in the new constructor and will not repeat changes you made.

If I was emitting classes from my TypeScript (say having an emit target of es2015 instead of es6) would I be fine without Babel? I'm not in that position now because of IE 11 but that day will one day come.

If you dont use babel plugin - the "mode" you are using does not make any sence. No babel - no cry.
And, yet again, RHL is for dev mode. Are you developing in IE11?

What's the minimum Babel usage I have to have in place? My guess is that I need to have TypeScript emitting es2015 and that's me done. Is that right?

Only RHL patch is enough. In dev mode.

I didn't realize import order was significant when it came to RHL. Can you tell me more about this please?

React-hot-loader should patch React before any component would be created. Import order is not significant, but better to import RHL before anything else.

As far as I can tell RHL v3 allows me to successfully hot module reload without babel in the mix. Debugging arrow functions seems fine as well. That being the case, what's the advantage of v4 over v3? Is it essentially down to this:

v4 could handle almost any code you may write, v3 could handle almost no code I wrote.

latest and greatest RHL but with Babel so slower build.

That's why I created this issue - how to setup RHL and TS and babel without any significant slowdown.

@RomanHotsiy
Copy link

Here is the minimal babel config I ended up with:

{
  loader: 'babel-loader',
  options: {
    plugins: [
      '@babel/plugin-syntax-typescript',
      '@babel/plugin-syntax-decorators',
      '@babel/plugin-syntax-jsx',
      'react-hot-loader/babel',
    ],
  },
}

I put it BEFORE ts-loader. Works fine for me but the build is noticeably slower.

@theKashey
Copy link
Collaborator Author

theKashey commented Mar 22, 2018

Actually we could try to make it better to TS users, and NOT require nor babel, nor compiling to ES6 instead of ES5.
The only thing one need - set a custom property on each "spotted" class, to wrap .bind with some custom hook (like React-Hot-Loader v1-2-3 did, actually), everything else ProxyComponent can handle.

That may make TS users more happier, or just make it possible to use RHL, as long most of them have a target: es5, and, as result, could not use RHL :(

@theKashey theKashey self-assigned this Mar 22, 2018
@RomanHotsiy
Copy link

The only thing one need - set a custom property on each "spotted" class, to wrap .bind with some custom hook (like React-Hot-Loader v1-2-3 did, actually), everything else ProxyComponent can handle.

@theKashey could you go into more details on this? I would be happy to remove babel from my pipeline and node_modules

@theKashey
Copy link
Collaborator Author

There is nothing you could do, but there is something that could be changed in the RHL's internals.
Thats is not an easy task, and we specially did not do it, as long it... you know.. hacks :)

@johnnyreilly
Copy link
Contributor

johnnyreilly commented Mar 22, 2018

There is nothing you could do, but there is something that could be changed in the RHL's internals.
Thats is not an easy task, and we specially did not do it, as long it... you know.. hacks :)

@theKashey I would ❤️ that to happen!

Question on this:

The only thing one need - set a custom property on each "spotted" class, to wrap .bind with some custom hook (like React-Hot-Loader v1-2-3 did, actually), everything else ProxyComponent can handle.

Would that work for arrow functions as class members? eg

export class SomeClass extends React.Component<IProps, IState> {

    // arrow function as class member; shorter than using `bind` in the constructor
    anArrowFunctionThatsWhatIAm = (event: React.MouseEvent<HTMLButtonElement>) => {
        event.preventDefault();

        // do stuff
    };

    render() {
      // ...
    }
}

@theKashey
Copy link
Collaborator Author

No. Only babel magic can handle arrow functions.
Yes. You are transpiling TS into ES5, and where arrow functions does not exists.

@johnnyreilly
Copy link
Contributor

That's a shame :-(

@theKashey
Copy link
Collaborator Author

Unfortunately nothing could change context of arrow function, is it bound to the single this, and nothing could change it.
That means - to create an arrow function that this should execute it. We are using eval for it.
The simple solution might look like

function renegenerateArrowFunction(code){
  return eval(code);
}
instance.arrowMember =  renegenerateArrowFunction.call(instance, newFunctionBody)

But arrow function might "consume" something from variable scope, and we have to preserve that scope.
As result - we need a babel plugin to find class and add a new method with eval inside :(

Nor Proxy, nor Reflection API could not help here :(

@vilicvane
Copy link

vilicvane commented Mar 22, 2018

Can we just use babel to transpile TypeScript, and do type checking with something else asynchronously?

Doesn't look like a good idea, just didn't think of the limitations transpiling TypeScript using babel.

@theKashey
Copy link
Collaborator Author

What about using typescript plugins to repeat the things babel do?
https://github.com/Microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin

@RomanHotsiy
Copy link

RomanHotsiy commented Mar 23, 2018

Seems that TypeScript plugins can't do it (at least right now):

image

UPD: but it seems to be possible with Custom Transformers which I was not aware of but seems ts-loader supports it.

I think @johnnyreilly can tell more about this

@evenfrost
Copy link

I was getting a hell lot of TS errors with the preferred config from README because with it the code is compiled first by Babel, and by TypeScript after, and TypeScript compiler goes nuts on 'babelified' code.

Working config for me (you must put babel-loader before ts-loader in config, but, due to webpack's reversed loaders order, it gets executed after):

      {
        test: /\.ts|\.tsx$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              plugins: [
                '@babel/plugin-syntax-typescript',
                '@babel/plugin-syntax-decorators',
                '@babel/plugin-syntax-jsx',
                'react-hot-loader/babel',
              ],
            },
          },
          {
            loader: 'ts-loader',
          },
        ],
      },

@theKashey
Copy link
Collaborator Author

May be you have .babelrc and it adds some transformations you are not expecting?
Add ‘babelrc: false’ to loader option and place it after ts.

Otherwise RHL will not properly work unless you are set es6 as a target.

@evenfrost
Copy link

evenfrost commented Apr 23, 2018

@theKashey Nope, I have no .babelrc and am getting errors like this:

ERROR in /.../components/ClientListItem.tsx
./client/components/ClientListItem.tsx
[tsl] ERROR in /.../components/ClientListItem.tsx(63,36)
      TS7006: Parameter 'key' implicitly has an 'any' type.

ERROR in /.../components/ClientListItem.tsx
./client/components/ClientListItem.tsx
[tsl] ERROR in /.../components/ClientListItem.tsx(63,41)
      TS7006: Parameter 'code' implicitly has an 'any' type.

I assume that type checking happens after Babel compiles the code as I see no such lines in ClientListItem.tsx and VS Code doesn't output such errors. It looks like transpiled key prop gets checked (which makes sense), not sure about the code though.

@theKashey
Copy link
Collaborator Author

theKashey commented Apr 23, 2018

😆😆😆😕☹️😟😣😖
Sure it is producing TS incompatible code. "We" just don't have linting enabled on build step(a common way to speed up builds).
Ways to fix:

  • change readme to show how split linting and compiling, thus make babel-plug work "without" errors.
  • add react-hot-loader/babel-ts which will emit code with type signatures. I am not 100% sure that babel will allow me to do it. babel 7 - yes, but not sure about 6.
  • propose ts->es6->babel then, to ship es5 into the production, one have to override config file location)
  • add some unsafe proxy magic, to hack this provided to bound handlers in "constructed" component, and redirect read/writes to the "real" this.
  • don't use babel plugin at all. We could restore old webpack loader to "register" something and provide some ground truth to reconciler, but bound method overload will not work anyway.

PS: It is easy to make .bind methods works, but, as long nowadays everybody uses arrow function, and they are transpiled "into" the constructor - we could not get them out without eval :(

Reopening task.

@theKashey theKashey reopened this Apr 23, 2018
@ghost
Copy link

ghost commented Apr 28, 2018

A hacky, but possibly easy to implement way of handling this problem is using //@ts-ignore to suppress the warnings.

i.e.

// @ts-ignore
__reactstandin__regenerateByEval(key, code) {
// @ts-ignore
    this[key] = eval(code);
}

I've never found a good source of documentation for //@ts-ignore, but the announcement blog post skims over it:

These comments are a light-weight way to suppress any error that occurs on the next line.

Unfortunately you can't block specific errors at the moment, only all errors.

@theKashey
Copy link
Collaborator Author

Ok. PR just opened.

  • // ts-ignore by @AndyCJ works perfectly! But only for babel-7
  • I also updated solution for babel6/babel after TS. As long ts-loader/awesome-loader support config override, and configs support "extend" by design - separating dev and prod TS conf, ie ES5 and ES6 - should not be a problem.

Please review.

@gregberge
Copy link
Collaborator

OK for me, but I think another look from a TS expert would be great.

@ghost
Copy link

ghost commented May 2, 2018

I don't know anything about babel, so I can't comment on that.

Thanks for updating the example project to show using babel 7. I struggled a little on the weekend with the differences between the documentation showing babel 7, and the example using babel 6, before hitting the "invalid" typescript issue.

I got there in the end by guessing which packages I needed to grab/update, so having that in the example should make things smoother for others who haven't had much exposure to babel.

You guys are very active and responsive on this project. It doesn't mean much, but I'm personally very impressed!

Thank you for all your time and effort.

@theKashey
Copy link
Collaborator Author

@Madou - are you TS expert?

@theKashey
Copy link
Collaborator Author

theKashey commented May 3, 2018

Meanwhile - found one more way to "babel" TS - it is build into awesome-typescript-loader!
https://github.com/s-panferov/awesome-typescript-loader#usebabel-boolean-defaultfalse

     loader: "awesome-typescript-loader",
            options: {
              silent: process.argv.indexOf("--json") !== -1,
              useBabel: true,
              babelOptions: {
                plugins: ['react-hot-loader/babel']
              }
            }

theKashey added a commit that referenced this issue May 7, 2018
feat. Better typescript with babel 7. implements #884
@theKashey
Copy link
Collaborator Author

Released in v4.1.3

@DDzia
Copy link

DDzia commented Jun 3, 2018

@theKashey, do you can provide your configs(webpack.config.js, tsconfig.json, package.json(for versioni))?

@theKashey
Copy link
Collaborator Author

Our example - https://github.com/gaearon/react-hot-loader/tree/master/examples/typescript - uses babel 7 under the hood, and might be not the best option.
The easiest way right now - ts -> es6 -> babel(with RHL plugin only) -> js(or es6)
All settings in all variants you might found in README.

@kunlongxu
Copy link

@theKashey Is there a way to use it without babel now? Both ts-loader and babel are compiler. I believe most people think that ts-loader and babel are redundant at the same time, and many people are forced to do so.

@theKashey
Copy link
Collaborator Author

It will be never possible to use all the features without babel. With webpack-loaders restored RHL will be almost usable for TS-only applications.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants