1 Introduction to TypepScript Syntax Compilation and Helper Functions
Using TypepScript’s built-in
tsconfig.json can be used to configure the specific scheme of
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 (
tsc compilation results and helper functions examples and analysis
Here is a simple example.
index.ts file, the contents are as follows.
tsconfig.json is configured as follows.
Then the output of
email@example.com is as follows.
You can see that the
__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
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 soon introduced the new variant parameter
importHelpers and the
tslib library to address the issue of helper function uniformity.
importHelpers parameter allows each project to import helper functions from
tslib once, instead of including them in every file.
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.
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.
--noEmitHelpers: only use helper functions but do not inject their implementation, maintain the global helper functions by themselves.
-importHelpers: helper libraries are added to the project as separate modules, and the compiler imports them on demand.
swc compilation results and helper functions
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
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.
swc provides the compilation parameters
externalHelpers and the
@swc/helpers library to handle helper functions.
externalHelpers parameter turned on,
export * from '. /common' will be compiled as follows.
You can see that the compilation result is very similar to
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
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.
@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,
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-runtimeplugin, but introduce
@babel/preset-envto configure the compilation options for
@babel/preset-envprovides parameters such as
useBuiltInsto enable custom introduction of
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
- When considering the size of the output, it is recommended to turn on the
helpersparameter and uniformly reference helper functions from libraries such as
- Alternatively, you can introduce
core-jsin combination with the
babelplugin to adapt to the lowest preset language version.
babelcan be used to analyze which specific polyfill subfiles in
core-jsare actually needed based on the source code.
- You can also decide how to introduce
core-json your own.
- If the project needs to be compatible with a very low runtime environment, you can also consider bringing in the full
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.
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
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.
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 (
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
ESNext, 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
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.
System.jsmodule loader and builder
UMD- Universal Module Definition, compatible with both the
CommonJSspecification 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
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
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
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
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.