Ext JS - Beyond ES5
Using modern ECMAScript syntax with Sencha Ext JS projects
Sencha Ext JS ships with a closed source build tool that bundles the Closure Compiler; in some cases, this prevents developers from using modern ECMAScript syntax. This article introduces @coon-js/delorean, a tool that works around this issue with the help of an additional transpilation layer.
One reason for frustration when working with Ext JS is the fact that modern JavaScript syntax cannot be used with the framework: As soon as Sencha CMD builds a package or application, code that was known to work in development or tests fails to compile for production, or even worse: it completely breaks during production.
Here are a few examples that are known to break a build with Sencha CMD:
The Nullish coalescing operator:
const foo = null ?? "default string";
Destructuring assignment syntax with function arguments:
const fn = ([x, y, z]) => ({x, y, z});
The Optional chaining operator:
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah'
}
};
const dogName = adventurer.dog?.name;
The reason for this is that Sencha CMD is a proprietary tool that is shipped with a baked-in Closure Compiler; the way it’s integrated prevents developers to fine tune build processes. The Closure Compiler obviously requires updates to work with current ECMAScript standards. There are a few configuration options for packages that allow for a more forgiving (read: compatible) parsing and transpiling of source code. However, there is code that is known to fail during compiling, even if the code is syntactically correct.
(Refer to this list to get an idea of what’s working with the Closure Compiler.)
Also note that the latest available version of the Closure Compiler is not necessarily shipped with the latest available version of Sencha CMD: v7.6.0.87 of Sencha CMD ships with v20220301.
@sencha\cmd> dir /s/b | findstr .*closure.*$
@sencha\cmd\dist\lib\closure-compiler-externs-v20220301.jar
@sencha\cmd\dist\lib\closure-compiler-v20220301.jar
Unfortunately, Sencha provides no official guide for upgrading the Closure Compiler independently.
When things go AWOL
Here’s an example that shows how production code of an Ext JS project breaks when modern ECMAScript is used. Consider the following lines as part of the development source code:
const fn = ([a, b, c]) => ({a, b, c});
This arrow function
uses the destructuring assignment syntax to unpack the first three entries of an array argument
creates an object from the entries using the notation for shorthand property names
returns this object
Let the code be part of a Sencha Ext JS package that can be built with the command:
$ npx @sencha/cmd package build
The version used with Sencha CMD is:
$ npx @sencha/cmd which
Sencha Cmd v7.6.0.87
And the configuration from the sencha section of the package.json is:
"output": {
"base": "${package.dir}/build"
},
"compressor": {
"type": "closure",
"polyfills": "none"
},
Compiling fails since the output level is set to ES5 by default; it’s an older ES spec, but compatible with a wide range of browsers:
The configuration can be adjusted so that the ES version used with the project can be specified: Knowing that NEXT is the level for the latest features supported, the configuration is changed to:
"output": {
"base": "${package.dir}/build",
"js": {
"version": "NEXT"
}
},
"language": {
"js": {
"input": "NEXT",
"output": "NEXT"
}
},
"compressor": {
"type": "closure",
"polyfills": "none"
},
The output on the console shows that NEXT is understood, but still fails with compiling: The build files stay empty.
Apparently, somewhere, something is broken.
The language.js.input seems to be causing the issues. According to the official Sencha docs, the following language levels are supported by the tool:
ES3 : ECMAScript 3 language level
ES5 : ECMAScript 5 language level
ES6 : ECMAScript 6 language level (2015)
ES7 : ECMAScript 7 language level (2016)
ES8 : ECMAScript 8 language level (2017)
NEXT : ECMAScript Next (or ES.Next) language level
whereas the Closure Compiler supports the following specs with language_in:
ECMASCRIPT3
ECMASCRIPT5
ECMASCRIPT5_STRICT
ECMASCRIPT_2015
ECMASCRIPT_2016
ECMASCRIPT_2017
ECMASCRIPT_2018
ECMASCRIPT_2019
ECMASCRIPT_2020
ECMASCRIPT_2021
STABLE
ECMASCRIPT_NEXT (latest features supported) (default: STABLE)
Unfortunately, an attempt at setting the language to any of the values supported directly by the Closure Compiler (or variations of it, such as ECMASCRIPT_2021, ES10…) results in the following error message:
ES8 seems to be the greatest common denominator for Sencha CMD and the Closure Compiler. The following configuration works — the build process does not cancel halfway through, and transpiled JavaScript ends up in the source files:
"output": {
"base": "${package.dir}/build",
"js": {
"version": "ES8"
}
},
"language": {
"js": {
"input": "ES8",
"output": "ES8"
}
},
"compressor": {
"type": "closure",
"polyfills": "none"
}
However, loading the build in production fails.
Here’s the original JavaScript again for reference:
const fn = ([a, b, c]) => ({a, b, c});
… and the JavaScript source that ends up with the debug code (e.g., build/project-debug.js):
const fn = [a,b,c] => ({
a,
b,
c
});
… and this is the code produced for the compressed JavaScript (e.g., build/project.js):
const c=()=>({a,b,c});
It does not take long to see why the code fails: It is syntactically wrong in the debug build, and functionally wrong in the compressed build.
Introducing @coon-js/delorean
Modern language features can be supported with the help of additional transpiling: Development sources using the latest syntax according to ES specifications need to get transpiled in advance, feeding the Closure Compiler with a JavaScript version it understands, resulting in correct and usable builds.
@coon-js/delorean is an npm tool that provides an additional transpilation layer on top of Sencha CMD with the help of Babel.
For making this work, delorean alters the project file of the package or application and redirects source-roots to the files that were processed and transpiled by Babel. This allows for using any JavaScript language feature and additional optimisations that can be injected with the help of Babel’s countless number of plugins.
The default configuration of **delorean** makes sure that Babel produces ES5 code:
delorean hardly affects the actual build process: It only updates existing mappings in the configuration file required by Sencha CMD; these usually represent directories containing project-relevant source files. Besides obvious scaffolding of the transpiled project, the infrastructure of the project remains untouched, making almost seamless integration into the development process and existing build pipelines possible.
Changes made to the project configuration can easily be tracked:
"classpath": [
- "${package.dir}/src",
- "${package.dir}/${toolkit.name}/src"
+ "${package.dir}/.deloreanbuild/src",
+ "${package.dir}/.deloreanbuild/${toolkit.name}/src"
],
"overrides": [
- "${package.dir}/overrides",
- "${package.dir}/${toolkit.name}/overrides"
+ "${package.dir}/.deloreanbuild/overrides",
+ "${package.dir}/.deloreanbuild/${toolkit.name}/overrides"
],
During development, the changes made by delorean should be reverted so that live reloading with sencha app watch respective webpack (being part of Ext JS projects used with npm) still works — and of course so that the browser uses the sources from the files currently being edited by the developer. (Otherwise, the development environment would rely on the transpiled files that were created previously by delorean.)
Reverting the changes is fairly easy, as a simple call to
$ npx delorean -r
removes any reference to the .deloreanbuild folder from the project configuration file:
CI/CD Integration
npx delorean -p and npx delorean -r are easily integrated with CI/CD pipelines commonly used with Sencha Ext JS projects. You can automate transpilation by configuring either the build.xml of a package or an application, or by adding additional scripts to the package.json .
build.xml strategy
This xml-file is usually available with any Ext JS package or application and provides a place for adding options and hooks for the Ant tool used with Sencha CMD. It allows for configuring -before-build /-after-build targets (you can read more about it **here**).
You can make use of delorean by configuring the targets with:
<target name="-before-build">
<exec executable="cmd">
<arg line="/c npx delorean -p"/>
</exec>
</target>
<target name="-after-build">
<exec executable="cmd">
<arg line="/c npx delorean -r"/>
</exec>
</target>
This will run npx delorean -p before Sencha CMD builds the project (hence -before-build), and revert all project specific changes once the build completes by invoking npx delorean -r (hence -after-build).
package.json strategy
If you already have a build script in your package.json which calls Sencha CMD, wrap the build command with additional calls to delorean. Here’s an example:
{
"scripts": [
"build": "npx delorean -p && npm run senchabuild && npx delorean -r",
"senchabuild": "npm run clean && cross-env webpack --env.profile=desktop --env.environment=production --env.treeshake=yes --env.cmdopts=--uses"
]
}
Additional Resources
The repository for the project can be found a https://github.com/coon-js/delorean. Feedback regarding configurations, CI/CD integrations and issue reports is greatly appreciated.
The official documentation for delorean is available at https://www.conjoon.org/docs/api/misc/@coon-js/delorean, which is also its project home, since it originated as part of the conjoon ecosystem.
Community Notes
In the Sencha Discord Server, users reacted to this article and noted that it would also be great to completely skip Sencha CMD and use tools like **webpack or [rollup](https://rollupjs.org/guide/en/)** exclusively for building projects. I strongly agree and think that providing a solution for bundling assets and sources with the help of these tools can be done with manageable effort.
Significant Revisions
05 March 2023: Migrated article to this site
02 December 2022: Minor corrections in preparation for the german translation
30 November 2022: Updated Figure 2 with externals configuration
23 November 2022: Updated wording, added community notes
21 November 2022: Published first installment