1. 前言
2020.6.10 日我写了这样的一篇文章:
里面简述了为什么要做这样一件事情,简单来说就是: 企业开发时多环境下需要前端页面通过获取宿主机的环境变量来动态的设置某个参数。
在上面这篇博客中,我利用 docker 在构建镜像时获取系统环境变量来注入一个属性给 html 文件,然后获取这个对应的属性,挂载到 vue 或者全局即可。但是正如我在文末中写到,我并不是很喜欢这种方式,不优雅也不正式,同时文末我给提出了另一种解决之法。
我思考和实践出了一种全新的方法,极度优雅和简单。
2. 利用 docker 构建镜像时替换变量
可能看标题,会觉得这跟第一种几乎相同,但是,这里的思想是不同的。
主要的区别在于一个是注入变量,一个是替换,一个是挂载变量然后获取,一个是直接获得,极度优雅。
在此记录整个思考和实现的过程。
2.1 思维转换
最开始是怎么思考的呢?
- JS 能不能直接获取到环境变量,发现不行,因为 JS 是运行在解释器里面的。
- 能不能有一个桥梁,这个桥梁可以获取到环境变量,然后这个桥梁再把值传给JS
(1) 首先,shell 可以直接获取到环境变量
(2) docker 在构建镜像时就可以运行 shell 脚本
(3) docker 可以做这样一个桥梁,获取到环境变量然后再把值注入到 html 文件中
这就是上面那篇文章实现的缘由。
后来,渐渐的想到,既然docker构建镜像时可以运行shell,那为什么还要去注入变量,然后 JS 再获取变量呢?不可以直接进行文本替换吗?当我想到的时候,简直就是一句点醒梦中人。
2.2 实现
2.2.1 获取系统环境变量
env 查看 然后 $ 对应名字即可,比如 $PATH
下面是在 shell 脚本中获取环境变量
#!/bin/bash
path=$PATH
2.2.2 sed 文本替换
简单说明一下 sed
命令
全局替换
sed 's/text/replace_text/g' file
默认替换后,输出替换后的内容,如果需要直接替换源文件,使用 -i
sed -i 's/text/replace_text/g' file
(1) 首先,做一个简单的测试。
创建两个文件,a.txt 和 run.sh
a.txt
arrow is a frontend engineer.
I am arrow
run.sh
#!/bin/bash
sed -i 's/arrow/arrow_zb/g' ./a.txt
运行 run.sh 文件
sh run.sh
运行 run.sh 后,a.txt 文件变为
arrow_zb is a frontend engineer.
I am arrow_zb
这里需要注意一下,如果是在 mac 里执行,直接运行
sed -i 's/arrow/arrow_zb/g' ./a.txt
会报如下错误:
sed: 1: "./a.txt": invalid command code .
这是因为 macOS 下强制要求备份你修改的文件,当前,你可以使用空字符来代替,因此,macOS 下修改为以下代码即可运行
sed -i ' ' 's/arrow/arrow_zb/g' ./a.txt
(2) 实际获取环境变量替换文件内容
通过以上测试,我想,你应该已经知道如何实现我们想要的功能了,不过我这里还是记录一下,因为这个过程中我还是遇到了一些问题,因为我使用的环境变量比较特殊,具体特殊之处往下一看便知。
假设我们想要获取系统变量 PATH
, 将 a.txt 中的 arrow
替换成 $PATH
。
根据上面的测试,我们可以写出如下脚本,run.sh
文件。
sed -i '' "s/arrow/$PATH/g" ./a.txt
值得一提的是,shell 脚本的变量只能在双引号里才能识别,因此这里需要改成双引号
此时运行 run.sh
,理想状况下会直接将 a.txt
里面的 arrow
替换成 $PATH
的内容。
但是,运行就会报错
sed: 1: "s/arrow//usr/local/bin: ...": bad flag in substitute command: 'u'
原因很简单,你将 PATH
打印出来就知道了
$ echo $PATH
# /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin
PATH 里面含有 /
, 与此同时,我们的 sed -i '' "s/arrow/$PATH/g" ./a.txt
里的正则表达式定界符就是 /
,所以自然命令就混乱了,解决办法就是,将正则表达式定界符改成特别的即可,因为sed您可以使用任何定界符,例如改成如下:
sed -i '' "s~arrow~$PATH~g" ./a.txt
此时,运行即可生效,a.txt
的内容变成
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin is a frontend engineer.
I am /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin
即达到了我们想到的目的。
到此,实现了我们的功能,而且只用了一行代码,极度极度的优雅!
2.3 实际应用
在实际的应用中,我们一般是想通过获取系统的环境变量来实现动态的在某个文件中修改值或实现功能,在这里我就以获取系统的 ENV_SYSTEM
(取值共 dev, text, beta, prod),来改变 api.js
文件中的 ENV_NOW
.
假设当前文件结构如下
.
├── run.sh
└── src
└── api.js
api.js
中内容如下:
const origin = "www.ENV_NOW.arrow-zb.cn";
export default {
prefix: origin,
get_user_list: {
get: '/api/user/list'
}
}
想要通过系统环境变量实现
// dev
"www.dev.arrow-zb.cn"
// test
"www.test.arrow-zb.cn"
// beta
"www.beta.arrow-zb.cn"
// prod
"www.arrow-zb.cn"
那么在 run.sh
中首先应该获取系统变量,然后判断,然后替换即可。
以下就是简单的 shell 脚本了。
#!/bin/bash
ENV_NOW="."
if [ $ENV_SYSTEM != '' ]
then
if [ $ENV_SYSTEM == 'prod' ]
then
ENV_NOW="."
else
ENV_NOW=".${ENV_SYSTEM}."
fi
fi
sed -i '' "s/.ENV_NOW./$ENV_NOW/g" ./src/api.js
测试:
- 系统变量
ENV_SYSTEM
为dev
运行run.sh
src/app.js
结果为
const origin = "www.dev.arrow-zb.cn";
export default {
prefix: origin,
get_user_list: {
get: '/api/user/list'
}
}
- 系统变量
ENV_SYSTEM
为prod
运行run.sh
src/app.js
结果为
const origin = "www.arrow-zb.cn";
export default {
prefix: origin,
get_user_list: {
get: '/api/user/list'
}
}
完美的实现了预期的结果。
相信大部分同学看到这里已经知道如何在自己的项目中使用了,你就没必要看第三部分了。如果你还不知道,看第三部分,以真实的项目为例。
3 真实项目 —— 以 vue 为例
这里博主就不赘述简单的项目创建等,下面是使用 vue-cli 创建的简单示例。接下来一步一步的带领大家在真实项目中使用。
假设我在 src/api.js 中要 2.3 的需求。
同时我在 app.vue中将api打印出来做测试
3.1 在根目录创建 run.sh 文件
#!/bin/bash
ENV_NOW="."
if [ $ENV_SYSTEM != '' ]
then
if [ $ENV_SYSTEM == 'prod' ]
then
ENV_NOW="."
else
ENV_NOW=".${ENV_SYSTEM}."
fi
fi
# mac 下
# sed -i '' "s/.ENV_NOW./$ENV_NOW/g" ./src/api.js
# 其他linux下
sed -i "s/.ENV_NOW./$ENV_NOW/g" ./src/api.js
npm i
npm run build
3.2 在根目录创建Dockerfile文件
# 以nginx为基础镜像
FROM nginx
# 复制 ./dist/ 到 /usr/share/nginx/html/
COPY ./dist/ /usr/share/nginx/html/
# /usr/share/nginx/html/为 nginx 默认的启动文件夹
3.3 在宿主机中运行
将代码clone或移动到宿主机,运行 run.sh
,其实一旦运行完成,就实现了获取宿主机的环境变量。
bash run.sh
3.4 docker构建并运行
docker build -t programming-career .
# 根据dockerfile来创建programming-career镜像
docker run -p 6090:80 -d -t programming-career
3.5 测试
运行直接测试即可,博主将宿主机的ENV_SYSTEM
设置为了beta, 最终博主的测试结果如下,完美的实现了
在这里贴出测试代码 点击查看
4 总结
从有这个想法到测试成功再到今天写博客,前后花了大量的时间,特别是写博客,更是花了不小于10个小时的时间,现在晚上快 9 点了,最终完成,虽然可能文本逻辑上还是不太好,但是尽量的做到了简单易懂,而且做了对应的测试,还适应了 mac 和 linux ,算是比较完整了。
其实,只要理解了思路,你就会发现,其实很简单,而且很容易想到。