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

WIP: Suggestions for executors #127

Closed
wants to merge 2 commits into from
Closed

Conversation

Schmale97
Copy link

@Schmale97 Schmale97 commented Jul 3, 2023

This is a PR with some suggestions for some executors to improve firebase's cli within nx. I have these changes currently as a workspace plugin that I have been playing around with and noticed a few issues pop-up which I think these executors help to fix ( #92 and #40 ). I have copied the local plugin into these repo quickly to gauge interest in including these in this plugin.

There are 3 executors:

  • cli: the way to interact with the firebase cli-tool - has been written to be similar to the firebase-tools when using in the terminal but with some tweaks to improve how it can be used in the project.json

  • kill-emulator: kills all the process running on the ports defined in the defined firebase.*.json

  • copy-local-files: used for handling .gitignored env files, namely .env.local and .secret.local

My project stucture is:

apps/
> example/
>> functions/
>> app/
>> firebase/

The project.json I have in the functions folder is

{
  "name": "example-functions",
  "$schema": "../../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "apps/example/functions/src",
  "projectType": "application",
  "targets": {
    "build": {
      "executor": "@nx/esbuild:esbuild",
      "outputs": ["{options.outputPath}"],
      "options": {
        "project": "apps/example/functions/package.json",
        "outputPath": "dist/apps/example/functions",
        "main": "apps/example/functions/src/index.ts",
        "tsConfig": "apps/example/functions/tsconfig.app.json",
        "assets": ["apps/example/functions/.env.*"],
        "bundle": true,
        "minify": true,
        "generatePackageJson": true,
        "platform": "node",
        "esbuildOptions": {
          "splitting": true
        }
      },
      "configurations": {
        "watch": {
          "watch": true,
          "minify": false
        }
      }
    },
    "lint": {
      "executor": "@nx/linter:eslint",
      "options": {
        "lintFilePatterns": ["apps/example/functions/**/*.ts"]
      },
      "outputs": ["{options.outputFile}"]
    },
    "test": {
      "executor": "@nx/jest:jest",
      "outputs": ["{workspaceRoot}/coverage/apps/example/functions"],
      "options": {
        "jestConfig": "apps/example/functions/jest.config.ts",
        "passWithNoTests": true
      }
    },
    "copy-local-files": {
      "executor": "@workspace-plugin/firebase:copy-local-files",
      "inputs": ["{projectRoot}/.(secret|env).local"]
    }
  },
  "tags": []
}

and the project json I have for the firebase folder is:

{
  "name": "example-firebase",
  "$schema": "../../../node_modules/nx/schemas/project-schema.json",
  "projectType": "application",
  "targets": {
    "cli": {
      "executor": "@workspace-plugin/firebase:cli",
      "options": {
        "config": "firebase.example.json"
      }
    },
    "deploy": {
      "executor": "@workspace-plugin/firebase:cli",
      "options": {
        "command": "deploy",
        "config": "firebase.example.json",
        "non-interactive": true,
        "force": true
      },
      "dependsOn": [
        { "projects": ["example-functions"], "target": "build" }
      ],
      "configurations": {
        "dev": {
          "project": "example-dev"
        },
        "prd": {
          "project": "example-prd"
        }
      }
    },
    "emulators:start": {
      "executor": "@workspace-plugin/firebase:cli",
      "dependsOn": [
        { "projects": ["example-functions"], "target": "build" },
        {
          "projects": ["example-functions"],
          "target": "copy-local-files"
        }
      ],
      "options": {
        "command": "emulators:start",
        "config": "firebase.example.json"
      },
      "configurations": {
        "dev": {
          "project": "example-dev"
        },
        "prd": {
          "project": "example-prd"
        }
      }
    },
    "emulators:exec": {
      "executor": "@workspace-plugin/firebase:cli",
      "dependsOn": [
        { "projects": ["example-functions"], "target": "build" },
        {
          "projects": ["example-functions"],
          "target": "copy-local-files"
        }
      ],
      "options": {
        "command": "emulators:exec",
        "config": "firebase.example.json"
      },
      "configurations": {
        "dev": {
          "project": "example-dev"
        },
        "prd": {
          "project": "example-prd"
        }
      }
    },
    "emulators:web": {
      "executor": "@workspace-plugin/firebase:cli",
      "dependsOn": [
        { "projects": ["example-functions"], "target": "build" },
        {
          "projects": ["example-functions"],
          "target": "copy-local-files"
        }
      ],
      "options": {
        "command": "emulators:exec",
        "config": "firebase.example.json"
      },
      "configurations": {
        "dev": {
          "project": "example-dev",
          "_": ["npx nx run example-app:serve:dev"]
        },
        "prd": {
          "project": "example-prd",
          "_": ["npx nx run example-app:serve:prd"]
        }
      }
    },
    "serve": {
      "executor": "nx:run-commands",
      "options": {
        "commands": [
          "npx nx run example-functions:build:watch",
          "npx nx run example-firebase:emulators:start"
        ],
        "parallel": true
      },
      "configurations": {
        "dev": {
          "commands": [
            "npx nx run example-functions:build:watch",
            "npx nx run example-firebase:emulators:start:dev"
          ]
        },
        "prd": {
          "commands": [
            "npx nx run example-functions:build:watch",
            "npx nx run example-firebase:emulators:start:prd"
          ]
        },
        "dev:web": {
          "commands": [
            "npx nx run example-functions:build:watch",
            "npx nx run example-firebase:emulators:web:dev"
          ]
        },
        "prd:web": {
          "commands": [
            "npx nx run example-functions:build:watch",
            "npx nx run example-firebase:emulators:web:pre"
          ]
        }
      }
    },
    "emulators:kill": {
      "executor": "@workspace-plugin/firebase:kill-emulator",
      "options": {
        "config": "firebase.example.json"
      }
    }
  },
  "tags": [],
  "implicitDependencies": ["example-app", "example-functions"]
}

