You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

验证步骤

  1. 优先修改EJS模板的输出方式为<%-,测试脚本是否执行;
  2. 若无效,注释HtmlWebpackFilter插件重新打包;
  3. 最后尝试添加defer属性;
  4. 打开浏览器开发者工具的Console面板,查看是否有语法错误或执行报错,快速定位问题。

内容的提问来源于stack exchange,提问作者philolegein

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 21:24:21