Skip to content

Commit

Permalink
Allow certain ES6 constructs in internal JS code
Browse files Browse the repository at this point in the history
With this change we allow these features by default.  If a user
explicitly opts into older browser support we trigger the running
of closure compiler with `--language_out ES5 --compilation_level
WHITESPACE_ONLY`.

For most users this will be a code size win, but otherwise a no-op.

For users who need older browser support they will now have their
output run though closure by default.  If they want to take a care
of the transpilaion process themselves rather than have emscripten
to it auto-magically they can run with `--closure=0`.

When we auto-magically run closure to do transpilation we generate
a warning.  This warning can be suppressed by add `--closure=1` to
explicitly opt in, or `--closure=0` to explicitly opt out.

This change in does not actually include any usage of these features
and so don't include the code size benefits. Those will be part of
followup PRs.

Fixes: #11984
  • Loading branch information
sbc100 committed Dec 16, 2021
1 parent 752cb0b commit f34c8e2
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 10 deletions.
7 changes: 7 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ See docs/process.md for more on how version tagging works.

3.0.1
-----
- Emscripten in starting to use ES6 features in its core libraries (at last!).
For most user targeting the default set of browser this is a code size win.
For projects targeting older browsers (e.g. `-sMIN_CHROME_VERSION=10`)
emscripten will now run closure compiler `WHITESPACE_ONLY` in order to
traspile any ES6 down to ES5. When this automatic transpilation is performed
we generate a warning which can disabled by either opting explicitly in-to or
out-of closure usage using `--closure=1` or `--closure=0`. (#15763).
- Deprecate `EMMAKEN_CFLAGS` is favor of `EMCC_CFLAGS`.
- Fixed an issue where user provided --js-library directives would not be
processed as the last item after all system provided JS libraries have been
Expand Down
31 changes: 24 additions & 7 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1811,6 +1811,24 @@ def default_setting(name, new_default):

setup_environment_settings()

if options.use_closure_compiler != 0:
# Emscripten requires certain ES6 constructs by default in library code
# - https://caniuse.com/let : EDGE:12 FF:44 CHROME:49 SAFARI:11
# - https://caniuse.com/const : EDGE:12 FF:36 CHROME:49 SAFARI:11
# - https://caniuse.com/arrow-functions: : EDGE:12 FF:22 CHROME:45 SAFARI:10
# - https://caniuse.com/mdn-javascript_builtins_object_assign:
# EDGE:12 FF:34 CHROME:45 SAFARI:9
# Taking the highest requirements gives is our minimum:
# Max Version: EDGE:12 FF:44 CHROME:49 SAFARI:11
settings.TRANSPILE_TO_ES5 = (settings.MIN_EDGE_VERSION < 12 or
settings.MIN_FIREFOX_VERSION < 44 or
settings.MIN_CHROME_VERSION < 49 or
settings.MIN_SAFARI_VERSION < 11000 or
settings.MIN_IE_VERSION != 0x7FFFFFFF)

if options.use_closure_compiler is None and settings.TRANSPILE_TO_ES5:
diagnostics.warning('transpile', 'enabling transpilation via closure due to browser version settings. This warning can be suppressed by passing `--closure=1` or `--closure=0` to opt into our explicitly.')

# Silently drop any individual backwards compatibility emulation flags that are known never to occur on browsers that support WebAssembly.
if not settings.WASM2JS:
settings.POLYFILL_OLD_MATH_FUNCTIONS = 0
Expand Down Expand Up @@ -3245,15 +3263,14 @@ def phase_binaryen(target, options, wasm_target):
def preprocess_wasm2js_script():
return read_and_preprocess(utils.path_from_root('src/wasm2js.js'), expand_macros=True)

def run_closure_compiler():
global final_js
final_js = building.closure_compiler(final_js, pretty=not minify_whitespace(),
extra_closure_args=options.closure_args)
if final_js and (options.use_closure_compiler or settings.TRANSPILE_TO_ES5):
if options.use_closure_compiler:
final_js = building.closure_compiler(final_js, pretty=not minify_whitespace(),
extra_closure_args=options.closure_args)
else:
final_js = building.closure_transpile(final_js, pretty=not minify_whitespace())
save_intermediate_with_wasm('closure', wasm_target)

if final_js and options.use_closure_compiler:
run_closure_compiler()

symbols_file = None
if options.emit_symbol_map:
symbols_file = shared.replace_or_append_suffix(target, '.symbols')
Expand Down
5 changes: 5 additions & 0 deletions src/settings_internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,8 @@ var HAS_MAIN = 0;

// Set to true if we are linking as C++ and including C++ stdlibs
var LINK_AS_CXX = 0;

// Set when some minimum browser version triggers doesn't support the
// minimum set of ES6 featurs. This triggers transpilation to ES5
// using closure compiler.
var TRANSPILE_TO_ES5 = 0;
4 changes: 2 additions & 2 deletions tests/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2601,7 +2601,7 @@ def test_doublestart_bug(self):
'': ([],),
'closure': (['-O2', '-g1', '--closure=1', '-s', 'HTML5_SUPPORT_DEFERRING_USER_SENSITIVE_REQUESTS=0'],),
'pthread': (['-s', 'USE_PTHREADS', '-s', 'PROXY_TO_PTHREAD'],),
'legacy': (['-s', 'MIN_FIREFOX_VERSION=0', '-s', 'MIN_SAFARI_VERSION=0', '-s', 'MIN_IE_VERSION=0', '-s', 'MIN_EDGE_VERSION=0', '-s', 'MIN_CHROME_VERSION=0'],)
'legacy': (['-s', 'MIN_FIREFOX_VERSION=0', '-s', 'MIN_SAFARI_VERSION=0', '-s', 'MIN_IE_VERSION=0', '-s', 'MIN_EDGE_VERSION=0', '-s', 'MIN_CHROME_VERSION=0', '-Wno-transpile'],)
})
@requires_threads
def test_html5_core(self, opts):
Expand Down Expand Up @@ -2666,7 +2666,7 @@ def test_webgl_unmasked_vendor_webgl(self):
@requires_graphics_hardware
def test_webgl2(self):
for opts in [
['-s', 'MIN_CHROME_VERSION=0'],
['-s', 'MIN_CHROME_VERSION=0', '-Wno-transpile'],
['-O2', '-g1', '--closure=1', '-s', 'WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG'],
['-s', 'FULL_ES2=1'],
]:
Expand Down
50 changes: 50 additions & 0 deletions tests/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -11316,3 +11316,53 @@ def test_hello_function(self):
# reference them in our docs. Should we move this file to somewhere else such
# as `examples/`?)
self.run_process([EMCC, test_file('hello_function.cpp'), '-o', 'function.html', '-sEXPORTED_FUNCTIONS=_int_sqrt', '-sEXPORTED_RUNTIME_METHODS=ccall,cwrap'])

def test_es5_transpile(self):
# Create a library file that uses the following ES6 features
# - let/const
# - arrow funcs
# - for..of
# - object.assign
create_file('es6_library.js', '''\
mergeInto(LibraryManager.library, {
foo: function() {
// Object.assign + let
let obj = Object.assign({}, {prop:1});
err('prop: ' + obj.prop);
// arror funcs + const
const bar = () => 2;
err('bar: ' + bar());
}
});
''')
create_file('test.c', 'extern void foo(); int main() { foo(); }')
self.emcc_args += ['--js-library', 'es6_library.js']
self.uses_es6 = True

def check_for_es6(expect):
js = read_file('test.js')
if expect:
self.assertContained('() => 2', js)
self.assertContained('const ', js)
self.assertContained('let ', js)
else:
self.assertNotContained('() => 2', js)
self.assertNotContained('const ', js)
self.assertNotContained('let ', js)

# Check that under normal circumstances none of these features get
# removed / transpiled.
self.do_runf('test.c', 'prop: 1\nbar: 2\n')
check_for_es6(True)

# If we select and older browser than closure will kick in by default
# to traspile.
self.emcc_args.remove('-Werror')
self.set_setting('MIN_CHROME_VERSION', '10')
self.do_runf('test.c', 'prop: 1\nbar: 2\n')
check_for_es6(False)

# If we add `--closure=0` that traspiler (closure) is not run at all
self.do_runf('test.c', 'prop: 1\nbar: 2\n', emcc_args=['--closure=0'])
check_for_es6(True)
15 changes: 14 additions & 1 deletion tools/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,16 @@ def add_to_path(dirname):
return closure_cmd, env


@ToolchainProfiler.profile_block('closure_transpile')
def closure_transpile(filename, pretty):
user_args = []
closure_cmd, env = get_closure_compiler_and_env(user_args)
closure_cmd += ['--language_out', 'ES5']
closure_cmd += ['--compilation_level', 'WHITESPACE_ONLY']
closure_cmd += ['--formatting', 'PRETTY_PRINT']
return run_closure_cmd(closure_cmd, filename, env, pretty)


@ToolchainProfiler.profile_block('closure_compiler')
def closure_compiler(filename, pretty, advanced=True, extra_closure_args=None):
user_args = []
Expand Down Expand Up @@ -794,7 +804,10 @@ def closure_compiler(filename, pretty, advanced=True, extra_closure_args=None):
# Tell closure not to do any transpiling or inject any polyfills.
# At some point we may want to look into using this as way to convert to ES5 but
# babel is perhaps a better tool for that.
args += ['--language_out', 'NO_TRANSPILE']
if settings.TRANSPILE_TO_ES5:
args += ['--language_out', 'ES5']
else:
args += ['--language_out', 'NO_TRANSPILE']
# Tell closure never to inject the 'use strict' directive.
args += ['--emit_use_strict=false']

Expand Down
1 change: 1 addition & 0 deletions tools/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
diagnostics.add_warning('map-unrecognized-libraries')
diagnostics.add_warning('unused-command-line-argument', shared=True)
diagnostics.add_warning('pthreads-mem-growth')
diagnostics.add_warning('transpile')


# TODO(sbc): Investigate switching to shlex.quote
Expand Down

0 comments on commit f34c8e2

Please sign in to comment.