ExtJS2React (e2r) migrates ExtJS applications to React [Native] + Redux + Blueprint by rewriting their entire codebase. Much of the work is automated while an ever-shrinking set of cases are left for manual intervention. This is an unofficial library in no way associated with Sencha. Use at your own risk.
For speed of development and depth of implementation, it is currently assumed that the source project is ExtJS 6.x with MVVM architecture. If your project isn't there but you want to use this tool, migrate to the common starting point and then run this tool to cross the bridge to React-land: Sencha Touch → ExtJS
<6.x → 6.x
MVC → MVVM
- Clone the repo
- Open
/config.json
and modify the params accordingly - Run
npm install
in the extjs2react directory - Run
npm start
- ...
- Profit!
- Run output through prettier (config:
prettier
) -
var
→let
when at root of function (config:varToLet
) -
function
→ arrow function when not usingthis
orarguments
(config:arrowFunctions
) -
me
→this
when redundant (config:arrowFunctions
) -
() => { return ... }
→() => ...
(config:arrowReturnShorthand
) -
() => { ... }
→() => ...
(config:arrowExpressionShorthand
) -
getCount() + ' widgets'
→${getCount()} widgets
(config:templateLiterals
)
- ClassManager → ES6 modules
- namespaced class names → simple names & references
- import class dependencies
- import alias dependencies
- exports
- name collision avoidance
- resolve alternateClassName to primary
- reactify all framework components w/ manifest
- ExtJS classes → ES6 classes
- extends →
extend
- statics →
static
- singleton →
export
instance - config/cachedConfig/eventedConfig → ES6 accessors & calls
- mixins → inline js (Object.assign)
- plugins
- extends →
- Component
- properties → local variables
- configs
- definition
- default values → merged
props
object - apply* →
useMemo
prop reassignment - update* →
useEffect
- default values → merged
- assignment
- declarative → JSX
- procedural (set*)
- reference (get*)
- inside component →
props.*
- outside component
- inside component →
- definition
- methods → inline functions
- initialize → useEffect(..., [])
- this.* references
- local class members → local scope
- inherited class members → JSX ExtJS component
- .lookup → React
reference
- ViewController
- properties/configs/methods → Component
- .getView() | .view → this
- variables referencing Component → this
- transform lifecycle methods
- ViewModel →
state
, render
- Ext
- .dataview
- .DataView
- .List
- .field
- .form
- .FieldSet
- .layout
- .Card
- .Button → Button
- text → text
- handler | tap listener → onClick
- pressed → active
- .Component → div
- html → child element
- .Container → div
- items → child elements
- .Image → img
- src → src
- html → div + background-image + child
- .Panel → div
- .SegmentedButton → ButtonGroup
- .TitleBar → div
- title → child element
- .Widget → div
- disabled → disabled
- .dataview
- Stores
- Models
-
.get('foo')
→.foo
-
foo.set(fieldOrObject[, value])
→modifyRecord(foo, fieldOrObject[, value])
action - .getData
- .isModel
-
- Proxies
- Ext
- .Ajax
- .getDefaultHeaders
- .on
- .request
- .Array
- .clean → _.compact
- .clone
- .each → Array.forEach
- .contains → Array.includes
- .difference → _.difference
- .flatten → _.flattenDeep
- .indexOf → Array.indexOf
- .intersect → _.intersection
- .map → Array.map
- .pluck → _.map
- .remove → _.pull
- .slice
- .sort
- .toArray
- .unique → _.uniq
- .browser
- .browser
- .name
- .version.version
- .is
- .AndroidStock2
- .WebView
- .browser
- .Date
- .add
- .between
- .clearTime
- .diff
- .format
- .getShortDayName
- .getShortMonthName
- .parse
- .DAY
- .MINUTE
- .monthNames
- .DomQuery
- .is
- .Function
- .bind → Function.bind
- .createBuffered
- .createSequence
- .createThrottled
- .interceptBefore
- .JSON
- .decode → JSON.parse
- .encode → JSON.stringify
- .Number
- .constrain → _.clamp
- .from
- .toFixed → Number.toFixed
- .Object
- .each → _.forEach
- .getSize → Object.keys(...).length
- .merge
- .String
- .capitalize → _.upperFirst
- .escapeRegex
- .htmlEncode
- .leftPad → String.padStart
- .repeat
- .trim → String.trim
- .trimRegex
- .Template → JSX
- Member functions
- Dynamically-determined template substrings
- {...}
- {(field|.):(fn|this.fn)(...)} → {(Ext.util.Format|helper).fn((field|data), ...)}
- {[ ... ]}
- values
- out
- parent
- xindex
- xcount
- xkey
- {% ... %}
- tpl if
- tpl elseif
- tpl else
- tpl for
- .util
- .Format
- .date
- .htmlEncode (see Ext.String.htmlEncode)
- .Inflector
- .plural
- .pluralize
- .singular
- .Format
- .apply → Object.assign
- .applyIf → _.assignWith
- .baseCSSPrefix → 'x-'
- .bind (see Ext.Function.bind)
- .clone → _.cloneDeep
- .defer → setTimeout
- .Deferred
- self
- .rejected
- .emptyFn → () => {}
- .encode (see Ext.JSON.encode)
- .isArray → _.isArray
- .isDate → _.isDate
- .isDefined → !_.isUndefined
- .isEmpty → _.isEmpty
- .isFunction → _.isFunction
- .isNumber → _.isFinite
- .isNumeric → _.isFinite(+...)
- .isObject
- .isString → _.isString
- .Promise
- .toArray
- .Ajax
ExtJS classes are namespaced while, with modules, ES6 class names are generally not. To make the transition, e2r uses the xtype or alias of your ExtJS class as the ES6 class name. To convert lowercase xtypes to properly-cased class names, we need to distinguish words. To this end, e2r builds a word list from the class names and namespaces used within your codebase and the ExtJS framework. While this works fairly well, it requires some manual tuning:
- Run
npm run classnames
in the extjs2react directory - Go through this list of all of your classes and add mis-capitalized words to the
words
array in/config.json
- Rinse and repeat until the class names look good
- Note: if a word has no effect, use a larger portion of the class name (longer words take precedence and yours may have been overriden)
e.g., FaceidSetup
→ add FaceID
to config → FaceIDSetup
(if no effect, add FaceIDSetup
)
ExtJS dependency management is global, while ES6 dependencies are local. To make the transition, e2r builds a registry of all class names and aliases, determines each source file's requirements, and adds corresponding import
and export
statements.
This modularization can lead to circular dependencies. In webpack, the imported class will become undefined and, if extended, will produce the following error:
Unhandled Rejection (TypeError): Super expression must either be null or a function
To resolve these issues, I recommend using a tool like circular-dependency-plugin and refactoring the original code as needed.
Since e2r does a static code analysis, it isn't able to pick up on dynamically generated Ext.define
calls. If you are dynamically creating ExtJS Classes and want to ensure these class definitions are properly imported and referenced throughout, add a comment to the file like so:
/**
* Classes:
* MyApp.model.Foo
* MyApp.model.Bar
* MyApp.user.List
*/
e2r will then add placeholder ES6 classes to the file's generated output. You can of course safely remove these as long as you export classes of the same names.
When compiling templates, ExtJS wraps tpl
conditional statements in native with(values)
blocks. This adds values
to the scope chain so we can write <tpl if="age > 1">
instead of <tpl if="values.age > 1">
.
To keep the generated JSX clean, e2r will instead auto-prefix unqualified, uncapitalized variables with values
. This will change the reference if you're referring to unqualified, uncapitalized variables outside of values
, requiring manual adjustment.
If your company is migrating an ExtJS app to React (or other framework) and would like some help, feel free to email me at my github username (looks like bfis----1121) at gmail.com