Implementing multi environment deployment in umi
Recently, at work, I heard my colleagues complain about the slow construction of projects on the internal platform. After a preliminary analysis, there are no more than two reasons: 1. The project itself is large, resulting in a long construction time. 2. Our construction and deployment process is: build qa and prod environment two packages at a time, and then deploy the construction products of different environments respectively, which will undoubtedly lead to a longer overall construction time.
The former is not the focus of this paper. This paper only discusses the second point: how to build multi environment deployment at one time.
problem
build once deploy anywhere is common on the back end, but there are some problems on the front end
1. Environmental variables
That is, process.env.XXX will be directly compiled into the current value in the build phase
For example, process.env.RUNTIME has been fixed as' this is qa runtime 'in the code after being compiled, which means that we can't change it in time. If you want to change to different values for different environments, you need to rebuild
export default function IndexPage() { return ( <div> <p> `{process.env.RUNTIME}` </p> </div> ); } // After yarn build, this code becomes function o() { return Object(r["jsx"])("div", { children: Object(r["jsxs"])("p", { children: [" `", "this is qa runtime", "` "] }) }) }
The simplest way to solve this problem is to write all the configurations into a config.json and use fetch to introduce them into the code.
public/config.json
{ "dev":{ "RUNTIME": "this is dev runtime" }, "qa":{ "RUNTIME": "this is qa runtime" }, "prod":{ "RUNTIME": "this is prod runtime" } }
It is introduced in umi app.tsx (during project deployment, the deployment information delopyEnv will be injected to determine which environment the project is in dev, QA and prod)
// app.tsx export function render(oldRender: any) { fetch('./config.json') .then((res) => res.json()) .then((config) => { window.config = config[window?.APP_METADATA?.deployEnv|| 'dev']; oldRender() }) } // pages/home/index.tsx export default function IndexPage() { return ( <div> <h1 className={styles.title}>Page index</h1> <p> `{window.config.RUNTIME}` </p> </div> ); }
At this point, we can see that we can use window.config.RUNTIME instead of process.env.RUNTIME
2.publicPath
When we run locally, the public path is usually the root directory address. However, in the online environment, css js img and other resource files are generally hosted on the CDN.
That is, the resource files we build in the QA environment will be similar to < script Src=“ https://static.qa.fiture.com/h1t86b7fg6c7k17v78ofck5d/umi.f6afc434.js "> < / script > exists in the project.
To access the img script link address dynamically according to the environment,
At present, my idea is to use placeholders as public paths when packaging. When the project starts, run a script to replace the placeholder.
When we set the non local environment, the publicpath is $$root / as a placeholder
const publicPath = development ? './' : '$$root/';
Then the corresponding build product index.html will be
<head> <link rel="stylesheet" href="$$root/umi.css" /> <script> //! umi version: 3.5.20 </script> </head> <body class="body"> <div id="root"></div> <script src="$$root/umi.js"></script> </body>
Therefore, we can execute commands before deployment
sed -i "" "s/\$\$root/publicpath/g" dist/*
Replace $$root with the real public path path (injected by environment variables during deployment)
Finally, we write it as a script in scripts/replacePath.js and add it to package.json
//package.json "scripts": { "replacePath": "node scripts/replacePath" }, // replacePath.js const child_process = require('child_process') const { cwd } = require('process') child_process.spawn('bash', ['./scripts/bash.sh'], { cwd: cwd(), shell: true, }); // bash.sh #!/bin/bash -x sed -i "" "s/\$\$root/publicpath/g" dist/*
Finally, the corresponding code is attached demo address