Invoke the text plugin from requirejs mapping - requirejs

I'm writing a web app using TypeScript, Backbone, and Mustache. I want to use Requirejs for dependency loading.
I'm also using the Web Essentials visual studio plugin for TypeScript with the AMD compilation option turned on. For those that are not familiar with this, it will wrap your type script file in an AMD module if you import external modules.
For example:
In type script I import the following modules in type definition files.
export import Backbone = module("Backbone");
import mainTemplate = module("MainTemplate");
The output is something like:
define(["require", "exports", "Backbone", "MainTemplate"], function(require, exports, __Backbone__, __mainTemplate__) {
//...code goes here ...
});
For the template, I've declared the following in a type definition file:
declare module "MainTemplate" { }
In order to support requirejs plugins, you need to declare your module as:
declare module "text!MainTemplate.html" { }
I'd like to keep the module name free of plugins and file extensions. This would leave me with some flexibility in the future.
I have the following mapping in require.
require.config({
map: {
"MyModule": {
"MainTemplate": "text!MainTemplate.html"
}
}
}
This successfully invokes the text plugin however, the plugin loads the wrong url. Sifting through the source code for the text plugin, I found that the following code is the culprit.
load: function (name, req, onLoad, config) {
...
url = req.toUrl(nonStripName),
//returns "scripts/**text!**MainTemplate.html**.html**"
...
}
If I name the module, 'MainTemplate.html' it works fine but I'd like to keep the extension out of the module name.
I've modified the text plugin with a simple regex replacement to strip out the plugin reference and the duplicate extension.
Is there a better way to handle this?

Ran into similar issue. Solved finally. See TypeScript: compiling removes unreferenced imports
/// <amd-dependency path="text!templates/application.htm" />
var applicationTemplate = require('text!templates/application.htm');

For Typescript 1.0 this works for me.
First I created a .d.ts file which stores all module declarations for each text template.
//workaround for typescript's lack of support for requirejs text template notation
//remember that a reference to the import variable has to be found in the class, otherwise typescript ignores the import
declare module "text!views/details/details.html" {
var text: string;
export = text;
}
declare module "text!views/layout/layout.html" {
var text: string;
export = text;
}
declare module "text!views/home/home.html" {
var text: string;
export = text;
}
then to refer to the text template I add these lines on top of the class/module.
/// <reference path="../texttemplate.d.ts"/>
import detailsTemplate = require('text!views/details/details.html');
The reference line is not actually needed, since the .d.ts file is picked up globally. But I added it as a reminder of the workaround. It also makes it easy to ctrl+click to go the d.ts. file.

There is a slightly nicer way to do this (I'm using typescript 2.0)
Referenced here: https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html
This code expects that your requirejs configuration and plugins are set up correctly:
/// <amd-dependency path="text!./about.html" name="template"/>
declare let template: string;
This helped me a lot to migrate lagacy code to typescript.

Since TypeScript 0.9.0 I think you need to do the following:
/// <amd-dependency path="text!templates/application.htm" />
declare var require:(moduleId:string) => any;
var applicationTemplate:string = require("text!templates/application.htm");
Check out more at http://www.codebelt.com/typescript/typescript-amd-with-requirejs-tutorial/

We are using Backbone and require.js for our TypeScript applications.
We don't use the
import backbone = module("Backbone")
syntax, but rather use a
/// <reference path="../../modules/Backbone.d.ts" />
reference, and then a BootStrapper.
This way, the 'text!htmlfile.html' syntax works perfectly with require.js.
I've put together a blog on using require.js with TypeScript and AMD:
http://blorkfish.wordpress.com/2012/10/23/typescript-organizing-your-code-with-amd-modules-and-require-js/

Related

Create a TypeScript library with optional dependencies resolved by application

I've written a library published to a private npm repo which is used by my applications.
This library contains utilities and has dependencies to other libraries, as an example let's choose #aws-sdk/client-lambda.
Some of my applications use only some of the utilities and don't need the dependencies to the external libraries, while some applications use all of the utilities.
To avoid having all applications getting a lot of indirect dependencies they don't need, I tried declaring the dependencies as peerDependencies and having the applications resolve the ones they need. It works well to publish the package, and to use it from applications who declare all of the peerDependencies as their own local dependencies, but applications failing to declare one of the dependencies get build errors when the included .d.ts files of the library are imported in application code:
error TS2307: Cannot find module '#aws-sdk/client-kms' or its corresponding type declarations.
Is it possible to resolve this situation so that my library can contain many different utils but the applications may "cherry-pick" the dependencies they need to fulfill the requirements of those utilities in runtime?
Do I have to use dynamic imports to do this or is there another way?
I tried using #ts-ignore in the library code, and it was propagated to the d.ts file imported by the applications, but it did not help.
Setup:
my-library
package.json:
peerDependencies: {
"#aws-sdk/client-lambda": "^3.27.0"
}
foo.ts:
import {Lambda} from '#aws-sdk/client-lambda';
export function foo(lambda: Lambda): void {
...
}
bar.ts:
export function bar(): void {
...
}
index.ts:
export * from './foo';
export * from './bar';
my-application1 - works fine
package.json:
dependencies: {
"my-library": "1.0.0",
"#aws-sdk/client-lambda": "^3.27.0"
}
test.ts:
import {foo} from 'my-library';
foo();
my-application2 - does not compile
package.json:
dependencies: {
"my-library": ...
}
test:ts:
import {bar} from 'my-library';
bar();
I found two ways of dealing with this:
1. Only use dynamic imports for the optional dependencies
If you make sure that types exported by the root file of the package only include types and interfaces and not classes etc, the transpiled JS will not contain any require statement to the optional library. Then use dynamic imports to import the optional library from a function so that they are required only when the client explicitly uses those parts of the library.
In the case of #aws-sdk/client-lambda, which was one of my optional dependencies, I wanted to expose function that could take an instance of a Lambda object or create one itself:
import {Lambda} from '#aws-sdk/client-lambda';
export function foo(options: {lambda?: Lambda}) {
if (!lambda) {
lambda = new Lambda({ ... });
}
...
}
Since Lambda is a class, it will be part of the transpiled JS as a require statement, so this does not work as an optional dependency. So I had to 1) make that import dynamic and 2) define an interface to be used in place of Lambda in my function's arguments to get rid of the require statement on the package's root path. Unfortunately in this particular case, the AWS SDK does not offer any type or interface which the class implements, so I had to come up with a minimal type such as
export interface AwsClient {
config: {
apiVersion: string;
}
}
... but of course, lacking a type ot represent the Lambda class, you might even resort to any.
Then comes the dynamic import part:
export async function foo(options: {lambda?: AwsClient}) {
if (!lambda) {
const {Lambda} = await import('#aws-sdk/client-lambda');
lambda = new Lambda({ ... });
}
...
}
With this code, there is no longer any require('#aws-sdk/client-lambda') on the root path of the package, only within the foo function. Only clients calling the foo function will have to have the dependency in their
node_modules.
As you can see, a side-effect of this is that every function using the optional library must be async since dynamic imports return promises. In my case this worked out, but it may complicate things. In one case I had a non-async function (such as a class constructor) needing an optional library, so I had no choice but to cache the promised import and resolve it later when used from an async member function, or do a lazy import when needed. This has the potential of cluttering code badly ...
So, to summarize:
Make sure any code that imports code from the optional library is put inside functions that the client wanting to use that functionality calls
It's OK to have imports of types from the optional library in the root of your package as it's stripped out when transpiled
If needed, defined substitute types to act as place-holders for any class arguments (as classes are both types and code!)
Transpile and investigate the resulting JS to see if you have any require statement for the optional library in the root, if so, you've missed something.
Note that if using webpack etc, using dynamic imports can be tricky as well. If the import paths are constants, it usually works, but building the path dynamically (await import('#aws-sdk/' + clientName)) will usually not unless you give webpack hints. This had me puzzled for a while since I wrote a wrapper in front of my optional AWS dependencies, which ended up not working at all for this reason.
2. Put the files using the optional dependencies in .ts files not exported by the root file of the package (i.e., index.ts).
This means that clients wanting to use the optional functionality must
import those files by sub-path, such as:
import {OptionalStuff} from 'my-library/dist/optional;
... which is obviously less than ideal.
in my case, the typescript IDE in vscode fails to import the optional type, so im using the relative import path
// fix: Cannot find module 'windows-process-tree' or its corresponding type declarations
//import type * as WindowsProcessTree from 'windows-process-tree';
import type * as WindowsProcessTree from '../../../../../node_modules/#types/windows-process-tree';
// global variable
let windowsProcessTree: typeof WindowsProcessTree;
if (true) { // some condition
windowsProcessTree = await import('windows-process-tree');
windowsProcessTree.getProcessTree(rootProcessId, tree => {
// ...
});
}
package.json
{
"devDependencies": {
"#types/windows-process-tree": "^0.2.0",
},
"optionalDependencies": {
"windows-process-tree": "^0.3.4"
}
}
based on vscode/src/vs/platform/terminal/node/windowsShellHelper.ts

Using TypeScript Declaration For External JavaScript Library

I'm not sure if this extreme edge case or something but I cannot seem to find straight forward documentation on how to do this (or I'm just really not understanding what is available):
I am developing an ionic application and as part of that I need to use the ALKMaps JavaScript library (which is similar to Google Maps API). To do so, I created a local npm module and within that I created a alkmaps.d.ts file as recommended by https://www.typescriptlang.org/docs/handbook/declaration-files/by-example.html#objects-with-properties). However, I cannot seem to figure out how to properly import it into my angular code. The same document suggests that using <reference path=''> tags is not good but that is the only thing that seems to satisfy the tsc compiler.
My declaration file, alkmaps.d.ts, looks like (inside excluded for brevity):
declare namespace ALKMaps {
export class Map { ... }
...
}
And I was trying to import it into a file like:
import { ALKMaps } from 'alkmaps'; // Error: File '.../alkmaps.d.ts' is not a module
I also tried the following but got the same error.
import ALKMaps = require('alkmaps');
Using the reference tag seems to work within this module but then the project that utilizes this module still throws the "is not a module" error (that might warrant a separate question)
From https://github.com/Microsoft/TypeScript/issues/11420 I found the idea of using export = ALKMaps or export as namespace ALKMaps but adding those to my declaration file resulted in different errors instead.
Can anyone please explain in a straightforward way how to use declaration files representing external JS libraries in a typescript node module?
This is how I was able to get alkMaps into my Angular 2 app
Insert the script into the index.html file.
Declare an ALKMaps variable in the component that you are adding the map
imports .....
declare let ALKMaps : any;
#Component({
selector: 'show-map',
templateUrl: 'show-map.component.html'
})
export class ShowMapComponent implements Oninit{
map : any;
constructor() {
}
ngOnInit() {
ALKMaps.APIKey = "apiKey";
this.map = new ALKMaps.Map("map", {displayProjection: new ALKMaps.Projection("EPSG:4326")});
}
}
This will get the map to display and you can put different layers on the map, however the map does not display correctly. #Mike, if you were able to get further than this, will you please comment?
EDIT: The tiles on the image were elongated and not connected. After inspecting the css the main.css, after building, set a global property on the img element to:
img {
max-width:100%
}
The tiles for the map are originally set to 256% for the width. To correct the element, I changed the property for img in the style sheet.
show-map {
img {
max-width: 256%
}
}