I have shared these jsons just to demonstrate how I am using the executors in the pr.

Again, this is just what I have found to be useful so far and am still working through a few things:

  1. As soon as you use "run-commands" the interactivity breaks so selecting options for the command runs breaks and killing the emulators is no longer clean - this is demonstrated in the serve command (but this is the only way I have got the build:watch to work)
  2. My pubsub emulators never seem to get a clean shutdown
  3. I don't currently have a good way of connecting the frontend to the emulators based on if the emulators are running (I think this is a limitation of the firebase cli so don't know if it should be addressed here)

Let me know what you think

Schmale97 added 2 commits July 3, 2023 10:19
cli: runs the firebase cli via exec sync with some input parsing
    - uses execSync to avoid issues with run-commands interactivity
    - confirms that active project is desired if not specified
    - only passes through params defined by firebase-cli

kill-emulator: kills processes running on ports in firebase.config.json
    - accepts a file path and if the port numbers are defined in the
      expected format (a firebase.config.json)
    - kills the processes ports if mac/linux
    - windows unimplemented
    - there to catch if firebase-cli doesn't shut down ports correctly

copy-local-files: copies .env.local and .secret.local to dist folder
    - the assets property for esbuild does not copy across .gitignore
    - this executor gets around this by manually copying the local files
      required for emulators
process.env.NX_REACT_APP_FIRESTORE_EMULATOR = 'true'
process.env.NX_REACT_APP_STORAGE_EMULATOR = 'true'
}
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is my current workaround for detecting if my frontend application should connect to the emulators when serving, but as you can see it is specific to cra applications. The frontend code then looks for these variables before in initialises each sdk.

Not sure if there is a more generic way of doing this/if it should be removed and handled elsewhere

@simondotm
Copy link
Owner

simondotm commented Jul 16, 2023

Hi @Schmale97 , thanks for your PR. It's great to have submissions & experiementations, and I can see how such solutions might be useful for customising a specific workflow/workspace, but for a generic plugin like this one, I'm not sure it adds value over what we already have:

  • There is already a firebase target in v2 plugin app generator, which provides much of the functionality of your cli executor, so you can already just run npx nx my-project:firebase <firebase cli options>
  • There is also already killports target that forcefully cleans up the emulator processes
  • We are still stuck with the problem of Nx not passing termination signals properly to child processes so that cleanup is correct. Personally I believe this needs to be fixed in Nx. As you have found, even having a custom executor doesnt get around that fundamental task runner problem with Nx.

The copy-local-files executor you have is an interesting workaround, but I believe we can achieve the same outcome by modifying the .nxignore files with exclusion rules and add local secret files to the build executor assets array - I just havent had time to investigate this better.

For your custom NX_REACT_APP_STORAGE_EMULATOR type env vars, I can see why you took that approach. For me, I have added an environment.dev.ts file to my FE project (Angular in my case), and set the appropriate emulator client app vars from that. eg.

  providers: [
    {
      provide: AUTH_EMULATOR,
      useValue: environment.localEmulation ? ['http://localhost:9099'] : undefined,
    },
    {
      provide: FIRESTORE_EMULATOR,
      useValue: environment.localEmulation ? ['localhost', 8080] : undefined,
    },
    {
      provide: DATABASE_EMULATOR, // i.e., Realtime Database
      useValue: environment.localEmulation ? ['localhost', 9000] : undefined,
    },
    {
      provide: FUNCTIONS_EMULATOR,
      useValue: environment.localEmulation ? ['http://localhost', 5001] : undefined,
    },
  ],

@simondotm
Copy link
Owner

I'm going to close this PR for now, but I welcome & will review future contributions.

@simondotm simondotm closed this Aug 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants