简体   繁体   中英

How to define the Google Closure Compiler externs for this class

I have the following JavaScript class definition that works as it should and that I compile using the Google Closure Compiler:

class State {
  constructor(x, y, z, rotationX, rotationY) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.rotationX = rotationX;
    this.rotationY = rotationY;
  }

  set matrix(value) {
    // Magic
  }

  get matrix() {
    // More magic
  }

  set red(value) {
    this.setAttribute(attributeRed, value)
  }

  get red() {
    return this.getAttribute(attributeRed);;
  }

  static fromUrlSearchParams(searchParams) {
    return new State(parseInt  (searchParams.get("x"), 10),
                     parseInt  (searchParams.get("y"), 10),
                     parseInt  (searchParams.get("z"), 10),
                     parseFloat(searchParams.get("rotationX")),
                     parseFloat(searchParams.get("rotationY")));
  }

  toUrlSearchParams() {
    let searchParams = new URLSearchParams();
    searchParams.set("x", this.red);
    searchParams.set("y", this.green);
    searchParams.set("z", this.blue);
    searchParams.set("rotationX", this.pitch);
    searchParams.set("rotationY", this.yaw);
    return searchParams;
  }
}

This type is part of the public interface of my code which means that I must prevent the Closure Compiler from renaming its symbols. I'm in the process of writing an externs file that I pass to the Closure Compiler using the --externs switch. Here's what I have so far:

State = class {
  /**
   * @constructor
   * @param {number} x
   * @param {number} y
   * @param {number} z
   * @param {number} rotationX
   * @param {number} rotationY
   */
  constructor(x, y, z, rotationX, rotationY) {
    /** @type {number} */
    this.x = x;
    /** @type {number} */
    this.y = y;
    /** @type {number} */
    this.z = z;
    /** @type {number} */
    this.rotationX = rotationX;
    /** @type {number} */
    this.rotationY = rotationY;
  }

  // Insert property export here. If you just knew how...

  /** @return {State} */
  static fromUrlSearchParams(searchParams) {}

  /** @return {URLSearchParams} */
  toUrlSearchParams() {}
};

I have three problems finishing that externs file:

  1. The arguments of the constructor ( x , y , z , rotationX , rotationY ) are renamed. What do I need to do to prevent that?
  2. The static method fromUrlSearchParams(searchParams) is removed by the compiler because it comes to the conclusion that it is dead code because it is not used internally in the compiled code. How do I export that static method?
  3. How do I mark the matrix property to be part of the public interface?

Edit:

After spending several hours with this problem, reading every piece of documentation I could find, crawling files on GitHub, testing various online externs generators and obtaining a copy of the book "Closure - The Definitive Guide" the problem is still unsolved.

After being around for more than a decade the Closure Compiler documentation is still useless for everything more than the most basic examples. You got to be kidding me.

Here's what I tried so far:

class State {
  /**
   * @constructor
   * @param {number} x
   * @param {number} y
   * @param {number} z
   * @param {number} rotationX
   * @param {number} rotationY
   */
  constructor(x, y, z, rotationX, rotationY) {
    /** @type {number} */
    this.x = x;
    /** @type {number} */
    this.y = y;
    /** @type {number} */
    this.z = z;
    /** @type {number} */
    this.rotationX = rotationX;
    /** @type {number} */
    this.rotationY = rotationY;
  }

  /**
   * @nocollapse
   * @param {URLSearchParams} searchParams
   * @return {State}
   */
  static fromUrlSearchParams(searchParams) {}

  /** @return {URLSearchParams} */
  toUrlSearchParams() {}
};

