- based on create-react-app & rescripts with Typescript
- used environment variables to include only files needed
- uses new React Features (Suspense and Lazy)
it has the both required environment variables: NODE_PATH
and REACT_APP_TL_TENANT
.
NODE_PATH
: let us avoid the relative path hell (../../../). video referenceREACT_APP_TL_TENANT
: adds the specific tenant extension file to be imported (see the.rescriptsrc.js
file for more explanation)
Adds the extra extension files to the webpack config on resolve.extensions
. This is important to maintain the imports inside the code as clean as possible and avoid referring specific tenants or use the process.env
variable. All the magic happens at build time
In conjunction with the NODE_PATH
, it let us work with paths from src
directory (avoid relative path hell as mentioned before)
checkout how setting cml
as REACT_APP_TL_TENANT
, we import:
- src/App/Config/theme/theme.foo.ts
- src/Login/Page.foo.tsx
sample code on how we import the Page
component:
import React, { Suspense, lazy } from "react";
const Login = lazy(() => import(`./Page`));
/*
you see that in the dynamic import above,
we are not adding the extra `cml` to the import,
but Webpack is doing that for us...
*/
export const LoginPage = () => (
<Suspense fallback={<p>loading...</p>}>
<Login />
</Suspense>
);
The tenant configuration is handled at Compile time. this means that all the code bundled depends on the environment variable we set at build time (REACT_APP_TENANT_CODE). the way we tell webpack how and what to compile is by changing the extension of the component, adding the tenant code before the real file extension (for example: App.foo.tsx
). The setup is actually pretty simple. Because webpack resolve all its modules by extensions, it has a property in the webpack config that accepts an array of extensions. Thankfully for us, the order of the list matters (ir resolve the extension it finds first). What we do is telling webpack to resolve first whatever file extension + the tenant code FIRST, and then the regular file extension. Here's the setup we override with rescripts:
# .rescriptsrc.js
module.exports = {
webpack: config => {
const prevExtensions = config.resolve.extensions
const newExtensions = ["mjs", "js", "ts", "tsx", "json", "jsx"]
.map(ext => `.${process.env.REACT_APP_TENANT_CODE}.${ext}`)
.filter(ext => !ext.includes("undefined"))
.concat(prevExtensions)
config.resolve.extensions = newExtensions
return config
}
}
what we are doing is adding the REACT_APP_TENANT_CODE
in front of the default extensions, and then concatenate the old extensions at the end of this new array. This way if there's 2 components (App.tsx
and App.foo.tsx
), and aour environment variable is equal to foo
, webpack will compile App.foo.tsx
because the .foo.tsx
has more priority (its first in the resolve.extensions
array).