Having error "Module 'name' resolves to an untyped module at..." when writing custom TypeScript definition file

I can't find TypeScript definition #type/{name} for one of my installed NodeJS packages, so I attempt to write a d.ts file for it, and put the file in {project root}\typings folder. This is how I do:
// My source code: index.ts
import Helper from 'node-helper-lib';
// My definition: \typings\node-helper-lib.d.ts
declare....(something else)
declare module 'node-helper-lib' {
class Helper { ... }
export = Helper;
}
However, Visual Studio Code keeps yielding this error and puts red line under declare module 'node-helper-lib':
[ts] Invalid module name in augmentation. Module 'node-helper-lib'
resolves to an untyped module at '{project
path}\node_modules\node-helper-lib\index.js', which cannot be
augmented.
Isn't it legit that because the library is untyped, so I should be allowed to add typing to it?
UPDATE:
I am using:
TypeScript: 2.1.4
Visual Studio Code: 1.9.1
Node JS: 6.9.4
Windows 10 x64
The actual solution is given in a comment by #Paleo in #hirikarate's answer:
Imports should be declared inside the module declaration.
Example:
declare module 'node-helper-lib' {
import * as SomeThirdParty from 'node-helper-lib';
interface Helper {
new(opt: SomeThirdParty.Options): SomeThirdParty.Type
}
export = Helper;
}
After some tries and errors, I found that augmentation means "declaring a module in the same file with other module declaration(s)".
Therefore if we want to write a definition file for an untyped 3rd-party JavaScript library, we must have ONLY ONE declare module 'lib-name' in that file, and 'lib-name' must exactly match the library name (can be found in its package.json, "name" property).
On the other hand, if a 3rd-party library already has definition file .d.ts included, and we want to extend its functionalities, then we can put the additional definition in another file that we create. This is called augmenting.
For example:
// These module declarations are in same file, given that each of them already has their own definition file.
declare module 'events' {
// Extended functionality
}
declare module 'querystring' {
// Extended functionality
}
declare module '...' { ... }
I leave my discovery here just in case somebody has same question. And please correct me if I missed something.
The issue for me was that I was trying to declare the module in a .ts file. I changed it to .d.ts and it all worked just fine.
I was getting that error message too. The issue for me was that I was trying to declare another module in an existing type definition file that had a module declaration in it. After I moved the new module declaration to a new file, the error went away.

How to use multiple compiled ts files in a node module?

