我的前端工程化实践

331 阅读8分钟

参考

自然博客:hsslive.cn/article/92

📢注意

本文内容涉及较多,主要是记录给自己备忘,其中交杂了不同时期的修改,可能有的内容会和图片对不上

简介

首先大致讲下本文需要准备以及涉及到的东西:

  • 服务器(linux)
  • 域名
  • jenkins
  • nginx
  • shell
  • github/gitee仓库(推荐gitee,github太慢了)
  • pm2
  • 前端项目(以我自己的vue和node项目举例)

ok,以上就是本文涉及到的东西,其实最后的前端项目是相对没怎么重要的,因为本文讲的主要是整个流程,而流程是通用的(看到最后就知道了)

而且,我相信能点进来看这篇文章的各位,多多少少应该都接触过上面涉及的东西了,因此,本篇文章并不会细到教大家怎么买服务器,怎么安装node、jenkins、nginx等等诸如此类的问题~

前置工作

服务器相关

首先,把服务器安排好了,我这里用的是腾讯云2h4g配置的轻量服务器,系统是centos7,然后在服务器安装并且配置好jenkins,nginx,node这仨,我这里已经配置好了:

jenkins:

1641830882904.jpg

node:

node.png

nginx:

nginx.png

代码仓库(gitee)

正式开始

好了,有了上面的准备,整件事情已经完成了20%了,下面让我们正式开始前端的工程化实践吧!!但是在做之前我们先思考一下我们的要做的前端工程化想要达到什么效果,要达到这个效果需要怎么做,这里给出鄙人的粗糙理解:

  1. 要有一个代码仓库,我们写的代码都可以提交到这里。
  2. 我们提交到仓库的代码只是源文件,是需要编译的。
  3. 代码编译之后,需要将他们托管/发布到服务器上。
  4. 发布到线上时,还得区分环境,比如test、preview、prod环境,且这些环境之间应该是相互隔绝、互不冲突的。

理论存在,实践开始!

一、代码仓库

这里用我之前的vue后台:vueblog-admin,这是后台是我自己用webpack5和vue搭建的,大致实现了vuecli的大部分功能,和vuecli一样,默认情况下最终打包出来的是一个dist文件,因此,我们把这打包dist这个工作交给shell,在这个项目的最外层新建一个build.sh文件,写上脚本:

#!/usr/bin/env bash
###
# Author: shuisheng
# Email: 2274751790@qq.com
# Github: https://github.com/galaxy-s10
# Date: 2022-01-10 09:57:30
# LastEditTime: 2022-01-12 00:32:00
# Description: https://gitee.com/galaxy-s10/vueblog-admin
###

# 约定$1为任务名,$2为Jenkins工作区,$3为环境
JOBNAME=$1 # 注意:JOBNAME=$1,这个等号左右不能有空格!
WORKSPACE=$2
ENV=$3
PUBLICDIR=/node

echo 删除node_modules:
rm -rf node_modules

echo 查看npm版本:
npm -v

echo 设置npm淘宝镜像:
npm config set registry http://registry.npm.taobao.org/

echo 查看当前npm镜像:
npm get registry

echo 开始安装依赖:
npm install

echo 开始构建:
npm run build

这样我们就可以通过进入到这个项目的目录,然后执行sh build.sh来进行打包dist了,到这里其实还是挺朴实无华的,而且到这大概完成整体进度的30%了~

二、jenkins(重要)

jenkins 是整篇文章的重头戏之一,非常的重要,而且非常的秀气哈哈哈, 上面我们已经在服务器安装了jenkins,启动jenkins后,会默认在服务器的8080端口下部署,首次进入找到默认的密码输入后,选中安装常用的插件,然后创建用户登录即可:

基本任务

jenkins1.png

jenkins2.png

jenkins3.png

登录进去后,首先新建一个item(任务):

jenkins4.png

输入一个任务名称,然后选中第一个风格,确定

jenkins5.png

填一下描述: jenkins6.png

源码管理选则git,然后填上仓库地址,分支暂时保留默认的master(后面会根据分支实现区分环境~) jenkins7.png

这里的用户名和密码填你的gitee登录用户名和登录密码,这里其实有个坑,举个例子:

  • 我的gitee主页是gitee.com/galaxy-s10 ,但是我gitee是叫shuisheng,jenkins的gitee用户名是gitee.com/galaxy-s10 里的galaxy-s10,而不是shuisheng
  • 我的github主页是github.com/galaxy-s10 ,但是我github是叫shuisheng,jenkins的github用户名就是shuisheng,而不是github.com/galaxy-s10 里的galaxy-s10....
  • 而且建议大家都用gitee,因为用github的话,拉代码太慢了,基本80%构建都是构建失败(拉代码卡住了导致的网络超时) jenkins8.png

然后继续往下,构建 - 增加构建步骤,选择execute shell

jenkins9.png

echo 当前路径:$(pwd)

echo 此构建的项目名称(JOB_NAME):$JOB_NAME

echo 远程 URL(GIT_URL):$GIT_URL

echo 远程分支名称(GIT_BRANCH):$GIT_BRANCH

echo 分支名(BRANCH_NAME):$BRANCH_NAME

echo 被检出的提交哈希(GIT_COMMIT):$GIT_COMMIT

echo 当前node版本:

node -v

echo 当前npm版本:

npm -v

echo 开始执行构建脚本:

sh ./build.sh $JOB_NAME $WORKSPACE

echo 列出当前目录

ls

ps: 这里面的$变量都是jenkins提供的环境变量,然后关键是sh ./build.sh,这个命令执行了项目里面的构建脚本!继续往下看~

最后点击保存,就可以进行构建了!

jenkins10.png

点击Build Now,可以看到下面的有构建进度条,构建成功后,可在工作空间里面查看当前项目的文件 jenkins11.png 还可以点击构建的进度条,进去看控制台输出:

jenkins12.png

jenkins13.png

我们可以看到,毫无疑问,jenkins在拉完代码下来后,执行了项目里面的build.sh构建脚本,因此,在工作空间里面也看到了有打包后的dist这个目录。到这里,其实进度已经到了50%了!!!现在的dist是放在jenkins的工作目录里面的,我们不希望把它放到这里面,我们把jenkins里所有的前端项目都统一放到一个地方,这样方便维护,也好为后面的nginx做铺垫~,那么该咋办,怎样才能把构建的dist放到我希望的地方,而这个地方也不是随便的一个地方,如果是大公司的话,应该是构建完成后,把文件都放到cdn服务器上面托管起来的,我这没有cdn服务器,就把自己的服务器当做cdn服务器来模拟这个操作了哈哈哈~

模拟cdn服务器

首先,我们需要一个jenkins插件:Publish Over SSH,通过ssh连接到cdn服务器(我自己的服务器),直接到jenkins的插件管理里面下载完这个插件然后重启jenkins:

jenkins14.png

下载完成后,配置一下自己的ssh

jenkins15.png

配置完成后,再回到任务里面,点击配置,在构建里面再配置多一个Send build xxxxxx SSH:

jenkins16.png

选中刚刚配置的ssh,然后连上了ssh要干啥,又回到之前的问题,构建好的文件都要放哪里,我这里就定了一个规则:所有项目都放在服务器的/noode/目录里面,比如vueblog-admin,就放到/node/vueblog-admin里面,reactblog-admin就放在/node/reactblog-admin里面!那么现在构建好的文件放在哪里的问题定下来了,那么我要怎么按照我的规则把构建好的文件放到指定位置呢,没错,还得是shell哈哈哈~~

jenkins17.png

为了让所有文件都清晰一目了然,我把构建的脚本也放到了/node/里面了,在服务器的/node/目录新建一个sh文件夹,里面新建一个frontend.sh文件,然后执行这个shell的时候,传了两个参数,$JOB_NAME是当前的任务名,即:vueblog-admin,$WORKSPACE是项目当前的工作目录(也就是说dist在这个目录里面),可以翻上面的有一张控制台输出的图片看看,其实就是在服务器的/var/lib/jenkins/workspace/vueblog-admin目录~

sh /node/sh/frontend.sh $JOB_NAME $WORKSPACE

/node/sh/frontend.sh的脚本如下:

#!/usr/bin/env bash
###
# Author: shuisheng
# Email: 2274751790@qq.com
# Github: https://github.com/galaxy-s10
# Date: 2022-01-10 10:56:03
# LastEditTime: 2022-01-10 17:52:21
# Description: 不区分环境的前端通用构建脚本
###

