nrwl / nx

Smart Monorepos · Fast CI
https://nx.dev
MIT License
23.17k stars 2.3k forks source link

[@nx/esbuild:esbuild] Support for fileReplacements in esbuild executor to replace file before build #19962

Closed kien-cs closed 3 months ago

kien-cs commented 10 months ago

Description

nx had existing support for fileReplacements for other plugin, but looks like it doesn't have one for esbuild executor.

I would like to do something like this, where I can replace a staging environment config for a staging configuration build.

{
  "targets": {
    "build": {
      "executor": "@nx/esbuild:esbuild",
      "outputs": [
        "{options.outputPath}"
      ],
      "options": {
      },
      "configurations": {
        "production": {
          "fileReplacements": [
            {
              "replace": "src/environments/environment.ts",
              "with": "src/environments/environment.prod.ts"
            }
          ]
        },
        "staging": {
          "fileReplacements": [
            {
              "replace": "src/environments/environment.ts",
              "with": "src/environments/environment.staging.ts"
            }
          ]
        }
      }
    }
  }
}

Motivation

This is already supported by other plugin (webpack, vite, etc) but not at esbuild

Suggested Implementation

Add fileReplacements support

Alternate Implementations

N/A

alljinx commented 9 months ago

+1 missing this feature is really a pain while migrate from webpack to esbuild...

kien-cs commented 9 months ago

My current workaround: create an esbuild plugin to simulate this.

esbuild.config.ts

// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require("fs");

// task config name which is the environment name: [staging, production]
const envName = process.env.NX_TASK_TARGET_CONFIGURATION;

// a simple plugin to replace environment source file based on the build environment
const fileReplacementsPlugin = {
  name: "fileReplacements",
  setup(build) {
    build.onLoad(
      { filter: /environments\/environment.ts/, namespace: "file" },
      async (args) => {
        const fileReplacementPath = args.path.replace(
          "environment.ts",
          `environment.${envName}.ts`
        );
        const fileReplacementContent = fs.readFileSync(
          fileReplacementPath,
          "utf8"
        );

        return { contents: fileReplacementContent, loader: "default" };
      }
    );
  },
};

module.exports = {
  plugins: envName ? [fileReplacementsPlugin] : [],
  outExtension: {
    ".js": ".js",
  },
};

project.json

{
  "name": "my-project",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "projectType": "application",
  "targets": {
    "build": {
      "executor": "@nx/esbuild:esbuild",
      "outputs": [
        "{options.outputPath}"
      ],
      "options": {
        "outputPath": "dist",
        "main": "my-project/src/main.ts",
        "tsConfig": "my-project/tsconfig.app.json",
        "esbuildConfig": "my-project/esbuild.config.ts"
      },
      "configurations": {
        "production": {},
        "staging": {}
      }
}
alljinx commented 9 months ago

Thx @kien-cs, your fix is effective !

skwny commented 5 months ago

My current workaround: create an esbuild plugin to simulate this.

esbuild.config.ts

// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require("fs");

// task config name which is the environment name: [staging, production]
const envName = process.env.NX_TASK_TARGET_CONFIGURATION;

// a simple plugin to replace environment source file based on the build environment
const fileReplacementsPlugin = {
  name: "fileReplacements",
  setup(build) {
    build.onLoad(
      { filter: /environments\/environment.ts/, namespace: "file" },
      async (args) => {
        const fileReplacementPath = args.path.replace(
          "environment.ts",
          `environment.${envName}.ts`
        );
        const fileReplacementContent = fs.readFileSync(
          fileReplacementPath,
          "utf8"
        );

        return { contents: fileReplacementContent, loader: "default" };
      }
    );
  },
};

module.exports = {
  plugins: envName ? [fileReplacementsPlugin] : [],
  outExtension: {
    ".js": ".js",
  },
};

project.json

{
  "name": "my-project",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "projectType": "application",
  "targets": {
    "build": {
      "executor": "@nx/esbuild:esbuild",
      "outputs": [
        "{options.outputPath}"
      ],
      "options": {
        "outputPath": "dist",
        "main": "my-project/src/main.ts",
        "tsConfig": "my-project/tsconfig.app.json",
        "esbuildConfig": "my-project/esbuild.config.ts"
      },
      "configurations": {
        "production": {},
        "staging": {}
      }
}

LEGEND