Back

【问题记录】解决前端需打包应用在打包时前端访问项目报403错误问题

目录

  1. 前言
  2. 问题出现原因及场景复现
  3. 问题解决思路和实现步骤
  4. 验证配置完成后的最终结果
  5. 参考文档

一. 前言

现在前端很多项目是借助webpack或vite等构建工具打包生成部署的代码,为了每次打包的静态资源不累加,打包之前都会把上一次生成的dist文件删除掉再打包,在这个过程中,dist文件是不存在的,浏览器访问或刷新前端,就会报403错误,直到新的dist打包完成,需要解决这个问题。

二.问题出现原因及场景复现

2.1 问题出现原因

现在前端项目开发完成后最终会借助构建工作打包成最终部署的dist代码,每一次发版都会重新打包构建一次。在重新打包构建时,因为各种原因,比如为了解决缓存问题给每一次打包的项目都生成hash值,每一次生成的资源名称会不一样,时间久了dist文件里面的文件会越来越多,所以需要在每次打包前就先把原先的dist文件删除掉,再重新打包,而在重新打包的时间内,原先的dist文件是不存在的,如果此时在前端访问项目或刷新浏览器,就会报403错误。

2.2.用nginx和react演示问题场景

2.2.1 安装nginx

安装nginx比较简单,网上也有很多教程,这里只演示mac安装nginx,借助brew来安装

brew install nginx

安装成功后就可以直接使用了,输入nginx -t,查看nginx安装所在目录

nginx -t

输出

localhost:~ guojiongwei$ nginx -t
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful

输入内容里面的/usr/local/etc/nginx/nginx.conf,就是nginx配置文件所在目录

2.2.2 用create-react-app创建react项目

创建项目

npx create-react-app dist-demo

创建成功用编辑器打开,用命令行进行打包,

npm run build

create-react-app打包默认生成的是build文件

在当前项目目录下输入pwd查看文件路径

copy.png

2.2.3 配置nginx托管该build文件

打开nginx配置在刚才看到的配置文件里面新增nginx配置server服务,root字段为刚才打包build文件所在目录绝对路径

server {
  listen 3001; # 端口30001
  server_name localhost;
  location / {
    root /Users/guojiongwei/Desktop/wuyou/react/dist-demo/build; # 项目打包后存放的目录
    index  index.html index.htm;
  }
}

新建成功后重启nginx服务

nginx -s reload

在浏览器打开http://localhost:3001, 即可看到刚才打包的react项目

copy.png

2.2.4 再react项目重新打包期间刷新浏览器查看问题

把浏览器打开的项目和react项目编辑器放到同一个电脑窗口上,编辑器命令行直接npm run build重新打包,

同时刷新浏览器http://localhost:3001页面。

copy.png

会出现上图情况,在打包刚开启,构建工具会因为刚才在问题出现原因里面说到的原因,而先对上一次的打包文件build进行删除,然后再打包。此时在浏览器访问项目,build文件已经不存在了,nginx会报403错误,前端就无法正常访问项目了。直到新的build包打包完成才可以正常访问,如果在打包过程中出现了异常,那前端出现403的时间会更久,会一直到解决打包问题重新打包生成build文件才能恢复正常,这对线上项目正常运行再说是致命的问题,所以肯定要想办法解决。

三.问题解决思路和实现步骤

3.1 解决该问题的思路

思路:区分构建工具最终打包输出的文件名称和nginx托管的文件名称,比如刚才nginx托管和react打包生成的目录都是build,每次打包清除build会影响到nginx。可以通过修改nginx的托管静态资源目录由builddist,每次构建工具成功打包完成后,删除原先的dist目录,再把本次打包的build文件夹名称改成dist。删除dist目录和修改build名称为dist的时间是以毫秒为单位的,对项目不会有太多影响。而且不打包完成不会删除原先的dist目录,还可以很好的避免刚才所提到的打包失败,由于nginx托管build资源被删除而出现很久的403问题。

3.2 实现步骤

修改前端打包配置(以刚才react项目为例)

第一步:需要修改react的webpack配置

create-react-app提供了reject方式把webpack配置给映射到项目目录中,但该过程是不可逆的,会是项目工程中出现很多和项目业务无关的配置代码,不符合关注点分离的概念,所以这里使用社区提供的react-app-rewired

第二步:安装react-app-rewired

customize-cra是和react-app-rewired配套使用的,提供了很多方法便于修改和新增配置,执行命令:

npm i react-app-rewired customize-cra -D

第三步:修改package.json文件scripts字段内容

