Logo

site iconMT | 面条

关注于前端开发的web工程师,Self-hosting Homelab 爱好者,email.ml dns.surf开发者。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

MT | 面条 RSS 预览

Vite SSR 项目 Docker 镜像最小化打包方案

2024-08-31 13:14:00

最近准备把部署在 Cloudflare, Vercel, Netlify 上的项目迁移到自己的 VPS 通过 Docker 运行,就复习了一下 Docker 镜像打包。 但是一个很小的项目打包出来就是 1.05GB, 这显然是不能接受的。所以研究了一下 Node.JS 项目 Docker 镜像最小化打包方案, 将镜像大小从 1.06GB 缩小到了 135 MB。

示例项目是一个 Astro 项目, 使用 Vite 作为构建工具, SSR 模式运行。

第 0 版

主要思路是使用最小化系统镜像,选用 Alpine Linux 镜像。

按照 Astro 官方文档服务端渲染模式(SSR), 将基础镜像替换为 node:lts-alpine, NPM 替换为 PNPM, 打包出来的体积是 1.06 GB。 也就是最差的状态。

FROM node:lts-alpine AS base

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

WORKDIR /app

COPY . .
RUN pnpm install --frozen-lockfile
RUN export $(cat .env.example) && pnpm run build

ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD node ./dist/server/entry.mjs
docker build -t v0 .
[+] Building 113.8s (11/11) FINISHED                                                                                                                                        docker:orbstack
 => [internal] load build definition from Dockerfile                                                                                                                                   0.0s
 => => transferring dockerfile: 346B                                                                                                                                                   0.0s
 => [internal] load metadata for docker.io/library/node:lts-alpine                                                                                                                     1.1s
 => [internal] load .dockerignore                                                                                                                                                      0.0s
 => => transferring context: 89B                                                                                                                                                       0.0s
 => [1/6] FROM docker.io/library/node:lts-alpine@sha256:1a526b97cace6b4006256570efa1a29cd1fe4b96a5301f8d48e87c5139438a45                                                               0.0s
 => [internal] load build context                                                                                                                                                      0.2s
 => => transferring context: 240.11kB                                                                                                                                                  0.2s
 => CACHED [2/6] RUN corepack enable                                                                                                                                                   0.0s
 => CACHED [3/6] WORKDIR /app                                                                                                                                                          0.0s
 => [4/6] COPY . .                                                                                                                                                                     2.0s
 => [5/6] RUN pnpm install --frozen-lockfile                                                                                                                                          85.7s
 => [6/6] RUN export $(cat .env.example) && pnpm run build                                                                                                      11.1s
 => exporting to image                                                                                                                                                                13.4s
 => => exporting layers                                                                                                                                                               13.4s
 => => writing image sha256:653236defcbb8d99d83dc550f1deb55e48b49d7925a295049806ebac8c104d4a                                                                                           0.0s
 => => naming to docker.io/library/v0

第 1 版

主要思路是先安装生产环境依赖,产生第一层。 再安装全量依赖,打包生成 JavaScript 产物,产生第二层。 最后将生产环境依赖和 JavaScript 产物复制到运行环境。

按照 多层构建(使用 SSR) 的方案, 将镜像大小缩小到了 306MB,缩小不小,但是这个方案有个缺点,需要明确的制定生产依赖,如果少指定了生产依赖,运行时会报错

FROM node:lts-alpine AS base

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

WORKDIR /app
COPY package.json pnpm-lock.yaml ./

FROM base AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile

FROM base AS build-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile

FROM build-deps AS build
COPY . .
RUN export $(cat .env.example) && pnpm run build

