Как включить Closure compressor и об потенциальных ошибках сборки

Небольшая заметка о том как правильно включить closure compressor, и понимать на что он ругается при сборке проекта. Так же покажем как заставить sencha cmd использовать вашу версию closure compiler.

Для включения closure compressor необходимо изменить app.json на следующие значения

...
"production": {
    ...
    "compressor": {
        "type": "closure",
        "compression": "simple",
        "foldConstants": true,
        "removeDeadCode": true,
        "removeUnusedVariables": "ALL",
        "variableRenaming": "LOCAL",
        "disambiguatePrivateProperties": true,
        "disambiguateProperties": true,
        "ambiguateProperties": true
    }
    ...
}
...

Так же для динамически загружаемых пакетов — вы столкнетесь с тем что closure не будет применяться, а вместо него будет использоваться стандартный YUI компрессор. Для этого вам необходимо создать плагин и подключить его при сборке пакета. Текст плагина ниже.

<project name="ForceClosure">
    <target name="-before-build">
        <x-echo>#### Forced run of closure minifier ####</x-echo>
        <var name="build.compile.js.compress" value="+${package.compressor.type}" />
    </target>
</project>

И теперь подключим плагин к сборке нашего пакета

<?xml version="1.0" encoding="utf-8"?>
<project name="example" default=".help">

    <import file="${basedir}/../closure.xml"/>

    <script language="javascript">
......

Если же при сборке проекта возникли ошибки, например

[ERR]
[ERR] BUILD FAILED
[ERR] java.lang.RuntimeException: INTERNAL COMPILER ERROR.Please report this problem.null  Node(ASSIGN): compression-input:179454:8        vm = me.getViewModel();
[ERR]   Parent(EXPR_RESULT): compression-input:179454:8        vm = me.getViewModel();
[ERR]
[ERR]   at sun.reflect.Nat
[ERR] iveMethodAccessorImpl.invoke0(Native Method)
[ERR]
[ERR] Total time: 36 seconds

[ERR] C:\Users\Swat2k\bin\Sencha\Cmd\6.6.0.14\plugin.xml:333: The following error occurred while executing this line:
C:\Users\Swat2k\bin\Sencha\Cmd\6.6.0.14\ant\build\app\build-impl.xml:274: The following error occurred while executing this line:
C:\Users\Swat2k\bin\Sencha\Cmd\6.6.0.14\ant\build\app\js-impl.xml:186: java.lang.RuntimeException: INTERNAL COMPILER ERROR.
Please report this problem.

null
  Node(ASSIGN): compression-input:179454:8
        vm = me.getViewModel();
  Parent(EXPR_RESULT): compression-input:179454:8
        vm = me.getViewModel();

Сразу бросается в глаза строчка кода которая привела к ошибке vm = me.getViewModel(); , в моем случае это было объявление глобальной переменной. Но как конкретно локализовать строчку кода в проекте ?

Для этого нам необходимо собрать проект без compressor’a, указав в качестве его типа none . Так мы получим не минифицированный файл, который можно удобно анализировать, то есть в данном случае в нем можно будет найти строку под номером 179454 и посмотреть что там не так.

Порой бывают различные ошибки связанные с компрессорами, например вот существующие ошибки компрессоров для последней sencha cmd 6.6.0.14 . Для воспроизведения создайте файл input.js , и вставьте туда код

YUI COMPRESSOR BUG (used by default, неактуально после extjs 6.7)

var a = function () {
    let fieldName = 'a' + 'b';
    var a = {
        [fieldName]: 'anyvalue'
    }
    console.log (a);
}

При сборке sencha fs minify -l ES6 -yui -from=input.js -to=out.js мы получим не валидный код! А все потому что он не понимает полную спецификацию ES6 в плане ComputedPropertyNames

{
    let b = 'a' + 'b';
    var a = {
        [fieldName]: 'anyvalue'
    };
    console.log(a)
};

CLOSURE COMPRESSOR BUG (неактуально после extjs 6.7)

var a = function(values) {    
    let {
        user, activitylines, filtersParam
    } = values;    
    console.log(user, activitylines, filtersParam);
}

