-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Should be possible to force import of "unused" modules in case there are side-effects #9191
Comments
It's possible: import './MyService`; |
I know I can do that, but I shouldn't have to say import './MyService';
import {MyService} from './MyService'; all over the place. It's just untidy. |
:) |
I am too dumb for getting why |
@Aleksey-Bykov Basically, in Angular code, the class is instantiated by the dependency injection system, so you import |
While I get understand the use case, I can't conceive of a clean syntax that would denote that the import should not be elided... Only except maybe: import *, { MyService } from './MyService'; In that the compiler would understand that the anonymous import * from './MyService';
/* is equivalent to */
import './MyService'; The one "edge case" is that if the default export of the module being something that gets erased (is that even possible to export default something that is erasable)? |
@markrendle for what it's worth, this is my pattern for registering services in angular 1.x: my-service.ts export class MyService {
static $inject = ['$q'];
constructor(readonly $q: ng.IQService) {}
} my-other-service.ts import { MyService } from './my-service';
export class MyOtherService {
static $inject = ['MyService'];
constructor(readonly myService: MyService) {}
} app.ts import { module } from 'angular';
import { MyService } from './my-service';
import { MyOtherService } from './my-other-service';
const app = module('app', [])
.service({
MyService,
MyOtherService
});
export default app; main.ts import { bootstrap } from 'angular';
import app from './app';
bootstrap(document.body, [app.name], { strictDi: true }); This ensures the service import will not be elided. It also has the advantage of decoupling your service classes from angular (sure $inject is there but they don't depend on angular). Edit: Updated example with explicit export from "app.ts" |
@aluanhaddad I see what you've done there, but I use ngAnnotate as part of my build process so I don't have to manually maintain This would all go away if my boss would just let me spend 6-9 months rewriting the entire application in Angular 2... |
@markrendle I don't use ngAnnotate so I wouldn't know if that would work but the only reason I write my "services" as classes registered with My example was just meant to show that you don't have to register each service in the module where it is defined. I also think that there are compelling reasons to avoid doing so. Regardless, using a tool like ngAnnotate shouldn't dictate the way in which you structure your code or the order in which you register your services. |
@aluanhaddad But it does, because it makes life so much easier that I'm willing to put up with its demands. |
@markrendle But if the import were not elided and the type were imported solely for use in type position in multiple dependent modules, you would end up registering the service multiple times. That just seems unsound. Anyway, how about exporting a function which registers the service. export class MyService {
static $inject = ['$q'];
constructor(readonly $q: ng.IQService) {}
}
export default function register(ngModule: angular.IModule) {
ngModule.service('myService', MyService);
} |
I'm going through this problem these days, exactly like @markrendle said.
|
|
@RyanCavanaugh Good thinking! |
@Cliveburr @RyanCavanaugh I don't think this use case is valid. In @markrendle's example, if the module import were not elided when the imported entity is used only in type position, the side effect of registering the service would occur an additional time for each such import. It is common for multiple components to depend on the same service. |
Are people seriously writing modules with non-idempotent load-time side effects? That seems insane |
Yes they are, and yes it is insane. It works for them precisely because TypeScript elides these imports. file0.ts angular.module('app', [])
.directive('myDirective', function() {
return {
scope: {}
};
}); file1.ts angular.module('app')
.directive('myDirective', function() {
return {
scope: {}
};
}); And here is a plinkr demonstrating the same: https://plnkr.co/edit/D53fDu1hwhvk5aKIsrJs |
@RyanCavanaugh to clarify what this does: <my-directive></my-directive> is that angular will explode with the error:
This is very bad! |
@aluanhaddad Yes, that does happen, I had to do a little helper to prevent it, not the way you showed, but the loading of routes in a SPA navigation, pages where is visited several times in the same session, but however, I believe that this effect is a framework problem or the loader mechanism problem, not the language itself .. |
😭 |
@Cliveburr Of course it is not a language problem. It is also not a loader problem or a framework problem. The problem is incorrect code. If you are having issues like this it is because you have a bug in your code. There are so many ways to avoid this particular problem.
If you follow one of these patterns, you will not ever have this problem. |
@RyanCavanaugh wrote:
It's not insane, it's JavaScript. JavaScript modules have inline code that is executed when the module is loaded. It is the responsibility of the module loader to ensure that this only happens once. In my particular case, Webpack does a perfectly good job of this, but the same is true of RequireJS, Node/CommonJS, SystemJS, etc. @aluanhaddad It is increasingly unclear to me what you think you are contributing to this issue. None of your comments have been constructive or helpful; the Plunkr you offered as a "repro" uses neither import statements nor module loading and is therefore entirely irrelevant in this context; the "workarounds" you have offered are either unusable in many contexts or horribly inelegant and fragile, and unintuitive to boot. Your assertion that "it only works precisely because TypeScript elides these imports" is flat-out wrong: I can and do repeatedly Your obvious misinformation aside, may I propose that we take it as read that you are not encountering this problem yourself and therefore don't need the feature requested, and, in return, you accept that some people are and do, and that their experience is as valid and important as yours, and move on? Thank you. |
All I am asking for here is some kind of directive so I can say to TypeScript "hey, don't tree-shake this import, because I know my code-base better than you". Asserting that there are never going to be cases where tree-shaking should not occur is short-sighted and hubristic. Side-effects exist, are already well-handled in the JavaScript module ecosystem, and clearly in some cases are desirable, so TypeScript needs to recognise that. ProposalsI can think of two directions from which to come at this. Firstly, from the side of the consumer of an import, such as this syntax (import-bang): import! {MyService} from './myservice'; The second option is to declare within the module itself that it should always be imported, which would seem to make more sense, since it does not require special knowledge of module internals on the part of the consumer. Could be done with a /// <ts-disable-elision>
export class Foo { }
angular.module('fubar').service('foo', Foo); |
Yes, it is.
Nope. That's too short-sighted. How about exported classes which have a static binding that is initialized by a static method that has side effects? Example with four files, all placed in a webserver's "root" folder: test1.ts:
test2.ts:
tsconfig.json:
test.html:
Remove or comment out the second code line in test2.ts and compile. You will not get any side effects in the browser console any more. :(
Invalid and wrong assumption! IMHO, it IS a "big dead" if it does not work correctly in all imaginable scenarios. It breaks my code! And my code is valid. I do not want to discuss this any further with you. I do not and will not agree with you. As long as I can disable the logic altogether, you can refine your rules as much as you want. Thanks for understanding. |
Please don't get me wrong. It is not my intention to agressively dismiss your contribution. We are all doing the best we can to make things better. But we are talking about compilers here. They should work correctly. In all scenarios. There's nothing more important for software developers. That's why I am so direct and defensive about my point of view here. |
one thing to take into account, lazy loading, what ever solution we have here next scenario should not load import {Bar} from 'foo';
export async function doSomeThing(): Bar {
const foo = await import('foo');
return foo.buildBar();
} In this case, |
hi I had this issue bite me. It's a simple question i guess, is typescript a superset of javascript or does it aim to create some of it's own rules regardless. I thought i was able to convert a code base of js slowly to ts without worrying because it's a super set? is that no longer the case? |
I had this issue too...I try 5 hours to found the decorators is useless for this reason. |
So, any work around?! |
@karianpour, to the original issue in this thread yes, just do double import once of the type and once just of the module with no types, look at the start of the thread for details |
Hi! Thanks for the project. Library already preserves all `.svelte` imports. This PR change behaviour to preserve _all_ imports, because imported variables can be not only svelte components but also values which svelte template actually uses. Example: ``` <script lang="ts"> import { writable } from "svelte/store"; import { count } from "./stores"; import Counter from "./Counter.svelte"; export let name: number; export let age: number; </script> <h1>Hello {name} ({age})!</h1> <p> <Counter /> <Counter value={1}>Counter 1</Counter> <Counter value={$count} step={3}>Counter 2</Counter> </p> ``` Without the fix compiler throws: ``` (!) svelte plugin: 'count' is not defined src/App.svelte <Counter /> <Counter value={1}>Counter 1</Counter> <Counter value={$count} step={3}>Counter 2</Counter> ^ </p> ``` Relates to: - PaulMaly#4 - microsoft/TypeScript#9191
I use If you use barrel files(index.ts which re-exports module), every file must export an empty default value as below, to prevent tree-shaked by ts compiler. index.ts
PS: I prefer |
In MainFilter.ts I use import
In browser console I have an error: |
Solved as
but forced to off import/no-duplicates rule =( |
FYI, I have a PR open to fix this at #35200. Feedback is welcome from anyone still interested in this issue. Thanks! |
This keeps tripping me up, so I thought I'd note it here. This code breaks when the import ClipboardCopyElement from "@github/clipboard-copy-element";
const elem1: ClipboardCopyElement = document.querySelector("clipboard-copy");
const elem2 = new ClipboardCopyElement(); Unfortunately, I find this is a pretty common situation when working with custom elements, and as a library author I'm frustrated that I can't fix this for library users. It also makes it trickier to recommend TypeScript to beginners. The only way to allow import "@github/clipboard-copy-element";
import ClipboardCopyElement from "@github/clipboard-copy-element"; Given that TypeScript will probably treat
(Ideally, I'd love to have tooling that encourages the use of |
Lately my eye caught a compiler/tsconfig option called |
…ort type`. Thanks to microsoft/TypeScript#9191 (comment) for the tip.
Sorry if I'm confused about why this is closed. This hasn't been fixed, has it? It would be nice to have something that is the opposite of Right now, we have to do this:
Is there a way to shorten this? If not, maybe something
|
@richardkazuomiller I think you want to set the option With this, all your imports will be kept, only explicit |
@jods4 Thanks, that looks like it might do the trick 😄 |
Write a single function that handles all custom elemeent definitions Work around TS behavior that doesn't run module side effects "It's not insane, it's JavaScript." - microsoft/TypeScript#9191 (comment)
TypeScript Version:
1.8.10
Code
MyService.ts
MyComponent.ts
Expected behavior:
(I know this sort-of-tree-shaking is "by design", but by the principle of least astonishment, expected behavior is this)
MyComponent.js
Actual behavior:
MyComponent.js
And then AngularJS is all "myServiceProvider not found" and crashy.
The text was updated successfully, but these errors were encountered: