.gitignore000066400000000000000000000003251516133266700130600ustar00rootroot00000000000000/node_modules /build /dist .idea/workspace.xml .idea/tasks.xml .idea/profiles_settings.xml .idea/inspectionProfiles/Project_Default.xml .idea/inspectionProfiles/profiles_settings.xml node_modules/.yarn-integrity .idea/000077500000000000000000000000001516133266700120505ustar00rootroot00000000000000.idea/angular-compile.iml000066400000000000000000000005201516133266700156270ustar00rootroot00000000000000 .idea/codeStyleSettings.xml000066400000000000000000000004251516133266700162470ustar00rootroot00000000000000 .idea/inspectionProfiles/000077500000000000000000000000001516133266700157275ustar00rootroot00000000000000.idea/inspectionProfiles/profiles_settings.xml000066400000000000000000000002341516133266700222130ustar00rootroot00000000000000 .idea/misc.xml000066400000000000000000000002561516133266700135300ustar00rootroot00000000000000 .idea/modules.xml000066400000000000000000000004321516133266700142410ustar00rootroot00000000000000 .idea/vcs.xml000066400000000000000000000002471516133266700133700ustar00rootroot00000000000000 .npmignore000066400000000000000000000003451516133266700130710ustar00rootroot00000000000000/.idea /artifacts /build /test /node_modules /*.iml /*.ipr /*.iws /.travis.yml /.scrutinizer.yml /Gruntfile.js /*.lock *.log /corifeus-boot.json # Corifeus / P3X /src /tsconfig* # need to allow ts for awesome=typscript-loader .scrutinizer.yml000066400000000000000000000010711516133266700142510ustar00rootroot00000000000000checks: javascript: true filter: excluded_paths: - node_modules/* build: cache: disabled: true environment: google_chrome: use_latest: true dependencies: before: - export LATEST=$(nvm ls-remote | tail -1) - nvm install $LATEST # - nvm use $LATEST - npm install grunt-cli -g - npm install - node_modules/protractor/bin/webdriver-manager update tests: override: - command: 'grunt coverage' coverage: file: 'build/coverage/clover.xml' format: 'clover' .travis.yml000066400000000000000000000005751516133266700132100ustar00rootroot00000000000000sudo: required dist: trusty addons: apt: sources: - google-chrome packages: - google-chrome-stable language: node_js node_js: - "node" before_script: - "export DISPLAY=:99.0" - "sh -e /etc/init.d/xvfb start" - sleep 3 # give xvfb some time to start - npm install grunt-cli -g - npm install - node_modules/protractor/bin/webdriver-manager update Gruntfile.js000066400000000000000000000023441516133266700133700ustar00rootroot00000000000000module.exports = (grunt) => { grunt.option('cory-loader-off', true); //node node_modules/protractor/bin/webdriver-manager update const builder = require('corifeus-builder-angular'); /* webdriver-manager/selenium/update-config.json node_modules/protractor/bin/webdriver-manager update */ const loader = new builder.loader(grunt); loader.angular({ root: builder.config.folder.test.angularWebpack.root, replacer: { type: 'p3x', npmio: true, }, config: { clean: { 'cory-publish': [ 'dist' ] } } }); grunt.registerTask('run', builder.config.task.run.angular); grunt.registerTask('default', builder.config.task.build.angular); grunt.registerTask('aot', builder.config.task.build.angularAot); grunt.registerTask('aot-jit', builder.config.task.build.angularAotJit); grunt.registerTask('aot-test', ['webpack:cory-build-aot', 'cory-test-connect']); grunt.registerTask('coverage', 'karma:cory-angular'); grunt.registerTask('publish', ['cory-replace', 'cory-npm', 'cory-publish-angular']); //cori-test:angular-protractor // cori-test:angular-karma }LICENSE000066400000000000000000000021411516133266700120730ustar00rootroot00000000000000MIT License Copyright (c) 2017 Patrik Laszlo / patrikx3 / https://patrikx3.com and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. README.md000066400000000000000000000147321516133266700123560ustar00rootroot00000000000000[//]: #@corifeus-header [![Build Status](https://travis-ci.org/patrikx3/angular-compile.svg?branch=master)](https://travis-ci.org/patrikx3/angular-compile) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/patrikx3/angular-compile/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/patrikx3/angular-compile/?branch=master) [![Code Coverage](https://scrutinizer-ci.com/g/patrikx3/angular-compile/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/patrikx3/angular-compile/?branch=master) [![NPM](https://nodei.co/npm/p3x-angular-compile.png?downloads=true&downloadRank=true&stars=true)](https://www.npmjs.com/package/p3x-angular-compile/) --- # Angular Dynamic Compile component, that turns a string into a compiled component v5.0.52-212 This is an open source project. Just code. ### Node Version Requirement ``` >=8.9.0 ``` ### Built on Node ``` v9.1.0 ``` The ```async``` and ```await``` keywords are required. Install NodeJs: https://nodejs.org/en/download/package-manager/ # Description [//]: #@corifeus-header:end # Use case Dynamic compile components by a string template for Angular. You can provide a context, that you can use with anything (for clicking for free etc..) ## NPM & Version It is a ```CommonJS``` bundle. The version reflects the Angular version (```AngularMajor.AngularMinor.Build-Commit```). You can find out your proper version with this command: ```bash npm show p3x-angular-compile versions --json ``` ## Install ```bash npm install --save p3x-angular-compile # or yarn add p3x-angular-compile ``` ## AOT + JIT ### Angular 5.x.x + For now we cannot use AOT + JIT at once. It is too new, we will need more releases and new functions that are removed. #### Info https://github.com/angular/angular/issues/20156 On the bottom, you can see: ```text To reduce the payload we do not ship the compiler in AoT ``` So right now, it is not possible. ### Angular 4.x.x It is not working out of the box (the default is either JIT or AOT, not both), but the apps become 10 folds faster. The ``@ngtools/webpack`` is AOT and the ```awesome-typescript-loader``` is JIT only. The solution can be architect with the ```@angular/compiler``` and the ```awesome-typescript-loader``` together. A miracle! Example here (since I am using Angular 5 not, it is not AOT + JIT anymore, but if you are on Angular 4, you can do it): [More info about AOT + JIT](https://pages.corifeus.com/github/corifeus-builder-angular/artifacts/readme/skeleton.html) ### Size If you want very small bundle, use ```gzip```. ## Usage ```typescript import { CompileModule} from "p3x-angular-compile" // the module settings @NgModule({ imports: [ // multiple directives in a shared module like this CorifeusWebMaterialModule, // usually it is enough CompileModule, // usually it is not required anymore // since Angular 5 changed // but if there is something missing, // you can add in as before CompileModule.forRoot({ // module = NgModule module: { schemas: [CUSTOM_ELEMENTS_SCHEMA], declarations: [MyDynamicElement ], // though you are better a shared component like imports: [ CorifeusWebMaterialModule ] } }) ], declarations: [ Page, ], providers: [ ], bootstrap: [ Page ] }) export class Module { }; ``` ```html
``` ```typescript // a page example export class Page { data: string = "
It is working
"; alert() { alert('ok'); } } ``` #### Actual used dynamic compiler I use a dynamic Markdown page with ```p3x-angular-compile```: [Module](https://github.com/patrikx3/corifeus-app-web-pages/blob/master/src/angular/module.ts) , [Example page](https://github.com/patrikx3/corifeus-app-web-pages/blob/master/src/angular/modules/cory-page.ts) ### Options [Reference for the Angular module settings.]( https://github.com/angular/angular/blob/master/packages/core/src/metadata/ng_module.ts) ### Deployed example [Corifeus Pages (JIT)](https://pages.corifeus.com) [https://github.com/patrikx3/corifeus-app-web-pages/blob/master/src/angular/modules/cory-page.ts](https://github.com/patrikx3/corifeus-app-web-pages/blob/master/src/angular/modules/cory-page.ts) ## Dev environment end test ```bash npm install -g yarn git clone https://github.com/patrikx3/angular-compile.git cd angular-compile yarn install grunt run|default ``` [http://localhost:8080](http://localhost:8080) # Errors ## Type x is part of the declarations of 2 modules Basically, you need a shared component. https://stackoverflow.com/questions/42993580/angular-2-type-childcomponent-is-a-part-of-the-declarations-of-2-modules-par [//]: #@corifeus-footer --- [**P3X-ANGULAR-COMPILE**](https://pages.corifeus.com/angular-compile) Build v5.0.52-212 [![Like Corifeus @ Facebook](https://img.shields.io/badge/LIKE-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) [![Donate for Corifeus / P3X](https://img.shields.io/badge/Donate-Corifeus-003087.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=LFRV89WPRMMVE&lc=HU&item_name=Patrik%20Laszlo&item_number=patrikx3¤cy_code=HUF&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted) [//]: #@corifeus-footer:endangular-compile.iml000066400000000000000000000005171516133266700146550ustar00rootroot00000000000000 index.ts000066400000000000000000000000271516133266700125460ustar00rootroot00000000000000export * from "./src"; package.json000066400000000000000000000020001516133266700133460ustar00rootroot00000000000000{ "name": "p3x-angular-compile", "version": "5.0.52-212", "corifeus": { "prefix": "p3x-", "publish": true, "type": "p3x", "code": "Logico", "nodejs": "v9.1.0" }, "description": "Angular Dynamic Compile component, that turns a string into a compiled component", "main": "dist/index.js", "typings": "dist/index", "scripts": { "test": "grunt" }, "repository": { "type": "git", "url": "git+https://github.com/patrikx3/angular-compile.git" }, "keywords": [ "p3x", "angular", "ng", "compile", "html", "angular", "dynamic", "aot" ], "author": "Patrik Laszlo ", "license": "MIT", "bugs": { "url": "https://github.com/patrikx3/angular-compile/issues" }, "homepage": "https://pages.corifeus.com/angular-compile", "devDependencies": { "corifeus-builder-angular": "^5.0.38-235" }, "dependencies": { "@types/lodash": "^4.14.85", "lodash": "^4.17.4" }, "engines": { "node": ">=8.9.0" } }src/000077500000000000000000000000001516133266700116575ustar00rootroot00000000000000src/CompileAttribute.ts000066400000000000000000000076051516133266700155130ustar00rootroot00000000000000import { Component, Input, Injectable, OnInit, OnChanges, SimpleChanges, Type, ModuleWithProviders, NgModule, Compiler, NgModuleFactory, } from '@angular/core'; import { CommonModule } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; //import { CompileService } from './CompileService'; let SingletonDefaultModule: NgModule; import { cloneDeep } from 'lodash'; //const cache : any = {}; @Component({ selector: '[p3x-compile]', template: ` ` }) @Injectable() export class CompileAttribute implements OnInit, OnChanges{ @Input('p3x-compile') html: string; @Input('p3x-compile-ctx') context: any; @Input('p3x-compile-error-handler') errorHandler: (ex: any) => void = console.error; dynamicComponent: any; dynamicModule: NgModuleFactory | any; @Input('p3x-compile-module') module: NgModule; @Input('p3x-compile-imports') imports: Array | ModuleWithProviders | any[]>; async update() { if (this.html === undefined || this.html.trim() === '') { // this.container.clear(); this.dynamicComponent = undefined; this.dynamicModule = undefined; return; } /* const cacheKey = this.html; if (Object.keys(cache).indexOf(cacheKey) > -1) { return cache[cacheKey]; } */ try { this.dynamicComponent = this.createNewComponent(this.html, this.context); this.dynamicModule = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent)); // cache[cacheKey] = this.dynamicComponent; } catch (e) { this.errorHandler(e); } /* await this.service.compile({ template: this.html, container: this.container, context: this.context, imports: this.imports, module: this.module }) */ } private createComponentModule (componentType: any) { let module : NgModule = {}; if (this.module !== undefined) { module = cloneDeep(this.module); } else if (SingletonDefaultModule !== undefined && SingletonDefaultModule !== null) { module = cloneDeep(SingletonDefaultModule); } module.imports = module.imports || []; module.imports.push( CommonModule ); // module.imports.push( BrowserModule ); if (this.imports !== undefined) { module.imports = module.imports.concat(this.imports) } if (module.declarations === undefined) { module.declarations = [ componentType ]; } else { module.declarations.push(componentType); } module.entryComponents = [ componentType ]; @NgModule(module) class RuntimeComponentModule { } return RuntimeComponentModule; } private createNewComponent (html:string, context: any) { @Component({ selector: 'dynamic-component', template: html }) class DynamicComponent { context: any = context; } return DynamicComponent; } async ngOnInit() { this.update(); } async ngOnChanges(changes: SimpleChanges) { //fixme only update with the required changes this.update(); } constructor( // private container: ViewContainerRef, // private service: CompileService private compiler: Compiler ) {} }src/CompileModule.ts000066400000000000000000000022751516133266700147730ustar00rootroot00000000000000 //import { CompileService, CompileServiceConfig, } from "./CompileService"; import { CompileAttribute} from "./CompileAttribute"; import { NgModule, ModuleWithProviders, } from '@angular/core'; import { CommonModule } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; import { Compiler } from '@angular/core'; /* import {JitCompilerFactory} from '@angular/compiler'; export function createJitCompiler () { return new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler(); } */ export class CompileServiceConfig { module: NgModule } // exports = component @NgModule({ imports: [ CommonModule ], declarations: [ CompileAttribute, ], providers: [ // CompileService, // { provide: Compiler, useFactory: createJitCompiler}, ], exports: [ CompileAttribute, ], entryComponents: [ ] }) export class CompileModule { static forRoot(config: CompileServiceConfig) : ModuleWithProviders { return { ngModule: CompileModule, providers: [ {provide: CompileServiceConfig, useValue: config } ] }; } } src/CompileService.ts000066400000000000000000000067411516133266700151500ustar00rootroot00000000000000/* import { Component, NgModule, Injectable, Compiler, ViewContainerRef, ModuleWithProviders, Type, Optional } from '@angular/core'; import { CommonModule } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; import { cloneDeep } from 'lodash'; export interface CompileOptions { template: string; container: ViewContainerRef; imports?: Array | ModuleWithProviders | any[]>; context?: any, onCompiled?: Function, onError?: Function; module?: NgModule; } const cache : any = {}; export class CompileServiceConfig { module: NgModule } let SingletonDefaultModule: NgModule; @Injectable() export class CompileService { constructor( private compiler: Compiler, @Optional() config: CompileServiceConfig, ) { if (config !== undefined && config !== null) { if (config.module !== undefined && config.module !== null) { SingletonDefaultModule = config.module; } } } public async compile(opts: CompileOptions) { try { const factory = await this.createFactory(opts); opts.container.clear(); const cmp : any = opts.container.createComponent(factory); cmp.instance.context = opts.context; } catch (e) { if (opts.onError) { opts.onError(e) } else { console.error(e); } } } private async createFactory(opts: CompileOptions) { const cacheKey = opts.template; if (Object.keys(cache).indexOf(cacheKey) > -1) { return cache[cacheKey]; } cache[cacheKey] = (async() => { try { @Component({ template: opts.template }) class TemplateComponent { context: any } let module : NgModule = {}; if (opts.module !== undefined) { module = cloneDeep(opts.module); } else if (SingletonDefaultModule !== undefined && SingletonDefaultModule !== null) { module = cloneDeep(SingletonDefaultModule); } module.imports = module.imports || []; module.imports.push( CommonModule ); module.imports.push( BrowserModule ); if (opts.imports !== undefined) { module.imports = module.imports.concat(opts.imports) } if (module.declarations === undefined) { module.declarations = [ TemplateComponent ]; } else { module.declarations.push(TemplateComponent); } @NgModule(module) class TemplateModule { } const component = await this.compiler.compileModuleAndAllComponentsAsync(TemplateModule); const factory = component.componentFactories.find((comp) => comp.componentType === TemplateComponent ); cache[cacheKey] = factory; if (opts.onCompiled) { opts.onCompiled(component); } return factory; } catch (e) { delete cache[cacheKey]; throw e; } })(); return cache[cacheKey]; } } */src/index.ts000066400000000000000000000001511516133266700133330ustar00rootroot00000000000000//export * from "./CompileService"; export * from "./CompileAttribute"; export * from "./CompileModule"; test/000077500000000000000000000000001516133266700120475ustar00rootroot00000000000000test/angular-karma/000077500000000000000000000000001516133266700145715ustar00rootroot00000000000000test/angular-karma/CompileModule.ts000066400000000000000000000020261516133266700176770ustar00rootroot00000000000000// you need unlinked npm-s!!! import { TestBed } from '@angular/core/testing'; import { inject } from '@angular/core/testing'; import { CommonModule } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; import {CompileAttribute } from '../../src/CompileAttribute' describe('CompileModule', () => { let attribute: CompileAttribute; beforeEach(() => { TestBed.configureTestingModule({ imports: [ CommonModule, BrowserModule ], declarations: [ CompileAttribute ], providers: [ ], }); }); /* beforeEach(inject([CompileAttribute], (_attribute: CompileAttribute) => { attribute = _attribute; })); */ it ('CompileAttribute', (/*done*/) => { // expect(service instanceof CompileService).toBeTruthy(); /* setTimeout(()=> { console.log('done later'); done(); }, 1000); */ }); });test/angular-protractor/000077500000000000000000000000001516133266700156755ustar00rootroot00000000000000test/angular-protractor/test.js000066400000000000000000000011331516133266700172100ustar00rootroot00000000000000browser.ignoreSynchronization = true; describe('CompileHtml', () => { browser.get(`index.html`); const clicker = (button, counter) => { const total = 5; for(let i =0; i < total - 1; i++) { button.click(); browser.waitForAngular(); } const count = counter.getText(); expect(count).toEqual(total.toString()); } it('Attribute', (done) => { const button = element(by.id('button-attribute')) const counter = element(by.id('counter-attribute')); clicker(button, counter); done(); }); });test/angular-webpack/000077500000000000000000000000001516133266700151125ustar00rootroot00000000000000test/angular-webpack/angular/000077500000000000000000000000001516133266700165435ustar00rootroot00000000000000test/angular-webpack/angular/bundle.aot.ts000066400000000000000000000004321516133266700211450ustar00rootroot00000000000000import { enableProdMode } from '@angular/core'; enableProdMode(); //import 'corifeus-web/src/bundle'; import { platformBrowser } from '@angular/platform-browser'; import {ModuleNgFactory} from './module.ngfactory'; platformBrowser().bootstrapModuleFactory(ModuleNgFactory); test/angular-webpack/angular/bundle.ts000066400000000000000000000004671516133266700203730ustar00rootroot00000000000000//import 'corifeus-web/src/bundle'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { enableProdMode } from '@angular/core'; import {Module} from './module'; if (process.env.ENV === 'production') { enableProdMode(); } platformBrowserDynamic().bootstrapModule(Module); test/angular-webpack/angular/module.ts000066400000000000000000000014131516133266700203770ustar00rootroot00000000000000import { NgModule, CUSTOM_ELEMENTS_SCHEMA, } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; //import { CorifeusMaterialModule } from 'corifeus-web-material' import {Page } from './page'; import { CompileModule, } from '../../../src'; @NgModule({ imports: [ BrowserModule, CompileModule, // CorifeusMaterialModule, /* CompileModule.forRoot({ module: { schemas: [CUSTOM_ELEMENTS_SCHEMA], declarations: [], imports: [ // CorifeusMaterialModule ], } }) */ ], declarations: [ Page, ], providers: [ ], bootstrap: [ Page ] }) export class Module { }; test/angular-webpack/angular/page.ts000066400000000000000000000053661516133266700200410ustar00rootroot00000000000000import { Component, Injectable, ViewChild, ViewContainerRef, OnInit, NgModule, CUSTOM_ELEMENTS_SCHEMA, OnDestroy } from '@angular/core'; @Component({ selector: 'p3x-compile-test', template: `

