Working on pull request #520, and reading the comments in #506, has made me realise that ts-loader is not a well behaved webpack loader.
From the webpack documentation for how to write a loader:
Guidelines
(Ordered by priority, first one should get the highest priority)
Loaders should do only a single task
Loaders can be chained. Create loaders for every step, instead of a loader that does everything at once.
This also means they should not convert to JavaScript if not necessary.
Loaders should do only a single task
ts-loader does only do a single task: compiling TypeScript modules.
Loaders should not convert to JavaScript if not necessary
ts-loader does convert to JavaScript, and it is necessary.
Loaders can be chained
This is where ts-loader is not a well behaved webpack loader.
The TypeScript compiler needs to have access to modules imported into the current module. ts-loader does this by reading directly from the file system, if the file has not been seen previously. It is therefore not always possible to make a previous loader in the same chain work properly (even with pull request #520).
Proposal
Goals
To make ts-loader a better webpack citizen I propose that the ts-loader is changed to achieve the following:
-
TypeScript files should be resolved as webpack modules, using whatever resolution rules are set up for webpack.
-
ts-loader should never read modules directly from the file system
The method loaderContext.loadModule, or similar, should be used in stead. This will enable other loaders to make changes to source files, or even generate virtual source files.
-
ts-loader should never read a module more than once during a compilation
This is essential for good performance, and should avoid problems with circular references.
-
During watch ts-loader should only read a module if it has changed
This is essential for good performance. It should be investigated whether ts-loader needs to track changed files to achieve this, or whether webpack can do so.
Implementation
Because loading modules in webpack is an asynchronous operation, and the TypeScript compiler reads files synchronously, it will probably be necessary to compile each file in several phases:
-
Save the actual TypeScript source code (as it is after running through previous loaders) in a module cache.
-
Get a list of all imported modules using ts.preProcessFile.
-
For each imported module, that is not already in the module cache, do the following (these are asynchonous operations):
-
Each possible path for the module should be resolved by trying to add .ts, .tsx, .d.ts, etc. and using loaderContext.resolve.
-
Try loading each of the resolved paths until loading succeeds. Save the succeeded module source code in the module cache, or mark the module as non existent. See if we can load any TypeScript modules without compiling them (for example by adding a query parameter to the module request).
This could be achieved using loaderContext.loadModule. However, loaderContext.loadModule does not load modules recursively, so it may be necessary to code our own loadModule function (as I have done in ts-css-loader).
-
Synchronously compile the TypeScript source using source code saved in the module cache.
Conclusion
I would be very happy to work on this, but only if there is interest.
Working on pull request #520, and reading the comments in #506, has made me realise that ts-loader is not a well behaved webpack loader.
From the webpack documentation for how to write a loader:
Loaders should do only a single task
ts-loader does only do a single task: compiling TypeScript modules.
Loaders should not convert to JavaScript if not necessary
ts-loader does convert to JavaScript, and it is necessary.
Loaders can be chained
This is where ts-loader is not a well behaved webpack loader.
The TypeScript compiler needs to have access to modules imported into the current module. ts-loader does this by reading directly from the file system, if the file has not been seen previously. It is therefore not always possible to make a previous loader in the same chain work properly (even with pull request #520).
Proposal
Goals
To make ts-loader a better webpack citizen I propose that the ts-loader is changed to achieve the following:
TypeScript files should be resolved as webpack modules, using whatever resolution rules are set up for webpack.
ts-loader should never read modules directly from the file system
The method
loaderContext.loadModule, or similar, should be used in stead. This will enable other loaders to make changes to source files, or even generate virtual source files.ts-loader should never read a module more than once during a compilation
This is essential for good performance, and should avoid problems with circular references.
During watch ts-loader should only read a module if it has changed
This is essential for good performance. It should be investigated whether ts-loader needs to track changed files to achieve this, or whether webpack can do so.
Implementation
Because loading modules in webpack is an asynchronous operation, and the TypeScript compiler reads files synchronously, it will probably be necessary to compile each file in several phases:
Save the actual TypeScript source code (as it is after running through previous loaders) in a module cache.
Get a list of all imported modules using
ts.preProcessFile.For each imported module, that is not already in the module cache, do the following (these are asynchonous operations):
Each possible path for the module should be resolved by trying to add
.ts,.tsx,.d.ts, etc. and usingloaderContext.resolve.Try loading each of the resolved paths until loading succeeds. Save the succeeded module source code in the module cache, or mark the module as non existent. See if we can load any TypeScript modules without compiling them (for example by adding a query parameter to the module request).
This could be achieved using
loaderContext.loadModule. However,loaderContext.loadModuledoes not load modules recursively, so it may be necessary to code our ownloadModulefunction (as I have done in ts-css-loader).Synchronously compile the TypeScript source using source code saved in the module cache.
Conclusion
I would be very happy to work on this, but only if there is interest.