start, build, test三个命令前面的react-scripts换成react-app-rewired,最终如下所示

{
  // ...
  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  },
  // ...
}

第四步:新建react-app-rewired的配置文件

在根目录下新建文件config-overrides.js文件

// config-overrides.js
module.exports = {}

第五步:安装webpack生命周期插件来监听打包完成

虽然也可以通过npm run build命令行执行结束后再进行删除原先dist文件,修改新打包文件build为dist操作,但是这样无法区分是否成功打包,所以还想需要通过webpack的生命周期插件,确保成功打包后再执行一系列操作

执行命令

npm i lifecycle-webpack-plugin -D

第六步:修改config-overrides.js配置文件

  1. 引入所需要的fs, path, child_processnode核心模块
  2. 再引入customize-cra提供的覆盖方法override和新增plugin方法addWebpackPlugin
  3. 引入webpack生命周期插件中的插件LifeCycleWebpackPlugin
  4. 借助NODE_ENV环境变量来区分打包模式
  5. 添加配置,流程为:在打包模式下,新增webpack生命周期监听插件LifeCycleWebpackPlugin,在成功打包的回调方法done里面,通过child_process.exec('rm -rf dist', (err) => {}),来先删除上一次打包的dist文件,删除成功后,通过fs模块修改新打包生成的build文件名称为dist
  6. 同时从打包完成后开始到删除文件修改名称结束阶段,用console.time('start')和console.timeEnd('start')记录了一下花费的时间

最终配置代码如下:

// config-overrides.js
const fs = require('fs') // node核心文件模块fs
const path = require('path') // node核心路径模块path

const child_process = require('child_process') // 通过node子进程来执行rm -rf dist删除dist目录命令(也可以采用别的删除方式,比如node循环删除文件夹)

const { 
  override, // 覆盖webpack配置方法
  addWebpackPlugin // 新增webpack插件plugin方法
} = require('customize-cra')

const { LifeCycleWebpackPlugin } = require('lifecycle-webpack-plugin') // webpack生命周期插件

const isProduction = process.env.NODE_ENV === 'production' // 获取当前构建模式是否是打包

module.exports = {
  webpack: override(
    // 只有在打包模式下才启用该插件
    isProduction && addWebpackPlugin(
      // 新增生命周期插件
      new LifeCycleWebpackPlugin({
        // 在打包结束后执行
        done() {
          // 记录删除dist和修改名称所用时间
          console.time('start')
          // 先删除dist目录
          child_process.exec(`rm -rf dist`, (error) => {
            if (error) return
            // 删除dist后,修改新打包生成的build文件为dist
            fs.renameSync(path.resolve(__dirname, 'build'), path.resolve(__dirname, 'dist'))
            console.timeEnd('start')
            console.log('打包完成')
          })
        }
      })
    )
  )
}

第七步:先执行一次build,配合nginx正常托管运行dist文件

项目目录位置,打开命令行执行npm run build命令

npm run build

copy.png

可以看到在结束后把build文件名称修改成了dist,说明配置成功了。

第八步 修改nginx配置,修改托管目录build为dist,并重启

修改nginx托管打包后生成静态资源的文件名称,比如刚才例子里面的build改为dist,最终nginx配置变成:

server {
  listen 3001; # 端口30001
  server_name localhost;
  location / {
    # root /Users/guojiongwei/Desktop/wuyou/react/dist-demo/build; # 项目打包后存放的目录
    root /Users/guojiongwei/Desktop/wuyou/react/dist-demo/dist; # 修改build为dist
    index  index.html index.htm;
  }
}

重启nginx

nginx -s reload

然后再在浏览器打开http://localhost:3001,可以看到项目可以正常运行

copy.png

接下来就需要再次打包,并在打包过程中频繁刷新浏览器,来验证还会不会出现403问题

四. 验证配置完成后的最终结果

实验步骤还是,把浏览器和编辑器放在同一个窗口,执行npm run build进行打包,同时不断刷新浏览器,看是否会出现403问题。

可以修改项目里面任意一处的代码,来代表打包生成了新版本的代码包,比如在src/App.js里面新增一个h1标签

<h1>我是修改配置后新生成的打包资源</h1>

保存代码后重新打包,在打包过程中刷新浏览器

npm run build

copy.png

在打包过程中一直在刷新浏览器,但没有出现过403的现象,而且在打包结束后,再次刷新,浏览器内容已经变成最新的了,说明该配置方法可以很好的解决打包时前端出现403问题。

五.参考文档

  1. react-app-rewired文档
郭炯韦个人博客 备案号: 豫ICP备17048833号-1
Top