Sunday, February 18, 2018

karma-typescript-es6-transform: Fixing preset problem after upgrading

Recently, I upgraded the devDependencies in one of my projects to the current versions. This included an update of karma-typescript-es6-transformfrom version 1.0.2 to 1.0.3. Didn't sound dangorous. But afterwards my tests didn't run by karma any more. I just got:

18 02 2018 11:20:49.774:ERROR [karma]: Error: /Users/markus/Development/Ritzlgrmft/ionic-logging-service/node_modules/ionic-configuration-service/dist/index.js: Couldn't find preset "es2015" relative to directory "/Users/markus/Development/Ritzlgrmft/ionic-logging-service/node_modules/ionic-configuration-service/dist"
Transform function:

function (context, callback) {
        if (!context.js) {
            return callback(undefined, false);
        }
        if (isEs6(context.js.ast)) {
            options.filename = context.filename;
            log.debug("Transforming %s", options.filename);
            try {
                context.source = babel.transform(context.source, options).code;
                context.js.ast = acorn.parse(context.source, { sourceType: "module" });
                return callback(undefined, true);
            }
            catch (error) {
                return callback(error, false);
            }
        }
        else {
            return callback(undefined, false);
        }
    }

    at Transformer.handleError (/Users/markus/Development/Ritzlgrmft/ionic-logging-service/node_modules/karma-typescript/dist/bundler/transformer.js:108:19)
    at /Users/markus/Development/Ritzlgrmft/ionic-logging-service/node_modules/karma-typescript/dist/bundler/transformer.js:89:27
    at transform (/Users/markus/Development/Ritzlgrmft/ionic-logging-service/node_modules/karma-typescript-es6-transform/dist/transform.js:57:24)
    at /Users/markus/Development/Ritzlgrmft/ionic-logging-service/node_modules/karma-typescript/dist/bundler/transformer.js:83:17
    at _combinedTickCallback (internal/process/next_tick.js:73:7)
    at process._tickCallback (internal/process/next_tick.js:104:9)

Raed more here: karma-typescript-es6-transform: Fixing preset problem after upgrading.

Saturday, December 2, 2017

Fixing RxJS import problems with Ionic's production build

Recently, I upgraded to Ionic 3.9.x, which included an upgrade to Angular 5.x as well. Everything went smooth, even I did not understand the RXJS stuff in Ionic's change log. But... when I did the first time a production build (ionic build --prod), I got a strange error:

Error: ./node_modules/rxjs/observable/BoundCallbackObservable.js Module build failed: TypeError: Cannot read property 'type' of undefined at Object.getEffectiveTypeAnnotationNode

Read more here: Fixing RxJS import problems with Ionic's production build.

Monday, May 1, 2017

Updating from Angular 2.4.8 to 4.0.0

Recently, I updated my component ionic-logging-service to the Angular version used by Ionic 3.0.0. After installing the new npm packages, I was able to build the component. But… tslint did no longer work. I got

.../ionic-logging-service/node_modules/tslint/lib/runner.js:92
    throw new Error(messages.join("\n"));
    ^

Error: Error at .../ionic-logging-service/node_modules/@angular/core/src/change_detection/differs/iterable_differs.d.ts:15:48: Cannot find name 'Iterable'.
  at Runner.run (.../ionic-logging-service/node_modules/tslint/lib/runner.js:92:27)
  at Object. (.../ionic-logging-service/node_modules/tslint/lib/tslint-cli.js:139:6)
  at Module._compile (module.js:409:26)
  at Object.Module._extensions..js (module.js:416:10)
  at Module.load (module.js:343:32)
  at Function.Module._load (module.js:300:12)
  at Module.require (module.js:353:17)
  at require (internal/module.js:12:17)
  at Object. (/Users/markus/Documents/Ritzlgrmft/ionic-logging-service/node_modules/tslint/bin/tslint:3:1)
  at Module._compile (module.js:409:26)
This happens due to Iterable, which is a es2015 feature. I solved it by adding the following to my tsconfig.json:

"compilerOptions": {
  "lib": [
    "dom",
    "es2015"
  ],
}

But now I got a lot of errors like

Error: Error at /Users/markus/Documents/Ritzlgrmft/ionic-logging-service/node_modules/@types/es6-shim/index.d.ts:6:14: Duplicate identifier 'PropertyKey’.

This happens, since es6-shim defines the same types as es2015 lib. Therefore I removed the package @types/es6-shim.

Now tslint was fine, again. But the karma tests failed. I got the error

Error: No provider for "framework:es6-shim"! (Resolving: framework:es6-shim)

This one was easy, since I uninstalled the package, I had to remove it from the frameworks. But now I got errors like

