使用Turbolinks时无法运行页面专属JavaScript包的问题咨询
嘿,结合你用Rails 5.1 + Webpacker + Vue.js + Turbolinks的场景,我来帮你拆解这两个问题:
一、单包内基于页面条件挂载多个Vue应用是否最优?
简单说:短期快速实现可以用,但长期来看不是最优解。
- 它的优点:实现简单,不用处理复杂的脚本加载时序,所有代码打包在一起,部署也少点麻烦。
- 它的缺点:正如你担心的,会让打包后的文件体积越来越大——哪怕用户只访问一个页面,也要加载所有页面的Vue组件和逻辑,既浪费带宽,又拖慢首屏加载速度。
如果你的项目页面不多、专属逻辑简单,包体积增长在可接受范围内,这个方案完全可行;但如果页面多、组件复杂,Webpack的代码分割(动态import)+ 按页面加载专属代码才是更优的长期方案,既能按需加载,又能避免冗余代码。
二、能否让Turbolinks加载脚本并在turbolinks:load前执行?
当然可以,这里有几种靠谱的方案:
方案1:利用Turbolinks更早的事件触发挂载
turbolinks:load是在页面完全渲染后触发,但Turbolinks还有更早的事件,比如turbolinks:before-render——这个事件会在新页面内容渲染到DOM前触发。你可以把挂载逻辑绑定到这个事件上:
// 比如在页面专属的pack文件中 document.addEventListener('turbolinks:before-render', () => { const appEl = document.getElementById('your-page-app'); if (appEl) { new Vue({ el: appEl, // 你的组件配置 }); } });
这样脚本加载后,会在页面渲染时就执行挂载,不会错过时机。
方案2:动态import + turbolinks:load(推荐)
这种方式既解决了代码分割的问题,又完美适配Turbolinks的时序:
在主pack文件中,监听turbolinks:load事件,根据当前页面的元素判断需要加载哪个Vue组件,然后动态import对应的代码:
document.addEventListener('turbolinks:load', async () => { // 商品详情页的Vue应用 const productAppEl = document.getElementById('product-show-app'); if (productAppEl) { const { default: ProductShowApp } = await import('../components/ProductShowApp.vue'); new Vue({ el: productAppEl, render: h => h(ProductShowApp) }); } // 购物车页面的Vue应用 const cartAppEl = document.getElementById('cart-app'); if (cartAppEl) { const { default: CartApp } = await import('../components/CartApp.vue'); new Vue({ el: cartAppEl, render: h => h(CartApp) }); } });
这个方案的好处是:
- 只有当页面需要对应组件时,才会加载对应的代码,初始包体积大大减小;
turbolinks:load事件触发时,页面DOM已经渲染完成,能确保找到对应的元素;- 不管是首次加载还是Turbolinks跳转,都能正确触发挂载。
方案3:手动控制脚本执行时机
在Rails布局中,针对特定页面加载专属pack时,加上data-turbolinks-eval="false",禁止Turbolinks自动执行脚本,然后手动在合适的时机执行:
<!-- 在application.html.erb中 --> <% if controller_name == 'products' && action_name == 'show' %> <%= javascript_pack_tag 'product_show', 'data-turbolinks-eval': 'false' %> <% end %>
然后在product_show.js中,同时处理首次加载和Turbolinks跳转的情况:
function mountProductApp() { const appEl = document.getElementById('product-show-app'); if (appEl) { new Vue({ el: appEl, // 配置 }); } } // 首次加载时执行 mountProductApp(); // Turbolinks跳转后执行 document.addEventListener('turbolinks:load', mountProductApp);
这种方式适合需要完全控制脚本执行时机的场景。
总结
- 如果项目小、页面少,单包条件挂载可以快速搞定;但项目规模变大后,一定要转向代码分割的方案。
- 解决Turbolinks时序问题的核心是:要么绑定更早的Turbolinks事件,要么用动态import配合
turbolinks:load,这两种方式都能避免脚本执行晚于事件的问题。
内容的提问来源于stack exchange,提问作者Ernesto
相关产品推荐
相关产品推荐

