模板继承链断裂后,我是如何用 `get_template_part` 的优先级规则把静态资源路径"锚"回插件目录的

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

上周给一个多站点插件做主题适配,踩了个挺隐蔽的坑:插件自带的前端模板在子主题覆盖后,连带的 CSS/JS 路径全崩了。不是 404,是更恶心的——加载了主题目录下的同名空文件,样式静默失效,用户以为功能坏了。

先交代下背景。插件结构大概长这样:

my-plugin/
├── assets/
│   ├── css/
│   │   └── frontend.css
│   └── js/
│       └── frontend.js
├── templates/
│   ├── archive-item.php
│   └── single-item.php
└── my-plugin.php

我的加载逻辑最初很直白,用 `locate_template` 找主题覆盖,找不到就回退插件默认:

function myplugin_load_template( $template_name ) {
    $theme_template = locate_template( 'my-plugin/' . $template_name );
    if ( $theme_template ) {
        load_template( $theme_template );
    } else {
        load_template( MYPLUGIN_DIR . 'templates/' . $template_name );
    }
}

问题出在静态资源引用上。模板里我写了相对路径:

<link rel="stylesheet" href="../assets/css/frontend.css">

这在插件默认模板里能跑,因为 `templates/` 和 `assets/` 同级。但一旦主题在 `child-theme/my-plugin/archive-item.php` 里覆盖了模板,这个 `../assets/` 就指向了 `child-theme/assets/`,鬼知道里面有什么。

第一反应是改成绝对路径,用 `plugins_url()`。但模板文件可能被主题覆盖,覆盖者不会知道你插件的目录常量叫什么。不能把 `MYPLUGIN_URL` 硬塞进主题作者的模板里。

最后用的解法是分两层:

第一层:模板里只暴露一个"资源句柄",不暴露真实路径

我在插件全局注册一个资源映射表,键是语义化 ID,值是实际 URL:

function myplugin_register_assets() {
    wp_register_style(
        'myplugin-frontend',
        plugins_url( 'assets/css/frontend.css', __FILE__ ),
        [],
        '1.2.0'
    );
    wp_register_script(
        'myplugin-frontend',
        plugins_url( 'assets/js/frontend.js', __FILE__ ),
        [ 'jquery' ],
        '1.2.0',
        true
    );
}
add_action( 'wp_enqueue_scripts', 'myplugin_register_assets' );

第二层:模板里只负责"标记位置",由插件主逻辑决定何时注入

模板文件改成这样,彻底不碰路径:

<!-- my-plugin/templates/archive-item.php -->
<div class="myplugin-archive">
    <!-- 内容 -->
</div>

然后我在 `template_include` 或短码回调里,根据"当前渲染的是哪个模板"来 enqueue 对应的资源:

function myplugin_enqueue_for_template( $template ) {
    if ( false !== strpos( $template, 'archive-item' ) ) {
        wp_enqueue_style( 'myplugin-frontend' );
        wp_enqueue_script( 'myplugin-frontend' );
    }
    return $template;
}
add_filter( 'template_include', 'myplugin_enqueue_for_template', 20 );

但这里又踩一坑:`template_include` 里 enqueue 的话,资源会出现在 footer(如果脚本设了 `in_footer`),而模板可能依赖 DOM 就绪前的样式。更稳妥的是用 `wp_enqueue_scripts` 钩子,结合一个全局状态标记:

class MyPlugin_Template_Engine {
    private static $current_template = null;

    public static function set_template( $template ) {
        self::$current_template = basename( $template, '.php' );
        return $template;
    }

    public static function maybe_enqueue() {
        $map = [
            'archive-item' => [ 'myplugin-frontend' ],
            'single-item'  => [ 'myplugin-frontend', 'myplugin-lightbox' ],
        ];
        if ( isset( $map[ self::$current_template ] ) ) {
            foreach ( $map[ self::$current_template ] as $handle ) {
                wp_enqueue_style( $handle );
                wp_enqueue_script( $handle );
            }
        }
    }
}

add_filter( 'template_include', [ 'MyPlugin_Template_Engine', 'set_template' ], 5 );
add_action( 'wp_enqueue_scripts', [ 'MyPlugin_Template_Engine', 'maybe_enqueue' ] );

这样主题作者覆盖模板时,完全不用关心资源怎么加载。他只需要保证 HTML 结构里的 class 名别改,CSS 和 JS 会自动跟过来。

还有个边缘情况:如果主题作者想在覆盖模板里额外加一段样式,怎么办?我加了一个显式的"资源声明"钩子,让覆盖模板可以反向注册:

// 主题模板里可以这样声明额外依赖
do_action( 'myplugin_template_assets', 'myplugin-custom-theme-style' );

插件侧收集这些声明,统一在 `wp_enqueue_scripts` 里处理。比让主题作者直接写 `wp_enqueue_style` 更安全,不会漏依赖顺序。

最后检查了下 `plugins_url()` 在多站点子目录模式下的行为,确认它返回的是 `https://example.com/wp-content/plugins/my-plugin/assets/...`,不受主题目录层级影响。这个"锚定"才算真正完成。

你们插件模板和资源分离是怎么做的?有没有遇到过主题覆盖后资源"漂移"的情况?

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