ERROR [compiler.karma-typescript]: node_modules/@angular/core/src/change_detection/differs/default_keyvalue_differ.d.ts(23,15): error TS2304: Cannot find name 'Map'. 
ERROR [compiler.karma-typescript]: node_modules/@angular/core/src/change_detection/differs/default_keyvalue_differ.d.ts(27,16): error TS2304: Cannot find name 'Map'.
ERROR [compiler.karma-typescript]: node_modules/@angular/core/src/change_detection/differs/iterable_differs.d.ts(15,48): error TS2304: Cannot find name 'Iterable'.

Again the problem are the es2015 features. Therefore I had to add the lib to the compilerOptions in karma.conf.js. However, this was not enough, since currently PhantomJS does not support the es2015 features. Therefore I had to add a polyfill to my files:

files: [
  { pattern:
      "src/**/*.+(ts|html)" },
      "node_modules/reflect-metadata/Reflect.js",
      "node_modules/babel-polyfill/dist/polyfill.js"
  }  
],

The used babel-polyfill was already there as a dependency of another package.

And finally, everything was working as before.

Saturday, March 25, 2017

karma-typescript: Invalid syntax in bundle: 'import' and 'export' may only appear at the top level

After upgrading some dependent modules, I got:
[Error: Invalid syntax in bundle: 'import' and 'export' may only appear at the top level (79532:186) in /var/folders/zk/6bvt6fls7g97ry69c8jykn_h0000gn/T/karma-typescript-bundle-62976QkhHVRO4DC9R.js]
Error: Invalid syntax in bundle: 'import' and 'export' may only appear at the top level (79532:186) in /var/folders/zk/6bvt6fls7g97ry69c8jykn_h0000gn/T/karma-typescript-bundle-62976QkhHVRO4DC9R.js
    at writeBundleFile (.../node_modules/karma-typescript/lib/bundler.js:171:23)
    at flushQueue (.../node_modules/karma-typescript/lib/bundler.js:99:9)
    at bundleQueuedModules (.../node_modules/karma-typescript/lib/bundler.js:94:9)
    at invokeFunc (.../node_modules/lodash.debounce/index.js:160:19)
    at trailingEdge (.../node_modules/lodash.debounce/index.js:207:14)
    at timerExpired [as _onTimeout] (.../node_modules/lodash.debounce/index.js:195:14)
    at Timer.listOnTimeout (timers.js:92:15)

The solution was to:
  • npm install karma-typescript@beta (I am using now 3.0.0-beta.2)
  • npm install karma-typescript-es6-transform@latest (I am using 1.0.0-beta.6)
  • add to karmaTypescriptConfig:
    bundlerOptions: {
      transforms: [
        require("karma-typescript-es6-transform")()
      ]
    }
    
For details see How to use es6 library.

Friday, March 24, 2017

Upgrading Angular dependencies to the versions used by Ionic 2.2

I provide a npm package, which contains a service thought for use in Ionic applications.
This service doesn't have any dependencies to Ionic itself, only to some Angular packages.
Since Ionic 2.2 uses newer versions of Angular (2.4.8 instead of 2.2.1), I updated the
Angular dependencies to the new versions. And then, ngc didn't work anymore. I got a strange error:
Error encountered resolving symbol values statically. Calling function 'NoOpAnimationDriver', function calls are not supported. Consider replacing the function or lambda with a reference to an exported function, resolving symbol AnimationDriver.NOOP in .../ionic-configuration-service/node_modules/@angular/platform-browser/src/dom/animation_driver.d.ts, resolving symbol BrowserTestingModule in .../ionic-configuration-service/node_modules/@angular/platform-browser/testing/browser.d.ts, resolving symbol BrowserTestingModule in .../ionic-configuration-service/node_modules/@angular/platform-browser/testing/browser.d.ts
The error message was not very helpful, also Google did not provide a lot of information.
I found only some posts from end of last year.

The solution was - finally - quite easy. I had to add a specific tsconfig.aot.json, which is used by ngc.
The differences to the original tsconfig.json are:
{
  "compilerOptions": {
    "module": "es2015"
  },
  "files": [
    "src/index.ts"
  ],
  "angularCompilerOptions": {
    "genDir": "aot"
  }
}
  • module es2015 is needed for tree shaking
  • files defines the entry point
  • genDir specifies where ngc stores its intermediate files
The last bit was to tell ngc to use the new configuration:
ngc -p tsconfig.aot.json
For a detailed explanation, just check the Angular Cookbook.

Tuesday, February 21, 2017

Ionic serve: watch failed: A watch configured to watch the following paths failed to start

Today I had the problem that every time, I called ionic serve, I got the following error:
...
[10:28:18]  build dev finished in 52.67 s
[10:28:22]  watch failed: A watch configured to watch the following paths failed to start. It likely that a file
            referenced does not exist:
            ...\src\**\*.(ts|html|s(c|a)ss)
[10:28:22]  dev server running: http://localhost:8100/
I found the error creation in node_modules\@ionic\app-scripts\dist\watch.js. The function startWatcher rejects the promise, if the start of watching needs more than 3 seconds. This is (roundabout) the timespan between "build dev finished" and "watch failed".

So I thought my machine might be too slow and increased the timeout value to 30 seconds:
[10:33:49]  build dev finished in 53.87 s
[10:34:09]  lint finished in 20.95 s
[10:34:19]  watch failed: A watch configured to watch the following paths failed to start. It likely that a file
            referenced does not exist: ...\src\assets\**\*,
            ...\src\index.html,
            ...\src\manifest.json,
            ...\src\service-worker.js,
            ...\node_modules\ionicons\dist\fonts\**\*,
            ...\node_modules\ionic-angular\fonts\**\*,
            ...\node_modules\ionic-angular\polyfills\polyfills.js,
            ...\node_modules\sw-toolbox\sw-toolbox.js,
            ...\environments\api-dwp\settings.json,
            ...\node_modules\leaflet\dist\leaflet.css
[10:34:19]  dev server running: http://localhost:8100/
As you can see, the error is the same, this time after 30 seconds, but there are more files reported. And indeed, the files ...\src\manifest.json and ...\src\service-worker.js do not exist. They are part of the default node_modules\@ionic\app-scripts\config\copy.config.js.

It seems, I have 2 possibilities now:
  • add the 2 files to my app
  • modify the copy.config.js

Since I have already a custom copy.config.js, and since I do not want to litter my source code with unneeded files, I used the second approach. In the copy.config.js, I had to override the key copyIndexContent (just remove '{{SRC}}/manifest.json' and '{{SRC}}/service-worker.js'):
copyIndexContent: {
  src: ['{{SRC}}/index.html'],
  dest: '{{WWW}}'
},
Now ionic serve didn't produce watch errors any more:
[11:19:40]  build dev finished in 50.56 s
[11:19:51]  watch ready in 62.29 s
[11:19:51]  dev server running: http://localhost:8100/
As you can see, the timespan for starting the watching is 11 seconds. So my modification of node_modules\@ionic\app-scripts\dist\watch.js is still needed.

Update 23-Feb-2017

Starting with version 1.1.4 of @ionic/app-scripts, there is a new option --startWatchTimeout to the ionic command. Instead of modifying the file node_modules\@ionic\app-scripts\dist\watch.js (which is not very reliable, anyway), it's enough to add the additional parameter: ionic serve --startWatchTimeout 30000.

See Issue 772.

Tuesday, November 1, 2016

Migrating to Ionic 2 RC 0 - ngc fails

Finally, I had my app migrated to Ionic 2 RC 0. I tested the app with ionic serve, I built and deployed the app with cordova run - everything was fine.
However, since the beginning of the process was ionic serve, this meant that from an Ionic point of view, everything happened in development mode. So I had no benefit from the performance gain by using Angular's AoT (Ahead-of-time compilation).

So I tried to do a real production build with ionic build. Unfortunately, it produced an error:
[12:51:11]  ionic-app-scripts 0.0.39
[12:51:11]  build prod started ... 
[12:51:11]  clean started ... 
[12:51:11]  clean finished in 7 ms 
[12:51:11]  copy started ... 
[12:51:11]  ngc started ... 
[12:51:11]  copy finished in 455 ms 
[12:51:12]  lint started ... 
[12:51:22]  lint finished in 9.64 s 
[12:51:32]  Error: Error at .../.tmp/app/app.module.ngfactory.ts:415:84 
[12:51:32]  Supplied parameters do not match any signature of call target. 
[12:51:32]  ngc failed 
[12:51:32]  ionic-app-script task: "build" 
[12:51:32]  Error: Error 
I had no idea, what was going wrong... Then I checked line 415 in app.module.ngFactory.ts (a generated file in the .tmp folder:
get _LoggingService_76():import46.LoggingService {
  if ((this.__LoggingService_76 == (null as any))) { (this.__LoggingService_76 = new import46.LoggingService()); }
    return this.__LoggingService_76;
}
Ok, it has something to do with my LoggingService, which I am using as provider in app.module.ts:
@NgModule({
  ...
  providers: [
    ConfigurationService
    LoggingService,
    ...
  ]
})
Then I checked the declaration of the LoggingService:
export declare class LoggingService {
  private configurationService;
  constructor(configurationService: ConfigurationService);
  ...
}
As you can see, its constructor has a parameter, but the code in app.module.ngFactory.ts is using a parameterless constructor.

One workaround could be to changed the parameter to an optional one. A better workaround is to change the declaration of the provider in app.module.ts:
@NgModule({
  ...
  providers: [
    ConfigurationService,
    {
      provide: LoggingService,
      useFactory: getLoggingService,
      deps: [ConfigurationService]
    },  
    ...
  ]
})
The last piece is the factory function:
export function getLoggingService(configurationService: ConfigurationService): LoggingService {
  return new LoggingService(configurationService);
}
That's it. Still a workaround, but working.