terser-webpack-plugin 选项的范围问题

Scoping issue with terser-webpack-plugin options

我有一种情况,我没有正确配置 Terser,这导致我的页面的压缩版本中断。我的问题是我的 index.js 中有一些功能声明。这些声明需要可以从 Bootstrap 模式 windows 访问,可以在以后加载。

例如,在我的 index.js 中有一个这样声明的函数:

function doThis() { }

然后,比方说,用户在模式 window 中打开一个地址表单,并加载另一个名为 'address-form.js' 的 javascript 文件。在此表单中,有一个带有调用 doThis() 的 onclick 处理程序的按钮。 onclick 处理程序位于 'address-form.js' 中,但能够访问父 index.js 中的 doThis()。当 index.js 未压缩时,该按钮工作正常。但是压缩后,我收到一条错误消息,提示 doThis() 不存在。

我认为这是一个范围问题,因为我确实在 index.js 中看到 doThis() 声明,但它似乎包含在一堆括号中。我不确定如何使 doThis() 的范围成为顶级 window。 index.js 中的数百个函数声明和变量都存在相同的范围问题,因此我正在寻找一种解决方案,我不必对这个庞大的文件进行太多修改。

请注意,如果我将函数声明更改为表达式,它似乎确实有效:

window.doThis = function() {
}

但是,由于文件中有数百个varsconstlet变量(此外还有数十个函数声明),所以对我来说不太实用更改所有这些的范围,以便压缩工作。

这是我的 webpack.config.js:

const TerserPlugin = require("terser-webpack-plugin")
const glob = require('glob')
const path = require('path')
const webpack = require('webpack')

module.exports = {
  entry: glob.sync('./js/Pages/*.js').reduce((object, element) => {
    object[path.parse(element).name] = element
    return object
  }, {}),
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, './js/Pages/minified')
  },
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true,
        test: /\.js(\?.*)?$/i,
        terserOptions: {
          mangle: false,
          compress: true,
          keep_fnames: true,
          keep_classnames: true,
          ie8: false,
          safari10: false,
          toplevel: false
        }
      })
    ]
  },
  plugins: [
    new webpack.optimize.LimitChunkCountPlugin({
      maxChunks: 1
    })
  ]
}

我运行的命令是:

webpack --mode=production --config webpack.config.js

解决方案

下面接受的解决方案是直接使用 terser-cli。我写了一个小脚本,可以在目录中的每个文件上运行 terser,如果有人觉得它有用的话:

const fs = require('fs')
const path = require('path')
const exec = require('child_process').exec;
const Terser = require('terser')
const srcDir = '../js/Pages'
const destDir = '../js/Pages/minified'


function minifyPagesDir() {
  let srcFileNames = fs.readdirSync(srcDir).filter(path => path.match(/\.js$/)) || []
  let srcFilePaths = []
  
  let destFilePaths = srcFileNames.map((item, i) => {
    srcFilePaths[i] = `${srcDir}/${srcFileNames[i]}`
    return `${destDir}/${item}`
  })

  if (!fs.existsSync(destDir))
    fs.mkdirSync(destDir)
  
  srcFileNames.forEach((item, i) => {
    exec(
      `terser ${srcFilePaths[i]} -c -o ${destFilePaths[i]} --ecma 2021`,
      (error, stdout, stderr) => {
        if (error !== null)
            console.log(`RUHROH: ${error}`, ' stderr: ', stderr);
      }
    )

    console.log(`Minified '${srcFilePaths[i]}' to '${destFilePaths[i]}'!`)
  })
  console.log('Minification complete!')
}

minifyPagesDir()

我用你的配置创建了一个 repro 项目,并验证了你的问题不是由 terser 引起的,而是 webpack 引起的。

我使用了以下测试文件:

function doThis() { }

With minimize disabled and no minimizer set, this is the result of building the test file with webpack:

/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
function doThis() { }
/******/ })()
;

如果我们重新格式化它并删除注释,很明显文件中的所有内容都是死代码。 Webpack 是一个捆绑器,将所有内容包装在 IIFE 中。这意味着函数不会分配给全局范围。

(() => {
  var __webpack_exports__ = {};
  function doThis() { }
})();

由于您没有导出任何内容或导致任何副作用,例如分配给 window,terser 将删除内容。这是正确的,即使不删除它,你仍然会得到同样的错误,因为 webpack 输出中的函数没有分配给全局范围,你的处理程序无法访问它。使用 terser cli 压缩 webpack 的导出结果是一个空文件。

运行 直接在测试输入文件上的 terser cli,产生所需的压缩输出:

function doThis(){}

你说你从命令行 运行 更简洁并且遇到了与通过 webpack 捆绑和压缩时相同的问题。你 运行 源文件是通过 terser 还是 webpack 的输出?因为 运行 通过 terser 编译源文件应该会产生所需的输出。请尝试 运行ning terser foo/bar.js -c -o foo/bar.min.js 并测试它是否能解决您的问题。

如果你想使用 webpack,你需要分配给 window 或者完全不使用全局范围。