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

add appendTsxSuffixTo option #581

Merged
merged 16 commits into from
Jul 15, 2017
Merged

add appendTsxSuffixTo option #581

merged 16 commits into from
Jul 15, 2017

Conversation

vhqtvn
Copy link
Contributor

@vhqtvn vhqtvn commented Jul 12, 2017

Add support for tsx.

@johnnyreilly
Copy link
Member

Thanks for this! Could you tell me what the use case for this is? Also please could you fix the tests and provide a new one for the new feature? Thanks!

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 12, 2017

Hi, this allow us to use options like this:

                        options: {
                            appendTsSuffixTo: [/\.vue$/],
                            appendTsxSuffixTo: [/\.vuex$/],
                        },

So we can use jsx in *.vuex:

<script lang="tsx" type="text/typescript">
import Vue from 'vue';
const MyElm = {
    functional: true,
    render (h: Vue.CreateElement, context: Vue.RenderContext) {
        return (<div>Hello world!</div>);
    }
}
...
</script>

src/index.ts Outdated
@@ -27,7 +27,8 @@ function loader(this: interfaces.Webpack, contents: string) {
}

const rawFilePath = path.normalize(this.resourcePath);
const filePath = utils.appendTsSuffixIfMatch(options.appendTsSuffixTo, rawFilePath);
let filePath = utils.appendTsSuffixIfMatch(options.appendTsSuffixTo, rawFilePath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Come the first uses rawFilePath and the second uses filePath?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So why this:

let filePath = utils.appendTsSuffixIfMatch(options.appendTsSuffixTo, rawFilePath);
filePath = utils.appendTsxSuffixIfMatch(options.appendTsxSuffixTo, filePath); // shouldn't this be rawFilePath too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only different is when filename is matched by both options.appendTsxSuffixTo and options.appendTsSuffixTo.
In that case, if both part use rawFilePath then .tsx append will overwrite .ts, but i think it should become .ts.tsx so user may know that their regexes has collision.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right - thanks for that. Is it likely that people will use appendTsxSuffixTo and appendTsSuffixTo together? Or is it an either / or thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They can use it together if they want (declare at a single place and then reuse for example), i can code it more generic but after consider backward-compability i chose this method.

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 13, 2017

Hi @johnnyreilly Please help me on the failed test of travis-ci, seems to be a nodejs problem?

@johnnyreilly
Copy link
Member

I spy green tests - looks like you're getting there!

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 13, 2017

I updated documents only to trigger re-test :( the CI's are stupid.

@johnnyreilly
Copy link
Member

Yeah - the integration tests are somewhat flaky which is a pain. Way too many false negatives.

README.md Outdated
entry: './index.vue',
output: { filename: 'bundle.js' },
resolve: {
extensions: ['.ts', '.vue']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does '.vuex' need to be here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes i'm sorry, i committed a new one.

src/utils.ts Outdated
@@ -88,6 +88,17 @@ export function appendTsSuffixIfMatch(patterns: RegExp[], path: string): string
return path;
}

export function appendTsxSuffixIfMatch(patterns: RegExp[], path: string): string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having pondered I think it might make for nicer code to combine appendTsSuffixIfMatch and appendTsxSuffixIfMatch.

Here's my reasoning:

Wherever appendTsSuffixIfMatch is called in the code, the output is then passed into the call to appendTsxSuffixIfMatch. They are never called in isolation and are intended to be used together.

That being the case, I think it might make sense to have an appendTsTsxSuffixIfMatch method which internally does the appendTsSuffixIfMatch and appendTsxSuffixIfMatch operations, feeding the output of the first into the second. This should result in clearer code at the call sites. It may also allow for some genericisation for the underlying methods (whether or not that makes sense I don't know)

Is that okay with you? I'm quite focused on keeping the source of ts-loader as comprehensible as possible to ease contributions by others.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just commit.

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 13, 2017

hi @johnnyreilly all tests passed :) please review and merge, thank you!

src/index.ts Outdated
@@ -26,7 +26,8 @@ function loader(this: interfaces.Webpack, contents: string) {
}

const rawFilePath = path.normalize(this.resourcePath);
const filePath = utils.appendTsSuffixIfMatch(options.appendTsSuffixTo, rawFilePath);
let filePath = utils.appendSuffixIfMatch(options.appendTsSuffixTo, rawFilePath, '.ts');
filePath = utils.appendSuffixIfMatch(options.appendTsxSuffixTo, filePath, '.tsx');
const fileVersion = updateFileInCache(filePath, contents, instance);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be appendSuffixesIfMatch also please? Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry i already code that but forgot to save...

@johnnyreilly
Copy link
Member

We've all been there 👍

@johnnyreilly
Copy link
Member

johnnyreilly commented Jul 14, 2017

The test is failing because of hyper-sensitivity on the part of the comparison test pack. Actually I've fixed that in my latest commit and so if you sync up we should get green tests. Want to give it a go? Instructions here (if you need them): https://help.github.com/articles/syncing-a-fork/

Also @HerringtonDarkholme are you happy with this PR? I ask since you originally added vue support to ts-loader...

@HerringtonDarkholme
Copy link
Contributor

HerringtonDarkholme commented Jul 14, 2017

If the only use case is using TSX in vue's SFC. I would not think this is an appropriate approach.
First, vuex isn't a file extension supported officially, other tools like vetur do not identify it. Second, vue's script type is identified by "lang" attribute, not file extension.

The most important issue is vue's tsx ecosystem. See vuejs/vetur#325 (comment) for more detail.

@johnnyreilly
Copy link
Member

@vhqtvn could you respond to @HerringtonDarkholme when you get a chance? Thanks.

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 14, 2017

@HerringtonDarkholme vuex is just my attempt to use tsx for my project with currently fixed structure setup, the regex can be used like /\/src\/vue-tsx\// or any kind of selector to indicate the target vue file use tsx. Current appendTsSuffixTo work very well for ts, and so will Tsx. I do understand tsx has limits but it is still very useful (and useable) for functional components and is currently working in my project.

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 14, 2017

You can also contribute to vue-ts-loader which is dedicated to type check vue file....

Again this is still a choosing problem, one can prefer dedicated tools to solve specified problems but one prefer using generic tools to provide more flexible configurations to the project, as long as it's maintainable by them.

If you cannot make Vue's JSX work in a .tsx file ...

We can, as long as build tool can handle JSX and TS preserve JSX to the building toolchain "jsx": "preserve",.

@HerringtonDarkholme
Copy link
Contributor

HerringtonDarkholme commented Jul 14, 2017

We can, as long as build tool can handle JSX and TS preserve JSX to the building toolchain "jsx": "preserve",.

No. The core aim of TS is to type check. Without type checking Vue JSX one cannot say Vue JSX works. For comparison, React's JSX works out of box. Type checking an completion work fine.

one can prefer dedicated tools to solve specified problems but one prefer using generic tools

appendTSSuffix sounds like a generic option but sadly it is only used by Vue. I'm quite sorry for introducing this to ts-loader but at the end it attracts many Vue users to ts-loader so I would argue it has significant advantage. One need to justify tsx would attract new users.

Besides advantages, Vue support in ts-loader is pretty much nothing but hack. It requires vue-loader to read all vue file before ts-loader kicks in. So you have constraints to use ts-loader + vue, for example, the entry file must include or recursively include all vue files. It is ideologically atrocious implementation. Yet it enables type checking in Vue, a trade off for significant usage.

If plugin is supported, it can be supported ideally.

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 14, 2017

Without type checking Vue JSX one cannot say Vue JSX works.

I think you mean Without type checking Vue JSX one cannot say Vue TSX works, but tsx here provide solutions for target users that need to use JSX for rendering function and still use TS for the rest, that's too messy to split out logics to standalone tsx file for every component that need it.

@HerringtonDarkholme
Copy link
Contributor

HerringtonDarkholme commented Jul 14, 2017

target users that need to use JSX for rendering function

I have demonstrated in length that it is a bogus requirement. You already have render function and template in Vue. If you do need JSX for functional component consider extracting it to external template. If you mix JSX and component template, you'd better refactor for cleaner separation.

appendTSXSuffix isn't a good fix. I have said that Vue uses lang attribute not vuex. It's nonstandard. Waiting is better than bike shedding.

https://blog.golang.org/toward-go2

In contrast, adding new API to Go would add new problems:

We did what we always do when there's a problem without a clear solution: we waited. Waiting gives us more time to add experience and understanding of the problem and also more time to find a good solution.

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 14, 2017

I have said that Vue uses lang attribute not vuex

We dont need vuex, it's just my personal way, we can use tsx in vue too as long as the regex match.

@johnnyreilly
Copy link
Member

johnnyreilly commented Jul 14, 2017

Hi @HerringtonDarkholme and @vhqtvn,

First of all, thanks both for your contributions - they're massively valuable and well done for keeping it rational. ❤️

Here's where I am:

I don't know Vue (at all) but looking at the docs it seems to have some concept of JSX: https://vuejs.org/v2/guide/render-function.html#JSX. This makes me think that it might be reasonable to allow support for TSX. However, it's not clear to me that React JSX === Vue JSX. I think @HerringtonDarkholme is essentially saying that React JSX !== Vue JSX and so there is no real concept of type checking.

Is that right? In that case, is there any value in adding this?

I have said that Vue uses lang attribute not vuex. It's nonstandard. Waiting is better than bike shedding.

I'm not clear about the whole vuex / lang thing here. (Again, not a Vue user.) Can anyone explain to me what you mean? Speak as you might to a young child. Or a golden retriever. (Couldn't resist the Margin Call hat tip 😉 ) Honestly I've followed only about 30% of the Vue-specific conversation above.

Finally, the fact that so many people have chimed in and said they want this makes me think that there's something valuable here. I don't want to ignore that - but I don't want to implement something that's subject to change in 10 minutes time. Is it?

Does this feature enable standard Vue usage? Or is it a custom approach that's quite popular? Both are legitimate but if this is custom I wonder if it's appropriate baking it in to ts-loader. If that's the case, would this suggestion resolve the issue in any way:

You can also contribute to vue-ts-loader which is dedicated to type check vue file

?

I'm totally on the fence right now.

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 14, 2017

Does this feature enable standard Vue usage? Or is it a custom approach that's quite popular? Both are legitimate but if this is custom I wonder if it's appropriate baking it in to ts-loader.

Yes it enables standard Vue usage because Vue already support JSX - with some limits of cause due to the different between Vue and React.
Despite to these limits there're still users that need JSX, so offical vue also supports it.
This JSX approach is also generic if we do not look into Vue's space, with the same purpose as appendTsSuffix, the usage is up to user to use typescript (because the compiler require correct extension) the right way to solve their problem.

@HerringtonDarkholme
Copy link
Contributor

I have tried tsx with Vue locally. Actually I think this option brings more problems than it solves.

  1. It is very hard to use tsx with strict / noImplicitAny. This is not acceptable to me.

  2. As mentioned above, users cannot mix ts/tsx within vue unless using some other non-standard extensions.

These two problems will cause more issues to Vue/TypeScript/ts-loader's repos.

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 14, 2017

It is very hard to use tsx with strict / noImplicitAny. This is not acceptable to me.

Please explain this more clear, what's the problems you came with?

As mentioned above, users cannot mix ts/tsx within vue unless using some other non-standard extensions.

Ts can be used in vue with standard extension (i.e. https://github.com/vuejs/vue-class-component), Tsx is the better way to code render function within Ts, and it use no more than vuejs/babel-plugin-transform-vue-jsx and ts-loader.

@HerringtonDarkholme
Copy link
Contributor

  1. Open a new ts project and set strict to true. Install VueJS and write tsx. Try it. If you can make it compile I think we can merge this pull request.

  2. I mean, mixing. In one vue file I use lang="ts" and <any> castVar. In another vue file I use lang="tsx" and <div></div>. You solution does not cover this.

@rhyek
Copy link

rhyek commented Jul 14, 2017

About the createElement vs JSX thing: there is no way I or anyone on my team is going to write a complex 100+ line template using createElement. Readability would be non-existant. I've always interpreted createElement as something other things will always compile to, not something I want to personally write.

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 14, 2017

@HerringtonDarkholme
Copy link
Contributor

@vhqtvn Do you ever know how to correctly set up tsconfig? strict should be inside "compilerOptioins"

@johnnyreilly
Copy link
Member

johnnyreilly commented Jul 14, 2017

Hey @HerringtonDarkholme,

I know you feel strongly but let's not move away from courteous disagreement to abuse. ts-loader has got this far powered by ❤️ Let's be good to each other!

@rhyek
Copy link

rhyek commented Jul 14, 2017

FYI, regarding a TS language service plugin - from https://github.com/Microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin:

TypeScript Language Service Plugins ("plugins") are for changing the editing experience only. The core TypeScript language remains the same. Plugins can't add new language features such as new syntax or different typechecking behavior, and plugins aren't loaded during normal commandline typechecking or emitting.

So, it wouldn't be possible to have a plugin transform JSX to vue's createElement. Babel (or something similar) would be required here.

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 14, 2017

Hi all, i updated the example repo, strict mode wont check JSX typings but all remain functions of ts work, but that's enough for us to write JSX templates within ts code. @HerringtonDarkholme

@octref
Copy link

octref commented Jul 14, 2017

Please no invention like .vuex.
@johnnyreilly Vuex is Vue's redux: https://vuex.vuejs.org/. It's an official library from Vue: https://github.com/vuejs/vuex
Such extension is going to confuse the hell out of people.

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 14, 2017

@octref .vuex is a personal choice and have nothing to do with this pull request.

@jbrantly
Copy link
Member

Disclaimer: I'm not a Vue user and I like JSX quite a bit

@HerringtonDarkholme

It seems clear to me that you don't like JSX/TSX, which is fine, but I'm having a hard time separating your dislike of the syntax from the logical rationale for not including this PR for those folks who do like it. I think I can distill your arguments into the following (hopefully?):

  1. (J|T)SX has various shortcomings in Vue (strict mode, event handlers, babel setup, IDE support, etc)
  2. lang should be used to determine TS vs TSX, not a .vuex or other file extension
  3. The majority of ts-loader users don't care or need this feature

My reactions would be:

  1. I don't think this is a reason to not support something, especially if we think it will be supported better in the future. I tend to agree there is a bit of a chicken and egg problem here. Also, most of the complexity here is not in ts-loader-land but needs to be solved elsewhere. The support that would need to come from ts-loader seems fairly minimal.
  2. Not a Vue user but it does seem like for SFC files the lang attribute is the appropriate way to identify what something is. I think a proper implementation of this should definitely be to use the lang attribute it at all possible and not file extensions.
  3. Again, I don't think this is necessarily a reason to not support something. "Chicken and egg", "if you build it they will come", etc.

I have a couple of questions though:

A. I was looking at https://github.com/Microsoft/TypeScript-Vue-Starter and for the life of me I can't figure out how that configuration works with SFC. In the case of SFC, doesn't vue-loader need to be explicitly configured to call ts-loader? I'm assuming that's the case and the example there is just wrong.
B. When vue-loader calls ts-loader with the contents of the <script /> tag what information can be gleaned from it? For example, is it possible to know whether lang="ts" vs lang="tsx" was specified?
C. Assuming that you can't determine the contents of the lang tag from (B), I'm wondering if we couldn't do a vue-loader configuration like so:

loaders: {
  'ts': 'ts-loader',
  'tsx': 'ts-loader?appendTsx'
}

For that matter, might it not be possible to get rid of "appendTsSuffixTo" and have something like:

loaders: {
  'ts': 'ts-loader?append=ts',
  'tsx': 'ts-loader?append=tsx'
}

I'm mostly thinking out loud here, there might be technical issues with actually implementing those since TypeScript is super-eager to load all files off disk.

@vhqtvn
Copy link
Contributor Author

vhqtvn commented Jul 14, 2017

@jbrantly
A. B. Yes vue-loader call ts-loader, i think it's a good way.
C. I already think about these, but then decided to keep thing backward-compatible so the pull wont affect existing users.

@johnnyreilly
Copy link
Member

Please no invention like .vuex.
@johnnyreilly Vuex is Vue's redux: https://vuex.vuejs.org/. It's an official library from Vue: https://github.com/vuejs/vuex
Such extension is going to confuse the hell out of people.

That's a valid point @octref - if we look to merge this then I'm sure @vhqtvn would be open to changing the docs example to something other than vuex to avoid confusion.

@jbrantly - thanks so much for commenting. As ever, you make some very useful points. What you've said makes sense and brings some useful clarity to the discussion. 👍

Here's my thinking at present:

  • We have appendTsSuffixTo which allows people to take non-ts files and treat them as ts files. Given TypeScript has 2 language "modes" if you will (ts and tsx) and we're already supporting one on the file-suffix front, it seems reasonable to have the other language mode supported as well. Even if you think it's terrible - the horse has already bolted. We're already half in, why not be all in? appendTsxSuffixTo is just the logical sibling of appendTsSuffixTo

  • In terms of complexity this actually doesn't add much to ts-loader. We're essentially just taking what we're already doing for appendTsSuffixTo and saying "once more with feeling". It's not a significant change to ts-loader code-wise.

  • If people don't use this feature of ts-loader (as I won't) it doesn't affect them. It's there and the way it's implemented is suitably generic. It's in no way tied to Vue itself thanks to solid design choices by @HerringtonDarkholme when appendTsSuffixTo was first implemented. If a future version of Vue changes significantly it has no bearing on the feature we're proposing to add to ts-loader.

@HerringtonDarkholme is there anything in my thinking that you disagree with? Anything I've missed? I understand that you're not that keen on appendTsSuffixTo anymore and that's fine. But putting personal taste aside is there a significant problem this PR might create? Your contributions to ts-loader have been really valuable and I'm really interested to know of any real harm you think this PR might do. I value your opinion.

Copy link
Contributor

@HerringtonDarkholme HerringtonDarkholme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just merge this since @vhqtvn has make tsx to compile.

But warning ahead, maintaining a tool is more than maintaining code. Vue TSX has more hairy complication than a simple appendSuffixTo can solve.

@johnnyreilly
Copy link
Member

johnnyreilly commented Jul 15, 2017

Thanks for that @HerringtonDarkholme - I appreciate you responding.

But warning ahead, maintaining a tool is more than maintaining code. Vue TSX has more hairy complication than a simple appendSuffixTo can solve.

I'm sure you're correct. I'm going to take this PR on the grounds that ts-loader won't be doing anymore than appending file suffixes to paths. That's minimal enough and it's agnostic to Vue. That's enough for me to be content.

I've made a tweak to ensure that the code doesn't get invoked unless the relevant options are passed - once the CI goes green I'll merge.

@johnnyreilly johnnyreilly merged commit 80e0349 into TypeStrong:master Jul 15, 2017
@johnnyreilly
Copy link
Member

This shipped with ts-loader 2.3.0 - thanks everyone for contributing to the discussion. https://github.com/TypeStrong/ts-loader/releases/tag/v2.3.0

@HerringtonDarkholme
Copy link
Contributor

Some framework support for VueJSX. I hope some on would have first made feature request to library before modifying build tools.

https://github.com/HerringtonDarkholme/av-ts#tsx-example

I would, again, argue that framework and language support should precede building tools. Premature releases only ships broken tools and harms user experience. Patience is the virtue.

@johnnyreilly
Copy link
Member

That's a fair point and I don't disagree with you. In the end, the simplicity of the addition from a ts-loader perspective and the clear usefulness to a number of people made it a reasonable PR in my view.

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

Successfully merging this pull request may close these issues.

10 participants