create-react-app 的 public 文件夹深度解析:PUBLIC_URL 静态资源逃生通道的用法与实现机制
2026/9/5 21:25:18 网站建设 项目流程

create-react-app 的 public 文件夹深度解析:PUBLIC_URL 静态资源逃生通道的用法与实现机制

【免费下载链接】create-react-appSet up a modern web app by running one command.项目地址: https://gitcode.com/gh_mirrors/cr/create-react-app

public文件夹是 Create React App(CRA)项目中最容易“用错”的目录:它既是唯一可以直接修改的index.html的所在地,也是绕过 webpack 模块系统投递静态资源的“逃生通道(escape hatch)”。本篇以官方文档 Using the Public Folder 为主体,逐条还原其中的规则与示例,并结合 react-scripts 源码,讲清楚%PUBLIC_URL%process.env.PUBLIC_URL在构建时究竟是如何被计算和替换的,以及开发服务器与生产构建分别如何处理public目录下的文件。读完本文,你将能够正确决定哪些文件该放public、哪些文件该改用import,并理解 CRA 在任意部署路径(非根 URL、客户端路由)下资源地址依然正确的底层原因。

public 文件夹的角色:可改的 HTML 与不经过 webpack 的静态文件

官方文档(using-the-public-folder.md)指出,该功能自react-scripts@0.5.0起可用。public文件夹承担两类职责:

  1. 存放可自定义的 HTML 文件。你可以直接编辑public/index.html,例如设置页面标题和 meta 标签(参见 title-and-meta-tags.md)。文档强调:编译产物对应的<script>标签会在构建过程中自动注入到 HTML 中,你不需要也不应该手动写。
  2. 存放不走模块系统的其他静态资源。把文件放进public,它不会被 webpack 处理,而是原样复制进build文件夹。要引用这些资源,必须使用PUBLIC_URL这个环境变量。

从源码可以印证“原样复制”这一行为:生产构建脚本在 build.js 中直接调用fs.copySync(paths.appPublic, paths.appBuild, {...})public整体拷入build;而public/index.html本身则作为 webpack 的模板参与构建,见 webpack.config.js 中template: paths.appHtml的配置,其中appHtml在 paths.js 中被解析为resolveApp('public/index.html')。也就是说,public目录在 CRA 中是“双通道”的:index.html走 HtmlWebpackPlugin 模板通道(因此会被注入 script 标签、被变量插值、在 production 下被压缩),而目录里的其他文件走文件系统直接拷贝通道(因此不会被处理、不会压缩、文件名不带内容哈希)。

为什么不推荐把资源放 public:import 通道的三大好处

文档明确指出,通常情况下推荐在 JavaScript 中import资源(参见 添加样式表 与 添加图片和字体),因为模块系统提供以下好处:

  • 脚本和样式表会被压缩、打包到一起,避免额外的网络请求;
  • 文件缺失会在编译期报错,而不是让用户遭遇 404;
  • 产物文件名包含内容哈希(content hash),无需担心浏览器缓存旧版本。

而放进public的资源则恰好是这三点的反面,文档列出了使用这种逃生通道必须接受的代价:

  • public里的文件不会被后处理或压缩;
  • 文件缺失在编译期不会被发现,用户访问时会直接 404;
  • 产物文件名不包含内容哈希,每次文件变更后你需要自行加查询参数或改文件名来破除缓存。

因此public的定位是 workaround,而不是默认路径。

在 index.html 中使用 %PUBLIC_URL%

index.html中,public下的资源通过%PUBLIC_URL%前缀引用,官方示例:

<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />

有两条硬性规则需要注意:

  • 只有public文件夹内的文件才能通过%PUBLIC_URL%前缀访问。如果你想引用srcnode_modules里的文件,必须先把它复制到public——文档把这视为一种“显式声明该文件属于构建产物”的意图表达;
  • 运行npm run build时,CRA 会把%PUBLIC_URL%替换为正确的绝对路径,这样即使项目使用了客户端路由(client-side routing)或部署在非根 URL 下,资源引用依然有效。

%PUBLIC_URL%的替换发生在构建期,由 InterpolateHtmlPlugin 完成。这个插件挂在 HtmlWebpackPlugin 的afterTemplateExecution钩子上,对 HTML 做全局正则字符串替换:

data.html = data.html.replace( new RegExp('%' + escapeStringRegexp(key) + '%', 'g'), value );

