I'm relatively new to ES6+ (called modern JavaScript) but it seems if I want to use it in browsers I need babel-minify or terser. (First I thought Babili is another player but it's just the old name of Babel-Minify)
About polyfills for the browser there are production ready solutions like #babel/polyfill or Polyfill.io and with them it can be achieved to send smaller + faster code to modern browsers because they need no/few polyfills (test the browser quickly, load the needed polyfills dynamically and then start our app's main script). So it seems absolutely reasonable to use these modern technologies.
Here comes my dilemma about choosing babel-minify or terser.
The Webpack team decided to switch to terser in upcoming Webpack 5.
The Babel team made a comparison table showing terser is much better in speed.
The docs says that terser is a fork of uglify-es which was widely used before.
These makes me think that I have to choose terser.
But on the other hand, Babel is still needed for transforming (and can be used for many useful things). They are in the business a long time ago (although Babili/babel-minify was first released on August 26, 2016, so uglify is older). They have a great developer community on GitHub, bugs may have discovered and fixed sooner. Based on these I feel more trust to them when it comes to production safe output. But I haven't found any article showing the pro's of babel-minify over terser.
Questions:
I would go with terser because it seems promising and the reasons written above, but:
What are the cases when I should use babel-minify over terser?
Does it have any advantages of doing all the things with Babel packages?
In most cases, it won't matter too much whether or not you use terser or babel-minify. That said, the benefit to using babel-minify would be tight integration with the rest of the babel ecosystem. If you are using babel outside of a bundler like webpack, or on the CLI, babel-minify can be ran at the same time as other babel transforms, and therefore requiring minimal additional config. Babel-minify would also be able to use the same cache as the rest of the babel plugins, if you have caching enabled through for example babel-loader.
Originally, babel-minify (then babili) was created because there was no uglify-js version compatible with ES6 or newer, and babel already had a parser that supported the new syntax. Since then, terser has become a good alternative, and performs faster than babel-minify while still supporting ES6 (probably because it's not tied in to babel's transform pipeline). Due to this and the reasons you listed, terser would probably be the best option to choose now.
One possible exception would be if you use experimental syntax that has not yet been standardized as part of ECMAScript, but is supported in babel's parser (possibly with a plugin). In this case, babel-minify might prove to be beneficial.
Related
I was considering updating my Node/Express code to use import instead of require by adding to my package.json file the line:
"type": "module",
However, I did not know why most Node/Express code I see, still uses require while code I see in the browser via webpack uses import.
Is there a reason for this duality? Is upgrading to import recommended in general? Is import because it is newer considered an upgrade and a better technology?
This previous Q/A is over 7 years old and has lot of historical information. I am looking for more current information as the language has evolved.
The reason for having two types of modules is purely historical.
Back in 2009 when Node.js was created, ES modules (import syntax) did not exist yet, but there existed other nonstandard alternatives, with the most important being AMD and CommonJS, for which several browser implementations were available. Node.js decided to adopt CommonJS (require syntax). For this reason, all packages deployed up until 2015 are CommonJS packages.
Native ES modules were introduced with the ECMAScript 2015 standard, but they only made it as an experimental technology in Node.js 8.5 in 2017, and it wasn't until Node.js 12 (2019) that their support became good enough to place them as an alternative to CommonJS (although this is subjective).
For this reason, a large part of software developed for Node.js still uses CommonJS modules with require statements.
Whether ES modules are better is a matter of opinion, and not a proper question for StackOverflow. At least, they are standard JavaScript, which makes them fit for use both in browsers and in Node.js, which is imo a big advantage.
A disadvantage is that ES modules cannot be required in CommonJS code - the opposite is true: CommonJS modules can be imported by ES modules - so if any of your dependencies uses ES modules, that may be a good opportunity to switch your code to ES modules, too.
So should we upgrade to use import in general? Of course, that's also a matter of opinion. And if that was my opinion, the answer would be "no", because there is no reason to change a running system. CommonJS code is not going to rust or worsen simply because a better alternative exists nowadays, although ES modules are a better choice for new code. Node.js, as long as people use it, is not going to drop support for CommonJS either.
There is a project called Deno which is designed to provide a more modern and secure alternative to Node.js to run JavaScript code on the server. Deno was originally designed to work with ES modules only, but it soon became clear that not supporting CommonJS modules was a major issue, because so many npm packages were not working, so it introduced a compatibility mode to support CommonJS modules under a flag. The moral of the story is: removing support for require is good idea, but re-adding it is an even better one.
How to make package built on top of TS 4.X compatible with 3.X? Like, if you have newer version -> use new features, else -> use any or unknown or whatever is supported in older version.
Is there any possibility to use directives for that purpose?
Preface
This answer assumes the question is about taking source code (.ts) of a project written using TypeScript 4.x constructs and making type declaration files (.d.ts) emitted from them compatible with a TypeScript 3.x compiler for the benefit of the users of the package- as opposed to writing source code that uses 4.x constructs and somehow making it compatible with a 3.x compiler (the way the question is phrased is ambiguous with respect to this). I make this assumption because:
If you decide you want to use language features in your source code that aren't supported by older compiler versions, you are (whether you realize it or not) making a decision to drop support for building the project using those older compiler version. If you have a strong reason to want to support building the project using those older compilers, then I'm pretty sure you just have to not use those newer language features (or somehow convince the maintainers of the compiler to backport those changes to older compiler versions, which I think is pretty rare).
I'm not personally aware of any reasons not to upgrade your compiler version unless you have very strict security policies and require your dependencies and build tooling to be audited. I'd wager that that's pretty rare in the JavaScript development scene where the landscape is known for changing rapidly.
downlevel-dts
Note: TypeScript has a feature to "downlevel" the JavaScript it emits to convert language constructs from later versions of the ECMA Script standard to constructs that work in older versions. (see the compileOptions.target field of tsconfig).
As far as I know, TypeScript itself doesn't have such a feature to downlevel the typings files it emits (including triple-slash-directives at the time of this writing), but Nathan Sanders (a maintainer of Definitely Typed) maintains an open-source project, downlevel-dts, to downlevel .d.ts files which can downlevel typings all the way down to typescript v3.4 syntax.
package.json.typesVersions and semver-ts.org
The "'Downleveling' Types" section of semver-ts.org explains how you can script downleveling types for each typescript version where new non-backwards-compatible language constructs were introduced and how to tell a package-user's compiler which version of the types to use and where to find them:
When a new version of TypeScript includes a backwards-incompatible change to emitted type definitions, as they did in 3.7, the strategy of changing the types directly may not work. However, it is still possible to provide backwards-compatible types, using the combination of downlevel-dts and typesVersions. (In some cases, this may also require some manual tweaking of types, but this should be rare for most packages.)
The downlevel-dts tool allows you to take a .d.ts file which is not valid for an earlier version of TypeScript (e.g. the changes to class field emit mentioned in Breaking Changes), and emit a version which is compatible with that version. It supports targeting all TypeScript versions later than 3.4.
TypeScript supports using the typesVersions key in a package.json file to specify a specific set of type definitions (which may consist of one or more .d.ts files) which correspond to a specific TypeScript version.
The recommended flow would be as follows:
To avoid copying too much from off-site material (plagiarism), I'll summarize the steps in my own words (go read the source for the full steps with examples):
Install downlevel-dts as a dev-dependency (and some other helper tools).
Call downlevel-dts to downlevel types to whichever older type declaration versions you want to support (this can be scripted).
Update your package.json to register call your script after generating types for the more recent type declaration verion.
Register your generated older-version type declaration files in your package.json file using the typesVersions field.
Make sure the generated files are included with your package's files (update the files field, or whichever similar fields you are using).
limitations of downlevel-dts
Note that there are limitations. The following is a quote from downlevel-dts's readme:
Note that not all features can be downlevelled. For example, TypeScript 4.0 allows spreading multiple tuple type variables, at any position in a tuple. This is not allowed in previous versions, but has no obvious downlevel emit, so downlevel-dts doesn't attempt to do anything. Be sure to test the output of downlevel-dts with the appropriate version of TypeScript.
Problematic aspects of other proposed solutions:
"Use an older typescript version to emit your typings"
This assumes the asker of the question is using the TS compiler to emit typings from .ts files and doesn't work if they are maintaining them by hand, which is often the case for very large projects that were first written in JS and don't have the bandwidth to migrate to TS.
This probably requires you to make your .ts source code to not use TypeScript language constructs that were introduced in a version newer than the compiler that you have to use to emit typings in the TypeScript language version you want to emit. This is not ideal because it may be much cleaner to write code using newer language constructs, or impossible to do something with just older constructs.
"Maintain typings for both TypeScript language version"
This seems to make the opposite assumption: That the project maintains manual typings files for manually written JS code instead of being transpiled to .js and emitting .d.ts from .ts files.
This is a big maintenance burden (it's already a big maintenance burden to manually maintain .d.ts files!). It may be acceptable for small projects, but not for libraries with a large or complicated API surface.
There is no way to switch between supported and unsupported features in TypeScript.
For instance, if you have a library which is built on top of TS 4.0 with variadic tuple types there is no way to use it in a package where TS 3.0 is used.
However, you can maintain two versions of your typings: before TS4 and after TS4. For instance, take a look how lodash or react maintaince several versions of typings.
I have read several "ESM in NodeJS" articles as of late, consequently; I decided to convert a CJS module I had into an ES-Module (2020). Before I even began writing code, I noticed something that was overwhelming — though it wasn't completely unexpected — my page was one big red squiggly error. I found it easier to just rewrite the whole module, even though it took me a couple days.
So now I am at a point where I can't help but to wonder if ESM is necessary. To put it more simply: Am I going to run into comparability issues down the road if I don't start using ESM modules today, or can I continue to use CJS?
All new projects should be written as ESM modules as that is the present and the future of the Javascript language and nodejs. It's highly, highly unlikely that CJS modules will lose support for a very long time because a huge portion of the nodejs eco-system is still CJS modules and I rather doubt that the nodejs leadership wants to create the kind of situation Python did with 2.0 => 3.0 which was not pleasant in the developer community. Plus, the dual compatibility in nodejs is already there and not really harming anything right now.
There are some CJS conveniences like __dirname that are not as convenient in EJS. And, EJS requires static export declarations (they can't be computed like they can be in CJS) which is good news for bundlers, but rules out some situations where you might to do dynamic exports.
ESM is the module standard in Javascript. Nodejs is moving to fully support that standard. New code should be written as ESM modules. I would expect some new features in the future to only be available in ESM modules as that's where the development and innovation is happening.
CJS is node-specific, not a Javascript standard. The developers behind nodejs took ideas from existing 3rd party module systems, molded them to fit into the needs of nodejs and built them into the first version of nodejs. They are not a Javascript standard. ESM are now part of the Javascript standard and nodejs has been adopting that new standard and filling in a few bits where they needed more than the standard offers.
I've recently gone down the rabbit hole of learning about modular programming with JavaScript, including some history of JS module systems, bundlers and ES2015 Modules. I now understand some of the pains that bundlers help/ed alleviate, such as:
network latency (more effective caching of a single bundle, HTTP/1.0 connections),
performance constraints on the size of application modules (minification, tree-shaking),
ES2015 not supporting some features (bare imports),
and backwards compatibility with older module systems (transpilation of ES2015 Modules syntax).
However I'd like to know if it is possible to create a production JS web application in 2020 that doesn't use a bundler like webpack or Parcel and uses ES2015 Modules? A caveat being that a source-to-source compiler like Babel could still be used provided it preserves ES2015 Modules syntax. I'm not saying I would like to do this but for the sake of argument, what would be the downsides?
Client-side code does not have to be bundled when run in a modern browser, but if you're going to design your code into tons of small modules (for the sake of development efficiency and reuse), then it will be very inefficient to load if you don't use a bundler that can reduce the number of separate files that need to be loaded.
A bundler will be desirable if you design your client JS files for best modular development and thus don't design your client JS files for efficient delivery as the bundler can bundle things together for efficient delivery in a build process.
"Part of the JS SDK" is a matter of terminology and opinion, not facts so I won't really comment on that assertion.
It is certainly possible to design client-side JS files from the beginning for efficient client-side delivery and not use a bundler (like we used to do), but you will not be able to also design the layout of the files for modular development. A bundler allows you achieve both goals which is probably why they are so popular.
However I'd like to know if it is possible to create a production JS web application in 2020 that doesn't use a bundler like webpack or Parcel and uses ES2015 Modules?
Yes, it's possible without a bundler if you run in a browser that supports import and export.
I'm not saying I would like to do this but for the sake of argument, what would be the downsides?
As explained above, you would either give up efficient client-side loading or you would give up ideal modular design as the two have conflicting design parameters.
I've noticed some web projects using typescript and webpack also use babel to finish off the compilation. For example, they use ts to compile to ES2015 and then use babel to compile to es5. Why not just use ts directly to compile to es5?
Is it in the case the project also has js that needs to be compiled so they just use babel for everything? Or what am I missing?
Thanks.
There's a few possible reasons for this.
They're using Babel to automatically polyfill - TypeScript only performs syntactic transformations, leaving the user to figure out what runtime libraries they'll need to be around (e.g. Promise, Symbol, etc). This allows you to decide which implementation of these polyfills will work the best for you, but it can be a pain. Babel spares you the burden of thinking about that. It's a tradeoff.
They need it for a custom transformation - TypeScript has a transform pipeline, but it's only accessible when you use the TypeScript API at this point in time. If you're already using Babel and want to start using TypeScript, but you're already using a transform, this is a reasonable compromise.
It was created when TypeScript didn't support compiling generators to ES5 (or even back when TypeScript didn't support async/await in ES5) - TypeScript has supported async/await in ES5 since 2.1, and has supported generators behind the downlevelIteration flag since 2.3. Before that, users often relied on Babel to pick up the slack, but Babel isn't needed here anymore.
It was created before Webpack 2 and the project used a specific way of importing modules - TypeScript has an allowSyntheticDefaultImport option which tells TypeScript that default imports can be used to import certain modules. Babel supports this behavior, but Webpack didn't until Webpack 2 came out. Babel isn't needed here anymore for newer versions of Webpack.
This might not be the complete set of reasons, but it's a few I can think of off the top of my head.
Why not just use ts directly to compile to es5?
That is what I do.
About mixing Babel and TypeScript
There is no flaw in using the both together. Since both do:
(non js OR js) => standard js
You can do
(non js OR js) => standard js => es5
Either by TS -> JS -babel> ES5 or Babel -> JS -ts> ES5
The reason people do it is for varied syntax support : https://kangax.github.io/compat-table/
Personally
As mentioned. I don't use Babel as I TypeSafety is big for me and don't need to use syntax that isn't yet type safe 🌹
Typescript is the evolution frOM ES2016 onwards. Typescript helps the developer from c# and java background to become javascript developers using various tools. Visual Studio code, WebStorm, Sublime etc.
Why: We can not use Typescript alone for converting ts to ES5
Compiling to ES5 with TypeScript is not as complete as it is with Babel. Some modern language features, such as Array.prototype.find, cannot be compiled to ES5 with TypeScript.
Here is the link that will help you out: https://www.stackchief.com/blog/TypeScript%20or%20Babel%3F