Currently I am trying to use TypeScript to create JavaScript-Files which are then required in a index.js file. I am using VS 2015 Update 3 with node.js tools 1.2 RC. Sadly it is not working like I thought it would.
To begin with here is my initial idea:
I have a node module (to be precise, it is a deployd module http://docs.deployd.com/docs/using-modules/). This module is handling payment providers like paypal or stripe. Now I want to use TypeScript to write interfaces, classes and use types to make it easier to add new payment providers. The old .js files should still be there and used. I want to migrate step by step and use the self-written and compiled .js files together. So I thought I can create .ts files, write my code, save, let VS compile to js and require the compiled js file in another js file. Okay, that is my idea... Now the problem
I have a PaymentProvider.ts file which looks like this
import IPaymentProvider = require("./IPaymentProvider.ts"); // Interface, can't be realized in JavaScript, just TypeScript
export abstract class PaymentProvider implements IPaymentProvider.IPaymentProvider
{
providerName: string;
productInternalId: number;
constructor(providerName : string)
{
this.providerName = providerName;
}
//... some methods
};
The other file is PaypalPaymentProvider.ts
import PaymentProvider = require("./PaymentProvider.ts");
export class PaypalPaymentProvider extends PaymentProvider.PaymentProvider
{
constructor()
{
super("paypal");
}
// more methods
}
VS 2015 doesn't show any errors. The js and .js.map files are generated. Now I thought I could require the files and that's it. I tried to use the PaypalPaymentProvider.js like this const PaypalPaymentProvider = require("./lib/payment-provider/PaypalPaymentProvider.js"); (yes, it is located there) but it's not working. When starting the index.js via node I get the following error:
...\Path\PaymentProvider.ts:1 (function (exports, require, module, __filename, __dirname) { import IPaymentProvider = require("./IPaymentProvider.ts"); Unexpected token import....
I find it strange that this is the error, because JavaScript doesnt't have Interfaces. The compiled IPaymentProvider.js is empty.
Also I thought that TypeScript is mainly for development and the compiled JavaScript for production. So why it is requiring a ts-file? I thought imports in typescript will be converted to require of the compiled js-file?
Do I need to require all compiled js files and not only the one I currently try to use? (I don't think so...)
To be honest, I think the main problem is that I am new to TypeScript and make something wrong from the very beginning.
Any help/advice? Thanks!
I have the solution... Thanks to Paelo's links I was able to see that I need to omit the file ending! So the really simple solution was to write
import IPaymentProvider = require("./IPaymentProvider");
instead of
import IPaymentProvider = require("./IPaymentProvider.ts");
When I changed that in every ts file it worked perfectly!

TypeScript, AMD (requireJS) & Importing 3rd Party-Libraries drives me insane

I'm trying to use 3rd-Party-Libraries such as Knockout in my TypeScript-project.
As of organising my code, I am using AMD (requireJS to be specific) and to use the libraries in my project, I load d.ts-files from the great definitelytyped-project.
Now while I can use my own modules and libraries - which are all built with TypeScript - with ththis e simple 1 liner:
import PeopleViewModel = require("../ViewModels/PeopleViewModel");
I can't do that with the d.ts-files supplied by definitelytyped.
Now I know about the amd-dependency-command, but I don't understand how that is useful.
In my JS-code, I do get a reference to the path in the in the define-call, but not the variable in the created callback.
To "say" it in code:
My TS-File (removed the openening-tag from the ///-commands, otherwise it won't get shown here in stackoverflow):
/// reference path="../../../../Libs/Knockout/knockout.d.ts" />
/// amd-dependency path="../../../../Libs/Knockout/knockout-3.1.0" />
import PeopleViewModel = require("../ViewModels/PeopleViewModel");
var johnViewModel = new PeopleViewModel("john");
var exampleVar = ko.observable(johnViewModel);
That compiles to:
define(["require", "exports", "../ViewModels/PeopleViewModel", "../Libs/Knockout/knockout-3.1.0"], function(require, exports, PeopleViewModel) {
var johnViewModel = new PeopleViewModel("john");
var exampleVar = ko.observable(johnViewModel);
});
Now you can see, that the amd-dependency created the correct link to the knockout-js-file, but it does not yiel the "ko"-variable, which is declared in the d.ts-file.
How do I get this to work without touching the js-file?
Maybe there is an argument in the amd-dependency-command, but there is still no documentation about that.
Thank you all every much!
To use the definition files you just need references like this at the head of your files:
/// <reference path="jquery/jquery.d.ts" />
Or if using visual studio you don't even need them!
If using visual studio 2012 and nuget you may have fallen for the incorrect content type issue. Make sure that all typing files (*.d.TS) are set as TypeScriptCompile in the solution explorer.
No need for require syntax for typing files.

Resources