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

webpack 5 migration #1251

Merged
merged 62 commits into from
Apr 18, 2021
Merged

webpack 5 migration #1251

merged 62 commits into from
Apr 18, 2021

Conversation

johnnyreilly
Copy link
Member

@johnnyreilly johnnyreilly commented Feb 4, 2021

This PR will be where the migration of ts-loader to webpack 5 takes place. Help is appreciated!

  • dependencies upgraded to webpack 5 (enhanced-resolve, webpack-cli, webpack)
  • drop support for webpack 4
  • make node 12 the minimum version
  • code compiles
  • drop support for experimental watch API - see webpack 5 migration #1251 (comment)
  • upgrade comparison test pack running against webpack 5
  • execution tests pass
  • increment major version to 9.0 and update CHANGELOG.md
  • update README.md

cc @alexander-akait @appzuka @JonWallsten - all advice and contributions towards this are gratefully received.

Feel free to PR or push into this branch to work on this.

Some discussion here: webpack/webpack#12475 (reply in thread)

UPDATE

Okay I think we're ready to ship. I'm going to:

  • create a branch for webpack 4 - so if there's a need to ship another change to support webpack 4 we can (I don't expect to but you never know)
  • write up a blog post to detail this
  • ship

@appzuka
Copy link
Member

appzuka commented Feb 4, 2021

Would this be a good point to drop support for experimentalWatchApi? As far as I can see it has never worked and it adds complexity to the code.

@JonWallsten
Copy link
Contributor

Don't forget to update webpack-cli to 4.5.0 (I think it's the latest).

@johnnyreilly
Copy link
Member Author

johnnyreilly commented Feb 4, 2021

Would this be a good point to drop support for experimentalWatchApi? As far as I can see it has never worked and it adds complexity to the code.

Go for it @appzuka - added to checklist above

@johnnyreilly
Copy link
Member Author

johnnyreilly commented Feb 4, 2021

Don't forget to update webpack-cli to 4.5.0 (I think it's the latest).

Good shout - added to checklist above

@JonWallsten
Copy link
Contributor

JonWallsten commented Feb 5, 2021

@johnnyreilly How do you normally run the loader when you are working? Do you have an example project you are running locally where you build with a local copy of the loader?

In short: What's your most optimized workflow?

Edit: I cloned a simple Webpack 4 + Typescript repo, upgraded all deps and added a symlink to my local ts-loader clone. Should be enough I think.

@JonWallsten
Copy link
Contributor

@johnnyreilly: How do you wanna handle these scenarios?
image

Potential fix:
image

@johnnyreilly
Copy link
Member Author

That seems like a legitimate workflow @JonWallsten - to your question about the fix: yeah maybe. Question: is it legitimate that a module may be a NormalModule? If so then seems reasonable

@JonWallsten
Copy link
Contributor

JonWallsten commented Feb 5, 2021

@johnnyreilly / @appzuka:
Getting back to this:
image

image

Am I correctly to assume that the first images shows where we ask TypeScript to compile and the second we pass those assets to Webpack?

Edit: So it seems like the ts-code is emitted in the first image and the d.ts and d.ts.map files are emitted in the second image. Makes sense since they should be "additional assets".

This seems to work fine:
image

@JonWallsten
Copy link
Contributor

That seems like a legitimate workflow @JonWallsten - to your question about the fix: yeah maybe. Question: is it legitimate that a module may be a NormalModule? If so then seems reasonable

According to the debugger, the current module I'm looking at is a NormalModule.
image
And accroding to the types:
image
NormalModule is the only Module related class that has the property resources. So we can assume that it has to be a NormalModule? I don't see any other way of getting the types to work besides setting it as any. And that would be sad! :(

@johnnyreilly
Copy link
Member Author

If that's what the debugger says, I trust it!

@JonWallsten
Copy link
Contributor

If that's what the debugger says, I trust it!

I can't promise that all modules are NormalModules, but if we trust the types, the resource prop only exists on NormalModule and since we do a nullcheck on that property in extension we should be able to check if it's a NormalModule so we get the correct Type information in the editor.

@appzuka
Copy link
Member

appzuka commented Feb 5, 2021

@JonWallsten,

I put some logging statements to clarify where assets are transpiled and emitted. When running on a small project (index.ts with sub.ts as a dependency) I get the following output:

Processing /home/nick/scratch/ts-loader-1243/src/index.ts
getEmitOutput:Sourcefile: /home/nick/scratch/ts-loader-1243/src/index.ts
getEmitOutput:Output: /home/nick/scratch/ts-loader-1243/dist/index.js
getEmitOutput:Output: /home/nick/scratch/ts-loader-1243/dist/index.d.ts.map
getEmitOutput:Output: /home/nick/scratch/ts-loader-1243/dist/index.d.ts
Returning transpiled file to webpack: /home/nick/scratch/ts-loader-1243/src/index.ts

Processing /home/nick/scratch/ts-loader-1243/src/sub.ts
getEmitOutput:Sourcefile: /home/nick/scratch/ts-loader-1243/src/sub.ts
getEmitOutput:Output: /home/nick/scratch/ts-loader-1243/dist/sub.js
getEmitOutput:Output: /home/nick/scratch/ts-loader-1243/dist/sub.d.ts.map
getEmitOutput:Output: /home/nick/scratch/ts-loader-1243/dist/sub.d.ts
Returning transpiled file to webpack: /home/nick/scratch/ts-loader-1243/src/sub.ts

** Running processAssets hook
getEmitOutput:Sourcefile: /home/nick/scratch/ts-loader-1243/src/index.ts
getEmitOutput:Output: /home/nick/scratch/ts-loader-1243/dist/index.js
getEmitOutput:Output: /home/nick/scratch/ts-loader-1243/dist/index.d.ts.map
getEmitOutput:Output: /home/nick/scratch/ts-loader-1243/dist/index.d.ts
>> outputFileToAsset: index.d.ts.map
>> outputFileToAsset: index.d.ts
getEmitOutput:Sourcefile: /home/nick/scratch/ts-loader-1243/src/sub.ts
getEmitOutput:Output: /home/nick/scratch/ts-loader-1243/dist/sub.js
getEmitOutput:Output: /home/nick/scratch/ts-loader-1243/dist/sub.d.ts.map
getEmitOutput:Output: /home/nick/scratch/ts-loader-1243/dist/sub.d.ts
>> outputFileToAsset: sub.d.ts.map
>> outputFileToAsset: sub.d.ts

I believe the program.emit statement in getEmitOutput is where the output from the TypeScript compiler is obtained. The input is a single .ts file and the output is a .js file and .d.ts and .d.ts.map files, if required.

The .js file is returned to webpack as the result of the compilation. This happens until all .ts files have been processed.

Then, during the processAssets hook, makeAfterCompile goes through all the files the TypeScript instance viewed, gets the outputs files from program.emit again, and adds .d.ts and .d.ts.map files as assets.

It seems inefficient that program.emit is called multiple times to get the output from .ts files, but I believe the output is cached so on subsequent calls it is just hitting the cache.

On this small example, each asset is only emitted to webpack once. So it seems that the asset output caching recommended by @alexander-akait may not be required. However, when I run this in watch mode the assets are emitted even if their contents have not changed, so there could be a benefit in implementing a cache around the asset output in watch mode. There could also be a benefit in more complex projects.

@JonWallsten
Copy link
Contributor

@appzuka: Thanks for the explanation! Do we know why the files are emitted in different steps? Is it supposed to be like that or just legacy?

@johnnyreilly
Copy link
Member Author

Hey @sokra

I hope you are keeping well!

I seem to remember you put together a doc which detailed the API changes between webpack 4 and 5 targeted at maintainers of loaders and plugins. We're finally getting round to building ts-loader with webpack 5 and bumping on a few missing pieces.

Do you happen to remember where that doc lives? For example we're trying to track down webpack.loader.LoaderContext and not finding it.

All help gratefully received!

@appzuka
Copy link
Member

appzuka commented Feb 6, 2021

@JonWallsten

Do we know why the files are emitted in different steps? Is it supposed to be like that or just legacy?

I understand why the js is returned separately from the .d.ts files but not why they are returned at different stages. A simple loader as described at https://webpack.js.org/contribute/writing-a-loader/ just takes an input file and transforms it to a string that is valid Javascript. In the case of ts-loader the input file is a .ts file, which is transformed into Javascript but also .d.ts and other files need to be added to the assets in the output directory. The Javascript output by typescript is not emitted as an asset but rather passed back to webpack for inclusion in the bundle.

What is not clear to me is why the .d.ts and other files are emitted together when all the .ts files have been processed rather than being emitted as assets during the getEmitOutput. It would make things simpler as we would not need the compilation/processAssets hooks at all, but perhaps there is a reason it was done this way.

It seems to me that processAssets is not really the right place to generate the assets. In the Terser example processAssets was used but then Terser is a plugin which processes assets generated by other plugins and loaders. In ts-loader we are not processing the assets, we are creating them.

