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

Guidance on migrating away from global namespaces to file modules #12473

Closed
brphelps opened this issue Nov 23, 2016 · 23 comments
Closed

Guidance on migrating away from global namespaces to file modules #12473

brphelps opened this issue Nov 23, 2016 · 23 comments
Labels
Discussion Issues which may not have code impact

Comments

@brphelps
Copy link

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?

@DanielRosenwasser
Copy link
Member

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?

@brphelps
Copy link
Author

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?

@brphelps
Copy link
Author

brphelps commented Nov 23, 2016

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 :) ).

@DanielRosenwasser
Copy link
Member

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. 😄

@brphelps
Copy link
Author

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.

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Nov 28, 2016

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;

@mhegazy
Copy link
Contributor

mhegazy commented Nov 28, 2016

As i noted in #8004 (comment), 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..

@TheLarkInn
Copy link
Member

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.

@brphelps
Copy link
Author

brphelps commented Nov 29, 2016

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,
};

@mhegazy mhegazy added the Discussion Issues which may not have code impact label Nov 29, 2016
@DanielRosenwasser
Copy link
Member

@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;

@brphelps
Copy link
Author

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.

@brphelps
Copy link
Author

brphelps commented Jun 2, 2017

@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?

@mhegazy
Copy link
Contributor

mhegazy commented Jun 2, 2017

. 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 };

@brphelps
Copy link
Author

brphelps commented Jun 2, 2017

That works! But I don't really understand why -- I didn't know you could "import X = <enum/ type/ ???>" . Is there a doc page I should look at?

Thank you :).

@mhegazy
Copy link
Contributor

mhegazy commented Jun 2, 2017

Does not seem that we have any documentation for import alias declarations on the handbook. filed microsoft/TypeScript-Handbook#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.

@brphelps
Copy link
Author

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...? :)

@CrlsMrls
Copy link

CrlsMrls commented Sep 6, 2017

@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...

@calamao
Copy link

calamao commented Jan 10, 2019

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/

@bradymholt
Copy link

bradymholt commented Aug 28, 2019

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.

@Mknight492
Copy link

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.

@acherkashin
Copy link

@Mknight492 Have you succeed? If yes, it will be cool to see your solution.

@JirkaDellOro
Copy link

I've experienced many times that people like myself struggle a lot with ts-namespaces and es-modules, especially when it comes down to try to work with both in the same project. I did some "structured" fiddling and found that on the Javascript-side after compilation, things appear to be quite simple, but can't be done in a standard way with typescript before compilation.

I wrote about it here and I'd be happy to see some comments on that before I start creating a plugin or extension, that tweaks the compiled output. I expect to be missing some important parts...

https://jirkadelloro.github.io/TS-Namespaces-ES-Modules/

@juanrgm
Copy link

juanrgm commented Feb 20, 2023

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.

Is the code available? @Mknight492

I have to migrate an old project and it would be interesting not to develop the tool from scratch, although with ts-morph it would be relatively easy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Discussion Issues which may not have code impact
Projects
None yet
Development

No branches or pull requests