在 Shopify 里,一个 .liquid Section 就是一块「彼此独立、可重复、可排序」的积木。
核心限制是:
- 每个 Section 的代码最终会被 碎片式地 拼到同一个页面上;
- 你 不能保证 两个 Section 谁在前谁在后,也 不能保证 只出现一次;
- Shopify 没有「公共 JS 打包器」——不能 在 Section 里直接写
import然后让主题统一去type="module"合并; - 浏览器仍然 重复下载 同一个 CDN 地址,除非你用
nomodule/defer做全局去重。
因此,「单模块复用」那一套在 Section 级别 是行不通的;必须把 Vue 3 当全局库来用,并且保证:
- 只加载一次;
- 每个 Section 只负责「自己挂载自己」;
- 即使两个相同 Section 被拖到同一页面,也能各自独立运行。
下面给出 Shopify 官方推荐 的「Section-内自闭环 + 全局单例脚本」方案。
一、theme.liquid(或 head 标签里)统一引入 Vue 3
{%- comment -%} 仅当页面至少有一个需要 Vue 的 Section 时才加载 {%- endcomment -%}
{%- if request.page_type contains 'index' or section_blocks_contains_vendor 'vue' -%}
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js" defer></script>
{%- endif -%}说明
vue.global.prod.js是 IIFE 版本,会把 Vue 暴露成全局Vue;defer保证执行顺序在 DOM 解析之后,且 只下载一次;- 用
section_blocks_contains_vendor是 Shopify 2.0 的过滤器,可自己换成任意条件。
二、写一个通用 Section:sections/vue-counter.liquid
<div class="vue-counter-section" data-vue-counter>
<h1>{{ section.settings.heading }}</h1>
<p>Counter 1: {{ count }}</p>
<p>Counter 2: {{ count2 }}</p>
<button @click="count++">+ Counter 1</button>
<button @click="count2++">+ Counter 2</button>
</div>
<script>
{% comment %} 把 Liquid 输出变成 JS 变量,避免全局污染 {% endcomment %}
(function(){
const mountEl = document.currentScript.previousElementSibling; // 上面的 div
const appName = 'vueCounterApp_{{ section.id }}'; // 唯一实例名
{% comment %} 等 Vue 全局对象就绪再初始化 {% endcomment %}
function init(){
if (typeof Vue === 'undefined') { setTimeout(init, 50); return; }
const { ref, createApp } = Vue;
createApp({
setup(){
const count = ref(0);
const count2 = ref(0);
return { count, count2 };
}
}).mount(mountEl);
}
init();
})();
</script>
{% schema %}
{
"name": "Vue Counter",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Counter Block"
}
],
"presets": [{ "name": "Vue Counter" }]
}
{% endschema %}三、同一个页面拖 N 个「Vue Counter」Section
- 每个 Section 输出 独立 DOM + 独立脚本;
- 由于
section.id不同,变量名、挂载点互不影响; - Vue 全局库只加载 一次;
- 即使后续用「主题编辑器」再拖一个进来,也能立刻工作(
document.currentScript永远指向当前脚本块)。
四、如果「多个不同 Section」都要用 Vue
- 统一在
theme.liquid里加一次<script src="vue.global.prod.js" defer>; - 每个 Section 照抄上面「自闭环」模板即可;
- 想再省 2 KB,可把
createApp逻辑抽成 全局函数:
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js" defer></script>
<script>
window.mountVueCounter = function(el){
const { ref, createApp } = Vue;
createApp({
setup(){ const count=ref(0), count2=ref(0); return {count,count2}; }
}).mount(el);
};
</script>然后在每个 Section 里只写:
<div class="vue-counter" data-vue-counter></div>
<script>
(function(){
const el = document.currentScript.previousElementSibling;
function run(){
if(typeof Vue==='undefined'){setTimeout(run,50);return;}
window.mountVueCounter(el);
}
run();
})();
</script>五、小结
| 目标 | Shopify Section 内的正确姿势 |
|---|---|
| 只加载一次 Vue | 在 theme.liquid 用 <script src="vue.global.prod.js" defer> |
| 不重复下载 | 浏览器缓存 + 全局变量 |
| 每个 Section 独立 | 用 section.id 做命名空间,自闭环脚本 |
| 可重复拖放 | document.currentScript 保证永远拿到当前块 |
| 不依赖打包器 | 直接用 IIFE 版本,不写 type="module" |
按上面模板,无论多少个 Section、多少个实例,都能做到「零重复下载、零全局污染、零冲突」。
评论 (0)