在umi中實現一次構建多環境部署

十一點我就睡發表於2021-11-01

在umi中實現一次構建多環境部署

最近在工作中,聽聞同事抱怨在內部平臺上構建專案很慢。初步分析了一下,原因無非兩個:1. 專案本身較大,導致構建時間較長 2. 我們的構建部署流程是:一次build qa和prod環境2個包,然後再分別部署不同環境的構建產物,無疑這樣會導致整體構建時間變長。
前者不是本文的重點,本文只討論第二點:即如何 一次構建多環境部署。

問題

build once deploy anywhere 在後端已經是比較常見了,但是對於前端來說,會有幾點問題

1.環境變數

即 process.env.XXX 會在build階段直接被編譯成當前值
舉例: process.env.RUNTIME 在被編譯之後,就已經在程式碼中固定為 ‘this is qa runtime’,這意味著我們不能及時更改它。如果想要針對不同環境,更改為不同的值,那就需要重新構建

export default function IndexPage() {
  return (
    <div>
      <p> `{process.env.RUNTIME}` </p>
    </div>
  );
}
// 在經過yarn build之後這段程式碼就成了
function o() {
      return Object(r["jsx"])("div", {
        children: Object(r["jsxs"])("p", {
          children: [" `", "this is qa runtime", "` "]
        })
      })
 }

要解決這個問題,最簡單的辦法就是將所有配置寫到一個config.json中,使用fetch將其在程式碼中引入。
public/config.json

{
  "dev":{
    "RUNTIME": "this is dev runtime"
  },
  "qa":{
    "RUNTIME": "this is qa runtime"
  },
  "prod":{
    "RUNTIME": "this is prod runtime"
  }
}

在 umi app.tsx中引入(專案部署時,會將部署資訊 delopyEnv注入進來,用於專案判斷是處於dev,qa,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>
  );
}

至此 我們可以看到,我們就可以使用window.config.RUNTIME 來代替 process.env.RUNTIME

2.publicPath

我們在本地執行的時候,publicpath一般就為根目錄地址。但是線上上環境的話,一般 會將css js img等資原始檔託管到CDN上。
即我們在qa環境構建出來的資原始檔 會以類似<script src="https://static.qa.fiture.com/h1t86b7fg6c7k17v78ofck5d/umi.f6afc434.js"></script>的形式存在於專案中。
若要動態的去根據環境改變訪問img script link 地址的話,
目前我的思路是:在打包的時候使用佔位符作為publicpath。等到啟動專案的時候,執行一個指令碼,將佔位符替換掉即可。
我們設定非本地環境時的publicpath 為$$root/作為佔位符

const publicPath = development ? './' : '$$root/';

那麼對應的構建產物index.html就會是

 <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>

故我們可以在部署前執行命令
sed -i "" "s/\$\$root/publicpath/g" dist/*
將$$root 替換成真正的public path路徑(部署時由環境變數注入).
最後我們將其寫成一個指令碼 在scripts/replacePath.js中 並新增到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/*

最後附上對應的程式碼 demo地址

相關文章