Currently, the documentation section seems somewhat anemic on the best way to migrate out of global namespaces and into file modules. From what I can tell, that whole process is a not-well-supported there-be-dragons-here type of experience that seems to involve wholly converting your codebase and dependencies on your codebase in one fell swoop.
I'm writing this issue up as someone trying to do it, and struggling to find an incremental approach. If there is a known incremental approach, can that be shared and added to the documentation (I would be glad to get a PR with the doc changes)? If there isn't an incremental approach, is that something that could be treated as a missing feature?
The only module systems that actually can mix local/global declarations are SystemJS and AMD (Require.js). I would utilize the existing namespaces as globals from your files written as modules, and progressively turn each namespace into a module.
Once you've moved all your namespaces into modules, you can use whatever module loader you want (e.g. CommonJS).
Does that sort of help? What questions have you encountered if not?
I think you're describing a pattern where, in the files that you are starting to convert "to" file moduling, you could import the globals if you were using AMD or SystemJS. I think that does sort of help -- it lets me take time making the change to my codebase in isolation. I'm starting to look into exactly how that works syntactically.
It does only "sort of" help, though, because what I really want to be able to do is keep our global namespaces in place (but deprecated) until both our conversion and dependent libraries' conversions are complete. Is there any avenue for doing something like that?
Also, I would be curious to know what strategy you're suggesting could be used to import global namespaces using either of the loaders you mentioned (I'm in webpack, so I think AMD). I'm not seeing much documented about how that is accomplished (or I'm searching for the wrong thing :) ).
what I really want to be able to do is keep our global namespaces in place (but deprecated)
You can simply keep the namespace versions around while also making the module versions available. They can coexist, but I'll just mention that in some places where you mean to use the module version, you may forget to use an import and accidentally use the global namespace. In practice, this won't cause any problems.
Using AMD/System.js as an intermediate step has the advantage that it can be used with --outFile
, and mix global/module files (@mhegazy correct me if I'm wrong). You can then switch to Webpack later on.
However, if I were determined to use Webpack throughout this process, I'd use the externals
field which can redirect your imports to global variables. Then you can include your global files separately (e.g. <script>
tags).
I don't know if Webpack has an easy way of concatenating global files with your output's bundle. @TheLarkInn would know better than I do. 馃槃
You can simply keep the namespace versions around while also making the module versions available.
How does this work tactically? From what I can tell, TS has a file level "Switch" that gets flipped when using imports statements that make it really difficult to do this without physically copying code between files (e.g. if I want File1 to be available in a global namespace as well as a file module, I need to have two different files). Am I missing something?
I'm mostly focused on the TS-specific parts of the problem in this issue, as I think it's very specific TS level behavior that has this "file-level switch" whenever imports or exports make it into the equation.
If you have a global declaration (i.e. your namespaces), then those will be visible to all your modules anyway. So you can have foo.global.ts
for your namespace, and then a foo.ts
which is just
export function abc() {
// This reference to 'foo' will be visible here.
foo.abc();
}
or
// This reference to 'foo' will be visible here.
export = foo;
As i noted in https://github.com/Microsoft/TypeScript/issues/8004#issuecomment-262673275, most of the work is in figuring what are your "modules" going to be.
you can then pick one of these, preferably at the leaf, and switch it to a module. a module can use a global, but not the opposite. then the user of this module (e.g. html page with a script tag today) will have to reference it using a module loader. then keep iterating until you change your core to modules as well..
don't know if Webpack has an easy way of concatenating global files with your output's bundle. @TheLarkInn would know better than I do. 馃槃
I always take the break-it-till-you-make-it-(work) approach with webpack and globals. Ideally you want to bundle as much lib source as possible. So I start out by just ripping off the bandaid at once: remove all script tags from your html page, then try and download locally as many npm deps to replace those scripts as possible so they can be bundled together the right way. This would involve additional shimming for "broken modules" like jquery, as well as downloading any typings for libs that you are using in your src.
Externals is an okay choice but the more you bundle src locally the more powerful/optimized your builds will be.
I know this doesn't cover the entire migration process but this is usually a great start to freeing yourselves from the global namespaces.
The biggest obstacle to moving from global namespaces is that I already have a number of webpack-specific concerns taken care of with a different approach (e.g. gulp doing concat and other things). I'm trying to isolate change as this isn't a small project, but rather a common SDK / Shell platform used by other teams, so ideally I would roll out these types of changes incrementally.
Step 1 for me would be getting my package to a point where it fully supports a module loader pattern and integrating webpack as a replacement for the existing gulp tasks. Along the way, I would _really_ like to maintain backwards compatibility until all consumers have snapped to the new model.
Right now, I'm actually creating a script to "automatically" create a module layer in front of my global namespaces, but am running into issues like being unable to export my interfaces in the same way I export my classes. E.g. interfaces seem not to export in the below syntax:
///<reference path="./some-global-file.ts" />
export = {
"SomeInterface": <Namespace>.SomeInterface,
"SomeClassImplementation": <Namespace>.SomeClassImplementation,
};
@brphelps you can use a namespace to re-export your types.
namespace reexports {
export SomeInterface extends SomeNamespace.SomeInterface {}
export const SomeClassImplementation = SomeNamespace.SomeClassImplementation;
}
export = reexports;
That's pretty interesting, let me give that a try. I have a script I'm working on that is going through and generating these wrapper files, and this looks cleaner than the alternative (which would've required me to default exports the interfaces in their own file).
I'll give it a try sometime today and follow up.
@DanielRosenwasser :
I'm finally returning to this, and noticing some edge cases with the above approach, specifically around enumerations. I can't see to "reexport" the enumerations in a reasonable way -- I end up having to do something like this:
export type CodeRedemptionStatus = ServiceDesk.Services.Fortification.CodeRedemptionStatus;
export const CodeRedemptionStatusValues = ServiceDesk.Services.Fortification.CodeRedemptionStatus;
Which sucks. Is there a better way?
. I can't see to "reexport" the enumerations in a reasonable way -- I end up having to do something like this:
why not just
import CodeRedemptionStatus = ServiceDesk.Services.Fortification.CodeRedemptionStatus;
export {CodeRedemptionStatus };
That works! But I don't really understand why -- I didn't know you could "import X =
Thank you :).
Does not seem that we have any documentation for import alias declarations on the handbook. filed https://github.com/Microsoft/TypeScript-Handbook/issues/598 to track that.
you can find more info though in the spec: https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#10.3.
import A = N.X
declares an alias A
for all meanings of N.X
. it is meant specifically for cases like yours where you want to have a shorter name for a namespace export.
I have something working at this point as I've had a few days to get back into it. It's a script that loads the AST of the global namespaced project, creates a graph for the namespace "nodes" and then creates corresponding files that wrap each node in their own file module and re-export moving vertically.
While this seems like it would be an antipattern in file module loading, it will unblock us from moving towards a hybrid solution while our consumers start adopting our file module wrapper package. The script is pretty horrible code, but one thing it shows is that I think the whole process could be automated-- e.g. what if TS just had a switch (or a script) you could flip and it would generate file module adapter files to global namespace code...? :)
@brphelps we are in a similar situation, it would be nice if you share the code. It does not matter how bad/good it is...
This was one of the threads I kept bumping into when looking how to convert namespaces to modules. Unfortunately I couldn't follow the solution commented here as it seems only @brphelps knew what he was doing. Also I feel many steps were missed along the way.
So after a long investigation, putting scattered pieces from the Web together, and some creativiy, I managed to get to a PROGRESSIVE migration solution, meaning, chosing exactly which files to migrate and which not.
As that was for me a lot of suffering and I did not find anywhere the solution I was looking for I decided to create a thorough article for the next one like me that was looking for a clear solution to "Migrating from namespaces to modules":
https://jorgeartieda.gitbook.io/typescript-from-namespaces-to-modules/
I wrote up a solution we ended up using here: https://www.geekytidbits.com/typescript-progressively-convert-namespaces-to-modules/. Also, I have an example repository of the approach here: https://github.com/bradymholt/ts-progressive-convert-namespace-modules.
I've been working on a transformer which uses the typescript compiler api to automatically transform namespaces to modules. Will post a link to the repo shortly but if anyone is interested please get in touch.
@Mknight492 Have you succeed? If yes, it will be cool to see your solution.
Most helpful comment
This was one of the threads I kept bumping into when looking how to convert namespaces to modules. Unfortunately I couldn't follow the solution commented here as it seems only @brphelps knew what he was doing. Also I feel many steps were missed along the way.
So after a long investigation, putting scattered pieces from the Web together, and some creativiy, I managed to get to a PROGRESSIVE migration solution, meaning, chosing exactly which files to migrate and which not.
As that was for me a lot of suffering and I did not find anywhere the solution I was looking for I decided to create a thorough article for the next one like me that was looking for a clear solution to "Migrating from namespaces to modules":
https://jorgeartieda.gitbook.io/typescript-from-namespaces-to-modules/