Open csimpi opened 3 years ago
@csimpi Hi, When coming from @nativescript/schematics (or nativescript-schematics) there's a few important things to understand on the surface:
In @nativescript/schematics (or nativescript-schematics) you might have something like this:
src/app/account/account.component.html
src/app/account/account.component.tns.html
src/app/account/account.component.tns.ts
src/app/account/account.component.ts
With Nx+xplat it would look like this:
libs/xplat/features/src/lib/ui/base/account.base-component.ts
libs/xplat/nativescript/features/src/lib/ui/components/account/account.component.html
libs/xplat/nativescript/features/src/lib/ui/components/account/account.component.ts
libs/xplat/web/features/src/lib/ui/components/account/account.component.html
libs/xplat/web/features/src/lib/ui/components/account/account.component.ts
The above uses a unique option createBase
(which is optional and off by default) but used when you want to drive platform decorated components with one singular class logic (account.base-component). This component structure can be generated in your own workspace with this command (using dry-run so you can try it and see result without actually generating):
nx generate @nstudio/angular:component --name=account --createBase --platforms=nativescript,web --no-interactive --dry-run
This allows any app in the Nx workspace to simply consume that component however it needs. For example, given you have 1 web app and 1 nativescript app in your workspace (created using nx g app
and choosing the appropriate platform), you would have a structure of apps like this:
apps
web-myapp
nativescript-myapp
Each of those apps can import the component from the specific location cleanly:
For apps/web-myapp, it could import that component like this:
import { AccountComponent } from '@yourscope/xplat/web/features';
For apps/nativescript-myapp, it could import that component like this:
import { AccountComponent } from '@yourscope/xplat/nativescript/features';
Same component name, no custom file extensions, natural folder organization for code, clarity in what you are dealing with (what platform is this thing?) leading to no custom file extension loaders, no overhead in maintenance and natural webpack handling.
There's more I can share later but that's one of biggest things to understand to start with.
What is the difference between ng g component and ng g feature?
ng g component
= Component which is generated into a module
ui
feature or a feature of your choice provided you had created a feature for it (more on that as follows...).ng g feature
= Generation of an entirely new module with or without a component attached to it
How can I add subfolders? nx g feature doesn't have --directory parameter? I would like to organize my features, for example, pages would go into the pages folder.
When using the feature
generator to create new page routes you can do this:
> nx generate @nstudio/angular:feature --name=page-account --onlyProject --projects=web-myapp --routing --no-interactive --dry-run
CREATE apps/web-myapp/src/app/features/page-account/page-account.module.ts
CREATE apps/web-myapp/src/app/features/page-account/index.ts
CREATE apps/web-myapp/src/app/features/page-account/components/index.ts
CREATE apps/web-myapp/src/app/features/page-account/components/page-account/page-account.component.html
CREATE apps/web-myapp/src/app/features/page-account/components/page-account/page-account.component.ts
UPDATE apps/web-myapp/src/app/app.routing.ts
That would auto annotate the app's routing and auto configure a route by that name page-account
(you can manually adjust that anytime in the routes yourself if you'd like). The same thing works for NativeScript if you just specify the nativescript app name with the --projects=nativescript-myapp
argument.
The key thing about the above feature
generation is the onlyProject
argument. This directs the generation to an app only. This is important to understand with xplat as the absence of that argument defaults to generating "shared code" in the libs, for example here's a very sophisticated example of shared code generation of a brand new feature "account" which would create web+mobile counterparts along with even a base to drive the default component it attaches to each:
nx generate @nstudio/angular:feature --name=account --createBase --platforms=nativescript,web --no-interactive --dry-run
CREATE libs/xplat/features/src/lib/account/account.module.ts
CREATE libs/xplat/features/src/lib/account/index.ts
CREATE libs/xplat/features/src/lib/account/base/account.base-component.ts
CREATE libs/xplat/features/src/lib/account/base/index.ts
CREATE libs/xplat/nativescript/features/src/lib/account/account.module.ts
CREATE libs/xplat/nativescript/features/src/lib/account/index.ts
CREATE libs/xplat/nativescript/features/src/lib/account/components/index.ts
CREATE libs/xplat/nativescript/features/src/lib/account/components/account/account.component.html
CREATE libs/xplat/nativescript/features/src/lib/account/components/account/account.component.ts
CREATE libs/xplat/web/features/src/lib/account/account.module.ts
CREATE libs/xplat/web/features/src/lib/account/index.ts
CREATE libs/xplat/web/features/src/lib/account/components/index.ts
CREATE libs/xplat/web/features/src/lib/account/components/account/account.component.html
CREATE libs/xplat/web/features/src/lib/account/components/account/account.component.ts
UPDATE libs/xplat/features/src/lib/index.ts
UPDATE libs/xplat/nativescript/features/src/lib/index.ts
UPDATE libs/xplat/web/features/src/lib/index.ts
This creates:
import { AccountModule } from '@yourscope/xplat/web/features';
// and...
import { AccountModule } from '@yourscope/xplat/nativescript/features';
Giving you the ability to immediately start working with your new cross platform feature.
what is the default shared.module for? Why UI module has been wrapped into shared.module? Why not importing UI module itself directly?
This is a great question and easily the most often confused notion of xplat. Since xplat is about sharing code in general and Nx is all about sharing code, the notion of "SharedModule" loses it's meaning in the scope of shared code alone like "libs" since everything in libs is "shared", whether it's called SharedModule or not. So xplat makes a distinction here by naming those "shared SharedModule's" UIModule because in reality that is what they are. UIModule is a sharable module housing ui counterparts that can be imported/exported from any other modules that need to consume those ui pieces (components, directives or pipes).
Each app has it's own "SharedModule" which auto imports the corresponding platform UIModule by default allowing you to just use the generators to begin rapidly building out code while the generators auto annotate the UIModule for you and thus since each app already imports UIModule from the respective correct platform shared code (xplat/web or xplat/nativescript) you can just start using that stuff - with the confidence that there will be no platform bleed or interference because they are neatly isolated and organized already.
The default behavior of the component, directive, pipe generators is to target the ui
feature, thus the UIModule for each respective platform as a convenience. It's up to you if you want to generate your own feature modules using the feature
generator to direct your shared code generation to attach itself to different features, for example provided the 'account' feature example above, you could generate more components for that feature like this:
nx generate @nstudio/angular:component --name=settings --feature=account --platforms=nativescript,web --no-interactive --dry-run
CREATE libs/xplat/nativescript/features/src/lib/account/components/settings/settings.component.html
CREATE libs/xplat/nativescript/features/src/lib/account/components/settings/settings.component.ts
CREATE libs/xplat/web/features/src/lib/account/components/settings/settings.component.html
CREATE libs/xplat/web/features/src/lib/account/components/settings/settings.component.ts
UPDATE libs/xplat/nativescript/features/src/lib/account/components/index.ts
UPDATE libs/xplat/web/features/src/lib/account/components/index.ts
That SettingsComponent
is now immediately usable via AccountModule
in any Web and NativeScript app in your workspace without any additional configuration. You just start using it and enhancing/building it out.
I highly recommend using Nx Console with VS Code as it provides a visual interaction tool to all the options the schematics provide (even outside of xplat) so it's insanely helpful in understanding generators, their options, and given it executes in dry-run
mode by default, it allows you to try things out and poke around at what other options would do.
Hope that helps clarify a few things.
The other thing worth mentioning is core vs. features.
This concept goes back to a widely talked about topic with regards to Angular specifically - CoreModule vs. SharedModule. For reference just a few discussions: https://stackoverflow.com/questions/42695931/angular2-coremodule-vs-sharedmodule https://levelup.gitconnected.com/where-shall-i-put-that-core-vs-shared-module-in-angular-5fdad16fcecc https://thetombomb.com/posts/app-core-shared-feature-modules
A well known and fascinating topic with Angular in general and you can find even more references to this concept.
With xplat, the understanding of core vs. feature runs strong as it uses each for the same reasons discussed widely in those references.
CoreModule is imported once into a module stack of an app and used for singleton services and used most often for runtime configuration of various services. xplat specifically uses them to that advantage to configure services for the various platform runtimes, you can see that at play in xplat/web/core -> CoreModule and xplat/nativescript/core -> CoreModule respectively. They both configure shared code for the bottom lowest layer of the architecture xplat/core which is a central location of widely used services across the whole spectrum of cross platform work you may build in a Nx + xplat workspace. This allows developers to use 1 service api with confidence that each platform CoreModule configures that service appropriately for the target runtime in which the app is running, be it web, ios or android.
Everything that's not core is generally a feature and typically organized as such since features are the often optional lazy loaded (or not) portions of code that certainly play a role in any app deployed from the workspace but may not be central to the core operation of any given app. For example, an http interceptor that controls all aspects of every app's interaction with your organizations backend api's would be considered core to all apps within the workspace whereas a SettingsComponent
provided by an 'account' feature may only be used in 2 out of 20 apps in the workspace and at that likely lazy loaded, therefore not considered core to the operational architecture of the workspace as a whole.
@NathanWalker Thank you for the answers, it's clearer now, and I see what is the approach.
What I'd still need to know how to organize features to directories. I understand you recommend using page-*
but think of a situation when you have 50+ pages, and any other features, all of them in the same folder... this would make the development almost impossible. IMHO, forcing users to NOT structure their codes into folders feeling nonsense to me. In our current web app we have ~100 components. Not possible to handle the codebase without folders.
I even have to sturcture our pages into sub-subfolder like: pages/auth/login
, or pages/dashboard/settings
I'd propose something here:
Why not support /
as Angular generators do originally?
Similarly to the page-account
, pages/account
could work => this should generate the feature in the pages
folder, and place AccountModule
there (or even PagesAccountModule
).
I've tested this and since Angular can handle /
in path, it generates the subfolder, BUT the name of the module is wrong:
nx generate @nstudio/angular:feature --name=pages/account --createBase --projects=web-app,nativescript-app --no-interactive --dry-run <
Could not format /libs/xplat/features/src/lib/pages/account/pages/account.module.ts because '{' expected. (6:19)
4 | schemas: [NO_ERRORS_SCHEMA],
5 | })
> 6 | export class Pages/accountModule {}
| ^
7 |
Could not format /libs/xplat/features/src/lib/pages/account/base/pages/account.base-component.ts because '{' expected. (5:28)
3 |
4 | @Directive()
> 5 | export abstract class Pages/accountBaseComponent extends BaseComponent {
| ^
6 |
7 | public text: string = 'Pages/account';
8 |
Could not format /libs/xplat/web/features/src/lib/pages/account/pages/account.module.ts because ',' expected. (2:15)
1 | import { NgModule } from '@angular/core';
> 2 | import { Pages/accountModule as SharedPages/accountModule } from '@mtp-ui/xplat/features';
| ^
3 | import { UIModule } from '../ui/ui.module';
4 | import { PAGESACCOUNT_COMPONENTS } from './components';
5 |
Could not format /libs/xplat/web/features/src/lib/pages/account/components/index.ts because ',' expected. (1:15)
> 1 | import { Pages/accountComponent } from './pages/account/pages/account.component';
| ^
2 |
3 | export const PAGESACCOUNT_COMPONENTS = [
4 | Pages/accountComponent
Could not format /libs/xplat/web/features/src/lib/pages/account/components/pages/account/pages/account.component.ts because ',' expected. (3:15)
1 | import { Component } from '@angular/core';
2 |
> 3 | import { Pages/accountBaseComponent } from '@mtp-ui/xplat/features';
| ^
4 |
5 | @Component({
6 | selector: 'mtp-ui-pages/account',
Could not format /libs/xplat/nativescript/features/src/lib/pages/account/pages/account.module.ts because ',' expected. (2:15)
1 | import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
> 2 | import { Pages/accountModule as SharedPages/accountModule } from '@mtp-ui/xplat/features';
| ^
3 | import { UIModule } from '../ui/ui.module';
4 | import { PAGESACCOUNT_COMPONENTS } from './components';
5 |
Could not format /libs/xplat/nativescript/features/src/lib/pages/account/components/index.ts because ',' expected. (1:15)
> 1 | import { Pages/accountComponent } from './pages/account/pages/account.component';
| ^
2 |
3 | export const PAGESACCOUNT_COMPONENTS = [
4 | Pages/accountComponent
Could not format /libs/xplat/nativescript/features/src/lib/pages/account/components/pages/account/pages/account.component.ts because ',' expected. (3:15)
1 | import { Component } from '@angular/core';
2 |
> 3 | import { Pages/accountBaseComponent } from '@mtp-ui/xplat/features';
| ^
4 |
5 | @Component({
6 | moduleId: module.id,
CREATE libs/xplat/features/src/lib/pages/account/index.ts
CREATE libs/xplat/features/src/lib/pages/account/pages/account.module.ts
CREATE libs/xplat/features/src/lib/pages/account/base/index.ts
CREATE libs/xplat/features/src/lib/pages/account/base/pages/account.base-component.ts
CREATE libs/xplat/web/features/src/lib/pages/account/index.ts
CREATE libs/xplat/web/features/src/lib/pages/account/pages/account.module.ts
CREATE libs/xplat/web/features/src/lib/pages/account/components/index.ts
CREATE libs/xplat/web/features/src/lib/pages/account/components/pages/account/pages/account.component.html
CREATE libs/xplat/web/features/src/lib/pages/account/components/pages/account/pages/account.component.ts
CREATE libs/xplat/nativescript/features/src/lib/pages/account/index.ts
CREATE libs/xplat/nativescript/features/src/lib/pages/account/pages/account.module.ts
CREATE libs/xplat/nativescript/features/src/lib/pages/account/components/index.ts
CREATE libs/xplat/nativescript/features/src/lib/pages/account/components/pages/account/pages/account.component.html
CREATE libs/xplat/nativescript/features/src/lib/pages/account/components/pages/account/pages/account.component.ts
UPDATE libs/xplat/features/src/lib/index.ts
UPDATE libs/xplat/web/features/src/lib/index.ts
UPDATE libs/xplat/nativescript/features/src/lib/index.ts
I didn't test it through but I think adding the below lines to XplatFeatureHelper
getTemplateOptions()
should do the trick. The dryRun
looks good so far, even if I use mixed /
and -
, for example pages/main-account
/packages/xplat/src/utils/xplat.ts:1196
const folderParts = options.name.split('/');
if (folderParts.length > 1) {
options.name = stringUtils.capitalize(folderParts[folderParts.length - 1]);
}
Result:
CREATE libs/xplat/features/src/lib/pages/dashboard/main-account/main-account.module.ts
CREATE libs/xplat/features/src/lib/pages/dashboard/main-account/index.ts
CREATE libs/xplat/features/src/lib/main-account/base/main-account.base-component.ts
CREATE libs/xplat/features/src/lib/main-account/base/index.ts
CREATE libs/xplat/web/features/src/lib/main-account/main-account.module.ts
CREATE libs/xplat/web/features/src/lib/main-account/index.ts
CREATE libs/xplat/web/features/src/lib/main-account/components/index.ts
CREATE libs/xplat/web/features/src/lib/main-account/components/main-account/main-account.component.html
CREATE libs/xplat/web/features/src/lib/main-account/components/main-account/main-account.component.ts
CREATE libs/xplat/nativescript/features/src/lib/main-account/main-account.module.ts
CREATE libs/xplat/nativescript/features/src/lib/main-account/index.ts
CREATE libs/xplat/nativescript/features/src/lib/main-account/components/index.ts
CREATE libs/xplat/nativescript/features/src/lib/main-account/components/main-account/main-account.component.html
CREATE libs/xplat/nativescript/features/src/lib/main-account/components/main-account/main-account.component.ts
UPDATE libs/xplat/features/src/lib/index.ts
UPDATE libs/xplat/web/features/src/lib/index.ts
UPDATE libs/xplat/nativescript/features/src/lib/index.ts
@csimpi that makes sense yeah - if you want to post a PR of the branch mention there I could run some tests here and we could try to get out in a patch update. I just need to check it against a few flows.
@NathanWalker Thank you, PR sent. I've built a version and the tests passed on my side. Sorry, but I had no time to create some extra tests to check the results but looks good so far, I'll continue the testing now with the locally built version with a real project now.
This is a great question with great answers! I was just wondering the same thing. I am just getting into Xplat, seems you have really been thinking when you did the design, great job! You should post these examples on your web @NathanWalker, including the use case with the directories. Thanks for this!
Thanks @s0l4r - lemme know if you've encountered any other questions past couple days. There's been a lot of evolution with Nx since xplat started and they have a @nrwl/devkit now which is super nice and we're in process of adhering to the new devkit sometime in 2022 which will help streamline more. We're also planning to swap some under hood processes to use other established packages (like @nxtend for ionic handling).
@NathanWalker Is there any advice on how to use Nativescript platform specific files (aka. .android.ts and .ios.ts)? the build fails with current default configurations: map.component.android.ts is missing from the TypeScript compilation.
. Adding */.android.ts and .d.ts in ts.config didn't fix the problem.
Hi NS team,
I'm also currently doing my best to finally migrate all my big codebase to brand new nx workspace... To be honest, it's a bit hard ... I don't know where to define yet all my old NS6 big shared core modules and split into features, core etc...
Previously with shared code project on NS6... I used to create helpers files with and without the tns.ts suffix in a very top shared modules.... so it was easy to share an helper for web and another for mobile and inject the proper runtime one into another class constructor etc...
But now I'm a bit stucked with them. I'm searching how I can achieve the same kind of injection behavior but within Nx Workspace between a xplat/web/core/src/lib/helpers/... and xplat/nativescript/core/src/lib/helpers/...
I should find a solution to be able to inject the correct helper into code that is upper than the ns/helpers and web/helpers for instance from features or even xplat/core.
I hope there is an easier solution than create all helperBase abstract classes and uses some kind of InjectionToken stuffs...
If you have advices to handle the helpers files ... ; )
Thanks in advance. Lo.
Hi @lostation could you provide an example of one of your helpers? Seeing how you had them setup may help me provide better guidance.
Each platform has a CoreModule which configures services per platform which is where you can provide platform specific behavior. An example of that is provided in the xplat/web/core and xplat/nativescript/core whereby window and language are configured.
Hi Nathan,
Thanks for your fast answer. I appreciate it.
So for instance with one of my helper so called app.service.ts with a single method called getElementById for the sake of simplicity.
I was using a shared core module that holds an helper's folder where we could find files like app.service.tns.ts and app.service.ts and a lot of others...Then in any component constructor, injecting AppService or other, was easy and the right one was selected during compile time. Thanks to suffix .tns for that. I had to only be sure both files contain the same function signatures with the right specific implementation.
From xplat/nativescript/core/src/lib/helpers/app.service.ts
static getElementById(id: string)
{
return Frame.topmost().currentPage.getViewById(id);
}
From xplat/web/core/src/lib/helpers/app.service.ts
static getElementById(id: string)
{
return document.getElementById(id);
}
Then I have a file used in a shared way, either used by nativescript and web helpers: visual-element.ts
import { AppService } from '../core/helpers/app.service'; <--- previous working import with NS6
export class VisualElement
{
private id: number;
width = 0;
height = 0;
constructor(x: number, y: number, type: VisualElementType, content: any)
{
this.id = ++VisualElement.nextId;
}
fitContentSize()
{
const contentElement = **AppService**.getElementById(`ve_content_${ this.id }`);
if (contentElement)
{
this.Width = contentElement.offsetWidth;
this.Height = contentElement.offsetHeight;
}
}
}
Where should I put this visual-element.ts file to be shared between folders xplat/nativescript and xplat/web to avoid to duplicate it ? How import the right helper implementation ?
From xplat/features or xplat/core ?
Those places are indeed the shared places between xplat/nativescript and xplat/web...but then again how to import AppService from visual-element.ts as it is only defined, a step lower, from within xplat/nativescript/helpers/xxx and web/helpers/xxx specific folders ?
I have a lot a shared files like that between mobile app and web where I used to import my helpers and NS was gently using the correct one.
Thanks for your time. Lo.
Is the NativeScriptConfig and shared attribute is still functioning with NS8 ? And can be use to retrieve the auto .tns.ts suffix discrimination ? between app.service.tns.ts and app.service.ts event from an xplat architecture... ?
Then if so, I could maybe (re)merge all my helpers from within a single xplat/core/helpers folder and if the "Shared" is still functioning NS will do the rest, like NS6 ?
Very nice thanks, this is what I would do:
libs/xplat/core/src/lib/services/app.service.ts
import { PlatformHelperToken } from './tokens';
@Injectable({ providedIn: 'root' }) export class AppService { static PlatformHelper = inject(PlatformHelperToken); static getElementById(id: string) { return AppService.PlatformHelper.getElementById(id); } }
Make sure that's exported from the `libs/xplat/core/src/lib/services/index.ts` to be usable from the workspace barrel.
* modify `libs/xplat/core/src/lib/services/tokens.ts` to include:
export interface IPlatformHelper {
getElementById(id: string): any; // you can strongly type return type if you'd like
// you can also include any other helpers you'd like here
}
export const PlatformHelperToken = new InjectionToken
## Now just provide each platform's CoreModule
### NativeScript
* update `libs/xplat/nativescript/core/src/lib/core.module` to provide it:
import { PlatformHelperToken, IPlatformHelper } from '@your-workspace/xplat/core';
export function platformHelperFactory(): IPlatformHelper { return { getElementById(id: string) { return Frame.topmost().currentPage.getViewById(id); } } }
@NgModule({ imports: [ ... CoreModule.forRoot([ { provide: PlatformHelperToken, useFactory: platformHelperFactory }
### Web
* update `libs/xplat/web/core/src/lib/core.module` to provide it:
import { PlatformHelperToken, IPlatformHelper } from '@your-workspace/xplat/core';
export function platformHelperFactory(): IPlatformHelper { return { getElementById(id: string) { return document.getElementById(id); } } }
@NgModule({ imports: [ ... CoreModule.forRoot([ { provide: PlatformHelperToken, useFactory: platformHelperFactory }
## Now enjoy using AppService everywhere
import { AppService } from '@your-workspace/xplat/core';
// this now works perfect in any environment/platform
It's the same thing you had but with bit better separation of concerns not prone to evolutionary changes to static analysis with Angular's compiler or webpack hoopla.
Note: this makes use of Angular 14's new/fantastic inline `inject` api.
Ok thanks !
I was also thinking to go into the XplatWindow direction. Then perfect, I'll go to this injection token best practices. Indeed add tokens + factories to each modules to use specific file is great solution. The drawback is maybe that we lost types, for that I will add some interfaces to implement in the services from the core modules.
I have 2 more questions...if you ok.
What's exactly the normal usage or goal of the libs/xplat/features/ because as I understood it, this folder is upper than xplat/nativescript and xplat/web... so it is some kind of a shared top folder above xplat/core... then why not put files or add modules in the xplat/core ?
Now with NS8 I need to use "nativescript.config.ts" from the root of my nativescript-app folder. In my projects I have several brandings or flavors and I want to be able to modify "id", "appResourcesPath" for each of flavors. How can I achieve that ? So override the nativescript.config.ts content before all the build process.
Thanks again. Lo
Great questions sure thing.
You can add modules to either - which one you add to is dependent upon that ^^ most often (follows the same coremodule vs sharedmodule discussion noted above). Furthermore that is the base level guidance with the architecture - you are free to generate additional Nx libraries in/around xplat architecture as you see fit. We do this in practice, we build our foundation within bounds of xplat architecture which helps delineate the cross platform concerns without worries and then have other nx libraries in/around it for different purposes from project to project as it scales.
project.json
, eg. Inside apps/nativescript-app/project.json
use configurations
like this:{
"projectType": "application",
"sourceRoot": "apps/nativescript-app/src",
"prefix": "lo",
"targets": {
"build": {
"executor": "@nativescript/nx:build",
"options": {
"noHmr": true,
"production": true,
"uglify": true,
"release": true,
"forDevice": true
},
"configurations": {
"prod": {
"fileReplacements": [
{
"replace": "../../libs/xplat/core/src/lib/environments/environment.ts",
"with": "./src/environments/environment.prod.ts"
}
]
},
"uat": {
"fileReplacements": [
{
"replace": "../../libs/xplat/core/src/lib/environments/environment.ts",
"with": "./src/environments/environment.uat.ts"
}
]
},
"qa": {
"fileReplacements": [
{
"replace": "../../libs/xplat/core/src/lib/environments/environment.ts",
"with": "./src/environments/environment.qa.ts"
}
]
}
}
},
"ios": {
"executor": "@nativescript/nx:build",
"options": {
"platform": "ios",
"noHmr": true
},
"configurations": {
"build": {
"copyTo": "./dist/build.ipa"
},
"prod": {
"combineWithConfig": "build:prod"
},
"qa": {
"combineWithConfig": "build:qa",
"id": "com.company.thisapp",
"plistUpdates": {
"Info.plist": {
"CFBundleDisplayName": "QA: My App",
"CFBundleName": "QA: My App"
}
}
},
"uat": {
"combineWithConfig": "build:uat",
"id": "com.company.differentid",
"plistUpdates": {
"Info.plist": {
"CFBundleDisplayName": "UAT: My App",
"CFBundleName": "UAT: My App"
}
}
}
}
},
"android": {
"executor": "@nativescript/nx:build",
"options": {
"platform": "android",
"noHmr": true
},
"configurations": {
"build": {},
"prod": {
"combineWithConfig": "build:prod"
},
"qa": {
"combineWithConfig": "build:qa",
"id": "com.company.thisapp",
"xmlUpdates": {
"src/main/res/values/strings.xml": {
"resources": {
"string": [
{
"app_name": "QA: My App"
},
{
"title_activity_kimera": "QA: My App"
}
]
}
}
}
},
"uat": {
"combineWithConfig": "build:uat",
"id": "com.company.differentid",
"xmlUpdates": {
"src/main/res/values/strings.xml": {
"resources": {
"string": [
{
"app_name": "UAT: My App"
},
{
"title_activity_kimera": "UAT: My App"
}
]
}
}
}
}
}
}
}
That shows several things:
Invoked like this:
npx nx run nativescript-app:ios:uat
That would debug iOS using the UAT configuration which would have the following set (all from project.json):
com.company.differentid
UAT: My App
Same configs can be built for release mode as follows:
npx nx run nativescript-app:build:uat --platform=ios
As far as id
and appResourcesPath
this actually is one of the biggest reasons why Nx is beneficial over prior schematics direction. Those changing indicate unique app deployments and thus should be clearly delineated/managed to avoid confusion. Each should really be a separate app inside Nx which would simplify the configurations a lot. You would then just invoke any app (with it's own resources and id) anytime, eg:
npx nx run nativescript-app:ios:uat
npx nx run nativescript-another:ios:uat
npx nx run nativescript-yetanother:ios:uat
// on and on...
With Nx, each app can consume/use everything from libs so each app just becomes a shell/deploy target to give it unique id/resources for what you're wanting.
Lastly keep in mind with latest you can also use this strategy which is also another approach that works well within Nx for various environment level handling: https://docs.nativescript.org/webpack.html#using-env-files
There's so many options here I keep recalling more 😁 You can also have multiple nativescript.config.{env}.ts
files in a single app and use the flags
option of the executor within Nx, for example you can pass --config=nativescript.config.uat.ts
to use any config file you want with any run. In project.json
you can then use the flags
option to specify:
"uat": {
"combineWithConfig": "build:uat",
"flags": "--config=nativescript.config.uat.ts",
Hi Nathan,
Ok ;) thanks a lot for all your advices!
I'm not building yet the apps ui part or env stuffs because I'm still dispatching specific and shared stuffs into xplat architecture... I'm starting to well know this new way of handling files...and start to appreciate it finally!
I'll let you know any further things if any, thanks a lot anyway for all your appreciated help. Lo.
Re Nathan,
mmm sorry to bother you again... I've got weird errors from Webpack :(
So now everything should be setup and ready to go for the mobile part ...
I've added a npm script to run from global package.json -> "android:br:dev": "npx nx run nativescript-app:android:br-dev"
"android": {
"executor": "@nativescript/nx:build",
"options": {
"platform": "android",
"production": false,
"uglify": false,
"release": false,
"noHmr": true
},
"configurations": {
"build": {
"copyTo": "./dist/build.apk"
},
"br-dev": {
"combineWithConfig": "build:dev",
"flags": "--config=ns-br.config.ts --env.sourceMap --env.verbose --env.target=MyTarget
}, ...
}
}
export default {
id: 'com.super.mobileapp',
appResourcesPath: 'App_Resources',
android: {
v8Flags: '--expose_gc',
markingMode: 'none',
codeCache: true,
suppressCallJSMethodExceptions: false,
id: 'com.super.mobileapp',
},
ios: {
discardUncaughtJsExceptions: false,
},
appPath: 'src',
cli: {
packageManager: 'npm',
},
webpackConfigPath: './webpack-custom.config.js',
} as NativeScriptConfig;
So, it starts my "webpack-custom.config.js" then it means that correct NativeScriptConfig from "flags": "--config=..." is selected and running. That already great!
From "webpack-custom.config.js", I'm just doing some files copy, selected brand resources into the App_Resources and also some version build number update.
module.exports = async (env) => {
...
env.nsReleaseVersion = NS_RELEASE_VERSION;
env.nsBuildVersion = NS_BUILD_VERSION;
console.log('\nStart webpack-custom.config.js');
console.log('\nProjectRoot: ' + __dirname);
console.log('\nEnv');
console.log(JSON.stringify(env, (k, v) => {
switch (('' + k).toLowerCase()) {
case 'pass':
case 'password':
case 'login':
case 'email':
return '************';
default:
return v;
}
}, 4));
// Copy the selected Brand files for (Android|iOS) into the nativescript default "./App_Resources" folder
await copyResourcesFiles(env).then(
ok => { },
err => {
console.error(err, 'copyResourcesFiles');
process.exit();
}
);
// Update app_name from ./i18n/ and set build number to AndroidManifest.xml or Info.plist
await updateFromDescriptors(env).then(
ok => { },
err => {
console.error(err, 'updateFromDescriptors');
process.exit();
}
);
// then run the regular webpack.config.js
const config = webpackConfig(env);
const customDefine = new webpack.DefinePlugin({
"global.TNS_WEBPACK": "true",
"process": "global.process",
"process.env": {
login:
Object.prototype.hasOwnProperty.call(env, 'login')
? JSON.stringify(env.login)
: undefined,
pass:
Object.prototype.hasOwnProperty.call(env, 'pass')
? JSON.stringify(env.pass)
: undefined,
nsReleaseVersion:
Object.prototype.hasOwnProperty.call(env, 'nsReleaseVersion')
? JSON.stringify(env.nsReleaseVersion)
: undefined,
nsBuildVersion:
Object.prototype.hasOwnProperty.call(env, 'nsBuildVersion')
? JSON.stringify(env.nsBuildVersion)
: undefined,
},
});
config.plugins.unshift(customDefine);
const appResourcesFullPath = resolve(__dirname, env.appResourcesPath);
const customCopy = new CopyWebpackPlugin(
{
patterns: [
{ from: "**/*.jpg" },
{ from: "**/*.png" },
{ from: "**/*.json" },
{ from: "**/*.ttf" },
]
}, { ignore: [`${relative(env.appPath, appResourcesFullPath)}/**`] });
config.plugins.unshift(customCopy);
return config;
};
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
ERROR in main
Module not found: Error: Can't resolve './src' in '/Volumes/Data/.../brfrontend/apps/nativescript-app'
resolve './src' in '/Volumes/Data/.../brfrontend/apps/nativescript-app'
using description file:/Volumes/Data/.../brfrontend/apps/nativescript-app/package.json (relative path: .)
Field 'browser' doesn't contain a valid alias configuration
using description file: /Volumes/Data/.../brfrontend/apps/nativescript-app/package.json (relative path: ./src)
no extension
Field 'browser' doesn't contain a valid alias configuration
/Volumes/Data/.../brfrontend/apps/nativescript-app/src is not a file .js
Field 'browser' doesn't contain a valid alias configuration
/Volumes/Data/.../brfrontend/apps/nativescript-app/src.js doesn't exist .json
Field 'browser' doesn't contain a valid alias configuration
/Volumes/Data/.../brfrontend/apps/nativescript-app/src.json doesn't exist .wasm
Field 'browser' doesn't contain a valid alias configuration
/Volumes/Data/.../brfrontend/apps/nativescript-app/src.wasm doesn't exist
as directory
existing directory /Volumes/Data/.../brfrontend/apps/nativescript-app/src
using description file: /Volumes/Data/.../brfrontend/apps/nativescript-app/package.json (relative path: ./src)
using path:/Volumes/Data/.../brfrontend/apps/nativescript-app/src/index
using description file: /Volumes/Data/.../brfrontend/apps/nativescript-app/package.json (relative path: ./src/index)
no extension
Field 'browser' doesn't contain a valid alias configuration
/Volumes/Data/.../brfrontend/apps/nativescript-app/src/index doesn't exist .js
Field 'browser' doesn't contain a valid alias configuration
/Volumes/Data/.../brfrontend/apps/nativescript-app/src/index.js doesn't exist .json
Field 'browser' doesn't contain a valid alias configuration
/Volumes/Data/.../brfrontend/apps/nativescript-app/src/index.json doesn't exist .wasm
Field 'browser' doesn't contain a valid alias configuration
/Volumes/Data/.../brfrontend/apps/nativescript-app/src/index.wasm doesn't exist
webpack 5.73.0 compiled with 1 error and 1 warning in 1706 ms
But I have a src folder into nativescript-app/... and also mode seems defined
from --env.verbose printed infos, I can see that mode is declared in Webpack
[@nativescript/webpack] Info:
{
mode: 'development',
...
If I remove the attribute webpackConfigPath from NativeScriptConfig... The warning and error disappear... ???
Can we still use custom webpackConfigPath within Webpack and NS8 ? Or maybe I missed something...probably.
Thanks for your precious help. Lo.
No in fact even without webpackConfigPath from NativeScriptConfig... The warning and error are still there...
Module not found: Error: Can't resolve './src' in '/Volumes/Data/project/apps/nativescript-app'
I don't really know why ./src is not resolvable....
And even WTH is that Field 'browser' doesn't contain a valid alias configuration
Please help, if you have any hint...
Waw... It took me days to find out what happened...
!!! async keyword is breaking all webpack build process and it probably takes then the by default webpack config ... as the async is not yet resolved.
In that webpack.config.js, previously with NS6 and the custom config path (now from NativeScriptConfig), that was working and I was able to make some files maintenance, set versions, clean and copy stuffs...before the real webpack was called.
So from nativescript-app/webpack.config.js:
I have also changed a bit the chainWebpack based from the webpack docs
webpack.chainWebpack((config) => {
...
config.plugin('DefinePlugin').tap(args => {
Object.assign(args[0], {
'global.isProduction': !!env.production,
'global.login': Object.prototype.hasOwnProperty.call(env, 'login')
? JSON.stringify(env.login)
: undefined,
'global.pass': Object.prototype.hasOwnProperty.call(env, 'pass')
? JSON.stringify(env.pass)
: undefined,
'global.nsReleaseVersion': Object.prototype.hasOwnProperty.call(env, 'nsReleaseVersion')
? JSON.stringify(env.nsReleaseVersion)
: undefined,
'global.nsBuildVersion': Object.prototype.hasOwnProperty.call(env, 'nsBuildVersion')
? JSON.stringify(env.nsBuildVersion)
: undefined
});
return args
});
});
webpack.Utils.addCopyRule("**/*.jpg");
webpack.Utils.addCopyRule("**/*.png");
webpack.Utils.addCopyRule("**/*.json");
webpack.Utils.addCopyRule("**/*.ttf");
return webpack.resolveConfig();
Hope this help someone else...
About local plugins to handlefrog nx and app...
I wonder what is the best practices with local npm custom nativescript plugins. Previously in NS6, I've used root folder called -> ns-libs/... that contained my modified plugins for instance nativescript-markdown or mqtt stuffs or other custom plugins...
Should I put that plugin folder inside the nativescript app level or workspace level ? If I put at workspace level...every project can have access to...I mean even the nativescript core features ... But If I put it at app level..then how achieve some kind of shared import from nativescript core features ?
I've tried to link all the plugins index.d.ts from the workspace references.d.ts...but that doesn't seem to work.
I've also seen that there is "@brfrontend/xplat-scss": "file:libs/xplat/scss/src" from workspace package.json dependencies... Maybe should I do the same for the local plugins located at ns app level ?
What is the best way to handle that from nx point of view @NathanWalker ?
Thanks
Ah and I tried with that plugins folder from the workspace root...but then some defined java flies are not compiled correctly and I've got some static error with interface declared inside the java class package name... But not from the ns app level, that seem to compile java to byte code as it was before....So seems better to put that custom things inside ns app level...It has the benefit to not pollute the root folder.
@lostation when possible might drop in Discord: https://nativescript.org/discord might be easier to chat there (#angular channel) You can setup workspace using npm or yarn workspaces - which you use within Nx for dependency management can affect how you want to set your package. The rule of thumb is that you want to add all dependencies to root package.json for sure. Then for any NativeScript app in the workspace you want it’s own package to contain NativeScript dependencies. That will ensure the cli picks up and builds the native dependencies into the app (webpack naturally picks up all the other standard js only stuff)
Hi, our app is an {N} Angular / Web + Android hybrid app and has been upgraded to NativeScript 8. It stopped working. On Discord I've got the info the schematics are not supported anymore and I have to use xplat if I want to maintain the shared code.
I've created a new workspace added xplat and now I'm stuck because:
nx g lib --parentModule=xy
doesn't work, and when I add a library with--routing
and--lazy
the routes don't appear in the parent project--project=main-ui
), I'm getting error reports about I have to use platform prefix which I don't understandCan I get some support here? I just need an example where I can see how the code sharing works between nativescript and web (angular) project.
Another questions:
ng g component
andng g feature
?nx g feature
doesn't have--directory
parameter? I would like to organize my features, for example, pages would go into thepages
folder.shared.module
for? Why UI module has been wrapped into shared.module? Why not importing UI module itself directly?