nstudio / xplat

Cross-platform (xplat) tools for Nx workspaces.
MIT License
366 stars 52 forks source link

Transitioning from {NS} Code Splitting to Nx/Xplat #239

Closed jwrascoe closed 3 years ago

jwrascoe commented 3 years ago

{NS} Code splitting has a feature that's fairly smart...

When you build for web it ignores files like foo.tns.ts and uses foo.ts if present

When you build for {NS} it will use foo.tns.ts if foo.ts is present

This is handy when you are in a piece of code that is platform agnostic, but it needs to call something else that is platform dependent under the same exported name

https://docs.nativescript.org/angular/code-sharing/code-splitting

Using Nx/Xplat out of the box and im finding I have to duplicate code when I really don't want to... is there a way to configure the projects via the tsconfig so it can do the same thing {NS} does?

If not I totally get it.. just figure I would ask...

Im REALLY enjoying this product and trying to shift all my code out of the {NS} way into the Xplat/Nx way... thank you for such a wonderful platform!

Update (2 hours after writing this post)

I had a crazy idea, I just wrote the code as I normally would... didn't split the code out (put it in the device agnostic area) and it just worked! No other changes were required, the out of the box configuration knew how to handle it.

Case closed, hope this post helps someone else.

wSedlacek commented 3 years ago

@jwrascoe I would love to see an example of what you did. I am a bit new to NativeScript and xplat so having an example would mean a lot to me.

And since you, and I, both had this same question, maybe it is something that is worth actually documenting in xplat, or perhaps making a schematic for?

jwrascoe commented 3 years ago

@wSedlacek, I was able to make it work by simply putting all of the existing code I had written with {NS} code splitting into multiple Xplat feature modules (since all of my code was lazy loaded) i.e. libs/xplat/features (not able to leverage the nativescript & web platform directories)

However.. this was not really the correct Xplat way because code that really should have been in the libs/xplat/nativescript/features & libs/xplat/web/features directories could not be migrated due to the tricks that {NS} code splitting allows you to do.

After understanding all of that, I split all of the .tns.ts / tns.html code into libs/xplat/nativecript/features, code that was web only related into libs/xplat/web/features.. and then finally anything then that was truly shared into libs/xplat/features

I also found several things to that belonged into the core side of things as well.

For me the correct choice was to just use my existing code as a guide and put it back into a different directory structure that lended itself much better to sharing.

wSedlacek commented 3 years ago

Alright alright, it is starting to make a bit more sense but I still have some questions.

In any case, I think documentation here would go a long way. This might even make a good blog post or something.

jwrascoe commented 3 years ago

@wSedlacek I may make some notes in another few days and post... but so much of this is really more of a personal choice of how you want to introduce things into your mono repo.

The designers did a real good job of explaining what goes where... https://nstudio.io/xplat/fundamentals

Its really up to each of us on how we want to fill it all in.. it just takes time to absorb what they are saying in that fundamentals doc.. thats really the key.

NathanWalker commented 3 years ago

It sounds there is going to be multiple .component.ts when having it split between libraries, is there any way to avoid that?

There's a big difference between code duplication and code clarity. xplat takes a distinctive approach with it's architecture which is more akin to large framework architecture (Think iOS and Android platform architecture - base class > super class) mixing standard OOP inheritance with compositional elements (a best of both worlds approach).

In this case having xplat/web/my.component and xplat/nativescript/my.component is not only clear, but also clean and 1-1 for build tooling. Making it superior to alternate approaches which more often than not need specific build hooks, burdening the maintenance of the architecture.

That approach also plays into valuable workspace tooling allowing one to focus in on segments of their codebase by platform. Lastly, the approach avoids the "extension hell" which is terribly burdensome to navigate through and prone to visual fatigue.

Are there any patterns (ie interfaces) to assure feature parity across the web and native modules?

The generators enforce this inherently. They provide a platforms option to allow you to generate for any/all supported platforms at once creating concise consistency. For components they also allow an auto base connection to be created and prewired.

Can a single app have entry points for both web and native or do you need two separate apps for that? (If you need two apps is there a way to share the styles and routes between the two?)

Separate apps please. The problem with @nativescript/schematics is just this. You are quite literally never creating 1 app. App's are deployment targets and thus should have distinct app deployment targets. This provides enumerable benefits for scalability and versatility. The "Nx way" encourages slim apps and fat libs and the same holds true with xplat. Apps are the shell to deliver your shared code distinctly and clearly.

Would schematics that generate components across all active platform modules be useful or simply unnecessary?

The generators do just this, for example:

nx generate @nstudio/angular:component --name=login --platforms=web,nativescript

Would create the following:

CREATE libs/xplat/web/features/src/lib/ui/components/login/login.component.html
CREATE libs/xplat/web/features/src/lib/ui/components/login/login.component.ts
CREATE libs/xplat/nativescript/features/src/lib/ui/components/login/login.component.html
CREATE libs/xplat/nativescript/features/src/lib/ui/components/login/login.component.ts
UPDATE libs/xplat/web/features/src/lib/ui/components/index.ts
UPDATE libs/xplat/nativescript/features/src/lib/ui/components/index.ts

Auto annotating their index with them already pre-wired. You can designate feature as an argument which allows you to attach to different segments of the architecture, even allowing code to be generated in just plain ole' Nx libraries using their barrel name as the feature target.

If it's desired that a base component should drive the logic behind each platform decorated view, the --createBase argument does just that, ie:

nx generate @nstudio/angular:component --name=login --createBase --platforms=web,nativescript

Giving you the following added benefit:

CREATE libs/xplat/features/src/lib/ui/base/login.base-component.ts
CREATE libs/xplat/web/features/src/lib/ui/components/login/login.component.html
CREATE libs/xplat/web/features/src/lib/ui/components/login/login.component.ts
CREATE libs/xplat/nativescript/features/src/lib/ui/components/login/login.component.html
CREATE libs/xplat/nativescript/features/src/lib/ui/components/login/login.component.ts
UPDATE libs/xplat/features/src/lib/ui/base/index.ts
UPDATE libs/xplat/web/features/src/lib/ui/components/index.ts
UPDATE libs/xplat/nativescript/features/src/lib/ui/components/index.ts

This works consistently no matter the platform you are working with (Electron, Ionic, etc).

This all play's in Nx's powerful tooling like the dep-graph allowing you to visualize your projects scalability and cross reference integrity lines between platform boundary lines, see the visual here:

https://twitter.com/wwwalkerrun/status/1347348739971706881

wSedlacek commented 3 years ago

Thank you! This helped clear up a lot of things! I feel a lot more confident in working with xplat now.

wSedlacek commented 3 years ago

@NathanWalker I do have one other question, If I want to use something like DDD or a similar pattern how might I go about that?

From what I can tell I could build domains outside of the xplat library, however it sounds like I would be losing out of some of the power of the generators that you had just mentioned. As far as I can tell the xplat library seems to be pretty opinionated in it's structure so it seems like it wouldn't play to nice with altering that directory structure to fit that of DDD.

Another question does come to mind as I think about this, is there any reason to have any libraries other than xplat?

NathanWalker commented 3 years ago

Great question. Use it absolutely, love the lib as well. You can build 100's of libs outside of and on top of xplat. Use the @nrwl/workspace:library generator when creating other libs.

You can even create your own Nx libraries within libs/xplat as well by using the --directory option.

You can actually still use the xplat generators with both, just use the feature=@npmScope/somelib to target creating inside a Nx library. Where @npmScope/somelib is the barrel name as named in tsconfig.base.json paths. However you are never limited to just xplat generators so feel free to use any generator from other libraries whenever you need them.

How you decide to build on top of and around and get most out of xplat is up to each team. The core of xplat is intended to provide the fundamental building blocks to keep cross platform pitfalls at bay, most notably with the WindowService it provides. The LogService and how tokens are used for PlatformLanguageToken gives the necessary guidance on how to properly scale cross platform features, in this case, using Angular's strengths. It also provides the platform split to provide guidance and generation to start building smart with cross platform in mind today. You can expand separate Nx libraries alongside that in multitude of ways; that part we leave up to each team as that's purely opinionated space.

A simple example is a user Nx library. For example @npmScope/user:

libs/
  user/
    src/
      lib/
         models/
         services/
         state/
         user.module.ts
         index.ts

This library is purely javascript, (models, services, state) thus "platform agnostic". Take note it doesn't provide any components which are often platform specific (thus requiring platform specific view decorations). It can be made as a dependant to many other libs (including xplat layers) and/or apps in however you see fit.

Hopefully that gives you some clarity there.