Webpack打包脚本内联至HTML后无法执行,如何解决?
Webpack打包脚本内联无法执行的解决方法
问题简述
将Webpack输出的脚本内联到HTML中时,脚本无法执行;但通过<script src=标签引入时运行完全正常。如何让Webpack打包后的脚本支持内联执行?
详细背景
原本在落地页静态HTML中内联的悬浮CTA交互脚本(代码如下)可正常工作:
"use strict"; let floatingCta, options = { root: null, rootMargin: "0px", threshold: 1 }, hasFloatingCtaIntersected = !1; function createObserverAtfCta() { null !== floatingCta && new IntersectionObserver(handleAtfCtaIntersect, options).observe(floatingCta); } function handleAtfCtaIntersect(t, e) { t.forEach(function (t) { hasFloatingCtaIntersected && (667 < window.innerHeight ? t.target.classList.toggle("pin-bottom") : t.target.classList.toggle("pin-top"), e.unobserve(t.target)), (hasFloatingCtaIntersected = !0); }); } document.addEventListener("DOMContentLoaded", function () { (floatingCta = document.querySelector(".atf.cta")), createObserverAtfCta(); });
随后将该脚本抽出,通过Webpack处理(目的是用Babel转译兼容更多浏览器、实现代码压缩),输出的压缩转译后脚本见文末。
使用HtmlWebpackPlugin整合脚本到静态HTML时:
- 若插件自动注入
<script defer src="./public/inlineStickycta.bundle.js"></script>标签至页面底部,脚本可正常运行; - 但将脚本内联到
<head>的<script>标签中后,脚本完全不执行,且多个同类脚本均出现此现象。
疑问:
- 是否Webpack打包过程中的特殊处理导致脚本无法内联执行?若有,如何配置避免?
- 若不存在上述情况,压缩后的脚本为何无法执行?
附:完整webpack.config.js配置
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // Homemade plugin to prevent HtmlWebpackPlugin from both inlining and adding a `src` tag. const HtmlWebpackFilter = require('./src/htmlWebpackFilter'); module.exports = (env, argv) => { console.log('Webpack mode: ', argv.mode); const config = { entry: { 'prague-a': [ path.resolve(__dirname, './src/sendtolog.js'), path.resolve(__dirname, './src/webvitals.js') ], inlineWebvitals: path.resolve(__dirname, './node_modules/web-vitals/dist/polyfill.js'), inlineTwitterpixel: path.resolve(__dirname, './src/twitterpixel.js'), inlineStickycta: path.resolve(__dirname, './src/stickycta.js') }, module: { rules: [ // JavaScript { test: /\.(js)$/, exclude: /node_modules/, use: ['babel-loader'] }, // Replace placeholders { test: /\.(js)$/, exclude: /node_modules/, loader: 'string-replace-loader', options: { search: '__API_SERVER__', replace: ('production' === argv.mode) ? '' : 'https://localhost:9293', flags: 'g' } }, ] }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, './views/prague-a.ejs'), filename: path.resolve(__dirname, 'prague-a.html'), // passed as options to the EJS template inlineHeadScripts: [ 'inlineWebvitals', 'inlineTwitterpixel', 'inlineStickycta' ] }), new HtmlWebpackFilter([ 'inlineWebvitals', 'inlineTwitterpixel', 'inlineStickycta' ]) // new CleanWebpackPlugin() ], output: { path: path.resolve(__dirname, './public'), filename: '[name].bundle.js' }, // temporarily comment out, in case it's not working because of code-splitting // optimization: { // splitChunks: { // chunks: 'all' // }, // runtimeChunk: true // }, devtool: ('production' === argv.mode) ? false : 'eval', mode: argv.mode } return config; };
压缩后的Webpack输出脚本:
!function(){"use strict";var e,t,n,o,r={root:null,rootMargin:"0px",threshold:1},s=!1,l=!1,c=!1,i=0;function u(){null!==e&&new IntersectionObserver(a,r).observe(e)}function a(e,t){e.forEach((function(e){s&&(667<window.innerHeight?e.target.classList.toggle("pin-bottom"):e.target.classList.toggle("pin-top"),t.unobserve(e.target)),s=!0}))}function d(t){t.forEach((function(){l&&("none"===e.style.display?e.style.display="flex":e.style.display="none"),l=!0}))}function f(t){t.forEach((function(){0<i&&0==i%2&&(e.classList.remove("pin-bottom","pin-top"),s=!1,u()),i++}))}function p(t){t.forEach((function(){c&&e&&("none"===e.style.display?e.style.display="flex":e.style.display="none"),c=!0}))}document.addEventListener("DOMContentLoaded",(function(){e=document.querySelector(".atf.cta"),u(),null!==(t=document.querySelector(".bonus-button"))&&new IntersectionObserver(d,r).observe(t),null!==(n=document.querySelector(".check.aida-a.atf"))&&new IntersectionObserver(f,{root:null,rootMargin:"0px",threshold:0}).observe(n),null!==(o=document.querySelector(".purchase-block"))&&new IntersectionObserver(p,{root:null,rootMargin:"0px",threshold:.1}).observe(o)}))}();
问题原因及解决方案
核心原因
最可能的问题是EJS模板内联脚本时的HTML转义:你在模板中使用<%=输出脚本内容时,EJS会自动转义HTML特殊字符,导致脚本语法被破坏,无法正常执行。此外,自定义插件或内联脚本的执行时机也可能是诱因。
具体解决方案
1. 修复EJS模板的内联写法
将模板中输出脚本内容的<%=改为<%-,后者会直接输出原始内容,避免HTML转义。示例:
<head> <% htmlWebpackPlugin.options.inlineHeadScripts.forEach(chunkName => { %> <script><%- compilation.assets[chunkName + '.bundle.js'].source() %></script> <% }) %> </head>
2. 排查自定义插件影响
暂时注释掉HtmlWebpackFilter插件,重新打包测试。如果脚本恢复正常,说明该插件修改了chunk内容或干扰了内联流程,需要调整插件逻辑。
3. 对齐执行时机
给内联脚本添加defer属性,让它的执行时机和通过src引入的defer脚本一致(DOM解析完成后执行):
<script defer><%- compilation.assets[chunkName + '.bundle.js'].source() %></script>
验证步骤
- 优先修改EJS模板的输出方式为
<%-,测试脚本是否执行; - 若无效,注释
HtmlWebpackFilter插件重新打包; - 最后尝试添加
defer属性; - 打开浏览器开发者工具的Console面板,查看是否有语法错误或执行报错,快速定位问题。
内容的提问来源于stack exchange,提问作者philolegein
相关产品推荐
相关产品推荐

