創造を続け、成長を加速させましょう!「ナゲッツデイリーニュープラン・6月アップデートチャレンジ」に参加した初日です。クリックしてイベントの詳細をご覧ください。
みなさん、こんにちは。山越です。新しく開いたコラム「フロントエンド展開シリーズ」です。Docker、CICDなどを含めた概略図は次のとおりです。
さらに、Bilibiliは関連するビデオを更新しています。フロントエンド展開シリーズの
フロントエンド展開シリーズが更新されています:5/15
前回の記事では、Dockerでのビルドキャッシングとマルチステージビルドによるキャッシングの最適化について説明しました。
ただし、シングルページアプリケーションをデプロイする場合でも、問題が1つあります。それは、クライアント側のルーティングです。
この記事では、単純な1ページのルーターをDockerによってreact-router-dom
実装し、Dockerを介してデプロイします。
PS:このプロジェクトでは、実際にcra -deployウェアハウスを使用しており、構成ファイルはrouter.Dockerfileにあります。
ルーティング
シングルページアプリケーションのルートreact-dom
をます。ルーティングはこの列のコアコンテンツではないため、ルーティングの使用は省略されます。最終的なコードは次のとおりです。
ソースコードはcra-deploy/src/App.jsにあります
import logo from './logo.svg';
import './App.css';
import { Routes, Route, Link } from 'react-router-dom';
function Home() {
return (
<div>
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1>当前在 Home 页面</h1>
<Link to="/about" className="App-link">About</Link>
</header>
</div>
)
}
function About() {
return (
<div>
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1>当前在 About 页面</h1>
<Link to="/" className="App-link">Home</Link>
</header>
</div>
)
}
function App() {
return (
<div>
<Routes>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
</Routes>
</div>
);
}
export default App;
复制代码
この時点で、2つのルートがあります。
/
、表紙/about
、ページについて
再デプロイします。ルーティングに問題があります
前の記事のdocker-compose
構成ページを再デプロイします。
$ docker-compose up --build simple
复制代码
この時点https://localhost:4000/about
で、404が表示されます。
其实道理很简单:在静态资源中并没有 about
或者 about.html
该资源,因此返回 404 Not Found。而在单页应用中,/about
是由前端通过 history API
进行控制。
解决方法也很简单:在服务端将所有页面路由均指向 index.html
,而单页应用再通过 history API
控制当前路由显示哪个页面。 这也是静态资源服务器的重写(Rewrite
)功能。
我们在使用 nginx 镜像部署前端应用时,可通过挂载 nginx 配置解决该问题。
nginx 的 try_files 指令
在 nginx 中,可通过 try_files 指令将所有页面导向 index.html
。
location / {
# 如果资源不存在,则回退到 index.html
try_files $uri $uri/ /index.html;
}
复制代码
此时,可解决服务器端路由问题。
除此之外,我们还可以通过 nginx 配置解决更多问题。
长期缓存 (Long Term Cache)
在 CRA 应用中,./build/static
目录均由 webpack 构建产生,资源路径将会带有 hash 值。
$ tree ./build/static
./build/static
├── css
│ ├── main.073c9b0a.css
│ └── main.073c9b0a.css.map
├── js
│ ├── 787.cf6a8955.chunk.js
│ ├── 787.cf6a8955.chunk.js.map
│ ├── main.a3facdf8.js
│ ├── main.a3facdf8.js.LICENSE.txt
│ └── main.a3facdf8.js.map
└── media
└── logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg
3 directories, 8 files
复制代码
此时可通过 expires
对它们配置一年的长期缓存,它实际上是配置了 Cache-Control: max-age=31536000
的响应头。
那为什么带有 hash 的资源可设置长期缓存呢: **资源的内容发生变更,他将会生成全新的 hash 值,即全新的资源路径。**而旧有资源将不会进行访问。
location /static {
expires 1y;
}
复制代码
nginx 配置文件
总结缓存策略如下:
- 带有 hash 的资源一年长期缓存
- 非带 hash 的资源,需要配置 Cache-Control: no-cache,避免浏览器默认为强缓存
nginx.conf
文件需要维护在项目当中,经过路由问题的解决与缓存配置外,最终配置如下:
该 nginx 配置位于 cra-deploy/nginx.conf
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html index.htm;
location / {
# 解决单页应用服务端路由的问题
try_files $uri $uri/ /index.html;
# 非带 hash 的资源,需要配置 Cache-Control: no-cache,避免浏览器默认为强缓存
expires -1;
}
location /static {
# 带 hash 的资源,需要配置长期缓存
expires 1y;
}
}
复制代码
Dockerfile 配置文件
此时,在 Docker 部署过程中,需要将 nginx.conf
置于镜像中。
修改 router.Dockerfile
配置文件如下:
PS: 该 Dockerfile 配置位于 cra-deploy/router.Dockerfile
FROM node:14-alpine as builder
WORKDIR /code
# 单独分离 package.json,是为了 yarn 可最大限度利用缓存
ADD package.json yarn.lock /code/
RUN yarn
# 单独分离 public/src,是为了避免 ADD . /code 时,因为 Readme/nginx.conf 的更改避免缓存生效
# 也是为了 npm run build 可最大限度利用缓存
ADD public /code/public
ADD src /code/src
RUN npm run build
# 选择更小体积的基础镜像
FROM nginx:alpine
ADD nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder code/build /usr/share/nginx/html
复制代码
PS: 该 docker compose 配置位于 cra-deploy/router.Dockerfile
version: "3"
services:
route:
build:
context: .
dockerfile: router.Dockerfile
ports:
- 3000:80
复制代码
使用 docker-compose up --build route
启动容器。
- 访问
http://localhost:3000
页面成功。 - 访问
http://localhost:3000/about
页面成功。
检验长期缓存配置
访问 https://localhost:3000
页面,打开浏览器控制台网络面板。
此时对于带有 hash 资源, Cache-Control: max-age=31536000
响应头已配置。
此时对于非带 hash 资源, Cache-Control: no-cache
响应头已配置。
百尺竿头更进一步
在前端部署流程中,一些小小的配置能大幅度提升性能,列举一二,感兴趣的同学可进一步探索。
构建资源的优化:
- 使用 terser 压缩 Javascript 资源
- 使用 cssnano 压缩 CSS 资源
- 使用 sharp/CDN 压缩 Image 资源或转化为 Webp
- 使用 webpack 将小图片转化为 DataURI
- 使用 webpack 进行更精细的分包,避免一行代码的改动使大量文件的缓存失效
小结
其实,从这里开始,前端部署与传统前端部署已逐渐显现了天壤之别。
従来のフロントエンドの展開は、運用と保守が主流であり、オンラインになるたびに、運用と保守によって完了するプロジェクトのフロントエンドのオンラインステップの運用と保守に通知されます。フロントエンドは展開の自由度が低くなります。
たとえば、gzip/brotli
圧縮Cache-Control
応答ヘッダーの制御、およびさまざまなルートのキャッシュ戦略は、運用と保守が完了したことを通知する必要があり、バージョン管理を行うことは困難です。
フロントエンドの展開の自由度の拡張は、次の2つの側面に反映されています。
- Dockerを介してフロントエンドをコンテナー化し、運用と保守のオンライン手順を電子メールで通知する必要がなくなりました
- Dockerおよびnginx構成ファイルを使用してフロントエンドでnginxを構成します。プロジェクトに強く関連するいくつかの小さくて些細な構成では、操作やメンテナンスの介入は必要ありません。
この時点で、Dockerにフロントエンドをデプロイする方法の章は終わり、実際には、静的リソースはCDNに配置されることがよくあります。
それでは、どのように対処するのですか?