Data1


Data2


Data3


Hidden is working? If show, below should say "Click me via a service!"

If there was no "Click me via a service!" above, it works.

Data2 - This always visible

` }) @Injectable() export class Page implements OnInit, OnDestroy { data1: string; data2: string = 'init'; data3: string = 'Just simple string red'; counter1: number = 0; counter2: number = 0; interval: any; constructor( // private compileHtmlService: CompileService ) { } private update1() { this.counter1++; this.data1 = `
Service
Click me via a service!
{{ context.counter1}}
`; } private update2() { this.counter2++; this.data2 = `
Attribute
Click me via an attribute!
{{ context.counter2}}
`; } ngOnInit() { this.update1(); this.update2(); /* let is = false; let newData = '123'; let defaultData = ''; let count = 0; this.interval = setInterval(() => { is = !is; if (is) { count++; defaultData = defaultData + newData; this.data3 = defaultData + defaultData; if (count > 10) { count = 0; defaultData = newData; } } else { this.data3 = '
321
' } }, 1000) */ } ngOnDestroy() { clearInterval(this.interval); } }test/angular-webpack/angular/polyfills.ts000066400000000000000000000004641516133266700211340ustar00rootroot00000000000000//import 'corifeus-web/src/polyfills'; import 'core-js/es6'; import 'core-js/es7/reflect.js'; import 'zone.js/dist/zone.js'; if (process.env.ENV === 'production') { // Production } else { // Development Error['stackTraceLimit'] = Infinity; require('zone.js/dist/long-stack-trace-zone'); } test/angular-webpack/index.html000066400000000000000000000003361516133266700171110ustar00rootroot00000000000000 angular-compile-html tsconfig.aot.json000066400000000000000000000014401516133266700143600ustar00rootroot00000000000000{ "compilerOptions": { "skipLibCheck": true, "outDir": "build/aot", "target": "es5", "module": "commonjs", "moduleResolution": "node", "sourceMap": false, "emitDecoratorMetadata": true, "experimentalDecorators": true, "noImplicitAny": true, "suppressImplicitAnyIndexErrors": true, "lib": [ "es5", "es6", "dom", "es2015.collection", "es2015.promise", "es2015.core", "es2016", "es2017" ] }, "includes": [ "src/angular/**/*", "test/angular-webpack/**/*" ], "exclude": [ "node_modules", "src/angular/boot.ts", "test/angular-webpack/angular/boot.ts", "build/browser" ], "angularCompilerOptions": { "annotationsAs": "decorators", "preserveWhitespaces": false } }tsconfig.build.json000066400000000000000000000016371516133266700147040ustar00rootroot00000000000000{ "compilerOptions": { "baseUrl": ".", "declaration": true, "stripInternal": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "noImplicitAny": true, "strictNullChecks": true, "noFallthroughCasesInSwitch": true, "module": "commonjs", "moduleResolution": "node", "outDir": "./dist", "paths": { }, "rootDir": ".", "sourceMap": true, "inlineSources": true, "target": "es5", "skipLibCheck": true, "lib": ["es2015", "dom"], // don't auto-discover @types/node, it results in a ///