angular / angular-cli

CLI tool for Angular
https://cli.angular.io
MIT License
26.76k stars 11.98k forks source link

Add project source dir as a module #1465

Closed filipesilva closed 8 years ago

filipesilva commented 8 years ago

It would be very useful to be able to address components via a relative path starting at the project root. A way to do this is to consider the project root as a special project, as if it was inside of node_modules as far as resolution is concerned.

Such a design greatly reinforces the 'app as a library' idea, and plays nice with most module loading scheme.

A proposed name is @app but configurable via angular-cli.json, where app is the project prefix specified in ng new --prefix app (this exists already and defaults to app).

Example:

// instead of 
import { something } from '../../../../../../../shared/something';
// we could do
import { something } from '@app/shared/something';

This would also work for CSS preprocessors.

Such implementation would address https://github.com/angular/angular-cli/issues/865 and also greatly reduce the need for https://github.com/angular/angular-cli/issues/900.

sirajc commented 8 years ago

Have seen similar thing in ng-bootstrap. The demo project inside refers to the main project using @ng-bootstrap and it is using webpack

TheLarkInn commented 8 years ago

In terms of webpack implementation would we be taking this @app property and mapping them to the webpack alias for module resolution @filipesilva

filipesilva commented 8 years ago

Something important that came up: editor tooling would never be able to find @app. You'd always have errors on your editor.

clydin commented 8 years ago

There are also tsconfig's baseUrl and paths options. This would only apply to typescript files but allows editors (e.g., vscode) to process and follow the mapping.

To make the current version of vscode pick up the settings, you'll need to add a workspace settings file with "typescript.tsdk": "./node_modules/typescript/lib" (make sure it points to your actual typescript location).

    "baseUrl": ".",
    "paths": {
      "@app/*": ["app/*"]
    }

UPDATE:

The above additions to tsconfig and the inclusion of the TsConfigPathsPlugin in the CLI's web pack config work well so far. It definitely simplifies and improves readability of typescript imports.

masaanli commented 8 years ago

Can't wait on this one :) Also struggling with FontAwesome Fonts implementation. Did a CDN for now...

chapati23 commented 8 years ago

Another thing to be aware of with aliases is tests. Just because webpack knows @app as an Ali's doesn't mean mocha or karma etc. will know how to resolve it

ghost commented 8 years ago

I've tried this, the TypeScript compiles, but I get bundle errors from webpack. I.e. I've added this to tsconfig.json:

"baseUrl": ".",
    "paths": {
      "@app/*": ["app/*"]
    },

but e.g.

import { CommonModule} from '@app/common';

throws the error:

Module not found: Error: Can't resolve '@app/common' in 'src\client\src\app'
 @ ./src/app/client.module.ts 17:0-68 26:0-43
 @ ./src/main.ts
 @ multi main

I feel like I have to update webpack configuration somewhere?

@clydin

ghost commented 8 years ago

The above additions to tsconfig and the inclusion of the TsConfigPathsPlugin in the CLI's web pack config work well so far. It definitely simplifies and improves readability of typescript imports.

@clydin How did you include TsConfigPathsPlugin, as the webpack configuration is blackboxed? And I had a look, and it's not in there?

ggranum commented 8 years ago

I have basically @rolandoldengarm's issue, but there's a case that might be worth testing for someone: npm link.

When I attempted anything using symbolic links it seemed that webpack would resolve the real path instead of the alias. Combined with 'tsconfig:paths' not working yet, I cannot fathom solution to 'develop using modules' that's supportable long term.

Mostly want to call this out because at the moment this is listed as 'nice to have', and I really can't imagine being able to use the tool in this state long term.

I tried npm link, obviously. But also a few direct symbolic links - the craziest failure was when I symbolically linked a module that depends on another. Consider B depends on A: With both A and B npm install'd, everything works fine. However, removing the install of B and instead creating a symbolic link so that it looks like the source is under the 'app' directory, (and modifying the app.module.ts accordingly) fails, because it can't find module A.

On the chance this might be of interest to someone here I added some scripts to my repos to help demo it. If not interested, no worries.

The repositories I'm working on are [https://github.com/ggranum/revector]() and [https://github.com/ggranum/revector-demo]()

Specifically, if you clone both of those into the same parent and run

npm install
ng serve

Everything should work. Now stop the server and run:

 ./symlink-email-module.sh 
ng serve

and you should reproduce the errors I'm seeing [though note that the sed command in the bash scripts may break on non-macs] :