The difference to the original file is using class State { instead of State = class { . Interestingly, this results in the following error message:

ERROR - [JSC_BLOCK_SCOPED_DECL_MULTIPLY_DECLARED_ERROR] Duplicate let / const / class / function declaration in the same scope is not allowed.

No clue why that would make a difference, but anyway, let's move on. Next attempt:

/**
 * @constructor
 * @param {number} x
 * @param {number} y
 * @param {number} z
 * @param {number} rotationX
 * @param {number} rotationX
 */
var State = {};

/** @type {number} */
State.prototype.x;

/** @type {number} */
State.prototype.y;

/** @type {number} */
State.prototype.z;

/** @type {number} */
State.prototype.rotationX;

/** @type {number} */
State.prototype.rotationX;

/**
 * @nocollapse
 * @param {URLSearchParams} searchParams
 * @return {State}
 */
State.fromUrlSearchParams = function(searchParams) {};

/** @return {URLSearchParams} */
State.prototype.toUrlSearchParams = function() {};

Running it with that code results in

ERROR - [JSC_VAR_MULTIPLY_DECLARED_ERROR] Variable ColorCubeState declared more than once. First occurrence: blabla.js

class State {
      ^^^^^

Well, here we go again. It is a mystery to me why the compiler would state that it is already defined if I pass a source file and an extern file. One defines it, the other annotates it, or so you would think.

No attempt could save the static method from being removed by the compiler.

Next to building and debugging the compiler with my code I don't see anything else that I could try. Luckily there is a guaranteed resolution for the problem: Simply not using the Google Closure Compiler.

I'm no expert in this, but here are some things to try. See also https://github.com/google/closure-compiler/wiki/JS-Modules

At the end of your file you might try adding

exports = State.

You might also need to add at the top:

goog.module('State');

  1. Why do you need to prevent the arguments of the constructor being renamed? These aren't visible outside the constructor, so should not be a problem.

  2. Try this:

goog.exportSymbol('fromUrlSearchParams', fromUrlSearchParams);

  1. JavaScript getter and setter properties might not be supported in the compiler. See this https://google.github.io/styleguide/jsguide.html#features-classes-getters-and-setters

The following worked:

Input file:

"use strict";

const attributeX         = "x";
const attributeY         = "y";
const attributeZ         = "z";
const attributeRotationX = "rotationX"
const attributeRotationY = "rotationY";

/** @implements {StateInterface} */
globalThis["State"] = class {
  constructor(x, y, z, rotationX, rotationY) {
    this.x         = x;
    this.y         = y;
    this.z         = z;
    this.rotationX = rotationX;
    this.rotationY = rotationY;
  }

  /**
   * @nocollapse
   * @suppress {checkTypes}
   */
  static "fromUrlSearchParams"(searchParams) {
    return new globalThis["State"](parseInt  (searchParams.get(attributeX), 10),
                                   parseInt  (searchParams.get(attributeY), 10),
                                   parseInt  (searchParams.get(attributeZ), 10),
                                   parseFloat(searchParams.get(attributeRotationX)),
                                   parseFloat(searchParams.get(attributeRotationY)));
  }

  toUrlSearchParams() {
    let searchParams = new URLSearchParams();
    searchParams.set(attributeX        , this.x        .toString(10));
    searchParams.set(attributeY        , this.y        .toString(10));
    searchParams.set(attributeZ        , this.z        .toString(10));
    searchParams.set(attributeRotationX, this.rotationX.toString(10));
    searchParams.set(attributeRotationY, this.rotationY.toString(10));
    return searchParams;
  }
}

Externs file:

/**
 * @fileoverview
 * @externs
 */

/** @interface */
class StateInterface {
  /**
   * @param {number} x
   * @param {number} y
   * @param {number} z
   * @param {number} rotationX
   * @param {number} rotationY
   */
  constructor(x, y, z, rotationX, rotationY) {
    /** @type {number} */
    this.x = x;
    /** @type {number} */
    this.y = y;
    /** @type {number} */
    this.z = z;
    /** @type {number} */
    this.rotationX = rotationX;
    /** @type {number} */
    this.rotationY = rotationY;
  }

  /**
   * @param {URLSearchParams} searchParams
   * @return {StateInterface}
   */
  static fromUrlSearchParams(searchParams) {}

  /** @return {URLSearchParams} */
  toUrlSearchParams() {}
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM