1 Introduction to TypepScript Syntax Compilation and Helper Functions
Using TypepScript’s built-in tsc
tool, you can translate ts source files into standard JavaScript code files. The configuration file tsconfig.json
can be used to configure the specific scheme of tsc
compilation.
The tsc
compilation parameter target
specifies the language standard version of the output, so in practice it can also be used as a tool to translate higher versions of ECMAScript source code to lower versions.
During the syntax translation process, additional helper functions may be needed for syntax degradation compatibility, such as compatibility with inheritance (_extends
), expansion operators (__assign
), asynchronous functions (__awaiter
), etc.
1.1 tsc
compilation results and helper functions examples and analysis
Here is a simple example.
For the index.ts
file, the contents are as follows.
tsconfig.json
is configured as follows.
Then the output of tsc@4.7.4
is as follows.
|
|
You can see that the __createBinding
and __exportStar
helper functions are injected directly into the output of the commonjs
specification, turning one line of code into 16 lines.
If there are many ts files that require the __exportStar
helper function, this obviously results in a lot of duplicate injections in the output.
1.2 Compilation parameters for tsc
noEmitHelpers
To solve this problem, TypeScript introduced the compilation parameter noEmitHelpers
in 2015. When this parameter is turned on, tsc
compilation results will no longer inject helper functions.
Let’s modify the tsconfig.json
configuration as follows.
The output of the index.ts
file in the above example will look like this.
Without the helper function injection, the code is much simpler. However, there is still the use of helper function names, which are defined in the global functions by default.
In this case, you only need to maintain a file for defining the global helper functions used in the translation results.
For small and medium-sized projects, maintaining a file of helper functions used in the project is not too complicated, but there are problems such as synchronized updates. For large, complex projects, the maintenance cost and mental burden is more obvious. In addition, if a project introduces multiple external tool libraries for tsc compilation and output, the helper functions will still have to be introduced repeatedly.
1.3 Compilation parameters for tsc
importHelpers
tsc
soon introduced the new variant parameter importHelpers
and the tslib
library to address the issue of helper function uniformity.
The importHelpers
parameter allows each project to import helper functions from tslib
once, instead of including them in every file.
Modify the tsconfig.json
configuration as follows.
The output of the index.ts
file in the above example will look like this.
As you can see, all helper functions are now imported from the tslib
library. This allows for the result that only one copy of the helper function will exist.
1.4 tsc
and auxiliary functions summary
To summarize briefly, the tsc
compilation results in three options for handling helper functions.
- inject the implementation of the helper function in every file that requires it.
- use
--noEmitHelpers
: only use helper functions but do not inject their implementation, maintain the global helper functions by themselves. - use
-importHelpers
: helper libraries are added to the project as separate modules, and the compiler imports them on demand.
Handling of babel
and swc
compilation results and helper functions
babel
, swc
and tsc
are popular tools that compile and convert Javascript code written in high-level syntax for backward compatibility.
2.1 babel
and @babel-runtime
babel
can achieve similar effects to importHelpers
by using the plugin @babel/plugin-transform-runtime
, which uses the helper library @babel/runtime
. Here is an example of the plugin’s configuration in the babel
configuration file .babelrc
.
As you can see, the helpers
parameter has a similar function to importHelpers
in tsc.
There is also a corejs
parameter, which sets whether to use the core-js
library where necessary to achieve compatibility with lower language versions of high-level syntax standards.
You can refer to the official babel documentation for more information about the functionality of the plugin’s parameters.
2.2 swc
and @swc/helpers
swc
provides the compilation parameters externalHelpers
and the @swc/helpers
library to handle helper functions.
With the externalHelpers
parameter turned on, export * from '. /common'
will be compiled as follows.
You can see that the compilation result is very similar to tsc
with importHelpers
turned on. And furthermore, each helper function is introduced as a single file, so as not to introduce unused other helper functions as much as possible.
2.3 Why do I need core-js
?
Compilers such as tsc
will only rewrite high-level syntax during syntax compilation that can be implemented using the low-level syntax standard-compatible, using helper functions. Those that cannot be directly implemented by the low-level syntax (e.g., prototype extension methods like 'abcabca'.repalceAll('a', '')
) are not handled and are output directly as is.
Both tslib
, @babel/runtime
and @swc/helpers
contain only the helper functions needed for syntax translation, not the polyfill implementation for advanced syntax. The es6 source code of the tslib
library is just over two hundred lines long.
For true full backward compatibility, a library like core-js
is needed. core-js
provides a number of polyfill implementations with advanced syntax.
As you can see, tsc
and swc
do not deal with polyfill, leaving it up to the user to decide how to do syntax-level backwards compatibility. babel
, on the other hand, uses the corejs
(based on the plugin babel-plugin-polyfill-corejs<2|3>
) parameter of the plugin @babel/plugin-transform-runtime
to decide how to inject the relevant syntax implementation.
So it can be said that only babel
and its official plugin system provide a compilation scheme that is really as backward compatible as possible.
Due to the plugin-based architecture of
babel
, there are so many plugins that it is easy to get lost between them. For the same type of problem, you may have different solutions and options in different plugins. In general, you might not actively use the@babel/plugin-transform-runtime
plugin, but introduce@babel/preset-env
to configure the compilation options forbabel
.@babel/preset-env
provides parameters such ascorejs
anduseBuiltIns
to enable custom introduction ofcore-js
.
3 Compilation options for helper functions
As we summarized in subsection 1.4
, there are three options for auxiliary functions. How should they be chosen?
In a real project, there is no single best option depending on the use and size of the project.
Generally speaking, under the current development model and tooling system, maintaining global helper functions on your own is not suitable for most projects. A tree-shaking
based build removes code from the final output that is not actually used.
3.1 Options for private business class projects
For regular business projects, a complete final solution is required. For larger projects with complex business logic, it is recommended to use libraries such as tslib
.
- When considering the size of the output, it is recommended to turn on the
helpers
parameter and uniformly reference helper functions from libraries such astslib
. - Alternatively, you can introduce
core-js
in combination with thebabel
plugin to adapt to the lowest preset language version.- Hint:
babel
can be used to analyze which specific polyfill subfiles incore-js
are actually needed based on the source code.
- Hint:
- You can also decide how to introduce
core-js
on your own. - If the project needs to be compatible with a very low runtime environment, you can also consider bringing in the full
core-js
library directly.
The helpers
parameter is turned off by default in several compilation tools, and actually injecting helper functions is irrelevant in most projects.
3.2 Public libraries for shared use
For public libraries published to npm, the priority is to adapt them to specific application scenarios as friendly as possible.
If the library is small, e.g. a few files, the output will contain very few helper functions. You can choose to inject helper functions by default.
If the project is large and complex and oriented towards Node.js-like projects, it is recommended to turn on the helper
option. Introducing a library of helper functions will avoid a lot of duplication in the final project.
3.3 Loose
: Avoid helper functions whenever possible
One of the purposes of some helper functions is to make the compiled logic as identical as possible to the high-level syntax. However, in many real-world scenarios, it is fine not to be completely consistent. Both babel
and swc
provide the loose
parameter, which when turned on, the output can be less strict and some helper functions will not be injected anymore.
Take the following example.
swc
with the externalHelpers
parameter turned on, in order to compile to the following.
swc
injects up to 6 helper functions in order to inject _toConsumableArray
without the externalHelpers
parameter turned on.
With the loose
parameter turned on, the compiled result of swc
is simplified as follows.
In the example above, the result of the variable a
is somewhat different, but there may not be a difference in the final processing of the actual business logic.
3.4 ESM (ES Module): a future-oriented solution
Analyzing the role and type of helper functions, there are two main types: high-level syntax compatibility (target
) and module loader adaptation (module
).
If the compiled result no longer requires compatibility between these two, the helper function is not needed. Just change the output to the ESM
latest specification approach.
In the case of tsc, the configuration in tsconfig.json
can be changed to the following.
Thanks to the development of language standards, the ESM modularity standard solution is gaining popularity in the community at a very fast pace.
- For non-Node.js oriented public libraries, they can be fully exported to the ESM model, leaving it up to the business application build process to decide exactly how to take it further.
- For Node.js-oriented public libraries, the community is also moving closer to the ESM standard.
- For business application development, the ESM module loading scheme is also the way forward.
It is important to note that.
- The ESM solution no longer requires module loader helper functions, and is left to the final business builder (e.g. webpack, rollup, esbuild, etc.) to integrate and handle.
- As long as the value of
target
is notESNext
, the introduction of helper functions is still unavoidable in order to handle the backward compatibility of high-level syntax.
4 Modularity in the Front End and the Development of the ESM (ES Modules) Standard
Before ES6(ECMAScript 2015)
, there was no modular loading standard for JavaScript. The open source community has precipitated well-known module loading schemes such as AMD, CommonJS, and CMD based on development practices in large projects.
4.1 UMD and ESM
For a long time, open source public libraries for multiple runtime environments in the community have been producing source code that follows the UMD
specification in order to be compatible with both AMD and CommonJS.
With the standardization of ESM
and the development of front-end engineering build tools, especially after rollup proposed an ESM-based tree shaking
build optimization scheme, a large number of public libraries for browser-oriented applications started to be built.
A large number of public libraries for browser-based applications started to provide ESM
-standardized content while exporting UMD-compliant source code.
CommonJS(CJS)
-Node.js
AMD
-require.js
CMD
-sea.js
System
-System.js
module loader and builderUMD
- Universal Module Definition, compatible with both theAMD
specification, theCommonJS
specification and the global variable approach.UMD + ESM
4.2 CJS and ESM in Node.js
Node.js has been designed and implemented from the beginning with the CommonJS (CJS) specification. For a long time there was no better option for a Node.js-only application module.
Because of the vast differences between the ESM
and CJS
solutions, it is difficult for them to coexist. A Node.js application can load a very large number of external dependencies, which makes moving to an ESM standardized solution, whether it is a public library or a private business module, a large package. The main manifestations are.
- Downward compatibility: changing the module loading scheme can result in downstream applications not being able to introduce and use it directly
- External dependencies: In order to introduce and use other public library dependencies, it is not possible to use the ESM solution directly
Because of this historical baggage, ES Module
has not been supported in Node.js, although it has been in the standard since ES6+.
The turning point came with ES2020
, which added the Dynamic import()
dynamic import specification to its language standard.
In May 2020, Node.js officially enabled support for the ES Module modularity scheme with the release of 12.17.0 LTS.
In Node.js, the Dynamic import()
syntax supports both CJS
and ESM
schemas. Thus, the backward compatibility baggage of being a public library is gone. Downstream applications of the CJS
specification simply need to change to dynamically import to reference the dependencies of the ESM
module.
Since then the ESM
modularity solution has really started to gain popularity in the Node.js community. Many popular open source libraries started to release new versions without the CJS
solution and only output the results of the ESM
solution. Some of the more representative ones are chalk@5
and others.
Based on the large adoption of the CJS
module loading scheme, the CJS
and ESM
schemes will continue to co-exist in front-end development applications for a long time, but will probably be completely replaced by the ESM
standard in the future.