静态资源"双路径"迷局:当 `plugins_url` 遇上符号链接与 CDN 回源时的真实路径漂移

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
插件开发 53 浏览 0 回复

上周给插件加前端构建流程,本地 `npm run build` 一切正常,推到预发环境后样式全崩。F12 一看,CSS 请求 404,但文件明明在服务器上。折腾半天发现是三条路径的参照系根本没对齐——记录一下这个让人血压升高的下午。

场景还原:我的目录结构

本地开发用 Docker,插件目录是通过 volume 挂载的符号链接:

/var/www/html/wp-content/plugins/my-plugin  ->  /host/path/to/my-plugin

构建后资源在 my-plugin/assets/dist/,代码里这样注册:

wp_enqueue_style(
    'my-plugin-admin',
    plugins_url( 'assets/dist/admin.css', __FILE__ ),
    [],
    '1.0.0'
);

本地输出:http://localhost:8080/wp-content/plugins/my-plugin/assets/dist/admin.css

预发环境输出:https://staging.example.com/wp-content/plugins/my-plugin/assets/dist/admin.css ✗ 404

第一层:符号链接的 "dirname 陷阱"

问题出在 __FILE__ 的解析。PHP 的 __FILE__ 在符号链接场景下会返回真实路径,而 plugins_url 内部用 plugin_basename 做相对路径计算,依赖的是 WordPress 扫描插件时记录的逻辑路径

本地因为 volume 挂载方式不同,恰好没触发;预发环境的符号链接让 plugin_basename(__FILE__) 返回了带 ../ 的奇怪路径,导致 plugins_url 拼接出来的 URL 指向了一个不存在的层级。

我的修复:不用 __FILE__ 做锚点,改用已知稳定的常量:

// 插件主文件顶部定义
define( 'MY_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'MY_PLUGIN_URL', plugin_dir_url( __FILE__ ) );  // 注意这个也有坑

// 实际注册时
wp_enqueue_style(
    'my-plugin-admin',
    MY_PLUGIN_URL . 'assets/dist/admin.css',
    [],
    '1.0.0'
);

plugin_dir_url 同样基于 __FILE__,符号链接下一样漂移。更稳的做法是从 WordPress 已注册的插件信息里反查

function my_plugin_get_asset_url( $relative_path ) {
    if ( ! function_exists( 'get_plugin_data' ) ) {
        require_once ABSPATH . 'wp-admin/includes/plugin.php';
    }
    
    // 用插件目录名做基准,而不是 __FILE__
    $plugin_dir = basename( dirname( __DIR__ ) ); // 假设当前文件在 includes/ 下
    return plugins_url( $relative_path, WP_PLUGIN_DIR . '/' . $plugin_dir . '/main-file.php' );
}

第二层:CDN 回源时的 "协议劫持"

修好符号链接,样式出来了,但混合内容警告炸了。预发走了 CDN,CDN 回源到源站是 HTTP,而页面本身是 HTTPS。plugins_url 默认会跟随当前请求的协议,但 CDN 边缘节点和源站之间的内部请求让 WordPress 误判了。

查了下 plugins_url 源码,它最终调用 set_url_scheme,而 is_ssl() 在 CDN 后面经常失效——$_SERVER['HTTPS'] 不存在,CDN 的 X-Forwarded-Proto 又没被 WordPress 识别。

我的解法:强制协议无关,或者显式处理转发头:

// 方案 A:协议无关(推荐静态资源)
wp_enqueue_style(
    'my-plugin-admin',
    set_url_scheme( plugins_url( 'assets/dist/admin.css', __FILE__ ), 'relative' ),
    [],
    '1.0.0'
);
// 输出://staging.example.com/wp-content/plugins/...  自动适配页面协议

// 方案 B:在 mu-plugin 里补全 CDN 头识别
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
    $_SERVER['HTTPS'] = 'on';
}

第三层:模板继承时的 "路径继承幻觉"

插件提供前端模板覆盖机制,允许主题在 my-plugin/single-item.php 下覆盖。但覆盖模板里引用的静态资源路径又出问题了:

<!-- 插件模板:views/single-item.php -->
<link rel="stylesheet" href="<?php echo plugins_url( 'assets/dist/front.css', __FILE__ ); ?>">

当主题覆盖了这个模板,__FILE__ 变成了 /wp-content/themes/my-theme/my-plugin/single-item.phpplugins_url 一算,资源路径直接指向主题目录去了。

这里不能用 __FILE__,必须用插件自身的固定锚点。我的做法是在模板加载时注入插件基础 URL:

// 插件加载模板前
function my_plugin_load_template( $template_name, $args = [] ) {
    $args['plugin_asset_url'] = MY_PLUGIN_URL; // 插件主文件里用非 __FILE__ 方式算好的
    
    // 优先找主题覆盖
    $template = locate_template( 'my-plugin/' . $template_name );
    if ( ! $template ) {
        $template = MY_PLUGIN_DIR . 'views/' . $template_name;
    }
    
    load_template( $template, false, $args );
}

模板里统一用 $args['plugin_asset_url'],彻底切断对 __FILE__ 的依赖。

我现在的资源路径检查清单

1. 符号链接环境:用 readlink -f 确认 __FILE__ 实际指向,对比 WordPress get_plugins() 里注册的路径

2. CDN/代理环境is_ssl() 是否可信,考虑 set_url_scheme(..., 'relative')

3. 模板覆盖场景__FILE__ 的"宿主"是谁,资源归属是否漂移

4. 构建工具链:webpack/vite 的 publicPath 是否和 WordPress 运行时路径对齐

最后一个小发现:plugin_dir_url 返回的字符串带尾部斜杠,但 plugins_url 不带——拼接时多一个斜少一个斜都能让人找半天。现在我在团队规范里强制要求:所有插件 URL 常量定义时统一带斜杠,使用时不再手动补。

你们遇到过什么更诡异的路径漂移场景?比如多站点子目录模式、或者 Bedrock 那种非标准目录结构的坑?

评论0
回复 · 0
还没有回复
微信客服 微信客服