它在 webpack.config.js 中以new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw)的形式注册。也就是说,凡是env.raw中的键(NODE_ENVPUBLIC_URLWDS_SOCKET_*FAST_REFRESH以及所有REACT_APP_*变量)都可以通过%键名%的形式写进index.html%PUBLIC_URL%只是其中最常用的一个。

在 JavaScript 中使用 process.env.PUBLIC_URL

在 JS 代码里,等价能力是process.env.PUBLIC_URL,官方示例:

render() { // Note: this is an escape hatch and should be used sparingly! // Normally we recommend using `import` for getting asset URLs // as described in “Adding Images and Fonts” above this section. return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />; }

文档特意标注这应当“sparingly”(节制地)使用。其注入机制在 env.js 的getClientEnvironment(publicUrl)函数中:PUBLIC_URL: publicUrl被放入环境对象,随后连同NODE_ENV和所有REACT_APP_*变量一起被JSON.stringify,最终通过 webpack 的DefinePluginnew webpack.DefinePlugin(env.stringified))在编译期做文本替换,把process.env.PUBLIC_URL换成具体的字符串字面量。因此它是构建时注入而非运行时读取——修改部署路径后必须重新构建才能生效。

PUBLIC_URL 的取值来源:从源码看计算优先级

%PUBLIC_URL%process.env.PUBLIC_URL拿到的是同一个值,其计算逻辑集中在 paths.js:

const publicUrlOrPath = getPublicUrlOrPath( process.env.NODE_ENV === 'development', require(resolveApp('package.json')).homepage, process.env.PUBLIC_URL );

核心解析函数是 getPublicUrlOrPath.js,取值优先级为:

  1. 环境变量PUBLIC_URL.env文件或 shell 中设置):若以.开头(相对路径,如.),development 下会规范为/,production 下则按原样保留以启用相对资源路径(服务于不使用 pushState 客户端路由的应用);若是带域名的完整 URL,development 下取pathname,production 下原样使用;
  2. package.jsonhomepage字段:规则类似,取 URL 的pathname部分;
  3. 默认值/:前两者都未设置时,public URL 就是根路径。

另外注意 webpack.config.js 中有一处细节:传给getClientEnvironment的值是paths.publicUrlOrPath.slice(0, -1),即刻意去掉了末尾斜杠(源码注释解释:%PUBLIC_URL%/xyz%PUBLIC_URL%xyz更好看),所以在 HTML 里书写时仍需手动补上/

开发服务器的行为与生产构建一致地“以 public 为静态根”:webpackDevServer.config.js 配置了static: { directory: paths.appPublic },源码注释也明确写道——“在index.html中,你可以用%PUBLIC_URL%获取public文件夹的 URL;在 JavaScript 代码中,你可以用process.env.PUBLIC_URL访问它”。因此开发期与构建期的行为是统一的,资源引用无需区分环境。

什么时候该用 public 文件夹

文档给出的推荐立场是:样式表、图片、字体仍应通过 JavaScriptimport引入public文件夹适用于以下较少见的情形:

  • 你需要在构建产物中得到指定文件名的文件,例如 PWA 的manifest.webmanifest
  • 你有成千上万张图片,需要动态拼接路径来引用;
  • 你想在打包代码之外引入一个类似pace.js小型独立脚本
  • 某些库与 webpack 不兼容,你只能以<script>标签方式引入。

文档同时提醒:如果你在index.html中加入了声明全局变量<script>,需要继续了解如何安全地引用这些变量,参见 Using Global Variables。

小结:一张决策速查表

场景正确做法
修改页面标题、meta 标签编辑public/index.html(参见 title-and-meta-tags.md)
引入样式表、图片、字体在 JS 中import,让 webpack 处理(参见 adding-a-stylesheet.md、adding-images-fonts-and-files.md)
需要构建产物中的固定文件名(如 manifest)放入public,用%PUBLIC_URL%/process.env.PUBLIC_URL引用
需要动态路径引用大量图片、引入 webpack 不兼容的脚本放入public
调整部署根路径设置PUBLIC_URL环境变量或homepage字段,重新构建

一句话总结:public是“HTML 可编辑 + 资源直通构建目录”的逃生通道,%PUBLIC_URL%由 InterpolateHtmlPlugin 在构建期替换、process.env.PUBLIC_URL由 DefinePlugin 注入,两者同源于getPublicUrlOrPathPUBLIC_URL环境变量、homepage字段和默认值/的优先级解析——理解这条链路,就能在任意部署路径下正确地组织 CRA 的静态资源。

【免费下载链接】create-react-appSet up a modern web app by running one command.项目地址: https://gitcode.com/gh_mirrors/cr/create-react-app

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询