# 约定$1为任务名,$2为Jenkins工作区,$3为环境
JOBNAME=$1 # 注意:JOBNAME=$1,这个等号左右不能有空格!
WORKSPACE=$2
ENV=$3
PUBLICDIR=/node

if [ -d $PUBLICDIR/$JOBNAME ]; then
    echo "$PUBLICDIR/$JOBNAME/目录已经存在,先删除它,然后再重新创建它"
    rm -rf $PUBLICDIR/$JOBNAME/
    mkdir -p $PUBLICDIR/$JOBNAME/
    cp -r $WORKSPACE/dist/* $PUBLICDIR/$JOBNAME/
else
    echo "$PUBLICDIR/$JOBNAME/目录还没有,创建它"
    mkdir -p $PUBLICDIR/$JOBNAME/
    cp -r $WORKSPACE/dist/* $PUBLICDIR/$JOBNAME/
fi

这个脚本其实就做了一件事,就是在/node/目录里面,把jenkins的任务名作为文件夹名,并且新建到/node/里面,然后/node/里面就要vueblog-admin这个文件夹了,再把jenkins的vueblog-admin的工作区里面的dist里面的所有文件复制到/node/vueblog-admin里面,下面让我们试下配置后的构建结果:

jenkins18.png 控制台看到是连接了ssh,那么再到服务器的/node目录下看看是不是真的有vueblog-admin这个文件夹,以及里面是不是真的有dist的文件了

jenkins19.png

结果当前是都有,事情按计划进行🎉 🎉 🎉,到这里其实完成了整体进度的60%,接下来就是轮到nginx发光发热的时刻~

区分环境

这里我们假设一个项目有三个环境:prod,preview,beta,然后我们希望可以把代码构建出不同的环境,那么总不可能只有一个master分支,然后根据改master分支的代码然后构建不同的环境,所以可以通过新增preview和beta分支,然后需要限制一下:master分支可以构建所有环境(prod,preview,beta),而非master分支只能构建preview和beta环境。

首先在jenkins管理插件里面安装一个Active Choices插件,安装完成后开始下面的步骤:

首先在general里面找到This project is parameterized,然后勾选上,勾选上后,原本的之前的Build Now就会变成Build with Parameters,可以选择我们配置的参数进行构建。然后点击添加参数:Choice Parameter,

image.png 输入以下内容:

image.png 这样的话就会在构建就会多一个下拉列表让我们选择prod,preview,beta环境,那么我们如何让我们选择的环境和我们要构建的分支对应上呢,其实只需要改一下之前的地方分支就行了:

image.png

改完之后,就可以做到我们选什么分支,就会使用什么分支进行构建了,接下来的设置环境也是一样的操作,但是如果我们还是使用Choice Parameter的话,就做不到之前说的限制了(只有master分支才可以构建所以环境),因此我们得使用另一个参数:Active Choices Reactive Parameter

image.png

image.png

image.png

这里涉及了一些Groovy Script,大概意思就是,当BRANCH选择的是master,则可以构建"null","beta","preview","prod"其中的一个环境,如果BRANCH选择的不是master,则只能构建"null","beta","preview"其中的一个环境。

改完之后,还需要改一下/node/sh/frontend.sh,jenkins的execute shell,还有连接ssh后执行的脚本

连接ssh后执行的脚本:

sh /node/sh/frontend.sh $JOB_NAME $ENV $WORKSPACE

image.png

jenkins的execute shell:

echo 当前路径: $(pwd)

echo 远程 URL(GIT_URL): $GIT_URL

echo 被检出的提交哈希(GIT_COMMIT): $GIT_COMMIT

echo 远程分支名称(GIT_BRANCH): $GIT_BRANCH

echo 分支名(BRANCH_NAME): $BRANCH_NAME

echo 此构建的项目名称(JOB_NAME): $JOB_NAME

echo 当前项目工作空间的绝对路径(WORKSPACE): $WORKSPACE

echo 当前构建的分支: $BRANCH

echo 当前发布的环境: $ENV

echo 当前node版本:

node -v

echo 当前npm版本:

npm -v

echo 开始执行构建脚本:

sh ./build.sh $JOB_NAME $ENV $WORKSPACE

echo 列出当前目录:

ls

/node/sh/frontend.sh:

#!/usr/bin/env bash
###
# Author: shuisheng
# Email: 2274751790@qq.com
# Github: https://github.com/galaxy-s10
# Date: 2022-01-10 10:56:03
# LastEditTime: 2022-01-16 03:56:26
# Description: 前端通用构建脚本
###

# 约定$1为任务名, $2为环境, $3为Jenkins工作区
JOBNAME=$1 # 注意: JOBNAME=$1,这个等号左右不能有空格!
ENV=$2
WORKSPACE=$3
PUBLICDIR=/node

if [ $ENV != 'null' ]; then
    echo "当前环境:$ENV"
    if [ -d $PUBLICDIR/$JOBNAME/$ENV ]; then
        echo "$PUBLICDIR/$JOBNAME/$ENV/目录已经存在,先删除它,然后再重新创建它"
        rm -rf $PUBLICDIR/$JOBNAME/$ENV/
        mkdir -p $PUBLICDIR/$JOBNAME/$ENV/
        cp -r $WORKSPACE/dist/* $PUBLICDIR/$JOBNAME/$ENV/
    else
        echo "$PUBLICDIR/$JOBNAME/$ENV/目录还没有,创建它"
        mkdir -p $PUBLICDIR/$JOBNAME/$ENV/
        cp -r $WORKSPACE/dist/* $PUBLICDIR/$JOBNAME/$ENV/
    fi
else
    echo "当前环境是null"
    if [ -d $PUBLICDIR/$JOBNAME ]; then
        echo "$PUBLICDIR/$JOBNAME/目录已经存在,先删除它,然后再重新创建它"
        rm -rf $PUBLICDIR/$JOBNAME/
        mkdir -p $PUBLICDIR/$JOBNAME/
        cp -r $WORKSPACE/dist/* $PUBLICDIR/$JOBNAME/
    else
        echo "$PUBLICDIR/$JOBNAME/目录还没有,创建它"
        mkdir -p $PUBLICDIR/$JOBNAME/
        cp -r $WORKSPACE/dist/* $PUBLICDIR/$JOBNAME/
    fi
fi

至此,多环境也已经完成,看看最终的Build with Parameters

image.png

image.png

三、nginx(重要)

nginx 是一个高性能的HTTP反向代理web服务器。这里用nginx主要就是代理到我们的项目地址。 注意:在此之前记得将域名解析好,以及将所需要的端口(如80,8080,443等)给在服务器防火墙/安全组里面给打开了

基本配置

好了,回到现在,我们已经把项目都放到服务器的/node/里面了,我们要访问/node/vueblog-admin里面的文件,有两个方法:

  1. 直接将80端口代理到/node/vueblog-admin,这样访问 www.hsslive.cn ,就会直接跳转到 www.hsslive.cn
    server {
        listen 80;
        server_name www.hsslive.cn;

        location / {
            root /node/vueblog-admin;
            index index.html index.htm;
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            root html;
        }

    }
  1. 第一种方法没毛病,但这样有个缺点,这个80端口就只用来放/node/vueblog-admin这一个项目了,其他/node/reactblog-admin就没地方放了,因此这里可以使用第二种方法,将80端口代理到/node/目录,这样访问 www.hsslive.cn ,默认就会代理到/node/目录,默认就会找index.html,然后我们访问:www.hsslive.cn/vueblog-adm… ,就会访问/node/vueblog-admin/目录,并且找/node/vueblog-admin/里面的index.html,那样岂不是我后面jenkins新增的reactblog-admin也可以通过www.hsslive.cn/reactblog-admin/访问到,实现了一举多得!
    server {
        listen 80;
        server_name www.hsslive.cn;

        location / {
            root /node/;
            index index.html index.htm;
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            root html;
        }

    }

这里采用第二种方法实践: 首先准备一个index.html,把它上传到/node/目录,这样/node/目录就有一个index.html文件了,我们看看访问 www.hsslive.cn ,是否会展示index.html里面的内容~

index.html

server1.png

访问 www.hsslive.cn :

hss1.png

不出所料,事情按计划如期进行~~~ 这时候已经完成了一大步了,那么按道理来说,这时访问 www.hsslive.cn/vueblog-adm… ,就能访问到部署的代码了:

hss2.png

果然能访问了(看标题已经变了hss-webpack5了,说明加载了我们的页面,只要不是404或者502都算可以访问哈哈哈)!,但是空白页,貌似出了点问题,其实问题很显而易见:

hss3.png

因为 cli.vuejs.org/zh/config/#…

默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上,例如 https://www.my-app.com/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.my-app.com/my-app/,则设置 publicPath 为 /my-app/

我的vue项目也是默认部署在域名根路径的,需要改造一下,让webpack.output.pulicPath为jenkins的任务名,也就是vueblog-admin这样才靠谱~

解决路径问题

要达到上面说的效果其实有很多种方式,最简单的就是直接把项目里面的webpack.output.pulicPath写死成/vueblog-admin,但是这样显然很不优雅,因此我这里针对我的项目就做了一个改变,使用了cross-env,cross-env是一个跨平台设置环境变量的插件。原本一开始的时候我是想着把package.json里面script的build改成 cross-env publicPath=vueblog-admin webpack --config ./config/webpack.common.js --env production,然后在可以拿到jenkins人物名的build.sh里面把这个vueblog-admin给传给package.json里面的script的,但是经过一番尝试后,发现貌似实现不了(如果能实现的可以评论留言告诉我一下哈哈哈),然后这个办法做不到了,就只能从build.sh这里下手了,我第一时间想到了就是npx,因为执行npm run build的本质就是执行package.json里面的script里面的build,而build就只是执行了webpack --config ./config/webpack.common.js --env production,我们完全可以在这个命令放到build.sh里面的执行:npx cross-env publicPath=/$JOBNAME webpack --config ./config/webpack.common.js --env production ,因此把原本的build.sh最后的npm run build改成了现在的npx方式,同时,还要把webpack.output.pulicPath的值改成: process.env.publicPath || '/',改完后,还有一点也要改别忘了,就是vue-router,里面有个base属性也是要改成process.env.publicPath || '/' 的,都改完后重新提交代码,然后jenkins重新构建,然后再次访问 www.hsslive.cn/vueblog-adm… ,看看效果:

hss4.png

点击 www.hsslive.cn/vueblog-adm… ,直接跳转控制台了(这是因为最近重构后台的时候把权限给去掉了,后面会改回来跳登录~) perfect 🎉~

上面是我的项目做法,如果你们用的是vuecli的话,其实本质都是一样的,把vuecli的package.json里面的build的命令替换到build.sh里面,用npx执行,带上cross-env的环境变量即可~

梦回起点

看完上面的是不是觉得已经解决了问题了,答案可以说是也可以说不是,因为我们的项目本质是部署在/node/目录里面的(直接访问:www.hsslive.cn ,其实就是找/node/index.html),而我们的项目如果只有一个,且搭建在www.hsslive.cn 里面其实是没问题的,但是这里我们耍了个机灵,在/node/里面放了多个项目,然后通过www.hsslive.cn/xxx ,这样访问里面的每一个项目,其实看上去没问题,但是一刷新就出问题了,会出现404,这个问题好解决,在nginx里面加上try_files $uri $uri/ /index.html;就完事了

    server {
        listen 80;
        server_name www.hsslive.cn;

        location / {
            root /node/vueblog-admin;
            index index.html index.htm;
            try_files $uri $uri/ /index.html;
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            root html;
        }

    }

但是事情并不是那么简单,我们的项目看似是独立的,是放在了www.hsslive.cn/xxx 里面,但是其实本质上的nginx是只匹配/的,如果www.hsslive.cn/xxx 刷新404了,重定向到www.hsslive.cn/index.html ,即/node/index.html,就会有这个问题,这个问题我们可以暂时通过将路由转换成hash路由解决。

hss5.png

所以,路漫漫其修远兮,还得继续探索如何优化解决这个问题,但看到这里了,其实已经很明确了我们要做的事情了,在这里我第一时间想到的就在nginx里面入手,能否在shell里面同步的修改nginx.config配置文件,然后重启nginx服务实现。

        location / {
            root /node/;
            index index.html index.htm;
            try_files $uri $uri/ /index.html;
        }
        location /vueblog-admin {
            root /node/vueblog-admin;
            index index.html index.htm;
            try_files $uri $uri/ /index.html;
        }
        location /reactblog-admin {
            root /node/reactblog-admin;
            index index.html index.htm;
            try_files $uri $uri/ /index.html;
        }

有时间继续尝试这种方式~

四、区分环境

已完成

github: github.com/galaxy-s10