Closure compiler renames properties despite externs

I’m creating a web-app and I use the closure compiler (v.20230411.0.0) to optimize the performance. In this app I create a popup and use postMessage() for the communication between the main window and the popup. For the communication I created a Message class. An instance of this class will be send and received that way. I compile the code twice with --dependency_mode PRUNE with different entry points. The Message class is used by both parts of the web-app. That is why it’s properties shouldn’t be renamed.

Here is the Message class as well as the enums MessageTopic and MessageCommand. In this example I only included files, whose property and variable names shouldn’t be renamed or removed, so there is no need for the dependency mode here.

// ###########################   message.js  ##################################

import MessageTopic from "./messagetopic.js";
import MessageCommand from "./messagecommand.js";

/**
 * A message for the communication between the main and the mod screen.
 * @template T
 */
class Message {
  /**
   * Constructor of {@link Message}.
   *
   * @param {!T} messageData - The transmitted data.
   * @param {!MessageTopic} messageTopic - The part of the app the message is about.
   * @param {!MessageCommand} messageCommand - The operation that should be executed with the data.
   */
  constructor (messageData, messageTopic, messageCommand) {
    /**
     * The data of this message.
     * @const {T}
     */
    this.data = messageData;

    /**
     * The part of the app the message is about.
     * @const {MessageTopic}
     */
    this.topic = messageTopic;

    /**
     * The operation that should be executed with the data.
     * @const {MessageCommand}
     */
    this.command = messageCommand;
  }
}

export default Message;

// ###########################   messagetopic.js  ##################################

/**
 * Enum for the topic this message is about.
 * @enum {number}
 *
 * @readonly
 */
const MessageTopic = {
  /** The topic of this message is the view system. */
  VIEW: 0,
  /** The topic of this message is the team system. */
  TEAM: 1
};

Object.freeze(MessageTopic);

export default MessageTopic;

// ###########################   messagecommand.js  ##################################

/**
 * Enum for the command that should be executed on the message target.
 * @enum {number}
 *
 * @readonly
 */
const MessageCommand = {
  /** A new object should be added. */
  ADD: 0,
  /** An object should be removed. */
  REMOVE: 1,
  /** An object should be shown. */
  SHOW: 2
};

Object.freeze(MessageCommand);

export default MessageCommand;

And here are the extern files:

// ###########################   message.extern.js  ##################################

/**
 * @fileoverview
 * @externs
 */

/**
 * A message for the communication between the main and the mod screen.
 * @template T
 */
class Message {
  /**
   * Constructor of {@link Message}.
   *
   * @param {!T} messageData - The transmitted data.
   * @param {!MessageTopic} messageTopic - The part of the app the message is about.
   * @param {!MessageCommand} messageCommand - The operation that should be executed with the data.
   */
  constructor (messageData, messageTopic, messageCommand) {
    /**
     * The data of this message.
     * @const {T}
     * @nocollapse
     */
    this.data = messageData;

    /**
     * The part of the app the message is about.
     * @const {MessageTopic}
     * @nocollapse
     */
    this.topic = messageTopic;

    /**
     * The operation that should be executed with the data.
     * @const {MessageCommand}
     * @nocollapse
     */
    this.command = messageCommand;
  }
}

// ###########################   messagetopic.extern.js  ##################################

/**
 * @fileoverview
 * @externs
 */

/**
 * Enum for the topic this message is about.
 * @enum {number}
 *
 * @readonly
 */
const MessageTopic = {
  /** The topic of this message is the view system. */
  VIEW: 0,
  /** The topic of this message is the team system. */
  TEAM: 1
};

// ###########################   messagecommand.extern.js  ##################################

/**
 * @fileoverview
 * @externs
 */

/**
 * Enum for the command that should be executed on the message target.
 * @enum {number}
 *
 * @readonly
 */
const MessageCommand = {
  /** A new object should be added. */
  ADD: 0,
  /** An object should be removed. */
  REMOVE: 1,
  /** An object should be shown. */
  SHOW: 2
};

When I call the closure compiler like this:

google-closure-compiler --warning_level VERBOSE --js message/* --compilation_level ADVANCED_OPTIMIZATIONS --js_output_file build/message.js

it generates this build/message.js:

Object.freeze({VIEW:0,TEAM:1});Object.freeze({ADD:0,REMOVE:1,SHOW:2});


It doesn’t give me any errors or warnings. But in the result the properties were still renamed or removed. In my project, where the Message class is actually used, its properties are being renamed. Only the data property stays the same.

Adding the --extern flag with the extern files, gives me the same result. I can see that the closure compiler recognizes the files as extern files, because:

  1. Using the --output_manifest flag I can see, that the extern files are not seen as source files. Yet they are in the message/ directory as well.
  2. Intentionally adding syntax errors in an extern file results in an error message from the compiler.

What might be the problem? Is there a way to get any information about the processed extern files in the compilation process? Do I write the extern files the wrong way?