I'm wary of changing the architecture in case we are repeating a mistake someone fixed several years ago, although simplifying ts-loader may also help improve performance and reduce errors.

@johnnyreilly
Copy link
Member Author

I'm wary of changing the architecture in case we are repeating a mistake someone fixed several years ago, although simplifying ts-loader may also help improve performance and reduce errors.

I have a feeling that there was work done around this but it was a long time ago and frankly I don't remember the details. I'm open to changes being made but it's possible there's a reason things are the way they are now and I'm just not recalling

@JonWallsten
Copy link
Contributor

JonWallsten commented Feb 6, 2021

It kind of make sense though that the d.ts and d.ts.map files are concidered additional files since they are not part of the bundle and Webpack is not built around TypeScript. So even though they are vital to us, it's not vital to Webpack in general. Or am i missing something?

@johnnyreilly
Copy link
Member Author

It kind of make sense though that the d.ts and d.ts.map files are concidered additional files since they are not part of the bundle and Webpack is not built around TypeScript. So even though they are vital to us, it's not vital to Webpack in general. Or am i missing something?

Sorry I missed this @JonWallsten - I don't quite follow the question.

Incidentally, it turns out that webpack isn't exporting all types... So some we'll have to hold onto ourselves

@johnnyreilly
Copy link
Member Author

Okay - the code compiles now; I borrowed a bunch of stuff from your branch @JonWallsten - thanks!

No tests pass alas

@johnnyreilly
Copy link
Member Author

Execution tests passing! (or it looks like it!)

@johnnyreilly johnnyreilly changed the title start webpack 5 migration webpack 5 migration Feb 18, 2021
@JonWallsten
Copy link
Contributor

It kind of make sense though that the d.ts and d.ts.map files are concidered additional files since they are not part of the bundle and Webpack is not built around TypeScript. So even though they are vital to us, it's not vital to Webpack in general. Or am i missing something?

Sorry I missed this @JonWallsten - I don't quite follow the question.

Incidentally, it turns out that webpack isn't exporting all types... So some we'll have to hold onto ourselves

This is like jeopardy. You give the answer I ask the question! 😂 Why is ts-files and d.ts/d.ts.map files emitted in different places in the code and Webpack lifecycle. I would say it's because declaration files are not part of the "code" in that sense, it's additional bonus stuff that will end up as separate files, not part of the bundle, in contrast to the transpiled/compiled (which is it?) code and the sourcemaps.

@johnnyreilly
Copy link
Member Author

Did you have a chance to try it out @JonWallsten? All feedback welcome!

@JonWallsten
Copy link
Contributor

Did you have a chance to try it out @JonWallsten? All feedback welcome!

Sorry! Got stuck with build issues in our project yesterday. I will try it out today instead.

@JonWallsten
Copy link
Contributor

JonWallsten commented Apr 16, 2021

I have tried to build a prod build, dev builds with --watch and changed a few files. Seems to work fine. Not slower nor faster then previously. No errors.

@JonWallsten
Copy link
Contributor

I guess we will get additional feedback once released! :)

@appzuka
Copy link
Member

appzuka commented Apr 16, 2021

@johnnyreilly , @JonWallsten, I don't believe I have contributed to this branch but I built a couple of my projects with the latest feat/webpack-5 branch today. They worked fine and I did not notice any performance or size issues. Well done.

@johnnyreilly
Copy link
Member Author

johnnyreilly commented Apr 16, 2021

Thanks @appzuka (very pleased that you gave it a test!)

// Consider changing this when ts-loader is built using webpack5
};
// It may be better to add assets at the processAssets stage (https://webpack.js.org/api/compilation-hooks/#processassets)
// This requires Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, which does not exist in webpack4
Copy link
Contributor

Choose a reason for hiding this comment

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

@johnnyreilly: I forgot about this. I think it was part of my PR when I sidetracked with the GitHub Workflow thing.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's fix that in a minor later!

@johnnyreilly
Copy link
Member Author

Cool - I'll finish writing up the blog post and we'll ship ⛴️

@johnnyreilly johnnyreilly merged commit c2d1ca2 into main Apr 18, 2021
@johnnyreilly johnnyreilly deleted the feat/webpack-5 branch April 18, 2021 18:25
@JonWallsten
Copy link
Contributor

@johnnyreilly: Woho! Super good work @johnnyreilly! 🏆Sorry I couldn't be more helpful the last couple of weeks!

@johnnyreilly
Copy link
Member Author

Thanks @JonWallsten - I appreciate the help that you gave!

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.

6 participants