I'm working on a polyfill that extends the behavior of the JSON.parse() function by adding an optional third argument to the reviver callback function.
The polyfill should be imported like this for the end users:
import 'my-polyfill';
The JSON.parse() is defined in the node_modules/typescript/lib/lib.es5.d.ts like this:
// lib.es5.d.ts
interface JSON {
// …
parse(
text: string,
reviver?: (this: any, key: string, value: any) => any
): any;
// …
}
How do I override this definition, so when the polyfill is installed/imported my definition is used instead? E.g.:
interface ContextType {
// …
}
interface JSON {
parse<Type = any>(
text: string,
reviver?: (
key: string,
value: any,
context?: ContextType
) => any
): Type;
}
You could create declarations file and name it, for example, as polyfill.d.ts. Then, you should use standard way of extending global types as follows:
declare global {
interface JSON {
// Here, you should write your own implementation of `parse` function.
parse(variable: string): void;
}
}
export {};
export is required by TypeScript and you can learn more about it in this answer and its comments.
Don't forget, that when you create a separate library, this file should exist in final build directory. So, when some user installs your package and imports it, file polyfill.d.ts would be automatically applied by TypeScript.
I am not sure, that you are able to make TypeScript transfer declarations file (.d.ts) with its own functionality. To do this, we should use some hack, described here. We should name file not polyfill.d.ts, but polyfill.ts and import it in library's index.ts file, so it would be imported by TypeScript and transferred to build directory.
I'm using a package written in TS in a JS app. I have a method with multiple overloads:
overloadedMethod(stringParameter: string): void;
overloadedMethod(stringParameter: string, otherParameter: any): void;
And I call it like this:
if (!myOtherParameter) {
overloadedMethod(myStringParameter)
} else {
overloadedMethod(myStringParameter, myOtherParameter)
}
I do not like this one bit. Have I approached the problem incorrectly, or is there some way I can include the otherParameter only if it is not undefined or null?
log.ts has the following code
import {LOG} from './log'
LOG.e("tag","error");
LOG.f("tag","error");
LOG.d("tag","error");
I want the IntelliSense support for the TS file but don't want the consequences of import on generated javascript.
I simply want the following in my Javascript file (log.js)
LOG.e("tag","error");
LOG.f("tag","error");
LOG.d("tag","error");
log.ts file must contain only declarations.
declare class LOG {
public static e(a: string, b: string): void;
public static f(a: string, b: string): void;
public static d(a: string, b: string): void;
}
If you want to use an imported class, and it should not be included in the generated javascript, then you need to declare it using declare.
This class is not functional javascript code, but only declares the type of the class and its methods. When generating javascript, this class will not be included in the build.
But to execute such javascript, LOG must already be declared in the global environment.
See playground...
I am trying to migrate from using compiler flag -XjsInteropMode JS but I run into a problem.
I have an interface called Module that looks like this:
#JsType
public interface Module {
#JsProperty
String getBasename();
}
Then I have another interface called AuthenticationModule which looks like this:
#JsType
public interface AuthenticationModule extends Module {
static final String MODULE_NAME = "authentication";
void logIn(String username, String password, JsConsumer<JavaScriptObject> onSuccess, JsConsumer<JavaScriptObject> onError);
void logOut(JsConsumer<JavaScriptObject> onSuccess, JsConsumer<JavaScriptObject> onError);
}
The module interface is just a marker interface, so when I was loading some module, I was able to cast it in the end to a module I wanted, example here:
#Override
public void getAuthenticationModule(
OnModuleLoaded<AuthenticationModule> onModuleLoaded) {
initializeIfNecessary();
JsArrayString requiredModules = JavaScriptObject.createArray().cast();
requiredModules.push(AuthenticationModule.MODULE_NAME);
modules.require(requiredModules, loadedModule -> {
onModuleLoaded.moduleLoaded((AuthenticationModule) loadedModule); // this line (the casting) throws ClassCastException
});
}
modules in this code is another interface which looks like this:
#JsType
public interface Modules {
#JsFunction
#FunctionalInterface
interface CallbackRequire {
void apply(Module module);
}
#JsProperty
String getBase();
#JsProperty
void setBase(String base);
void require(JsArrayString modules, CallbackRequire onload);
}
I followed the rules on how to migrate in this document:
https://docs.google.com/document/d/10fmlEYIHcyead_4R1S5wKGs1t2I7Fnp_PaNaa7XTEk0/edit#
I was not able to solve this issue. The best I could get was changing #JsType to #JsType(isNative = true). Then the casting was working, but another error occured, can't remember now, but I'm not sure if the isNative is really the right way to solve this issue.
After adding "(isNative = true)" to most of the interfaces, it all works again. The only thing I had to change were the MODULE_NAME fields, since they cannot be initialized in a native JsType.
Edit: I rewrote the post to better explain the steps I've taken and the issues I'm facing
I started out with a simple html/js page.
I'm also using xregexp from a cdn.
var reg = XRegExp("^lights:(?<option>on|off)$", "xgi");
var match = XRegExp.exec("lights:on", reg);
document.body.innerText = "Lights are: " + match["option"];
<!DOCTYPE html>
<html lang="en">
<head></head>
<body></body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xregexp/3.1.1/xregexp-all.js"></script>
<script src="app.js"></script>
</html>
Next, I want to convert the js file to typescript. So I:
rename .js to .ts
added declare var XRegExp:any; to the top of the ts file (described here)
ran tsc --init to create tsconfig file
ran tsc
So far, Typescript compiles fine, and page is still working.
Next, I want to replace the declare var XRegExp:any with an interface, one from a community-maintained declaration file.
I install the typings for xregexp via typings install dt~xregexp -SG (d.ts file link)
But I'm unable to access the interfaces declared in the .d.ts file, because the declaration uses declare module 'xregexp' and export =.
According to Typescript's documentation on Modules, export = is used with import = require(). But that involves module loading, which is not what I want.
I've tried Pelle's suggestion, but still have no idea how to declare an ambient variable using it.
declare var XRegExp:xregexp.OuterXRegExp; // <-- TS error: Module 'xregexp' has no exported member 'OuterXRegExp'
I would like to know:
The d.ts declaration uses declare module "xregexp". Can I refer to any of its inner interfaces/types without an import?
is it possible for me to create an additional d.ts to declare the ambient variable? (like what dt~react-global does) How do I do this, without modifying the downloaded d.ts ?
reason: I would rather not change the declaration file pulled via typings in case of future changes.
At this point, I'm not doing Node development, nor do I want to set up a module loader and/or browserify/webpack.
I simply want to convert an already-working js file to ts, and would like to make Typescript aware of an ambient variable XRegExp, which has an interface as specified in the downloaded d.ts. Is it possible for me to achieve this without too much ceremony?
[Edit]
So, I rewrote the declaration file, using jquery.d.ts as a guide.
This appears to work when loading via script or import, but doesn't answer my original question.
interface TokenOpts {
scope ? : string;
trigger ? : () => boolean;
customFlags ? : string;
}
interface XRegExp {
(pattern: string, flags ? : string): RegExp;
(pattern: RegExp): RegExp;
addToken(regex: RegExp, handler: (matchArr: RegExpExecArray, scope: string) => string, options ? : TokenOpts): void;
build(pattern: string, subs: string[], flags ? : string): RegExp;
cache(pattern: string, flags ? : string): RegExp;
escape(str: string): string;
exec(str: string, regex: RegExp, pos ? : number, sticky ? : boolean): RegExpExecArray;
forEach(str: string, regex: RegExp, callback: (matchArr: RegExpExecArray, index: number, input: string, regexp: RegExp) => void): any;
globalize(regex: RegExp): RegExp;
install(options: string): void;
install(options: Object): void;
isInstalled(feature: string): boolean;
isRegExp(value: any): boolean;
match(str: string, regex: RegExp, scope: string): any;
match(str: string, regex: RegExp, scope: "one"): string;
match(str: string, regex: RegExp, scope: "all"): string[];
match(str: string, regex: RegExp): string[];
matchChain(str: string, chain: RegExp[]): string[];
matchChain(str: string, chain: {
regex: RegExp;
backref: string
}[]): string[];
matchChain(str: string, chain: {
regex: RegExp;
backref: number
}[]): string[];
matchRecursive(str: string, left: string, right: string, flags ? : string, options ? : Object): string[];
replace(str: string, search: string, replacement: string, scope ? : string): string;
replace(str: string, search: string, replacement: Function, scope ? : string): string;
replace(str: string, search: RegExp, replacement: string, scope ? : string): string;
replace(str: string, search: RegExp, replacement: Function, scope ? : string): string;
replaceEach(str: string, replacements: Array < RegExp | string > []): string;
split(str: string, separator: string, limit ? : number): string[];
split(str: string, separator: RegExp, limit ? : number): string[];
test(str: string, regex: RegExp, pos ? : number, sticky ? : boolean): boolean;
uninstall(options: Object): void;
uninstall(options: string): void;
union(patterns: string[], flags ? : string): RegExp;
version: string;
}
declare module "xregexp" {
export = XRegExp;
}
declare var XRegExp: XRegExp;
This is not really a typescript problem: if you want to use npm packages, it is strongly recommended to use a dependency manager like browserify or webpack to them. (I would recommend browserify, if you're only looking for something to handle your modules).
If you really don't want to use the amazing power of npm, there is also a hacky way of solving this specific problem: changing the declaration file to declare a namespace instead of a module. Now you can effectively use the types anywhere in your project.
Just make these two changes to your declaration file, and you should be good to go:
declare a namespace instead of a module:
declare module 'xregexp' {
//becomes
declare namespace xregexp {
get rid of the export on the bottom of the page:
// remove this:
export = OuterXRegExp;
Now you can call anywhere in your project. Eg:
var outerxregexp: xregexp.OuterXRegExp.TokenOpts = { trigger: () => true }
But again, it is strongly recommended to pull in browserify or so 🙂
Try adding the following line on top of the file where you are using XRegExp:
/// <reference path="./path/to/downloaded/xregexp.d.ts" />
More on the matter triple slash directives
if you have a tsconfig.json you'll need to add the d.ts file like this:
{
"files": [
"path/to/definition/xregexp.d.ts",
]
}