При сборке sencha fs minify -l ES6 -closure -from=input.js -to=out.js так же не валидный код, из-за деструктурирующего присваивания.

var a = function(values$jscomp$5) {
    let {
        user: values$jscomp$5,
        activitylines: activitylines,
        filtersParam: filtersParam
    } = values$jscomp$5;
    console.log(values$jscomp$5, activitylines, filtersParam)
};

Так же если очень нужно то closure compiler можно обновить до последней версии — с помощью грязного хака. Для этого достаточно заменить два соответствующих файла в ...\bin\Sencha\Cmd\6.6.0.14\lib естественно с сохранением оригинального наименования. Прикладываю рабочую версию от 2018.08.05 , взять можно тут closure-compiler-20180805 .

PS: Sencha вняла нашим мольбам и зарегистрировала SDKTOOLS-1984 , обещали исправить в релизе extjs 6.7

Дополнительные overrides необходимые для clouse

/**
 * @class Overrides.Template
 * @author Dmitry Kazarin <dikazarin@gmail.com>
 * Исправляет ошибку при использовании google closure с включенной опцией variableRenaming
 * исправление работает только для FireFox'a. Можно просто сказать useEval: false
 * но в FireFox будет ~десятикратная потеря производительности.
 * */

Ext.define('Overrides.Template', {
  override: 'Ext.Template',

  /**
   * Compiles the template into an internal function, eliminating the RegEx overhead.
   * @return {Ext.Template} this
   */
  compile: function () {
    const me = this;
    let code;

    code = me.html.replace(me.compileARe, '\\\\')
      .replace(me.compileBRe, '\\n')
      .replace(me.compileCRe, '\\\'')
      .replace(me.tokenRe, me.regexReplaceFn.bind(me));

    // fix here
    code = `${(this.disableFormats !== true ? 'var fm=Ext.util.Format;' : '')
            + (me.useEval ? 'arguments[0]=' : 'return')
    } function(v){return ['${code}'];};`;

    me.fn = me.useEval ? me.evalCompiled(code) : (new Function('Ext', code))(Ext);
    me.compiled = true;

    return me;
  },

});

и

/* eslint-disable no-multi-assign */
/* eslint-disable no-new-func */
/* eslint-disable prefer-const */
/**
 * @class Overrides.app.bind.Parser
 * @author Dmitry Kazarin <dikazarin@gmail.com>
 * Исправляет ошибку при использовании google closure с включенной опцией variableRenaming
 * исправление работает только для FireFox'a. Можно просто сказать useEval: false
 * но в FireFox будет ~десятикратная потеря производительности.
 */

Ext.define('Overrides.app.bind.Parser', {
  override: 'Ext.app.bind.Parser',

  privates: {

    /**
     * Parses the expression tree and compiles it as a function
     *
     * @param expr
     * @param {Boolean} debug
     * @return {Function}
     * @private
     */
    parseSlot: function (expr, debug) {
      const me = this;
      let defs = [];
      let body = [];
      const tokens = me.tokens || [];
      let fn; let code; let i; let length; let temp;

      me.definitions = defs;
      me.body = body;

      body.push(`return ${me.compile(expr)};`);

      // now we have the tokens
      length = tokens.length;
      code = 'var fm = Ext.util.Format,\nme,';
      temp = 'var a = Ext.Array.from(values);\nme = scope;\n';

      if (tokens.length) {
        for (i = 0; i < length; i++) {
          code += `v${i}${(i === length - 1) ? ';' : ','}`;
          temp += `v${i} = a[${i}]; `;
        }
      } else {
        code += 'v0;';
        temp += 'v0 = a[0];';
      }

      defs = Ext.Array.insert(defs, 0, [code]);
      body = Ext.Array.insert(body, 0, [temp]);
      body = body.join('\n');

      // <debug>
      if (debug) {
        body = `debugger;\n${body}`;
      }

      // </debug>

      // fix here
      defs.push(
        `${me.useEval ? 'arguments[0]=' : 'return'} function (values, scope) {`,
        body,
        '}'
      );

      code = defs.join('\n');

      fn = me.useEval ? me.evalFn(code) : (new Function('Ext', code))(Ext);

      me.definitions = me.body = null;

      return fn;
    },
  },
});

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *