I am trying to understand the typescript module compiler option.
I went through typescript documentation - docs
It says module option is to Specify module code generation.
What does that mean?
Does it mean if I put module option as commonjs, then the compiler compiles the code to commonjs? But then we have options like esnext, es16. After I went through Docs: Difference between esnext, es6, es2015 module targets, I understood that import() expressions are understood in esnext. Anyway the purpose of compiler is to compile the code into browser understandable syntax(commonjs). So compiling code to the module type given doesn't make sense.
So does it mean the module type you give tells the compiler in what syntax the code is written? Meaning from which code it has to compile it to commonjs? But then we have module type commonjs which is frequently used but we almost never write code in pure commonjs syntax.
what is the purpose of tsconfig.json? stackoverflow answer says module specifies module manager. I don't understand what that means.
I also went through Understanding “target” and “module” in tsconfig and tsconfig module options - does 'System' refer to SystemJS?.
None of these seem to answer my question properly.
tsconfig.json
{
"compilerOptions: {
"module": "esnext"
}
}
TLDR; module in tsconfig.json tells the compiler what syntax to use for the modules in the emitted .js files. Frequently used values are "commonjs" (require/module.exports) or "ES2015" (import/export keywords), but there are other module systems. module affects the module syntax of emitted code while target affects the rest.
What does Specify module code generation mean?
"module" in tsconfig.json tells the Typescript (TS) compiler what module syntax
to use when the files are compiled to Javascript (JS).
When you set "module" to "commonjs" in tsconfig.json, this means that the modules in the compiled .js files will use the commonJS (CJS) syntax, so var x = require(...) and module.exports = {...} to import and export.
If you changed "module" to "ES2015" for example, the compiled code would use the import and export keywords used in ES2015 module syntax. For an overview of the other syntaxes you can take a look here.
There are several different module systems with CJS and the
native ES Module (ESM) format probably being the ones most widely used.
What to choose depends on your requirements. If it's for a server-side project
that uses Node.js then probably CJS, if it's for an Angular front-end application
than perhaps ESM (or their own NgModules but that's going beyond scope here).
A somewhat similar situation is library and package designs and how you would
like to expose them to consumers. This depends on what sort of users are going to consume
the code, what are they working with (browser, Node) and which of the module systems
is best suited for the job?
ES Modules are now the built-in standard for importing/exporting modules in JS but back when there was no native solution other module systems were designed: This is why we also have CJS, AMD and UMD modules around. They are not all obsolete, CJS is still used a lot in Node.js and the AMD module loader for example allows non-JS imports which can be useful in some cases.
Nowadays, all the modern browsers and Node 13.2.0+ support the ESM format (see this page for compatibility data and more background on modules).
But then we have options like esnext
Newer JS versions sometimes contain more features for module import/export.
setting "module" to "ESNext" enables support for these features which often are not added to official specifications yet. Such as the import(...) expression which is a dynamic import.
Does it mean if I put module option as commonjs, then the compiler compiles the code to commonjs?
The "module" setting does not affect the rest of the code, "target" is used for that instead and specifies what JS version the outputs should be compatible with.
This has been explained in other threads, I'm just adding it here for clarity.
Say you want to use require(...) and module.exports = {...} in a Node project but also want the code to utilise ES2015 features like let and const in the code (for readability/performance/other reasons).
In this case you would set "module" to "commonjs" and "target" to "ES2015" in tsconfig.
Anyway the purpose of compiler is to compile the code into browser understandable syntax(commonjs).
Yes, the compiler has to turn TS code into JS that a browser understands.
However, JS is no longer limited to browsers, Node runs in other environments (servers) for example. CJS was in fact intended as a server-side module format while AMD modules were/are used for browser imports/exports.
So does it mean the module type you give tells the compiler in what syntax the code is written?
It tells the compiler in what syntax to write the modules in the output .js files
Related
Time is flying and now the Node.js ver.10.0.0 have been out for the public. While I'm working with my MERN stack, frequently I have to switch between ES6 import/export syntax for the front-end (React.JS) and the CommonJS module.exports/require syntax in my Node/Express server-side. And I'm really wish the writing style could be unified in import/export shortly without using Babel and get it from the native support.
A good news is since the last year I read James' post on Medium addressing in the difficulty of implementing the ES6 module system in Node.js, the experimental ECMAScript Modules is now in stability 1 for a while, which means it could be enabled by --experimental-modules flag.
However, when I'm trying to use import/export syntax on Node, it is never working. For example, I have try this:
import fs from 'fs';
console.log(typeof fs.readFile);
The above code will throw me error:
(function (exports, require, module, __filename, __dirname) { import fs from 'fs';
^^
SyntaxError: Unexpected identifier
I'm really sure I have enabled the experimental flag calling as $node --experimental-modules, so what should I really do in order to kick the ES6 import/export experimental module working on my Node local server? What am I missed?
Added:
The reason why I want to try this experimental feature is to have consistent coding style in both front and back. And because it is now available natively, so I want to get ready to it. Once it have been reach in stage 2, I then can adapted to import/export quickly and have less pain.
But obviously, Google (ref) and AirBnb(ref) have different view points upon if we should use import/export syntax or not in their code-style guide. And based on Google, I'm still surprising that the semantics of ES6 import/export is not yet finalized while ECMA2019 is on its way. I'm just wonder when I can really use import/export in my project, or what is really need to be finalized?
Updated:
After pointed out by Jaromanda X, if I changed my file name to .mjs then it works. Originally I thought is the module to be loaded have to be named in .mjs extension, but it seems I was wrong. However, if that is the case, which means I will need to renamed all my project file in .mjs... and that is not appealing. Just wonder if there is a way to use this feature it in the traditional .js file? What should I configure?
Right at this point ECMAScript Module is not yet finalized in node.js v10, so it is not recommended to use it in the production environment. Since the use of import/export is standardized in the ECMAScript specification, so no matter how, it is important for node to provide such supports for the community.
The major differences between require() and ECMAScript Module is how node is going to handle its cache. Unlike require(), so far ECMAScript Module have no such API to manipulate the module cache. Also, it is unknown if it would be directly supported in the normal *.js files without a loader, so stay tuned.
The direction right now is try to introduce a new *.mjs file to explicitly showing it used the standard JS module (in accordance with ES6), but if one read the doc closely you actually could see it is also possible to specify your own extension match, including the use of the more traditional *.js. However, it required more configurations and the use of --loader flag to load the extra configuration.
There are 6 different formats are supported, including the one for modules written in C++ (it can also be loaded by require()), which is very powerful. In short, to use *.js file extension, the key setting in the loader is to have:
const JS_EXTENSIONS = new Set(['.js', '.mjs']);
And the loader function will need to check as if the file to be loaded is in JS_EXTENSIONS data set. Here, the key in the loader function is that it should return an object that have two properties: url and format, i.e.:
return {
url: (...), // e.g. './x?n=1234'
format: 'esm', // there are 5 + 1 dynamic formatting
};
It would be recommended to named the loader as custom-loader.mjs, and use it as $node --experimental-modules --loader ./custom-loader.mjs since it was demonstrated in the official doc. Once you have configured the loader correctly (based on what you need), then you should be able to use import/export as if you may used in a front-end react app in your node project.
I am totally new to Typescript and Javascript as well, so this is undoubtedly a noobie question. I was looking at some Typescript 2.0 tutorials on writing modules. In the typescript documentation, the authors describe both the ES6 module import/export methods, and the CommonJS, AMD, and other import/export methods.
However, it seems that in my tsconfig.json I can specify which module handler I want to use when compiling: AMD, CommonJS, etc.
So my confusion is, can I write the import/exports using the standard ES6 statements with import {}..., and then those get converted to the appropriate CommonJS or RequireJS syntax on compilation? Or do I need to write the appropriate CommonJS/RequireJS, etc. syntax in typescript and then the compilation step will just use whatever I produce?
Yes, it affects the generated code, not how you should write your code.
As a learning exercise, I'd recommend looking at the output with each of the values: None, CommonJS, AMD, System, UMD, ES6, ES2015 and ESNext.
Note that some compiler options aren't available in some configurations:
outFile can only be used with AMD or System
However ES6 / ES2015 may be used when targeting ES5 or lower
can I write the import/exports using the standard ES6 statements with
import {}..., and then those get converted to the appropriate CommonJS
or RequireJS syntax on compilation?
Yes exactly, import and export are part of TypeScript language, and they are virtually identical to import and export in ES6.
TypeScript will compile them according to the combination of module and target settings in tsconfig.json - if target is es6 or above (or if module itself is es6), import and export will remain in generated javascript, otherwise they will get converted to AMD or commonJS syntax.
I'm using Webpack 1 for a frontend project and I have a legacy internal Javascript library which uses its own module system similar to AMD. A module is defined with a code similar to this:
MyLib.define('module id', ['my', 'module', 'deps'], function (my, module, deps) { /* module code */});
Then you can use the modules with a code like this:
MyLib.require(['dep1', 'dep2'], function (dep1, dep2) {});
I would like to be able to use MyLib along with all the other CommonJS modules I'm already using. Can webpack support this somehow?
As far as I know, Webpack doesn't support custom module formats (if someone else knows otherwise, I'd love to be proven wrong, though).
You perhaps could work around this by writing a Webpack loader or a Babel plugin that converts the syntax to one of the module formats that Webpack supports. The latter is what Babel used to do for ES6 imports before Webpack supported them out of the box - it'd just transform them into CommonJS require calls.
For example, can we write using AMD?
define([
'hb!./some/file.hb'
], function(template) {
//
})
To some extent, yes.
Because TypeScript is just a superset of JavaScript, any valid JavaScript is also valid TypeScript.
In your example, the compiler would complain about define:
error TS2304: Cannot find name 'define'.
You need to tell the compiler that define exists before you use it:
declare var define;
define([
'hb!./some/file.hb'
], function(template) {
//
})
This will let the compiler know that define exists, but it does not provide the compiler with any additional information about it. So, another solution would be to add the proper type definition.
To consume amd modules, you will still need to include an amd module loader. That isn't something that is built into TypeScript. TypeScript is just a super set of JavaScript.
Using TypeScript enables compiler type checking, and allows you to use newer JavaScript features, while compiling to older versions of JavaScript. However, TypeScript won't be able to understand what it is that another module exports, therefore you will need a declaration for each AMD module describing what the module exports.
To that end, as another answer points out, you can also write modules using the ES6 syntax, and compile them to the amd format. From the documentation at TypeScriptLang.org:
Depending on the module target specified during compilation, the compiler will generate appropriate code for Node.js (CommonJS), require.js (AMD), isomorphic (UMD), SystemJS, or ECMAScript 2015 native modules (ES6) module-loading systems. For more information on what the define, require and register calls in the generated code do, consult the documentation for each module loader.
I was going through Angular2 quickstart tutorial with Javascript and Typescript as well, In javascript version I observed that components and modules are first assigned to a variable (window.app which I understood as some global variable that can be accessed across js files or script blocks) and that is fine. Coming to type script version just export and import were used, I tried to analyze the generated javascript code but understood nothing. Can some one explain me how this export and import works in Tyepescript.
Import and export in typescript are explained well by the documentation here https://www.typescriptlang.org/docs/handbook/modules.html.
Like toskv said in his comment, how those statements in your TypeScript files get transpiled into statements in your JavaScript files depends largely on the module system you set up in your tsconfig.json file.
For example, setting "module": "commonjs" will cause the TypeScript compiler (tsc) to transform your import/export statements into essentially node.js-style require() statements. This documentation has a few simple, but helpful, examples of how node.js modules work: https://nodejs.org/api/modules.html.
Using a setting of "systemjs" instead of "commonjs" will make TypeScript translate your import/export statements into a format that SystemJS understands, of which I am no expert.
This process is further complicated by the fact that Angular 2 projects also require build steps that take the transpiled JavaScript files and turn them into packaged "bundles." These bundled files are (depending on your configuration settings) concatenated, minified, and perhaps even uglified. So looking at the final javascript code that is run is really not helpful, as it was not written by humans.
For example, the Webpack build system (google webpack.js) takes require() statements it finds in JavaScript code and does some magic to wrap each module in its own __webpack_require__ function, which allows the build system to take your whole project file structure and bundle it in to one or several JavaScript files which still maintain their dependencies on each other.
In other words, by the time you look at the production JavaScript code, it's not meant to be intelligible by human readers. The flow can be simply represented by TS Source Code > TS Transpilation into JS Code > Module/Dependency Build Steps into Production JS Code.
TL;DR TypeScript doesn't actually handle the module importing/exporting. During transpilation, it converts those statements into statements other module systems (node.js or SystemJS) can understand, which are in turn converted into production code for serving an Angular 2 application.