facebook / create-react-app

Set up a modern web app by running one command.
https://create-react-app.dev
MIT License
102.71k stars 26.85k forks source link

"homepage:" should work for "npm start" instead of just "npm build" #9690

Closed cheslijones closed 4 years ago

cheslijones commented 4 years ago

Is your proposal related to a problem?

Yes.

We use skaffold and minikube for local development. Every engineer uses these to launch a local development k8s cluster for developing in. We have routes corresponding to the following microservices:

For example, the minikube ip would be 192.168.64.3 and the routes above appended to it.

The routes / and /api work just fine.

The /admin is the problematic one and after hours of trying to get it to serve the static assets properly, "homepage:" was not designed to work with npm start.

Even though you get the following console output when starting up the cluster:

[admin] 
[admin] > adminv2@0.1.0 start /app
[admin] > PORT=4050 react-scripts start
[admin] 
[admin] ℹ 「wds」: Project is running at http://172.17.0.8/
[admin] ℹ 「wds」: webpack output is served from /admin
[admin] ℹ 「wds」: Content not from webpack is served from /app/public
[admin] ℹ 「wds」: 404s will fallback to /admin/
[admin] Starting the development server...
[admin] 
[admin] Compiled successfully!
[admin] 
[admin] You can now view admin in the browser.
[admin] 
[admin]   Local:            http://localhost:4050/admin
[admin]   On Your Network:  http://172.17.0.8:4050/admin
[admin] 
[admin] Note that the development build is not optimized.
[admin] To create a production build, use npm run build.
[admin] 

Navigating to 192.168.64.3/admin is just a blank page with the following dev console ouputs:

image image

Describe the solution you'd like

I'd like to be able to navigate 192.168.64.3/admin and have it serve that microservice correctly with npm start... for "homepage:" to also apply to dev deployments and not just npm build.

Describe alternatives you've considered

Only this is what I've found that is recommended:

# package.json
...
"homepage": "/client"
...
# App.js

 <BrowserRouter basename={process.env.PUBLIC_URL}>
  {/* other components */}
 </BrowserRouter>

Two other things do work, but they aren't desirable:

  1. localhost:4050/admin: works, but isn't desirable as it will circumvent some k8s routing issues that will come up in staging and production (an issue I encountered with the Django Admin portal which has made me reluctant to use localhost ever since). And just testing it now with /, it is unable to communicate with /api when using localhost:3000 (the port for /). So basically doesn't work.
  2. Using a production / staging build configuration of Dockerfile that uses npm build... not desirable because I lose the debugging outputs.
  3. I considered just combining the / and /admin microservices into one service and /admin just being a <Route path="/admin">, but no... these are entirely separate services who need their own CI / CD pipelines.

Additional context

Here is code for reference and the create-react-app is basically a clean install of npx create-react-app admin:

# ingress-dev.yaml

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/add-base-url: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$1
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.org/client-max-body-size: "500m"
    nginx.ingress.kubernetes.io/use-regex: "true"
  name: ingress-service-dev
  namespace: default
spec:
  rules:
    - http:
        paths:
          - path: /admin/?(.*)
            backend:
              serviceName: admin-new-cluster-ip-service-dev
              servicePort: 4050
          - path: /?(.*)
            backend:
              serviceName: client-cluster-ip-service-dev
              servicePort: 3000
          - path: /api/?(.*)
            backend:
              serviceName: api-cluster-ip-service-dev
              servicePort: 5000
# admin.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: admin-new-deployment-dev
spec:
  replicas: 1
  selector:
    matchLabels:
      component: admin-new
      environment: development
  template:
    metadata:
      labels:
        component: admin-new
        environment: development
    spec:
      containers:
        - name: admin-new
          image: app-admin-new
          ports:
            - containerPort: 4050
          env:
            - name: PGUSER
              valueFrom:
                secretKeyRef:
                  name:app-dev-secrets
                  key: PGUSER
            - name: PGHOST
              value: postgres-cluster-ip-service-dev
            - name: PGPORT
              value: "5432"
            - name: PGDATABASE
              valueFrom:
                secretKeyRef:
                  name: app-dev-secrets
                  key: PGDATABASE
            - name: PGPASSWORD
              valueFrom:
                secretKeyRef:
                  name: app-dev-secrets
                  key: PGPASSWORD
            - name: SECRET_KEY
              valueFrom:
                secretKeyRef:
                  name: app-dev-secrets
                  key: SECRET_KEY
            - name: SENDGRID_API_KEY
              valueFrom:
                secretKeyRef:
                  name: app-dev-secrets
                  key: SENDGRID_API_KEY
            - name: DOMAIN
              valueFrom:
                secretKeyRef:
                  name:app-dev-secrets
                  key: DOMAIN           
            - name: DEBUG
              valueFrom:
                secretKeyRef:
                  name: app-dev-secrets
                  key: DEBUG
          volumeMounts:
          - mountPath: /mnt/files/client-submissions
            name: file-storage-dev
            subPath: client-submissions
          - mountPath: /mnt/files/client-downloads
            name: file-storage-dev
            subPath: client-downloads
          - mountPath: /mnt/files/client-logos
            name: file-storage-dev
            subPath: client-logos
          - mountPath: /mnt/files/xls-exports
            name: file-storage-dev
            subPath: xls-exports
          - mountPath: /mnt/files/xls-imports
            name: file-storage-dev
            subPath: xls-imports
      volumes:
        - name: file-storage-dev
          persistentVolumeClaim:
            claimName: file-storage-dev
---
apiVersion: v1
kind: Service
metadata:
  name: admin-new-cluster-ip-service-dev
spec:
  type: ClusterIP
  selector:
    component: admin-new
    environment: development
  ports:
    - port: 4050
      targetPort: 4050
# Dockerfile.dev

FROM node:13-alpine
WORKDIR /app
COPY ./package.json ./
ENV CI=true
RUN npm install
COPY . .
CMD ["npm", "start"]
# package.json

{
  "name": "admin",
  "version": "0.1.0",
  "private": true,
  "homepage": "/admin",
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.5.0",
    "@testing-library/user-event": "^7.2.1",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-router-dom": "^5.2.0",
    "react-scripts": "3.4.3"
  },
  "scripts": {
    "start": "PORT=4050 react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
# index.js

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import App from "./App";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
  <Router basename={process.env.PUBLIC_URL}>
    <Switch>
      <Route path="/">
        <App />
      </Route>
    </Switch>
  </Router>,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
serviceWorker.unregister();

And as expected, this does work properly when using a Dockerfile production build, which isn't exactly helpful in dev:

# Dockerfile

FROM node:13-alpine as builder
WORKDIR /app
COPY ./package.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx
EXPOSE 4050
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/build /usr/share/nginx/html
cmacdonnacha commented 3 years ago

@cheslijones Did you find a workaround for this?