FROM base AS runtime
COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist

ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD node ./dist/server/entry.mjs
docker build -t v1 .
[+] Building 85.5s (15/15) FINISHED                                                                                                                                         docker:orbstack
 => [internal] load build definition from Dockerfile                                                                                                                                   0.1s
 => => transferring dockerfile: 680B                                                                                                                                                   0.0s
 => [internal] load metadata for docker.io/library/node:lts-alpine                                                                                                                     1.8s
 => [internal] load .dockerignore                                                                                                                                                      0.0s
 => => transferring context: 89B                                                                                                                                                       0.0s
 => [base 1/4] FROM docker.io/library/node:lts-alpine@sha256:1a526b97cace6b4006256570efa1a29cd1fe4b96a5301f8d48e87c5139438a45                                                          0.0s
 => [internal] load build context                                                                                                                                                      0.3s
 => => transferring context: 240.44kB                                                                                                                                                  0.2s
 => CACHED [base 2/4] RUN corepack enable                                                                                                                                              0.0s
 => CACHED [base 3/4] WORKDIR /app                                                                                                                                                     0.0s
 => [base 4/4] COPY package.json pnpm-lock.yaml ./                                                                                                                                     0.2s
 => [prod-deps 1/1] RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile                                                                           35.1s
 => [build-deps 1/1] RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile                                                                                 65.5s
 => [runtime 1/2] COPY --from=prod-deps /app/node_modules ./node_modules                                                                                                               5.9s
 => [build 1/2] COPY . .                                                                                                                                                               0.8s
 => [build 2/2] RUN export $(cat .env.example) && pnpm run build                                                                                                                       7.5s
 => [runtime 2/2] COPY --from=build /app/dist ./dist                                                                                                                                   0.1s
 => exporting to image                                                                                                                                                                 4.2s
 => => exporting layers                                                                                                                                                                4.1s
 => => writing image sha256:8ae6b2bddf0a7ac5f8ad45e6abb7d36a633e384cf476e45fb9132bdf70ed0c5f                                                                                           0.0s
 => => naming to docker.io/library/v1

第 2 版

主要思路是将 node_modules 内联进 JavaScript 文件,最终只复制 JavaScript 文件到运行环境。

之前看 Next.JS 的时候,记得可以将 node_modules 内联进 JavaScript 文件,这样就不需要 node_modules 了。 所以就研究了一下,发现 Vite SSR 也是支持的,所以判断 Docker 环境就使用内联的方式,不需要复制 node_modules ,只复制最终的 dist 产物,将镜像大小缩小到 135MB 了。

打包脚本改动:

vite: {
  ssr: {
    noExternal: process.env.DOCKER ? !!process.env.DOCKER : undefined;
  }
}

最终的 Dockerfile 如下

FROM node:lts-alpine AS base

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

WORKDIR /app
COPY package.json pnpm-lock.yaml ./

# FROM base AS prod-deps
# RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile

FROM base AS build-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile

FROM build-deps AS build
COPY . .
RUN export $(cat .env.example) && export DOCKER=true && pnpm run build

FROM base AS runtime
# COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist

ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD node ./dist/server/entry.mjs
 docker build -t v2 .
[+] Building 24.9s (13/13) FINISHED                                                                                                                                         docker:orbstack
 => [internal] load build definition from Dockerfile                                                                                                                                   0.0s
 => => transferring dockerfile: 708B                                                                                                                                                   0.0s
 => [internal] load metadata for docker.io/library/node:lts-alpine                                                                                                                     1.7s
 => [internal] load .dockerignore                                                                                                                                                      0.0s
 => => transferring context: 89B                                                                                                                                                       0.0s
 => [base 1/4] FROM docker.io/library/node:lts-alpine@sha256:1a526b97cace6b4006256570efa1a29cd1fe4b96a5301f8d48e87c5139438a45                                                          0.0s
 => [internal] load build context                                                                                                                                                      0.3s
 => => transferring context: 240.47kB                                                                                                                                                  0.2s
 => CACHED [base 2/4] RUN corepack enable                                                                                                                                              0.0s
 => CACHED [base 3/4] WORKDIR /app                                                                                                                                                     0.0s
 => CACHED [base 4/4] COPY package.json pnpm-lock.yaml ./                                                                                                                              0.0s
 => CACHED [build-deps 1/1] RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile                                                                           0.0s
 => [build 1/2] COPY . .                                                                                                                                                               1.5s
 => [build 2/2] RUN export $(cat .env.example) && export DOCKER=true && pnpm run build                                                                                                15.0s
 => [runtime 1/1] COPY --from=build /app/dist ./dist                                                                                                                                   0.1s
 => exporting to image                                                                                                                                                                 0.1s
 => => exporting layers                                                                                                                                                                0.1s
 => => writing image sha256:0ed5c10162d1faf4208f5ea999fbcd133374acc0e682404c8b05220b38fd1eaf                                                                                           0.0s
 => => naming to docker.io/library/v2

