-
Notifications
You must be signed in to change notification settings - Fork 669
feat: support multiple ionic projects #3297
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
feat: support multiple ionic projects #3297
Conversation
…. Related ionic-team#3281 Adding `--project` support for almost every Ionic command. Create an `ionic.json` config in project root. This closely mirrors `angular.json` but doesn't depend on it. ``` { "defaultProject": "app", "projects": { "app": { "name": "Ionic App", "type": "angular", "integrations": { "cordova": { "root": "cordova" } }, "hooks": {} } } } ``` Example commands ``` # With project name ionic cordova platform add android --project app # With defaultProject ionic cordova platform add android # Other commands ionic cordova resources --project app ionic cordova plugin add cordova-plugin-splashscreen --project app ionic cordova build --project app ionic cordova run --project app ionic cordova emulate --project app ionic cordova requirements --project app ionic cordova prepare --project app ```
By treating each "project" as its own self-contained ionic configuration within "projects" we can reuse most of the existing command logic.
/** | ||
* Find the base directory based on the path given and a marker file to look for. | ||
*/ | ||
export async function findProjectDirectory(dir: string, file: string): Promise<string | undefined> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't look like this is used.
@@ -98,6 +99,15 @@ export interface ProjectFile { | |||
key?: string; | |||
cert?: string; | |||
}; | |||
|
|||
type: ProjectType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I remember correctly, I took this property out because I wanted to remember to use env.project.type
, not projectConfig.type
. The project configuration is kind of bad and annoying, sorry. I've been meaning to refactor it.
protected configFile?: T; | ||
protected originalConfigFile?: { [key: string]: any }; | ||
|
||
constructor(directory: string, public fileName: string) { | ||
constructor(directory: string, readonly fileName: string, readonly name: string | undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is name
needed in BaseConfig
? It should only relate to Project
, right?
export interface MultiProjectConfig { | ||
defaultProject: string; | ||
projects: { | ||
[key: string]: ProjectConfig; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably be [key: string]: ProjectConfig | undefined;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the type guards isProjectConfig()
and isMultiProjectConfig()
to check if the projects
key exists and toggle behaviour accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But still if I access projectConfig.projects.asndgksjehbnrgfvkljahsefdkjghasekjhfgakjhsdg
then TypeScript will say it's a ProjectConfig
, when it possibly is not there at all.
} | ||
|
||
return { | ||
enabled: integration.enabled === undefined ? false : integration.enabled, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Integrations are only disabled when the enabled
key exists and is false
, so this should be:
integration.enabled === undefined ? true : integration.enabled,
or
integration.enabled !== false,
@@ -146,6 +165,11 @@ class BuildAfterHook extends Hook { | |||
export async function build(env: IonicEnvironment, inputs: CommandLineInputs, options: CommandLineOptions): Promise<void> { | |||
try { | |||
const runner = await BuildRunner.createFromProject(env, env.project); | |||
|
|||
if (env.project.name) { | |||
options['project'] = env.project.name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to think of a better spot for this, but I think this is it 😂
} | ||
|
||
for (const projectType of PROJECT_TYPES) { | ||
const p = await Project.createFromProjectType(projectDir, PROJECT_FILE, deps, projectType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Project type detection doesn't work for a multi-app config. Here's the config I tried:
{
"defaultProject": "sidemenu",
"projects": {
"sidemenu": {
"root": "packages/sidemenu"
},
"tabs": {
"root": "packages/tabs"
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try this:
ionic.config.json
{
"defaultProject": "sidemenu",
"projects": {
"sidemenu": {
"type": "angular"
},
"tabs": {
"type": "angular"
}
}
}
angular.json
{
"defaultProject": "sidemenu",
"projects": {
"sidemenu": {
"root": "packages/sidemenu"
},
"tabs": {
"root": "packages/tabs"
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I was just trying to convey that determineType()
doesn't work for multi-app projects. I'm thinking the function needs to be refactored to either work or throw a fatal error.
Remove the type
attributes from ionic.config.json
to see what I mean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this perhaps throw an error instead?
Edit: I see what you mean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added more exceptions so it should give more meaningful error messages now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Traditionally determineType()
wasn't meant to throw fatal errors. If a project type cannot be determined, it's actually fine, it just means we can't use project commands. The CLI switches to "global mode" if project type cannot be determined.
But if the behavior is changed, it's fine. We don't have integration tests for behavior, so it's mostly going to be me testing things and making tweaks. 😆
@@ -26,6 +39,11 @@ export const COMMON_BUILD_COMMAND_OPTIONS: ReadonlyArray<CommandMetadataOption> | |||
summary: `Target platform on chosen engine (e.g. ${['ios', 'android'].map(e => chalk.green(e)).join(', ')})`, | |||
groups: [OptionGroup.Advanced], | |||
}, | |||
{ | |||
name: 'project', | |||
summary: 'The name of the project', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason to add this here? --project
isn't a common build option, it's a global option for angular projects, right?
If the reason is to add it to the help output, we can do something else instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Project needs to be a top level option to support ionic commands that are not related to angular:
Eg:
ionic cordova requirements --project name
ionic cordova resources --project name
ionic cordova platform add ios --project name
ionic cordova plugin add cordova-plugin-splashscreen --project name
I also wanted to make sure these changes aren't tightly coupled to angular. The only hard requirement for angular projects right now is that a project in ionic.config.json
maps to a project with the same name in angular.json
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With that said, I don't see --project
appearing in these help dialogs, so I'm not sure I put this in the right place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That all makes sense, I just don't think it should be listed here (or in the common serve options). It's okay to leave it for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to do some refactoring for these commands. Maybe have "runners" for each command/project type combination. Then it'd be easy to add the option to the project type(s) that support global options like this.
packages/ionic/src/commands/link.ts
Outdated
}); | ||
} | ||
|
||
proId = await this.createApp({ name }, runinfo); | ||
if (name) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I was getting some TypeScript validation error, but I'm not seeing it now so I'll revert it.
packages/ionic/src/commands/start.ts
Outdated
@@ -434,7 +434,7 @@ ${chalk.cyan('[1]')}: ${chalk.bold('https://ionicframework.com/docs/cli/starters | |||
// start is weird, once the project directory is created, it becomes a | |||
// "project" command and so we replace the `Project` instance that was | |||
// autogenerated when the CLI booted up. This has worked thus far? | |||
this.env.project = await getProject(projectDir, this.env); | |||
this.env.project = await getProject(projectDir, String(options['name']), this.env); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably better to pass undefined
here instead? options['name']
refers to something else
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I wasn't sure what to put here, is this after the project is created? Will the name entered in ionic start
also be used as the project name for the app that gets generated? Or will they always default to app
?
Overall this is great work. 💪 I can't wait to get this in! |
I am very happy to see this PR merged. (and how can we use the client with this new feature ?) |
FYI this does not work with the Snapshot Build of Ionic Pro (yet). I've opened a support ticket to have this issue addressed. |
@kevin-lot Yes, this will work for Capacitor. @timbru31 Thanks for the heads up. I've been working with @nphyatt to get this feature working in Ionic Pro by using the Ionic CLI for builds. Note that this will only be for Ionic 4 beta, so some rough spots are to be expected. 😊 |
I've been working with the new multi project setup and it's been fantastic, though have run into an issue: when running any Cordova commands on Windows, the use of an absolute path in Using a relative path fixes this (I believe it's when the absolute path is normalized, which converts |
@edcarroll Interesting. I wonder if the Angular Devkit has issues with using an absolute path within a workspace. There are levels of abstraction with paths and it may get confused on Windows. If you're willing to look into it (I would love a PR ❤️), check out these points in the code base: Ionic CLI provides
ng-toolkit (using Angular Devkit) picks up the option and normalizes the path here:
(also look for the same option in the serve code for Cordova livereload as well) Maybe the solution is to use a relative path, because it's likely what the Angular Devkit expects within a workspace. |
This PR adds experimental support for multiple Ionic apps within a shared workspace by allowing separate cordova integrations to be configured for each project. This is achieved by augmenting the
ionic.config.json
object with new properties in a way that is backwards compatible with existing single app projects. This is currently limited toangular
project types withcordova
integrations only.Related issue: #3281
Tests should be passing but I need to write some more that cover multi-app scenarios.