How does angular define whether aot compiler should be used - javascript

This page about modules states that there are two approaches to bootstrapping - dynamic and static. They are configured only in main.ts:
// The browser platform with a compiler
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
// The app module
import { AppModule } from './app.module';
// Compile and launch the module
platformBrowserDynamic().bootstrapModule(AppModule);
versus static (aot compiler):
// The browser platform without a compiler
import { platformBrowser } from '#angular/platform-browser';
// The app module factory produced by the static offline compiler
import { AppModuleNgFactory } from './app.module.ngfactory';
// Launch with the app module factory.
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
My question is how does angular know that aot compiler should be used? There seem to be no option to indicate that. I doubt that it parses main.ts and checks whether I used #angular/platform-browser' or #angular/platform-browser-dynamic'.

You're not just importing a different module: the whole set-up is different.
In the dynamic scenario, your AppModule is loaded through the platformBrowserDynamic loader. This object knows how to JIT-compile the rest of the modules in your application.
In the static scenario, you instead provide an AppModuleNgFactory to the platformBrowser loader. This other object knows where to find the AOT-compiled files of the application.
Think about it this way: angular cannot decide on the spot whether to use the AOT compiler or not, because that wouldn't be ahead of time! What it does decide is whether to search for existing compiled files, or to generate them locally. The precompiled files already exist: it's only a matter of going after them or not.
As for deciding whether to compile the files AOT, note that the Typescript compiler configuration (stored in tsconfig.json) is also different. The angularCompilerOptions section enables AOT compilation.
Documentation: https://angular.io/docs/ts/latest/cookbook/aot-compiler.html

Related

Node package.json Export VS Imports fields

So after a bit of digging around the diferences from Export and Imports declared from the package.json file I was wondering what is the best use case for both?
For example the following fields:
"name": "node-api",
"exports": {
".": "./application.js",
"./config/*": "./config/*.js",
"./controllers": "./controllers/index.js",
"./helpers/*": "./helpers/*.js",
"./models": "./models/index.js",
"./routes": "./routes/index.js"
},
"imports": {
"#config/*": "./config/*.js",
"#controllers": "./controllers/index.js",
"#helpers/*": "./helpers/*.js",
"#models": "./models/index.js",
"#routes": "./routes/index.js"
}
And then each of the following with their output in the main JS file:
import routes from './routes/index.js'; // works
import routes from './routes'; // error - ERR_UNSUPPORTED_DIR_IMPORT
import routes from 'node-api/routes'; // works (with the package name)
import routes from '#routes'; // works (without the package name but need the #)
So why not just use the imports field?
In my opinion seems friendlier than to type your package name every time you want to import your own file.
Based on the NODE JS official docs (https://nodejs.org/api/packages.html) it says the following: "The "exports" field allows defining the entry points of a package when imported by name loaded either via a node_modules lookup or a self-reference to its own name.".
Then for the imports field says the following: "it is possible to define internal package import maps that only apply to import specifiers from within the package itself."
From my testing to reference my relative (my own created) files I just use the imports field so that I don't need to type in the package for every import that I want.
So long story short, when is it best to use exports and imports field and in my case does it make sense to use only imports?
exports is for consumers, while imports is for the internal usage (it even uses the same prefix as private class fields). If you aren't publishing a package, then you don't need to care about exports. Its main usecase is to organize the API surface of the module without spilling all its implementation guts on the consumer.

AngularJS with Typescript and Webpack managing dependencies

My team is tasked with migrating an AngularJS application to use Webpack as a build system combined with TypeScript. This is all done as a prep to gradually move an application from AngularJS to Angular. (We are following migration guides provided by the Angular team)
Today I ran into an issue with TypeScript's 'Import' statement and I can't seem to find the reason for it. I found a solution, but want to know why this is the case.
In this test run I am having 2 modules in 2 different files. AuthModule and LoginModule . I think their dependency is Circular in that AuthModule -> LoginModule -> AuthModule
//in auth.module.js (this module is loaded before LoginModule)
import {LoginModule} from './login.module'; //is just an exported string
const authModule = angular.module('auth.module', [LoginModule])';
...
//in login.module.js
import {AuthModule} from './auth.module'; //again just an exported string
const LoginModule = angular.module('login.module', [AuthModule]);
...
Those are just 2 small snippets and this built and when viewed in the browser everything was working fine.
However, the moment I converted the files to TypeScript it did not seem to like that circular dependency. The app built in webpack without errors, but bombed in the browser.
To fix it I needed to not 'Import' AuthModule in LoginModule and when providing the dependency to the LoginModule I needed to provide a local string instead of the imported String.
//in login.module.ts (don't import AuthModule)
const LoginModule = angular.module('login.module', ['auth.module']); //just provide the name of the module here as a string
...
This builds and works fine and I am fine with that, but want to understand why. What is the difference between a TypeScript import and a 'webpack' import. My .tsconfig module property is set to CommonJS . Is that why?
From what I understand, when you are importing the LoginModule you are importing an instance of that LoginModule, thus angular.js throw an error. Instead you need to provide a string inside that module declaration, that's why it's working.

Multiple TypeScripts Files Configuration

Here comes silly and simple question:
I am new to this whole webpack tools.
I have been trying to build a simple web-app using typescript and webpack. In the old days, I created typescript and compile them without bundling them.
With webpack, I already installed necessary loaders, typescript, and jQuery.
The problem is, I have 3 typescript files:
main.ts -> imports all assets (images, css) and other typescripts
functions.ts -> consist all of my custom functions/modules
ui-controller.ts
in functions.ts I always created namespaces such as:
module Functions{
export module StringTools{
export function IsEmpty(): boolean{
//some code
}
}
}
in the browser, I knew that the snippet code above will be called, but it is not recognized in the main.ts (in the run time) even thou I already import it.
This is how I import it in main.ts:
import '.src/functions'
Any suggestion how I can resolve this?
Typescript module keyword is confusing, and in version 1.5 it was indeed changed to namespace to better reflect it's meaning. Look here.
Namespaces are also called internal modules. They are meant to be used when your files are evaluated at the global scope. You can use typescript playground to see how namespaces are transpiled. The point is - namespaces are not modules.
Webpack however, does not evaluate files in the global scope, it evaluates them inside a function in order to provide real module behavior.
So what does make your typescript file into a module? the keywords export and import (but not inside a namespace like in your example).
Typescript will see those keywords, and will transpile them into commonjs\AMD\es6 require or define statements, according to your configuration in tsconfig.json. Now you have "real" modules. Then it's Webpack's job to do something (lots of info about that online) with those modules so that they will work in the browser where you don't have modules, but that part is not related to typescript.
TL;DR -
So how would you do this in Webpack?
/* functions.ts */
export function IsEmpty(): boolean{
//some code
}
and
/* main.ts */
import {isEmpty} from './functions';
if you want better code organisation as suggested by your use of module StringTools, just split into different files. You can read more about es6 import syntax for more info.
The first part of this answer is that your main module (which has been replaced with the namespace keyword) is not exported... so there are no exported members for your functions file.
export namespace Functions { //...
Part two of the answer is that if you are using modules, you don't need to use namespaces as the module is enclosed within its own scope.
Working version of the file as you designed it, I'd say drop the wrapping namespace:
functions.ts
export namespace StringTools{
export function IsEmpty(): boolean{
//some code
}
}

Do ES6 modules only import what is used?

Is there is a performance or behavioural difference between importing from an index file or importing individual modules?
For example, with an index file (#/modules/user) of...
import routes from './routes'
import controller from './controller'
const user = {
routes,
controller
}
export default user
If I import just the routes from that file...
import user from '#/modules/user'
const routes = Router()
routes.use('/user', user.routes)
Is this any different to just importing the routes individually from their own file (#/modules/user/routes)? Does the controller get imported as it's in the user object?
import userRoutes from '#/modules/user/routes'
const routes = Router()
routes.use('/user', userRoutes)
There are currently no native ES modules in Node.js. The actual difference depends on the toolchain. When the application is built with Webpack/Rollup (a minifier is potentially needed too) and configured to use ES modules internally, tree-shaking can be applied. This is best case scenario.
This would be a case for tree-shaking if there were a reexporting module:
import routes from './routes'
import controller from './controller'
export {
routes,
controller
}
And it was imported like
import { routes } from '#/modules/user'
However, the cases in the original post are different. In one case, once user constant is imported, it's impossible to remove unused controllers property from it with tree-shaking. In another case, #/modules/user/controller module remains unused and doesn't need tree-shaking. It will be ignored even if the application is configured to use CommonJS modules.
So yes, it's possible for ES modules to import only modules that are in use, and this heavily depends on actual code and project configuration.
Client-side applications primarily benefit from tree-shaking because it affects bundle size. This concern shouldn't be taken into account in server-side Node.js application - unless unused module imports massive amount of third-party modules that aren't used anywhere else.
Currently you can only use the import syntax using transpilers(converters from one syntax to another) as it is not yet natively supported by engines. Lets take babel for example.
Babel converts the import to CommonJS style code that can work within Node.js. While the syntax is conformant to ES6, the implementation is not.
Babel converts the syntax into CommonJS, evaluating the imported code before determining what is being imported. With ES6, the imports and exports are determined before the code is evaluated.
With future support of es6 imports inside node, importing a specific function inside the code will only return that exported function, without evaluating the whole script in the targeted file.
Currently they work the same since transpilers convert them to traditional node require syntax.
However, you can use webpack Treeshaking to remove unused code from the output file, this way they are almost identical in behavior.
The # syntax
The # syntax depends on the module loader or module bundler. The module loader is not part of the ECMAScript spec. You most likely have something like babel-plugin-root-import in your webpack/babel config.
Basically it means from the root of the project.. it avoids having to write things like import Component from '../../../../components/component'. in this case, it has nothing to do with partial imports.

Adding typescript import prevents compiling to a single js file

I am use grunt-typescript to generate a single js file from a set of ts files. This works fine until I add an import statement to one of the ts files.
Example grunt-typescript config
typescript: {
server: {
src: ["./ts/file1.ts", "./ts/file2.ts"],
dest: "./js/out.js",
options: {
module: 'amd', //or commonjs
target: 'es5', //or es3
basePath: '',
sourcemap: false,
declaration: false,
ignoreError: false
}
}
}
If I add an import statement to the top of file2.ts e.g.
import PG = require("pg");
Then I get errors that code in File1.ts can't find types defined in File2.ts and I get an unexpected File2.js generated in the /ts directory ignoring the dest file parameter. The import seems to cause it to compile File2.ts totally separately.
Is this to be expected with import or how can I fix this to create the expected single js file without compile errors?
As soon as you import an AMD module, or export from outside of any internal module, your file will be compiled as an AMD module. AMD and single-file compilation are inherently different modes of working and don't like to be mixed. To read up on internal vs external modules check out this TypeScript wiki page.
You technically can still import AMD modules using the standard JavaScript method, but it's awkward. For example, using the require.d.ts file from DefinitelyTyped:
/// <reference path="require.d.ts"/>
require(["somemodule"], (SomeModule: namespace.SomeModule) => {
// async code called after the module is retrieved
});
Without the import keyword TypeScript does nothing about the require and leaves you on your own.
Alternately I would recommend going full AMD. If any of your libraries are AMD it's easier to work with, and you can still compile down to a single file when it's time to release.

Categories

Resources