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

Apply other loaders when updating files in watch mode #1115

Merged
merged 5 commits into from
May 24, 2020

Conversation

iorate
Copy link
Contributor

@iorate iorate commented May 21, 2020

Fix #1111

Currently watch-run does not apply loaders other than ts-loader to TypeScript files when updating them in watch mode.
This behavior may lead to complication errors when files are "preprocessed" by other loaders (e.g. ifdef-loader).

@iorate
Copy link
Contributor Author

iorate commented May 21, 2020

I need help... 😢

One test failed in Travis CI + Node.js v10 + TypeScript v3.8.2, while all tests passed in Travis CI + Node.js v12 + TypeScript v3.8.2 and AppVeyor + Node.js v10 + TypeScript 3.8.2.

The first comparison test with transpileOnly failed:

  aliasResolution
    ✓ should have the correct output (4391ms)
    1) should work with transpileOnly
  1 passing (7s)
  1 failing
  1) aliasResolution
       should work with transpileOnly:
      Uncaught AssertionError [ERR_ASSERTION]: patch0/output.txt is different between actual and expected
      + expected - actual
           Asset     Size  Chunks             Chunk Names
       bundle.js A-NUMBER-OF KiB main  [emitted]  main
       Entrypoint main = bundle.js
      -[./app.ts] A-NUMBER-OF bytes {main} [built]
      +[./app.ts] A-NUMBER-OF bytes {main}
       [./common/components/myComponent.ts] A-NUMBER-OF bytes {main} [built]

I don't understand why [built] was output only in this environment.

@johnnyreilly
Copy link
Member

Comparison tests are somewhat flaky - requeued

@johnnyreilly
Copy link
Member

Thanks for the PR! We really appreciate your time. I can see this adds a dependency to https://github.com/webpack/loader-runner#readme

I don't know much about loader-runner and we tend to be cautious about adding dependencies because they don't all have great futures 😉

Can you provide some details about what loader-runner does, and some details around whether you believe it has a bright future? I ask as webpack 5 is on the horizon and I am curious as to whether loader-runner remains relevant. I have looked at the loader-runner repo but there aren't tons of docs.

You may want to reach out to @sokra and see if he can advise.

@iorate
Copy link
Contributor Author

iorate commented May 21, 2020

Thank you @johnnyreilly ! All tests have passed.

what loader-runner does
whether you believe it has a bright future

It applies webpack loaders to a resource and returns a result. webpack uses it internally to apply loaders (lib/NormalModule.js@4.43.0). So it is the best way to run loaders in the same manner as webpack.

I think it has a good future. webpack v5.0.0-beta.16 also depends on loader-runner (lib/NormalModule.js@5.0.0-beta.16). Changelog of webpack 5 explicitly says loader-runner was upgraded (link). We can expect it to be used by upcoming webpack 5.

src/watch-run.ts Outdated
.slice(loaderIndex + 1)
.map(l => ({ loader: l.path, options: l.options })),
context: {},
readResource: fs.readFile.bind(fs)
Copy link
Member

Choose a reason for hiding this comment

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

It looks like this will create a new instance of readFile each time updateFile runs. Would it be more performant to create this once and reuse it? In fact is the bind necessary at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It may be a bit efficient to cache the result of fs.readFile.bind(fs) as you say.
I think bind is necessary, because there is no guarantee that fs.readFile does not use this internally.

... In the first place, readResource is not necessary, because it is optional and defaulted to readFile. As @types/loader-runner does not mark it as optional, I pass the default value explicitly to avoid a type error.
This may be better:

runLoaders(
  {
    resource: ...,
    loaders: ...
  } as loaderRunner.RunLoaderOption,

src/watch-run.ts Outdated
@@ -27,7 +37,7 @@ export function makeWatchRun(instance: TSInstance) {
}

lastTimes.set(filePath, date);
updateFile(instance, filePath);
promises.push(updateFile(instance, loader, loaderIndex, filePath));
Copy link
Member

Choose a reason for hiding this comment

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

What is the significance of making this array of Promises vs just calling updateFile there and then?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

runLoaders called in updateFile is an async function. We must wait for all of them to finish before calling callback.
To implement waiting for multiple async functions, I think Promise.all is a good way.

@sokra
Copy link
Contributor

sokra commented May 21, 2020

Instead of using loader-runner directly, use this.loadModule from loader context. Otherwise you are missing out webpack and other plugin modifications to the loader context.

@johnnyreilly
Copy link
Member

Thanks @sokra - really appreciate you advising 🌻🥰

@iorate
Copy link
Contributor Author

iorate commented May 22, 2020

@sokra
Thank you for your advice.

However, this.loadModule does not seem to fit my purpose.
What I attempt is to manually get a TypeScript file preprocessed by loaders other than ts-loader.
When a rule is, for example,

{
  test: /\.ts$/,
  use: ['ts-loader', 'preprocess-ts-loader']
}

I want a result of 'preprocess-ts-loader'.
this.loadModule('!!preprocess-ts-loader!unpreprocessed-ts-file.ts', (err, source) => { ... }) seems to handle the result as a JavaScript module (actually it is a TypeScript file), to fail in most cases.

Otherwise you are missing out webpack and other plugin modifications to the loader context.

I can understand it, but I expect simply running runLoaders will work for ts-loader and the few TypeScript-preprocessing loaders in most cases.

@sokra
Copy link
Contributor

sokra commented May 22, 2020

Put a stringify loader in front to make it valid js. JSON.parse the result. This has been done before in some css related loader.

@iorate
Copy link
Contributor Author

iorate commented May 23, 2020

@sokra I really appreciate your kindness. The "stringify loader" approach is working well for my problem.

@iorate
Copy link
Contributor Author

iorate commented May 23, 2020

@johnnyreilly I replaced runLoaders with loadModule, thanks to @sokra.

(Please ignore the second commit, in which I tried raw-loader as a stringify loader)

) {
return new Promise<void>((resolve, reject) => {
if (
loaderIndex + 1 < loader.loaders.length &&
Copy link
Member

Choose a reason for hiding this comment

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

Could you put a comment here to clarify the purpose of the 2 code branches here please?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, I will.

@iorate
Copy link
Contributor Author

iorate commented May 23, 2020

Added a comment to updateFile.

}
}

callback();
Promise.all(promises)
Copy link
Member

Choose a reason for hiding this comment

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

I can't remember if we've discussed this already, are there any issues with us moving to a Promise (ie async) approach instead of the current approach? Given we're dealing with callbacks I suspect not, but I wanted to check

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 think there are no new issues introduced by the 'async' approach.

Copy link
Member

@johnnyreilly johnnyreilly left a comment

Choose a reason for hiding this comment

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

Could you update the package.json version number and add an entry to the CHANGELOG.md please?

@iorate
Copy link
Contributor Author

iorate commented May 24, 2020

May I "thank myself" in the changelog?

* [Apply other loaders when updating files in watch mode](https://github.com/TypeStrong/ts-loader/pull/1115) - thanks @iorate 

@johnnyreilly
Copy link
Member

That's the tradition!

@iorate
Copy link
Contributor Author

iorate commented May 24, 2020

Updated package.json and CHANGELOG.md. Thank you.

@johnnyreilly johnnyreilly merged commit d187a7c into TypeStrong:master May 24, 2020
@johnnyreilly
Copy link
Member

Will ship with https://github.com/TypeStrong/ts-loader/releases/tag/v7.0.5 - thanks for your help! 🌻

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.

Other loaders are not applied to unchanged files in watch mode
3 participants