Skip to content
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

Support question - declaring RequireJS deps / telling TSC about RequireJS deps #13606

Closed
ORESoftware opened this issue Jan 20, 2017 · 38 comments
Closed
Labels
External Relates to another program, environment, or user action which we cannot control. Question An issue which isn't directly actionable in code

Comments

@ORESoftware
Copy link

ORESoftware commented Jan 20, 2017

I am having trouble finding good information about using TypeScript with RequireJS (I am a big fanboy of AMD/RequireJS) - I have this question on SO:

http://stackoverflow.com/questions/41761172/declaring-requirejs-dependencies-with-typescript

installing @types/redux helps but it doesn't solve the problem entirely. My primary concern is twofold:

(1) how do we tell TypeScript about the globals "require"/"requirejs" that exist in the main requirejs load script? As you can see in the images, Webstorm highlights them as red because they are not recognized by TS. (If you look at the image below, I am not sure why requirejs is red, the first require is recognized, but the second require gets a typing error saying "invalid number of arguments, 1 expected".)

(2) how do we tell TypeScript about the dependencies that are loaded as paths in the requirejs.config({paths:{}}) object? For example, how do I tell TS that 'immutable' or 'rxjs' is loaded as a network dependency and is not loaded until runtime?

e.g.:

screenshot from 2017-01-20 15-26-23

please advise, thanks very much! once I figure this out, I will publish an article on it, to share the knowledge

@ORESoftware ORESoftware changed the title Support question - declaring RequireJS deps / telling TS about RequireJS deps Support question - declaring RequireJS deps / telling TSC about RequireJS deps Jan 20, 2017
@mhegazy
Copy link
Contributor

mhegazy commented Jan 21, 2017

(1) how do we tell TypeScript about the globals "require"/"requirejs" that exist in the main requirejs load script? As you can see in the images, Webstorm highlights them as red because they are not recognized by TS. (If you look at the image below, I am not sure why requirejs is red, the first require is recognized, but the second require gets a typing error saying "invalid number of arguments, 1 expected".)

Use @types/requirejs, this should get you the global as well as the "module" module.

(2) how do we tell TypeScript about the dependencies that are loaded as paths in the requirejs.config({paths:{}}) object? For example, how do I tell TS that 'immutable' or 'rxjs' is loaded as a network dependency and is not loaded until runtime?

Set paths property in your tsconfig.json to reflect these in your requirejs config file. you can find more information about this in www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping

@ORESoftware
Copy link
Author

thanks so much, let me try this

@ORESoftware
Copy link
Author

ORESoftware commented Jan 21, 2017

thanks, I looked at the module-resolution #path-mapping section

so I tried this (you can see the paths are CDN urls, they are not paths to node_modules, although I suppose I could add those if necessary to get things to compile)

{
  "compilerOptions": {
    "baseUrl": "public",
    "paths": {
      "rxjs": [
        "//cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.1/Rx"
      ],
      "async": [
        "//cdnjs.cloudflare.com/ajax/libs/async/2.1.4/async"
      ],
      "react-dom": [
        "//cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react-dom"
      ],
      "react": [
        "//cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react-with-addons"
      ],
      "socketio": [
        "//cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.2/socket.io"
      ],
      "@hot-reload-handler": [
        "js/hot-reload-handler"
      ],
      "@hot-reloader": [
        "js/hot-reloader"
      ],
      "tslib": [
        "vendor/tslib"
      ],
      "immutable": [
        "//cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable"
      ],
      "react-redux": [
        "//cdnjs.cloudflare.com/ajax/libs/react-redux/4.4.5/react-redux"
      ],
      "redux": [
        "//cdnjs.cloudflare.com/ajax/libs/redux/3.5.2/redux"
      ]
    },
    "types": [
      "node",
      "lodash",
      "react",
      "react-dom",
      "redux",
      "react-redux",
      "async",
      "requirejs"      // <<< added this, and npm installed @types/requirejs
    ],
    "typeRoots": [
      "node_modules/@types"
    ],
    "compileOnSave": true,
    "target": "es5",
    "module": "amd",
    "noImplicitAny": false,
    "removeComments": true,
    "preserveConstEnums": true,
    "allowJs": true,
    "allowUnreachableCode": true,
    "moduleResolution": "classic",
    "lib": [
      "es2015",
      "dom"
    ]
  },
  "include": [
    "./public/**/*.ts",
    "./public/**/*.tsx"
  ]
}

If I use the above tsconfig.json

I still get:

screenshot from 2017-01-20 20-33-23

and

screenshot from 2017-01-20 20-32-13

any idea what might be up? thanks for your support

if the pictures are too hard to read please let me know

I assume that something else should go in the paths arrays, besides the URLs pointing to CDN? The link you gave me didn't see to make it clear what to put in there.

@ORESoftware
Copy link
Author

ORESoftware commented Jan 21, 2017

I think this might answer my question:

https://weblog.west-wind.com/posts/2016/Sep/12/External-JavaScript-dependencies-in-Typescript-and-Angular-2

it says:

De-referencing Globals
In order to keep the Typescript compiler happy and not end up with compilation errors, or have a boat load of type imports you may only use once or twice, it's sometimes easier to simply manage the external libraries yourself. Import it using a regular script tag, or packaged as part of a separate vendor bundle and then simply referenced in the main page.

So rather than using import to pull in the library, we can just import using <script> tag as in the past:

Then in any Typescript class/component where you want to use these libraries explicitly dereference each of the library globals by explicitly using declare and casting them to any:

declare var $:any;
declare var toastr: any;

might that solve my problem? Do I really have to put those declarations in all my files which reference <script> tag imports?

@ORESoftware
Copy link
Author

ORESoftware commented Jan 21, 2017

Unfortunately those declare declarations didn't seem to help, weird

@mhegazy
Copy link
Contributor

mhegazy commented Jan 21, 2017

your paths section is not correct. the compiler will not look up your CDN files. I think I was not clear. your requirejs config stays the way it is, the compiler is not going to write that for you. the compiler will try to find your modules in node_modules\@types, e.g. import ... from "react" will look it up under node_modules\@types\react. if your modules are in another place, use path mapping to match that.

A few things from your config file:

  • why not use node for "module-resolution"?
  • why have allowJs? i do not see any .js files?
  • compileOnSave is in the worng place, it is not under compilerOption, it is a sippling
  • you do not need typeRoots. the value you set is already the default..

@mhegazy
Copy link
Contributor

mhegazy commented Jan 21, 2017

here is what i think you need:

  • "moduleResolution": "node"
  • install @types packages for missing depenedencies, e.g. immutable
  • I do not know know where @hot-reloader, and @hot-reload-handler come from, but either you have .d.ts for them, then add a path mapping from "@hot-reload" : "declarationFiles\hot-reload\index.d.ts" or just remove it, if hte compiler can not find it, it will get the type any.

here is what i think your config file should look like:

{
  "compilerOptions": {
    "baseUrl": "public",
    "paths": {
      "@hot-reload-handler": [ "<path to .d.ts for js/hot-reload-handler>"],
      "@hot-reloader": ["<path to .d.ts for js/hot-reloader>"]
    },
    "types": [
      "node",
      "lodash",
      "react",
      "react-dom",
      "redux",
      "react-redux",
      "async",
      "requirejs",
      "immutable"
    ],
    "target": "es5",
    "module": "amd",
    "removeComments": true,
    "allowUnreachableCode": true,
    "moduleResolution": "node",
    "lib": [
      "es2015",
      "dom"
    ]
  },
  "include": [
    "./public/**/*.ts",
    "./public/**/*.tsx"
  ]
}

let me know if this helped.

@mhegazy
Copy link
Contributor

mhegazy commented Jan 21, 2017

now i see what hot-reloader is. The compiler does not understand AMD module enclosures, so you will need to rewrite them in ES6 import syntax, e.g.:

import * as Application as from "js/application"
Application.start();

the compiler will generate a hot-reloader.js file that has the define for you.

@ORESoftware
Copy link
Author

ORESoftware commented Jan 22, 2017

The TS docs say that for AMD the module resolution should be 'classic'; but I guess since I am using ES6 import syntax (for the most part), I should use moduleResolution: 'node'?

@mhegazy
Copy link
Contributor

mhegazy commented Jan 23, 2017

The TS docs say that for AMD the module resolution should be 'classic'; but I guess since I am using ES6 import syntax (for the most part), I should use moduleResolution: 'node'?

I did not know where the @hot-reloader were coming from originally, and assumed they were private npm repos. i then looked at your snippets and figured it is your own files.

classic is correct. node follows nodeJs module resolution, that means import ... from "./foo" will resolve to either ./foo.js or ./foo/index.js, the second being wrong in AMD.

sorry for the confusion.

@mhegazy
Copy link
Contributor

mhegazy commented Jan 23, 2017

so is it working now?

@ORESoftware
Copy link
Author

ORESoftware commented Jan 24, 2017

my code runs fine; but it still compiles with unnecessary errors

my goal is to get 0 compile errors (obviously) but I still see a few related to RequireJS/TS integration:


10 const globalRequire = window.require;
                                ~~~~~~~

public/js/hot-reloader.tsx(10,30): error TS2339: Property 'require' does not exist on type 'Window'.



 20 requirejs.config({
                     
    
 72 });
    

public/js/main.tsx(20,18): error TS2345: Argument of type '{ enforceDefine: false; waitSeconds: number; baseUrl: string; bundles: { 'optimized/bundles/commo...' is not assignable to parameter of type 'RequireConfig'.




416 declare var require: Require;
                ~~~~~~~

node_modules/@types/requirejs/index.d.ts(416,13): error TS2403: Subsequent variable declarations must have the same type.  Variable 'require' must be of type 'NodeRequire', but here has type 'Require'.



 import hotReloader = require('@hot-reload-handler');
                               ~~~~~~~~~~~~~~~~~~~~~

public/js/application.tsx(4,30): error TS2307: Cannot find module '@hot-reload-handler'.



import config = require('@config');
                          ~~~~~~~~~

public/js/application.tsx(5,25): error TS2307: Cannot find module '@config'.


TS doesn't recognize these modules even though they are OK. I am still looking for a mechanism to tell the tsc compiler about the modules that are loaded via the requirejs.config paths.

There must be a heavy-handed way to just tell tsc that these imports are loaded via RequireJS network dependencies.

@hot-reload-handler and @config are defined by my requirejs.config paths, but TS doesn't know about them. How can I tell TS about them?

Also I see this error unrelated to RequireJS:

93         function startProgressBar() {
                    ~~~~~~~~~~~~~~~~

public/js/hot-reload-handler.tsx(93,18): error TS1252: Function declarations are not allowed inside blocks in strict mode when targeting 'ES3' or 'ES5'. Modules are automatically in strict mode.

I am trying to get rid of that error, how can I turn off strict mode with tsc?

@ORESoftware
Copy link
Author

ORESoftware commented Jan 24, 2017

All of the above errors should be avoidable. I would really love some help to fix these.. Can I share my project with you?

@mhegazy
Copy link
Contributor

mhegazy commented Jan 24, 2017

declare var require: Require;

you do not need this.

import hotReloader = require('@hot-reload-handler');

did you add a path mapping entry for it? e.g.

    "paths": {
      "@hot-reload-handler": [ "js/hot-reload-handler"]
    },

or just

    "paths": {
      "@*": [ "js/*"]
    },

function startProgressBar() {

I think the error there is self-explanatory, you either need to make this a const startProgressBar = function () or move it out of the if block. the behavior of functions in blocks have changed in ES6.

@ORESoftware
Copy link
Author

ORESoftware commented Jan 24, 2017

nice, thanks let me try it, your explanation is clearer now

but this line of code:

declare var require: Require;

is not in my project, I think it's a dependency...I think it's in here
node_modules/@types/requirejs/index.d.ts(416,13)

@ORESoftware
Copy link
Author

ORESoftware commented Jan 24, 2017

It's really close to working

is there a way to do something like this:

"paths": {
     "@config": [ false]
   },

or

  "paths": {
      "@config": [ "empty:"]
    },

I just want to ignore module resolution for the @config path

@ORESoftware
Copy link
Author

I tried explicitly declaring the paths in my tsconfig.json, same problem

screenshot from 2017-01-23 17-39-49

is there a way to escalate this issue with a ticket? Would love to solve this, I will do anything! thanks for your help

@ORESoftware
Copy link
Author

ORESoftware commented Jan 24, 2017

I would really love it if TS had 1st class support for RequireJS / AMD :)

@ORESoftware
Copy link
Author

Just FYI some of the TS errors were resulting from:

screenshot from 2017-01-23 17-57-05

e.g.

416 declare var require: Require;
                ~~~~~~~

node_modules/@types/requirejs/index.d.ts(416,13): error TS2403: Subsequent variable declarations must have the same type.  Variable 'require' must be of type 'NodeRequire', but here has type 'Require'.


@blakeembrey
Copy link
Contributor

Why are you using the node.js types if you're using RequireJS? That's the source of the require conflict.

@ORESoftware
Copy link
Author

@blakeembrey you're probably right! for my front-end tsconfig I should probably get rid of

    "types": [
      "node",  // get rid of this

@ORESoftware
Copy link
Author

I have two tsconfig.json files, one for backend one for frontend; the frontend config only includes the public directory, the backend config is everything but public directory.

I tried putting requirejs for the frontend config and node for the backend config and I get the same issues

@mhegazy
Copy link
Contributor

mhegazy commented Jan 24, 2017

I just want to ignore module resolution for the @configpath

in a .d.ts file add:

declare module "@config";

@mhegazy
Copy link
Contributor

mhegazy commented Jan 24, 2017

I tried explicitly declaring the paths in my tsconfig.json, same problem

what is your baseUrl? can you share your project?

@ORESoftware
Copy link
Author

ORESoftware commented Jan 25, 2017

yep, will share, thanks; note that I have tsconfig.json for the backend and tsconfig-fe.json for the frontend:

https://github.com/ORESoftware/hr4r2

to build the project you can use

./build.sh

and you should see the compile errors I mention above

@ORESoftware
Copy link
Author

ORESoftware commented Jan 25, 2017

since @hot-reload-handler and @hot-reloader are both .tsx files, can't I just point "paths" to those files, instead of pointing "paths" to d.ts files? I am not sure I understand the reason why d.ts exists if there are all .ts/tsx files.

Also regarding:

import * as Application as from "js/application"
Application.start();

I am not sure if that will work for RequireJS since that will create a module, but what I need is an executable file/script. So I left that code alone without altering it to TS module syntax code.

@mhegazy
Copy link
Contributor

mhegazy commented Jan 26, 2017

So the main issues were in tsconfig. PR sent to address these: ORESoftware/hr4r2#1

the error message you were getting about require with "subsequent declarations" is because @types\node\index.d.ts and@types\requirejs\index.d.ts both define a function called require and the compiler does not know which one to pick. you do not depend on @types/node, but @types/uuid does, and that what causes the issue. the fix for this issue is for https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/uuid/index.d.ts#L6 not to have this line.

the remaining issues seem self-explanatory to me.

@mhegazy mhegazy added Question An issue which isn't directly actionable in code External Relates to another program, environment, or user action which we cannot control. labels Jan 26, 2017
ORESoftware added a commit to ORESoftware/hr4r2 that referenced this issue Jan 27, 2017
@blakeembrey
Copy link
Contributor

@mhegazy I don't think it's possible to fix the uuid definition until TypeScript supports #7753 because removing Buffer from that definition is incorrect. It's a universal module that supports both node.js and browsers.

@mhegazy
Copy link
Contributor

mhegazy commented Jan 27, 2017

I think you can define a buffer in both, just make sure they are structurally compatable.

@ORESoftware
Copy link
Author

ORESoftware commented Jan 27, 2017

@mhegazy thanks so much for helping me out with this, the project compiles much better now

one thing I don't understand

you pared this (in tsconfig-fe.json):

  "types": [
      "lodash",
      "react",
      "react-dom",
      "redux",
      "react-redux",
      "redux-thunk",
      "async",
      "requirejs",
      "socket.io",
      "socket.io-client",
      "immutable",
      "uuid",
      "whatwg-fetch"
    ],

down to this:

   "types": [
      "react",
      "react-dom",
      "whatwg-fetch",
      "requirejs"
    ],

but I am using socket.io-client, async, redux, etc on the front-end. So...why don't I need to reference those in my front-end tsconfig?

thanks!

@blakeembrey
Copy link
Contributor

It's not a buffer on the front-end though, it's an array. It's only a buffer in node and TypeScript doesn't support the ability to define universal module like that.

@mhegazy
Copy link
Contributor

mhegazy commented Jan 27, 2017

Then there should be uuid-node and uuid-browser?

@mhegazy
Copy link
Contributor

mhegazy commented Jan 27, 2017

@ORESoftware

  • redux and reduc-thunk packages already carry a .d.ts file with them, so no need to install @types\redux, as a matter of fact, that package is empty, it only has a readme file that tells you it is not needed.
  • lodash is a node package, i am assuming you are not using it in your front end project. The others, i removed the ones that were not used.

@blakeembrey
Copy link
Contributor

blakeembrey commented Jan 27, 2017

@mhegazy How would that work when TypeScript doesn't know which one to resolve when you install @types/uuid?

Edit: Sorry for the thread within the thread, I'm happy to move this to the other issue I have open. I just don't see how you intend to workaround this when people need to able to use TypeScript with universal modules.

@mhegazy
Copy link
Contributor

mhegazy commented Jan 27, 2017

it won't :), you will have to add a mapping from "uuid" : ["uuid-node"] or "uuid" : ["uuid-browser"] .

@blakeembrey
Copy link
Contributor

blakeembrey commented Jan 27, 2017

@mhegazy In the path mapping section? If that works, it sounds like a decent solution. I guess the same goes for a module author then, tell their TypeScript users to use path mapping if they're using a browser? E.g. "uuid": "uuid/browser" - would that work? What about recursive support for this, if someone publishes a module that uses x but that x has a browser or node.js version? What about people building modules on top of something like uuid and need to use the types? They can no longer do import x = require('uuid') since path mapping only works at the top level.

Edit: If it helps, I do have half a dozen examples of this that I'm stuck on supporting with TypeScript. For instance, I want to provide support for browsers using https://github.com/blakeembrey/node-servie as request/response interfaces but I can't today because TypeScript doesn't know how to require the browser version (doesn't expose the buffer method but might support blobs, for instance). Even if path mapping worked here, it'd just break when a different module is published using servie (e.g. popsicle uses it as the interface layer but now people using the browser version of popsicle end up with the server version of servie). Anyway, that's what the other issue is for, just trying to figure out if maybe it can be resolved today already if there's recursive path mapping.

@basarat
Copy link
Contributor

basarat commented Jan 29, 2017

@blakeembrey I'd like to subscribe to that other issue. Can I have a link 🌹

@blakeembrey
Copy link
Contributor

@basarat It was #7753, issue for "node browser resolution" so it's possible to model "browser" resolution.

@mhegazy mhegazy closed this as completed Feb 28, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
External Relates to another program, environment, or user action which we cannot control. Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

4 participants