I'm building a React / TypeScript app via Create React App and am looking to include environment specific configuration in a type safe way. Caveat, I really would rather not eject the app.
Ideally, I would like to have a set of files local.ts, dev.ts, prod.ts, etc. Each file would export an object of the same type, something like the following:
interface EnvConfig {
key: string
// ...
}
export const config: EnvConfig = {
key: 'value',
// ...
}
Then, in my app I'd like to import the appropriate config based on the environment. Something along the lines of:
// This is bundling BOTH prod and local configs and running logic client side!
if (process.env.NODE_ENV === 'production') {
return require('./prod')
} else {
return require('./local')
}
I have tried this and it sort of works, but if you look at the bundle react-scripts is including ALL of the config objects and running the logic on the client to determine which one to serve up. I don't want to ship ALL of my configs, only the appropriate config for the build.
Is there a way to do this without ejecting the app?
Related
Context
I have a three projects inside my Nx workspace, two applications which are react apps (both of them have shared logic, however they are intended for different platforms web and microsoft teams and must be separated) and a library which contains logic for an api client, which both of the applications use.
The api client project requires the variable base URL which is environment specific.
I can define environment variables and introduce file replacements in the build process for .ts files (e.g. environment.ts is replaced with environment.production.ts, when configuration is production), however I do not want to reference this file (environment.ts) in the api client project so as not to introduce two way dependencies.
What have I tried
From the api project I was not able to extract the logic which depends on the URL variable as this is tied to some code generation which is changeable.
I succeeded in providing this variable by using .env file in the root of the application project, the variable is in the format NX_MY_URL, and could be accessed with process.env.NX_MY_URL.
However I was not able to change this variable when changing the build configuration (e.g. development, test, production). I have tried adding a fileReplacements task such as
"configurations": {
"development": {
"fileReplacements": [
{
"replace": "apps/ra-web/src/environments/environment.ts",
"with": "apps/ra-web/src/environments/environment.development.ts" //<----- This works fine
},
{
"replace": "apps/ra-web/.env",
"with": "apps/ra-web/.development.env" //<----- This does not work, .env values remain
}
],
Question
How can .env files be replaced based on Nx target's configuration?
Why the mentioned approach does not work?
The reason of why the fileReplacements approach is not working is because those replacements are meant for the building process, which is the bundler the one in charge (webpack or vite, etc). That file content replacement is actually working, yet it doesn't happen at file system level but at memory level (for the bundling process). As your application code does not "import" that .env file (and you should not directly depend on it), then those file replacements make no difference in the build output.
On the other side, Nx is in charge of reading the .env file (at file-system level) and loading the data as environment variables so that when the bundling process starts, those are available. This a completely separate process than the fileReplacements done by the bundler.
How to achieve what you are looking for?
If you think about projects in libs as shareable/re-usable code, then you can imagine those are external dependencies added to your apps (or other libs). As those dependencies are external, they should rely on their implementor to get the data needed for them to work.
Having the above in mind, your lib public API (the main index.ts file) should be parametrized to receive that base URL that will depend on each application. With that you can keep working with the environment.ts file replacements, get the value in the app and pass it down to the lib.
Example:
// app's main.tsx
import { environment } from './environments/environment';
import { apiClient } from '#myorg/api-client';
apiClient.init({ baseUrl: environment.baseUrl });
// api-client's index.ts
export { apiClient } from './lib/api-client';
// api-client's api-client.ts
export const apiClient = {
init: ({ baseUrl }) => { ... }
};
If you still need to work with the .env file (not trying to replace it), just the the env vars in the environment.ts as work with it as mentioned above.
I’m using react-app-rewired and I want to disable the overlay for Typescript warnings that appears every time I compile. For reasons I don't understand, warnings that the VSCode Typescript checker doesn't pick up appear on the overlay; webpack is being a stricter enforcer (stricter than I want it to be in fact).
Anyway, I tried react-app-rewired start --no-client-overlay and I tried this for my config-overrides.js file:
module.exports = {
webpack: function (config, _) {
config.devServer = {
client: {
overlay: false
}
}
return config
}
}
Neither has any effect. It would be good to know how to disable the overlay but an equally good solution would be how to have the compiler use the same level of strictness that VSCode does.
create-react-app generates a separate Webpack configuration for use with the dev server, so you need to use the devServer function, like so:
module.exports = {
devServer: function (configFunction) {
return function (proxy, allowedHost) {
// Create the default config by calling configFunction with the proxy/allowedHost parameters
const config = configFunction(proxy, allowedHost);
config.client = {
overlay: false,
};
return config;
};
},
};
From the react-app-rewired docs:
Webpack Dev Server
When running in development mode, create-react-app does not use the
usual Webpack config for the Development Server (the one that serves
the app pages). This means that you cannot use the normal webpack
section of the config-overrides.js server to make changes to the
Development Server settings as those changes won't be applied.
Instead of this, create-react-app expects to be able to call a
function to generate the webpack dev server when needed. This function
is provided with parameters for the proxy and allowedHost settings to
be used in the webpack dev server (create-react-app retrieves the
values for those parameters from your package.json file).
React-app-rewired provides the ability to override this function
through use of the devServer field in the module.exports object in
config-overrides.js. It provides the devServer function a single
parameter containing the default create-react-app function that is
normally used to generate the dev server config (it cannot provide a
generated version of the configuration because react-scripts is
calling the generation function directly). React-app-rewired needs to
receive as a return value a replacement function for create-react-app
to then use to generate the Development Server configuration (i.e. the
return value should be a new function that takes the two parameters
for proxy and allowedHost and itself returns a Webpack Development
Server configuration). The original react-scripts function is passed
into the config-overrides.js devServer function so that you are able
to easily call this yourself to generate your initial devServer
configuration based on what the defaults used by create-react-app are.
If you are using Webpack v4 (CRA v4), this should be the documentation you are looking for https://v4.webpack.js.org/configuration/dev-server/#devserveroverlay
module.exports = {
webpack: function (config, _) {
config.devServer = {
overlay: {
warnings: true,
errors: true
}
}
return config
}
}
The config you provided above is for Webpack v5 (CRA v5), so make sure you are using CRA v5 (and also check that react-app-rewired supports that version).
Since I can't use browser's pdf viewer in the network where the app is going to be used, I am testing a react-pdf package for loading PDF's with React.
I have made a component where I am sending a url of my PDF that I get from backend:
import React, { useState } from 'react';
import { Document, Page } from 'react-pdf';
const PDFViewer = ({url}) => {
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }) {
setNumPages(numPages);
}
function onLoadError(error) {
console.log(error);
}
function onSourceError(error) {
console.log(error);
}
return (
<div>
<Document
file={window.location.origin + url}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={onLoadError}
onSourceError={onSourceError}
>
{[...Array(numPages).keys()].map((p) => (
<Page pageNumber={p + 1} />
))}
</Document>
</div>
);
};
export default PDFViewer;
But, on opening the PDFViewer I get an error
Error: Setting up fake worker failed: "Cannot read property 'WorkerMessageHandler' of undefined"
In documentation it says that you should set up service worker and that the recommended way is to do that with CDN:
import { pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;
But, I can't use CDN links for my project, and in the documentation it also says:
Create React App uses Webpack under the hood, but instructions for Webpack will not work. Standard instructions apply.
Standard (Browserify and others)
If you use Browserify or other bundling tools, you will have to make sure on your own that pdf.worker.js file from pdfjs-dist/build is copied to your project's output folder.
There are no instructions on how to do that with create-react-app. How can I set this up locally then?
Install pdfjs-dist
import { Document, Page, pdfjs } from "react-pdf";
import pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
Reference: https://github.com/mozilla/pdf.js/issues/8305
found a more efficient way of including the worker
by including the library from the dependencies of react-pdf itself, this way you will never get a version mismatch like this The API version "2.3.45" does not match the Worker version "2.1.266"
if you install pdfjs-dist manually you will have to check react pdf dependency version on every build
import { Document, Page, pdfjs } from "react-pdf";
import pdfjsWorker from "react-pdf/node_modules/pdfjs-dist/build/pdf.worker.entry";
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
see similar error on pdfjs library : https://github.com/mozilla/pdf.js/issues/10997
hope it helps people
You can install worker loader module for webpack:
npm install worker-loader --save-dev
Then use it inline where you are going to work with a worker:
import SomeWorker from 'worker-loader?inline=true!../workers/some.worker'
const someWorker: Worker = new SomeWorker()
someWorker.postMessage(...)
I haven't tried this solution with react-pdf, but it might help.
You may need to add types for TypeScript if you are using it:
declare module 'worker-loader*' {
class SomeWorker extends Worker {
constructor()
}
export default SomeWorker
}
Just to add that in some .d.ts file in your project.
Install pdfjs-dist then use the webpack module:
import { pdfjs } from 'react-pdf'
import worker from 'pdfjs-dist/webpack'
pdfjs.GlobalWorkerOptions.workerSrc = worker
If your build process uses cli commands, (i.e. AWS buildspec), you can use this:
mkdir -p build && cp ./node_modules/pdfjs-dist/build/pdf.worker.js build
If you are in a corporate codebase environment and have little to no experience configuring WebPack, I wanted to share a little more info if (like me) you struggled with this for quite a long time.
My environment has several complicated WebPack config files (base, production, and development), and the resolution ended up being pretty simple but it escaped me for quite a while because I was unfamiliar with the complicated build process.
1) The Implementation
Quite simple, just as the docs recommend (I went with the minified file). Our React environment required me to use React-PDF#4.2.0, but there aren't any differences here.
import {Document, Page, pdfjs} from 'react-pdf'
pdfjs.GlobalWorkerOptions.workerSrc = 'pdf.worker.min.js'
Note: a previous solution recommended grabbing the source from the react-pdf node_modules folder, however, my codebase is setup to install dependencies separately somehow because when I npm install react-pdf, pdfjs-dist is also installed separately. Regardless, this method did not work for my codebase (importing the worker as a variable) due to the way the project is built. The import command acted like it couldn't find the proper named export inside a node_modules folder. It was top-level or nothing.
2) WebPack Config
Since I do not know WebPack at all, but found pretty easily that what I needed to do was take advantage of CopyWebpackPlugin, I searched through those existing dev and prod webpack config files, and found existing copy commands for JQuery and polyfill and added a new plugin to that array:
new CopyWebpackPlugin({from: 'node_modules/pdfjs-dist/build/pdf.worker.min.js})
I had to do this in multiples places in both config files as this large project has several entry point server files for the different services of the website.
3) Inserting Script Tag to HTML Head
This was the crucial part I was missing. There was a "ComponentFactory" file whose job it was to insert chunks of html in the <head> and tail of the html file. I wasn't used to something like this on small projects. So there, I simply copied what was already done for the jquery and polyfill, which included a string literal of the location of the assets folder the webpack was building out to. In my case, that was something like "assets/v1/". So the tag looked like this:
<script src=`${STATIC_ASSETS_URL}/pdf.worker.min.js` defer></script>
It works perfectly, however I am still getting the "Setting Up a Fake Worker" but immediately after that, it loaded it successfully in console and checking the dev tools, it was using the proper file. It's probably just a timing thing of the src set not running high enough in the code, but it was not effecting the outcome, so I let it go.
(Sidebar, if you also get the "TT unknown function" (paraphrasing) error, that can be ignored. It's just a font issue with whatever PDF you are loading and is just a warning, not an error.)
I was facing this issue once I had to use "react-pdf" from within a package.
It was solved by importing the worker conditionally into the code:
Conditional import:
export const getWorker = () => {
try {
return require('react-pdf/node_modules/pdfjs-dist/legacy/build/pdf.worker.entry.js')
} catch () {
return require('pdfjs-dist/legacy/build/pdf.worker.entry.js')
}
}
usage:
import { Document, Page, pdfjs } from 'react-pdf/dist/umd/entry.webpack'
pdfjs.GlobalWorkerOptions.workerSrc = getWorker()
I was wondering what the recommended way is to run a script that generates some static XML files at build time. Ideally, those scripts should be ES Modules so code can be shared between those scripts and my Next / React application code.
I think I need to customize the Webpack config but I'm not sure how I can run code there that uses ES Modules (and not just uses require).
EDIT: I have something like this in mind, which should also work with Modules:
{
webpack: (config, { isServer }) => {
if (isServer) {
require('./scripts/generate-xml');
}
return config;
}
It would be helpful if you could be more descriptive but from what I understand you need to run a particular script at Build-time for your NextJS app. You can make use of the getStaticProps() function provided by NextJS.
// your NextJS page
function Mypage() {
return (
<div></div>
)
}
// This is the function you need
export async function getStaticProps() {
// do anything you want to do at build-time
// A good way would be to call a function by importing and the function will be executed at runtime.
}
You can see more detail at official docs - https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops
On another note, If you want to generate XML for SEO reasons at Build time, a better way would be by using API routes and feeding that API route as your XML file location inside robot.txt. See this - https://github.com/toughyear/blog/blob/master/pages/api/posts-sitemap.js
if you use a custom babel.config.js, you can use babel macros, but this will opt out from SWC in Nextjs 12. I am searching for an alternative to be compatible with SWC. For now the only workaround is to have a "pre-build" script.
You can wrap your npm run start script to run anything you want before calling next build
In a react-native app that is also targeting web & Tizen, I'm trying to use webpack aliasing to replace a subcomponent of react-native-web with a custom package.
Relevant webpack config looks as follows:
module.exports = {
//...
resolve: {
alias: {
'react-native': 'react-native-web',
'react-native/Libraries/Alert': 'web/MyOwnAlertComponent', // does not work
'react-native-web/dist/exports/Alert': 'web/MyOwnAlertComponent', // does not work either
...
}
}
};
The first alias successfully aliases all react-native imports towards react-native-web. The Alert component is also part of react-native, and is imported in code as:
import { Alert } from 'react-native';
In react-native-web however, this Alert component is only a stub with no implementation. So in the second line of my webpack aliases I wanted to alias the react-native Alert towards my own implementation. This does not seem to work like this.
So how can I point react-native/Libraries/Alert or 'react-native-web/dist/exports/Alert towards my own Alert implementation? I'd like to resolve this, if possible, without touching the original code, which is shared between Android, iOS, tvOS, web & Tizen.