ERROR in ../revector/src/lib/email-password-top-nav-login/top-nav-login.component.ts
Module not found: Error: Can't resolve '@revector/auth-service' in '/Users/ggranum/github/ggranum/revector/src/lib/email-password-top-nav-login'
 @ ../revector/src/lib/email-password-top-nav-login/top-nav-login.component.ts 12:0-68
 @ ../revector/src/lib/email-password-top-nav-login/email-password-top-nav-login.module.ts
 @ ../revector/src/lib/email-password-top-nav-login/index.ts
 @ ./src/app/app.module.ts
 @ ./src/app/index.ts
 @ ./src/main.ts
 @ multi main

There's a bash script to create npm links as well (but run the uninstall-revector.sh script first).

If nothing else, hopefully this post serves to point out how hard it will be to write modular code in external projects without this feature.

Thanks much

ghost commented 8 years ago

We're using symlink (Windows) and it's working fine with system-config. Have not tested npm link yet.

And I agree priority should be increased. Previously our code was quite a mess with all the ../../../.. and ../../.., etc., and requiring changing all the imports when moving stuff around. Now it is super clean with "import {..} from 'app/domain"

ggranum commented 8 years ago

@rolandoldengarm I am now deeply curious: what do you mean when you referenced system-config? Are you using system-config in combination with the webpack version of the CLI? If so, I'd love to hear more...

As for symlinks... this is unquestionably a design failure of Node. I have a few horrible, evil ideas about how to make the ' --preserve-symlinks' flag propagate through Node's call chain, but nothing I'd be proud of. And certainly nothing that would 'just work' when I clone my repository onto another machine, for example. It might be possible that there is something relatively simple the CLI team could do to enable symlink'd directories to work properly with node/webpack... but a) I doubt it and b) why bother? Leveraging tsconfig:paths just makes so much more sense.

ghost commented 8 years ago

@ggranum no sorry, I'm still on the "old" Angular CLI that uses system-config and systemJS. We've got a symlink called "app" in our node_modules folder, pointing to the app folder. Then, we can do "import {} from 'app/domain'" to point to src/app/domain/index.ts, instead of ugly relative paths like "import {} from '../../domain'"

This works OK in the "old" CLI, but not with webpack. And, as the webpack angular-cli does not support the way it should work in TS2.0 yet, we're stuck at the old CLI. Very disappointing.

ghost commented 8 years ago

Just curious, is there any solution for us in the Webpack CLI at this stage? As said, right now we add a symlink to node_modules (Windows) pointing to the app folder. That works on systemJS, Webpack throws errors. This is our only blocker for switching to Angular CLI webpack. Maybe there is an alternative??

ghost commented 8 years ago

This should definitely be bumped in the priority. With the old CLI using systemjs I got it to work by myself (without symlinks in node_modules), but since index.ts files don't work well with webpack I dropped that and made all imports relative, which looks like a total mess. This is absolutely unmaintainable for medium to large sized projects.

ghost commented 8 years ago

index.ts does not work well with webpack either?? We've got a pretty big project, and created separate files for our domain objects; about 50 of them. Some services import 20, which right now is

import { DTO1, DTO2, ... DTO20 } from 'app/domain'

so we would be back to

import {DTO1} from '../../domain/dto1
import {DTO2} from '../../domain/dto2
import {DTO3} from '../../domain/dto3

Unacceptable for us.

How come not many people see this as priority??

ghost commented 8 years ago

The not-working barrels are another issue. I can understand that imports relative to project root are more like syntactic sugar, but it would be really, really, really nice to have, not just "p3: (nice to have)"

ghost commented 8 years ago

Relative imports are just a configuration change, but as that is blackboxed we cannot change it... But technically it would be a minor change.

ggranum commented 8 years ago

@rolandoldengarm: I'm not sure what you mean about index.ts not working. The scenario you indicate as app/domain doesn't work, but using an index file does -- you can write

import { DTO1, DTO2, ... DTO20 } from '../../domain'

You might need to use ../../domain/index if you happen to keep a file named 'domain.ts' as a sibling of the /domain/ directory? Not sure about that.

My assumption is that once paths work with webpack we'll able to search for (say) from '\..*domain' and replace with from 'app/domain'.

Something I've noticed that could possibly be in play here is that webpack doesn't pick up on new paths during the watch builds (e.g. ng serve). So you'll get path not found warnings until you kill the process and start it again.

ghost commented 8 years ago

@ggranum I don't know about index.ts not working, that's what @christiandreher said.

I just don't want relative paths. That's so ugly. It's just a simple configuration change in Webpack, but apparently nobody here thinks it's important enough to bother.

ghost commented 8 years ago

At least they didn't work for me, like described here: https://github.com/angular/angular-cli/issues/1585.

abner commented 8 years ago

This two steps allowed the proposed @app thing works

I just submited a pull request about it: https://github.com/angular/angular-cli/pull/2210

  • changed the tsconfig.json, adding:
"baseUrl": ".",
 "paths": {
      "@app/*": [
        "app/*"
      ],
      "app/*": [
        "app/*"
      ]
    }

and then changed the resolve on webpack-build-common.js

var aliasConfig = {
    'app': path.join(appRoot, 'app')
  };
  aliasConfig['@' + appConfig.prefix] = path.join(appRoot, 'app');

....
resolve: {
            extensions: ['', '.ts', '.js'],
            root: appRoot,
             alias: aliasConfig,
        },
clydin commented 8 years ago

For full tsconfig.json paths support, change the resolve object located at https://github.com/angular/angular-cli/blob/master/packages/angular-cli/models/webpack-build-common.ts#L38 to:

    resolve: {
      extensions: ['', '.ts', '.js'],
      root: appRoot,
      plugins: [
        new atl.TsConfigPathsPlugin({
          tsconfig: path.resolve(appRoot, appConfig.tsconfig)
        })
      ]
    },

Note the addition of the atl.TsConfigPathsPlugin.

Also, depending on the future implementation of the ngc integration, this may become unnecessary and/or incompatible in the future.

ggranum commented 8 years ago

@clydin Thanks for that, and @abner for triggering the additional attention. I am running @clydin's changes locally and it works exactly as advertised for ng serve. For ng test I had to add:

// insert at line 5
const atl = require('awesome-typescript-loader');
// ...
// replace near line 14
resolve: {
      extensions: ['', '.ts', '.js'],
      root: appRoot,
      plugins: [
        new atl.TsConfigPathsPlugin({
          tsconfig: path.resolve(appRoot, appConfig.tsconfig)
        })
      ]
    },

To https://github.com/angular/angular-cli/blob/master/packages/angular-cli/models/webpack-build-test.js. Works so far. I leave it up to those who know about the interplay between the various affected systems to say if this is a good idea or not :~)

abner commented 8 years ago

Great! I just updated the PR #2210 to use the TsConfigPathsPlugin as sugested.

ValeryVS commented 8 years ago

Also, depending on the future implementation of the ngc integration, this may become unnecessary and/or incompatible in the future.

@clydin Why, unnecessary? Are ngc already support tsconfig's path?

ValeryVS commented 8 years ago

awesome-typescript-loader is not compatable with becoming ngc update. The best approach, that I see is using webpack config's resolve {alias: ...} https://gist.github.com/sokra/27b24881210b56bbaff7#resolving-options

filipesilva commented 8 years ago

Fixed by https://github.com/angular/angular-cli/pull/2470. We're not looking at also doing the CSS preprocessors right now, but will look at it later.

craigcosmo commented 7 years ago

can some one consolidate a guide on how to config and use this?

wayneashleyberry commented 7 years ago

I'm still running into this using the latest release of the cli. The path resolves using tsc, but not ng.

❯ tsc --traceResolution | grep @app
======== Resolving module '@app/types/element.type' from '/path/to/my/project/src/app/components/library/element-list/element-list.component.ts'. ========
'baseUrl' option is set to '/Users/wayne/src/github.com/overhq/over-cms', using this value to resolve non-relative module name '@app/types/element.type'
'paths' option is specified, looking for a pattern to match module name '@app/types/element.type'.
Module name '@app/types/element.type', matched pattern '@app/*'.
======== Module name '@app/types/element.type' was successfully resolved to '/path/to/my/project/src/app/types/element.type.ts'. ========
❯ ng build
Hash: 3c4a820798b36084d24b
Time: 12929ms
chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 231 kB {4} [initial] [rendered]
chunk    {1} main.bundle.js, main.bundle.js.map (main) 260 kB {3} [initial] [rendered]
chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 15.2 kB {4} [initial] [rendered]
chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 4.51 MB [initial] [rendered]
chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]

ERROR in /path/to/my/project/src/app/components/library/element-list/element-list.component.ts (2,23): Cannot find module '@app/types/element.type'.)

Here are my versions:

{
  "dependencies": {
    "@angular/common": "2.4.9",
    "@angular/compiler": "2.4.9",
    "@angular/core": "2.4.9",
    "@angular/forms": "2.4.9",
    "@angular/http": "2.4.9",
    "@angular/platform-browser": "2.4.9",
    "@angular/platform-browser-dynamic": "2.4.9",
    "@angular/router": "3.4.9",
    "@types/moment-timezone": "^0.2.34",
    "angular2-notifications": "0.4.53",
    "core-js": "2.4.1",
    "ie-shim": "0.1.0",
    "ionicons": "3.0.0",
    "less": "2.7.2",
    "lodash": "^4.17.4",
    "moment": "2.17.1",
    "moment-timezone": "^0.5.11",
    "ng2-bs3-modal": "0.10.4",
    "ng2-select": "1.2.0",
    "pace-progress": "1.0.2",
    "rxjs": "5.2.0",
    "zone.js": "0.7.8"
  },
  "devDependencies": {
    "@angular/cli": "1.0.0-rc.1",
    "@angular/compiler-cli": "2.4.9",
    "@types/jasmine": "2.5.38",
    "@types/node": "7.0.8",
    "codelyzer": "2.0.1",
    "jasmine-core": "2.5.2",
    "jasmine-spec-reporter": "3.2.0",
    "karma": "1.4.1",
    "karma-chrome-launcher": "2.0.0",
    "karma-cli": "1.0.1",
    "karma-coverage-istanbul-reporter": "^0.2.0",
    "karma-jasmine": "1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "5.1.0",
    "ts-node": "2.1.0",
    "tslint": "4.5.1",
    "typescript": "2.2.1"
  }
}

Here's my .angular-cli.json:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "project"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "plugins",
        "dist",
        "static",
        "bootstrap",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "mobile": false,
      "styles": [
        "styles.css"
      ],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "local": "environments/environment.local.ts",
        "dev": "environments/environment.dev.ts",
        "staging": "environments/environment.staging.ts",
        "prod": "environments/environment.prod.ts",
        "dynamic": "environments/environment.dynamic.ts"
      }
    }
  ],
  "e2e": {
    "protractor": {
      "config": "./protractor.conf.js"
    }
  },
  "lint": [
    {
      "files": "src/**/*.ts",
      "project": "src/tsconfig.app.json"
    },
    {
      "files": "src/**/*.spec.ts",
      "project": "src/tsconfig.spec.json"
    },
    {
      "files": "e2e/**/*.ts",
      "project": "e2e/tsconfig.e2e.json"
    }
  ],
  "test": {
    "karma": {
      "config": "./karma.conf.js"
    }
  },
  "defaults": {
    "styleExt": "css",
    "component": {
      "inlineTemplate": false,
      "spec": true
    }
  }
}
antonberezan commented 7 years ago

Does anyone know how to resolve this issue? Property paths in tsconfig doesn't help at all. Actually, it helps on .ts compilation phase, but it fails in WebPack then.

wayneashleyberry commented 7 years ago

@antonberezan I actually found using the default cli setup, this kinda works out of the box now. You can import {ElementService} from 'app/services/element.service'; automagically.

ekaitzht commented 7 years ago

@antonberezan I actually found using the default cli setup, this kinda works out of the box now. You can import {ElementService} from 'app/services/element.service'; automagically.

To me is giving me cannot find module.

ggranum commented 7 years ago

@ekaitzht I summarized some of the issues I had getting named paths to work here. The project I link in there is a working example - not claiming it's a quality working example, but it uses named paths and it loads, runs tests and the libraries it emits don't break AoT for consuming projects.

kuncevic commented 7 years ago

Just split up my app.module in to features modules, implement lazy loading, etc, it is quite time consuming to change the paths basically you had to do it twice in components and in spec files - nightmare. Looking forward for absolute path feature in CLI a lot.

undeletable commented 7 years ago

To make the current version of vscode pick up the settings, you'll need to add a workspace settings file

@clydin Is there a similar solution for WebStorm/PHPStorm?

Update: Found out the cause and the solution, see https://intellij-support.jetbrains.com/hc/en-us/community/posts/115000158164-Angular-CLI-with-paths-in-tsconfig

VirajNimbalkar commented 7 years ago

After struggling by searching over internet n trying to understand what exactly problem and trying different troubleshooting option, I came to know baseUrl and Path how works together

Note: This solution is for Angular Cli 1.x . Not sure about other tool,

If you use baseUrl:"." like below it works in VScode but not while compiling

{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "baseUrl": ".",
    "paths": {
      "@myproject/*": ["src/app/*"]
    }    
}

As far my understanding and my working app and checked in angular aio code, I suggest use as baseUrl:""src like below

{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "baseUrl": "src",
    "paths": {
      "@myproject/*": ["app/*"],
      "testing/*": ["testing/*"]
    }    
}

By having base URL as source(src directory), compiler properly resolves modules.

I hope this helps to people resolve this kind of issue.

Zeioth commented 7 years ago

I don't get how something so simple can be so hard to achieve.

Fortunately this guide worked for me.

In tsconfig.json

"compilerOptions": {
    "outDir": "./dist/out-tsc",
    "baseUrl": "."
}

in webpack.config.js

module.exports = {
  "resolve": {
    "extensions": [
      ".ts",
      ".js"
    ],
    "modules": [
      "./node_modules",
      ".",
    ]
}

Now let's imagine you have the next project structue.

/** 
 * Assuming the following project structure
 *   /src
 *     /app
 *       /services/file1.js
 *       /models/file2.js
 *     /node_modules
 *     .webpack.config.js
 *     tsconfig.json
 */

If you wanna import the file example 2 from example1, from now on you can use

import { AnythingIWant} from 'src/app/models/file2';
atte-backman commented 7 years ago
"compilerOptions": {
    "baseUrl": ".",
    "paths": {
        "app": [
            "src/app/*"
        ]
    },
    "include": ["src/**/*"],
    "exclude": []
}

... and it should be all good.

ppozniak commented 7 years ago

Here's how I handled it: in tsconfig.json

"compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "@directives/*": ["app/shared/directives/*"],
      "@services/*": ["app/shared/services/*"],
      "@models/*": ["app/shared/models/*"],
      "@assets/*": ["assets*"],
      "@components/*": ["app/components/*"]
    },
...

Example: import { ShoppingItem } from "@models/shopping-item.model";

darkbasic commented 7 years ago

They removed the above mentioned solution with awesome-typescript-loader and they're now using AotPlugin: https://github.com/angular/angular-cli/commit/f9a7c01602546dea6c92adb165a5c437689c41b4#diff-3c07f218821da114292666210f358440

If I use something like import { AppComponent } from 'app/app.component' in app.module.ts then tooling doesn't work and editor complains about TS2307: Cannot find module 'app/app.component'.

If I put

    "baseUrl": ".",
    "paths": {
      "app/*": ["./src/app/*"]
    },

into tsconfig.json

then the editor reports an error inside app.component.ts:

Angular: Component 'AppComponent' is not included in a module and will not be available inside a template. Consider adding it to an NgModule declaration.
darkbasic commented 7 years ago

@abner @filipesilva does the new AotPlugin solution work for you?

Is it possible to reopen this issue? I also tried to eject the config and use Webpack's alias but it didn't work either.

jongunter commented 7 years ago

Seems to work for me if all 3 (app, spec and the one in the root of the project for my IDE) of my tsconfig files have the following lines so they use the @app alias:

"compilerOptions": {
    "paths": {
      "@app/*": [ "app/*" ]
    }
  }
darkbasic commented 7 years ago

Inside app.component.ts the editor still reports:

Angular: Component 'AppComponent' is not included in a module and will not be available inside a template. Consider adding it to an NgModule declaration.

Try to use something like import { AppComponent } from 'app/app.component' in app.module.ts.

tihomir-kit commented 6 years ago

For me this worked fine (tsconfig.json, not tsconfig.app.json):

    "baseUrl": "./",
    "paths": {
      "~/*": [ "*" ],
      "@app/*": [ "src/app/*" ],
      "@core/*": [ "src/app/core/*" ],
      "@shared/*": [ "src/app/shared/*" ],
      "@feature/*": [ "src/app/feature/*" ]
    }

the critical part that I was missing at the beginning was * at the end of both keys and relative paths.

VIsual Studio recognized the aliases after I restarted it.

Xapuu commented 6 years ago

Here is my solution


{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "@models": [
        "./app/models/index.ts"
      ],
      "@environment": [
        "./environments/environment.ts"
      ],
      "@services/*": [
        "./app/services/*"
      ]
    }
  }
}

Be aware that you must restart the CLI each time you make any changes in the tsconfig.json, so that they can take effect, also be aware that although the routes will work, you probably wont have any autocomplete for them.

sergey-morenets commented 6 years ago

Works for me this way (in tsconfig.json only):

"paths": {
      "@domain/*": [
        "./app/domain/*"
      ]
    }

And import it:

import {User} from '@domain/User';

otaviodecampos commented 6 years ago

@Xapuu worked for me too. thanks

nweldev commented 6 years ago

@clydin / @filipesilva How do you think we should handle this in a CLI v6 project with libraries ? Especially when following https://github.com/angular/devkit/issues/730#issuecomment-382379686.

stephaneeybert commented 5 years ago

For a library type project, how to have these paths mappings seen by a client application ?

angular-automatic-lock-bot[bot] commented 5 years ago

This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.