Is there a way to tell Google Closure Compiler to *NOT* inline my local functions?

Here's what I'm looking for:

  • I want to use the wonderful features of SIMPLE mode minification while disabling just one specific feature (disable local function inline).


  • UPDATE: The answer is NO, it's not possible given my setup. But for me there is a workaround given I am using Grails.
  • As @Chad has explained below, "This violates core assumptions of the compiler". See my UPDATE3 below for more info.



IN QUESTION FORM:

  • I'm using CompilationLevel.SIMPLE_OPTIMIZATIONS which does everything I want, except that it's inlining my local functions.
  • Is there any way around this? For example, is there a setting I can place in my JS files to tell Google Closure not to inline my local functions?

It would be cool to have some directives at the top of my javascript file such as:

// This is a JS comment...
// google.closure.compiler = [inlineLocalFunctions: false]

I'm developing a Grails app and using the Grails asset-pipeline plugin, which uses Google Closure Compiler (hereafter, Compiler). The plugin supports the different minification levels that Compiler supports via the Grails config grails.assets.minifyOptions. This allows for 'SIMPLE', 'ADVANCED', 'WHITESPACE_ONLY'.

AssetCompiler.groovy (asset-pipeline plugin) calls ClosureCompilerProcessor.process()

That eventually assigns SIMPLE_OPTIMIZATIONS on the CompilerOptions object. And by doing so, CompilerOptions.inlineLocalFunctions = true as a byproduct (this is hard coded behavior in Compiler). If I were to use WHITESPACE_ONLY the result would be inlineLocalFunctions=false.

So by using Asset Pipeline's 'SIMPLE' setting, local functions are being inlined and that is causing me trouble. Example: ExtJS ext-all-debug.js which uses lots of local functions.

SO post Is it possible to make Google Closure compiler *not* inline certain functions? provides some help. I can use its window['dontBlowMeAway'] = dontBlowMeAway trick to keep my functions from inlining. However I have LOTS of functions and I'm not about to manually do this for each one; nor would I want to write a script to do it for me. Creating a JS model and trying to identity local functions doesn't sound safe, fun nor fast.

The previous SO post directs the reader to https://developers.google.com/closure/compiler/docs/api-tutorial3#removal, where the window['bla'] trick is explained, and it works.

Wow thanks for reading this long.

Help? :-)


UPDATE1:

Okay. While spending all the effort in writing this question, I may have a trick that could work. Grails uses Groovy. Groovy makes method call interception easy using its MetaClass API.

I'm going to try intercepting the call to:

com.google.javascript.jscomp.Compiler.compile(
    List<T1> externs, List<T2> inputs, CompilerOptions options)

My intercepting method will look like:

options.inlineLocalFunctions=false
// Then delegate call to the real compile() method

It's bed time so I'll have to try this later. Even so, it would be nice to solve this without a hack.



UPDATE2: The response in a similar post (Is it possible to make Google Closure compiler *not* inline certain functions?) doesn't resolve my problem because of the large quantity of functions I need inlined. I've already explained this point.

Take the ExtJS file I cited above as an example of why the above similar SO post doesn't resolve my problem. Look at the raw code for ext-all-debug.js. Find the byAttribute() function. Then keep looking for the string "byAttribute" and you'll see that it is part of strings that are being defined. I am not familiar with this code, but I'm supposing that these string-based values of byAttribute are later being passed to JS's eval() function for execution. Compiler does not alter these values of byAttribute when it's part of a string. Once function byAttribute is inlined, attempts to call the function is no longer possible.



UPDATE3: I attempted two strategies to resolve this problem and both proved unsuccessful. However, I successfully implemented a workaround. My failed attempts:

  1. Use Groovy method interception (Meta Object Protocol, aka MOP) to intercept com.google.javascript.jscomp.Compiler.compile().
  2. Fork the closure-compiler.jar (make my own custom copy) and modify com.google.javascript.jscomp.applySafeCompilationOptions() by setting options.setInlineFunctions(Reach.NONE); instead of LOCAL.

Method interception doesn't work because Compiler.compile() is a Java class which is invoked by a Groovy class marked as @CompileStatic. That means Groovy's MOP is not used when process() calls Google's Compiler.compile(). Even ClosureCompilerProcessor.translateMinifyOptions() (Groovy code) can't be intercepted because the class is @CompileStatic. The only method that can be intercepted is ClosureCompilerProcessor.process().

Forking Google's closure-compiler.jar was my last ugly resort. But just like @Chad said below, simply inserting options.setInlineFunctions(Reach.NONE) in the right place didn't resurrect my inline JS functions names. I tried toggling other options such as setRemoveDeadCode=false to no avail. I realized what Chad said was right. I would end up flipping settings around and probably destroying how the minification works.

My solution: I pre-compressed ext-all-debug.js with UglifyJS and added them to my project. I could have named the files ext-all-debug.min.js to do it more cleanly but I didn't. Below are the settings I placed in my Grails Config.groovy:

grails.assets.minifyOptions = [
    optimizationLevel: 'SIMPLE' // WHITESPACE_ONLY, SIMPLE or ADVANCED
]

grails.assets.minifyOptions.excludes = [
    '**ext-all-debug.js',
    '**ext-theme-neptune.js'
]

Done. Problem solved.




Keywords: minify, minification, uglify, UglifyJS, UglifyJS2

Answers:

Answer

In this case, you would either need to make a custom build of the compiler or use the Java API.

However - disabling inlining is not enough to make this safe. Renaming and dead code elimination will also cause problems. This violates core assumptions of the compiler. This local function is ONLY referenced from within strings.

This code is only safe for the WHITESPACE_ONLY mode of the compiler.

Answer

Use the function constructor

var fnc = new Function("param1", "param2", "alert(param1+param2);");

Closure will leave the String literals alone.

See https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Function

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.