最终对比,体积从 1.06GB 降低到 135MB, 构建时间从 113.8s 降低到 24.9s

docker images
REPOSITORY                         TAG         IMAGE ID       CREATED          SIZE
v2                                 latest      0ed5c10162d1   5 minutes ago    135MB
v1                                 latest      8ae6b2bddf0a   6 minutes ago    306MB
v0                                 latest      653236defcbb   11 minutes ago   1.06GB

示例项目是开源的,可以在 GitHub 查看。

BroadcastChannel

stat

BroadcastChannel - 将你的 Telegram Channel 转为微博客

2024-08-11 17:18:12

之前一直在 X 分享一些有趣的工具,而且也会同步到 Telegram Channel 上。看到 Austin 说准备建立一个网站,把分享内容都收录进去。 刚好想到最近看到的一个模板 Sepia,就想到把 Telegram Channel 转成微博客。

难度不大,主功能一个周末就搞完了。过程中做到了浏览器端 0 JS, 分享一些有趣的技术点:

  1. 防剧透模式和移动端搜索框隐藏展示,使用的 CSS ":checked 伪类" 和 "+ 紧邻兄弟组合器" 实现的,参考

  2. 过渡动画使用的 CSS View Transitions, 参考

  3. 图片灯箱用的 HTML 的 popover 属性,参考

  4. 返回顶部的展示和隐藏使用 CSS animation-timeline 实现 Chrome 115 以上版本专属,参考

  5. 多图瀑布流布局,使用 grid 布局实现,参考

  6. 访问统计使用一个 1px 的透明图片做 LOGO 背景,上古技术了,现在几乎没有访问统计软件支持了

  7. 禁止浏览器端 JS 运行,使用的 Content-Security-Policy 的 script-src 'none' 参考

搞完以后,就直接开源了,很意外有那么多人喜欢,一周就收获了 800+ 的 star 了。

如果你也有兴趣,可以去 GitHub 上看看。

https://github.com/ccbikai/BroadcastChannel

GitHub 上的 BroadcastChannel 仓库

stat

阿里云 AP8220 刷机教程

2024-07-29 21:39:55

最近在准备给家用网络升级 2.5G, 在咸鱼收了一台阿里云 AP8220 来折腾。 但是这个机器的刷机资料太少了。折腾了2天才成功刷机。所以写一篇文档记录一下。

提醒

非必要,不刷机。

刷机有风险,请做好刷砖准备。

任何刷机问题与我无关,我只分享我的刷机过程。

准备工作

  1. 阿里云 AP8220 机器一台,咸鱼基本 200 上下。
  2. DC 12V2A 电源一个,原厂不带电源。
  3. USB 转 Console 线。
  4. tftp32.exe。
  5. Putty。
  6. OpenWrt 固件(目前只有 LEAN 的收费固件,我无法提供,希望后续有更多的人适配)。

进入 Uboot

使用 USB 转 Console 线连接到设备 Console 口, 网线连接到设备 LAN 口。

Putty 设置链接 COM3(取决于你电脑设备USB口,自己去设备管理看下), 波特率设置 115200

设备通电,立即按键盘的 shift+@ 中断启动,即可进入 Uboot, 不行就多试几次。

电脑设置静态IP: 192.168.10.1

刷入大分区

下载文件 mibib.bin 文件和 tftp32.exe 放入同目录,打开 tftp32 后,切换到 192.168.10.1

在 Putty 开始敲命令:

tftpboot mibib.bin
nand device 1
nand erase 0x50000 0x10000
nand write $fileaddr 0x50000 $filesize

刷完以后,断电。

刷入固件

设备重新通电,进入 Uboot。

将固件以 factory.bin 结尾的文件命名为 ap8220.bin 放入 tftp32.exe 同目录。

在 Putty 开始敲命令刷写固件:

tftpboot ap8220.bin
flash rootfs

set boot3 "set mtdparts mtdparts=nand0:0x8000000@0x0(fs)"
set boot4 "ubi part fs && ubi read 42000000 kernel"
set setup1 "partname=1 && setenv bootargs ubi.mtd=rootfs ${args_common}"
set setup2 "partname=2 && setenv bootargs ubi.mtd=rootfs ${args_common}"

saveenv

刷完以后,断电。

重新通电等待后可正常启动。

电脑改回 DHCP 获取 IP 地址, 访问 http://192.168.1.1 进入 Web 界面。

在 Web 界面升级刷入 sysupgrade.bin 文件。

刷机完成。

交流

这个机器的刷机资料很少,我想建一个微信群交流。有兴趣的加我微信 ccbikai

AP8220

stat

使用 Cloudflare Zero Trust 制作 Google Safe Browsing 平替

2024-07-07 22:48:53

之前做 L(O*62).ONG 的第一版时,使用的服务端跳转,上线第二天就被 Google 警告了安全风险,只能改成本地跳转提醒后跳转再去申诉。

Google警告

对于这种场景最好的做法是使用 Google Safe Browsing 来做跳转,但是 Safe Browsing 有使用限制,每天只能调用 10000 次,而且不支持自定义名单。由于我只想依赖 Cloudflare 一个平台就没有使用 Google Safe Browsing。

前段时间和一个网友交流的时候,突然脑洞大开,想到使用带成人和非法网站过滤的安全 DNS 服务器来做域名安全性的检查。 于是使用 家庭版 1.1.1.1 做了一下尝试发现是可行的。 但是 1.1.1.1 不支持自定义名单,想到之前在 HomeLab 用过 Cloudflare Zero Trust Gateway 就去研究了一下,发现了这个几乎完美的方案。

Cloudflare Zero Trust 提供的 Gateway 自带 DNS(DoH) 服务器,而且可以配置防火墙规则,支持按域名风险等级、内容分类、自定义名单等多种规则屏蔽解析。而且它收集的数据源来自 Cloudflare 专有数据、30 多个开放情报源、机器学习模型、社区反馈等,可以说是相当齐全了。 更多的细节可以在官方文档查看。

我屏蔽了高风险分类、成人、赌博、政府、少儿不宜、新注册等高风险的域名,并且手动维护了一些黑名单和白名单。

风险名单

配置完成以后就可以得到一个 DoH 地址:

DoH

我们使用下面的代码接入到项目中:

async function isSafeUrl(
  url,
  DoH = "https://family.cloudflare-dns.com/dns-query"
) {
  let safe = false;
  try {
    const { hostname } = new URL(url);
    const res = await fetch(`${DoH}?type=A&name=${hostname}`, {
      headers: {
        accept: "application/dns-json",
      },
      cf: {
        cacheEverything: true,
        cacheTtlByStatus: { "200-299": 86400 },
      },
    });
    const dnsResult = await res.json();
    if (dnsResult && Array.isArray(dnsResult.Answer)) {
      const isBlock = dnsResult.Answer.some(
        answer => answer.data === "0.0.0.0"
      );
      safe = !isBlock;
    }
  } catch (e) {
    console.warn("isSafeUrl fail: ", url, e);
  }
  return safe;
}

Cloudflare Zero Trust 管理面板也提供了一个可视化的界面,方便查屏蔽情况。可以看到一些成人网站和新注册域名都被屏蔽了。

可视化界面

如果域名被封了,还可以在日志查看原因。

日志

stat

浏览器本地使用 AI 移除图片背景

2024-07-07 21:28:51

最近在学习 AI 相关的前端知识,看到 Transformers.js 的一个实例很感兴趣就把它做成了一个工具。

通过在 WebWorker 中使用 Transformers.js 调用 WebGPU 运行 RMBG-1.4 模型,可以在浏览器本地使用 AI 移除图片背景。在 M1 PRO 上处理一张 4K 图片只需要 500ms。

工具地址:https://html.zone/background-remover

AI 移除图片背景


相关的源码在 https://github.com/xenova/transformers.js/tree/main/examples/remove-background-client ,如果想自己实现,可以参考这个仓库。提醒一下向调用 WebGPU,需要切换到 Transformers.js V3 版本。

stat