I've just been tasked with converting a fairly large project from Javascript to Typescript. One of the difficulties is that one of the 3rd-party modules we are using is also in Javascript, but ideally we'd like to add the types for that as well. This 3rd-party module is used extensively throughout the codebase, so we'd like to add the types progressively.
So, let's say we have two components like this:
// -----| FirstComponentConsumer.tsx |-------------------------
import { FirstComponent } from 'third-party';
export const FirstComponentConsumer = () => {
return (
<FirstComponent name="pants" />
);
};
// -----| SecondComponentConsumer.tsx |------------------------
import { SecondComponent } from 'third-party';
export const SecondComponentConsumer = () => {
return (
<SecondComponent type="text" />
);
};
Now, I'd like to add the types for FirstComponent but not SecondComponent YET - I'll add these later (in reality there are hundreds of exports that will need to be typed). So I can create my types file, something like:
// types.d.ts
import type { ReactNode } from 'react';
declare module 'third-party' {
export type FirstComponentProps = {
name?: string;
}
export const FirstComponent = (props: FirstComponentProps) => ReactNode;
}
This works as expected - FirstComponent is now typed! But, it breaks SecondComponentConsumer because I haven't said that the third-party module exports a SecondComponent:
Module '"third-party"' has no exported member 'SecondComponent'. TS2305
Of course this makes perfect sense, but can I turn off this checking for "undeclared types"? Or is that trying to have my cake and eat it too?
Something like this might do the trick, basically you are just exporting everything as any, but specifically typing a single component. I haven't tested this, but I think it should work.
declare module "third-party" {
const FirstComponent: (name?: string) => ReactNode;
type Exports = {
FirstComponent: typeof FirstComponent;
} & {
[key: PropertyKey]: any;
}
const exports: Exports;
export default exports;
export { FirstComponent };
}
I have a constant library that has many files such as the following structure:
#1 index.ts
#2 src/
contact.ts
const first = [];
const second = [];
const third = [];
location.ts
const first = '';
const second = '';
const third = '';
Is it possible to import and export all of the constants from each file into index.ts such that I can import into a project such as the following:
// Imports: Constants
import { constants } from '#jeff/constants-library';
console.log(constants.contact.first);
console.log(constants.contact.second);
console.log(constants.contact.third);
What is the fastest/most efficient way to dynamically export the constants from my library files so I can import the into my projects?
In your files like contact and location, you'll need to mark the const values you want as exports. Modules are designed for encapsulation, so dynamic export isn't really an option here. However, it's just a matter of adding the keyword export:
export const first = [];
After that, you can create a constants-library.ts or constants-library/index.ts which automatically exports and imports:
import * as foo from './foo';
import * as bar from './bar';
export const constants = { foo, bar };
At this point, assuming your #jeff path is set up, your code should work as expected:
import { constants } from '#jeff/constants-library';
console.log(constants.contact.first);
Note that this will include all of the exports in your files, since TypeScript can't tell which constants you want or didn't want--it only knows which exports you've listed. As an alternative, rather than exporting all of your consts individually, you could bundle them into a single export using object shorthand.
// in contact.ts and location.ts
export constants = { first, second, third };
// in constant-library.ts or constant-library/index.ts
import { constants as contact } from './contact';
import { constants as location } from './location';
export constants = { contact, location };
With ES6, I can import several exports from a file like this:
import {ThingA, ThingB, ThingC} from 'lib/things';
However, I like the organization of having one module per file. I end up with imports like this:
import ThingA from 'lib/things/ThingA';
import ThingB from 'lib/things/ThingB';
import ThingC from 'lib/things/ThingC';
I would love to be able to do this:
import {ThingA, ThingB, ThingC} from 'lib/things/*';
or something similar, with the understood convention that each file contains one default export, and each module is named the same as its file.
Is this possible?
I don't think this is possible, but afaik the resolution of module names is up to module loaders so there might a loader implementation that does support this.
Until then, you could use an intermediate "module file" at lib/things/index.js that just contains
export * from 'ThingA';
export * from 'ThingB';
export * from 'ThingC';
and it would allow you to do
import {ThingA, ThingB, ThingC} from 'lib/things';
Just a variation on the theme already provided in the answer, but how about this:
In a Thing,
export default function ThingA () {}
In things/index.js,
export {default as ThingA} from './ThingA'
export {default as ThingB} from './ThingB'
export {default as ThingC} from './ThingC'
Then to consume all the things elsewhere,
import * as things from './things'
things.ThingA()
Or to consume just some of things,
import {ThingA,ThingB} from './things'
The current answers suggest a workaround but it's bugged me why this doesn't exist, so I've created a babel plugin which does this.
Install it using:
npm i --save-dev babel-plugin-wildcard
then add it to your .babelrc with:
{
"plugins": ["wildcard"]
}
see the repo for detailed install info
This allows you to do this:
import * as Things from './lib/things';
// Do whatever you want with these :D
Things.ThingA;
Things.ThingB;
Things.ThingC;
again, the repo contains further information on what exactly it does, but doing it this way avoids creating index.js files and also happens at compile-time to avoid doing readdirs at runtime.
Also with a newer version you can do exactly like your example:
import { ThingsA, ThingsB, ThingsC } from './lib/things/*';
works the same as the above.
You now can use async import():
import fs = require('fs');
and then:
fs.readdir('./someDir', (err, files) => {
files.forEach(file => {
const module = import('./' + file).then(m =>
m.callSomeMethod();
);
// or const module = await import('file')
});
});
Great gugly muglys! This was harder than it needed to be.
Export one flat default
This is a great opportunity to use spread (... in { ...Matters, ...Contacts } below:
// imports/collections/Matters.js
export default { // default export
hello: 'World',
something: 'important',
};
// imports/collections/Contacts.js
export default { // default export
hello: 'Moon',
email: 'hello#example.com',
};
// imports/collections/index.js
import Matters from './Matters'; // import default export as var 'Matters'
import Contacts from './Contacts';
export default { // default export
...Matters, // spread Matters, overwriting previous properties
...Contacts, // spread Contacts, overwriting previosu properties
};
// imports/test.js
import collections from './collections'; // import default export as 'collections'
console.log(collections);
Then, to run babel compiled code from the command line (from project root /):
$ npm install --save-dev #babel/core #babel/cli #babel/preset-env #babel/node
(trimmed)
$ npx babel-node --presets #babel/preset-env imports/test.js
{ hello: 'Moon',
something: 'important',
email: 'hello#example.com' }
Export one tree-like default
If you'd prefer to not overwrite properties, change:
// imports/collections/index.js
import Matters from './Matters'; // import default as 'Matters'
import Contacts from './Contacts';
export default { // export default
Matters,
Contacts,
};
And the output will be:
$ npx babel-node --presets #babel/preset-env imports/test.js
{ Matters: { hello: 'World', something: 'important' },
Contacts: { hello: 'Moon', email: 'hello#example.com' } }
Export multiple named exports w/ no default
If you're dedicated to DRY, the syntax on the imports changes as well:
// imports/collections/index.js
// export default as named export 'Matters'
export { default as Matters } from './Matters';
export { default as Contacts } from './Contacts';
This creates 2 named exports w/ no default export. Then change:
// imports/test.js
import { Matters, Contacts } from './collections';
console.log(Matters, Contacts);
And the output:
$ npx babel-node --presets #babel/preset-env imports/test.js
{ hello: 'World', something: 'important' } { hello: 'Moon', email: 'hello#example.com' }
Import all named exports
// imports/collections/index.js
// export default as named export 'Matters'
export { default as Matters } from './Matters';
export { default as Contacts } from './Contacts';
// imports/test.js
// Import all named exports as 'collections'
import * as collections from './collections';
console.log(collections); // interesting output
console.log(collections.Matters, collections.Contacts);
Notice the destructuring import { Matters, Contacts } from './collections'; in the previous example.
$ npx babel-node --presets #babel/preset-env imports/test.js
{ Matters: [Getter], Contacts: [Getter] }
{ hello: 'World', something: 'important' } { hello: 'Moon', email: 'hello#example.com' }
In practice
Given these source files:
/myLib/thingA.js
/myLib/thingB.js
/myLib/thingC.js
Creating a /myLib/index.js to bundle up all the files defeats the purpose of import/export. It would be easier to make everything global in the first place, than to make everything global via import/export via index.js "wrapper files".
If you want a particular file, import thingA from './myLib/thingA'; in your own projects.
Creating a "wrapper file" with exports for the module only makes sense if you're packaging for npm or on a multi-year multi-team project.
Made it this far? See the docs for more details.
Also, yay for Stackoverflow finally supporting three `s as code fence markup.
Similar to the accepted answer but it allows you to scale without the need of adding a new module to the index file each time you create one:
./modules/moduleA.js
export const example = 'example';
export const anotherExample = 'anotherExample';
./modules/index.js
// require all modules on the path and with the pattern defined
const req = require.context('./', true, /.js$/);
const modules = req.keys().map(req);
// export all modules
module.exports = modules;
./example.js
import { example, anotherExample } from './modules'
If you are using webpack. This imports files automatically and exports as api namespace.
So no need to update on every file addition.
import camelCase from "lodash-es";
const requireModule = require.context("./", false, /\.js$/); //
const api = {};
requireModule.keys().forEach(fileName => {
if (fileName === "./index.js") return;
const moduleName = camelCase(fileName.replace(/(\.\/|\.js)/g, ""));
api[moduleName] = {
...requireModule(fileName).default
};
});
export default api;
For Typescript users;
import { camelCase } from "lodash-es"
const requireModule = require.context("./folderName", false, /\.ts$/)
interface LooseObject {
[key: string]: any
}
const api: LooseObject = {}
requireModule.keys().forEach(fileName => {
if (fileName === "./index.ts") return
const moduleName = camelCase(fileName.replace(/(\.\/|\.ts)/g, ""))
api[moduleName] = {
...requireModule(fileName).default,
}
})
export default api
I've used them a few times (in particular for building massive objects splitting the data over many files (e.g. AST nodes)), in order to build them I made a tiny script (which I've just added to npm so everyone else can use it).
Usage (currently you'll need to use babel to use the export file):
$ npm install -g folder-module
$ folder-module my-cool-module/
Generates a file containing:
export {default as foo} from "./module/foo.js"
export {default as default} from "./module/default.js"
export {default as bar} from "./module/bar.js"
...etc
Then you can just consume the file:
import * as myCoolModule from "my-cool-module.js"
myCoolModule.foo()
Just an other approach to #Bergi's answer
// lib/things/index.js
import ThingA from './ThingA';
import ThingB from './ThingB';
import ThingC from './ThingC';
export default {
ThingA,
ThingB,
ThingC
}
Uses
import {ThingA, ThingB, ThingC} from './lib/things';
Nodejs ? Do like this:
Create a folder with index.js, in index file, add this:
var GET = require('./GET');
var IS = require('./IS');
var PARSE = require('./PARSE');
module.exports = { ...GET, ...IS, ...PARSE};
And, in file GET.js, or IS.js export as normal:
module.exports = { /* something as you like */}
ANd now, you need only including index.js like:
const Helper = require('./YourFolder');
Helper will include all of function in YourFolder.
Good day!
This is not exactly what you asked for but, with this method I can Iterate throught componentsList in my other files and use function such as componentsList.map(...) which I find pretty usefull !
import StepOne from './StepOne';
import StepTwo from './StepTwo';
import StepThree from './StepThree';
import StepFour from './StepFour';
import StepFive from './StepFive';
import StepSix from './StepSix';
import StepSeven from './StepSeven';
import StepEight from './StepEight';
const componentsList= () => [
{ component: StepOne(), key: 'step1' },
{ component: StepTwo(), key: 'step2' },
{ component: StepThree(), key: 'step3' },
{ component: StepFour(), key: 'step4' },
{ component: StepFive(), key: 'step5' },
{ component: StepSix(), key: 'step6' },
{ component: StepSeven(), key: 'step7' },
{ component: StepEight(), key: 'step8' }
];
export default componentsList;
You can use require as well:
const moduleHolder = []
function loadModules(path) {
let stat = fs.lstatSync(path)
if (stat.isDirectory()) {
// we have a directory: do a tree walk
const files = fs.readdirSync(path)
let f,
l = files.length
for (var i = 0; i < l; i++) {
f = pathModule.join(path, files[i])
loadModules(f)
}
} else {
// we have a file: load it
var controller = require(path)
moduleHolder.push(controller)
}
}
Then use your moduleHolder with dynamically loaded controllers:
loadModules(DIR)
for (const controller of moduleHolder) {
controller(app, db)
}
I was able to take from user atilkan's approach and modify it a bit:
For Typescript users;
require.context('#/folder/with/modules', false, /\.ts$/).keys().forEach((fileName => {
import('#/folder/with/modules' + fileName).then((mod) => {
(window as any)[fileName] = mod[fileName];
const module = new (window as any)[fileName]();
// use module
});
}));
if you don't export default in A, B, C but just export {} then it's possible to do so
// things/A.js
export function A() {}
// things/B.js
export function B() {}
// things/C.js
export function C() {}
// foo.js
import * as Foo from ./thing
Foo.A()
Foo.B()
Foo.C()
After installing types for a library such as "#types/openlayers" how can I then use these for inline type definitions for things like React props:
import Map from "ol/Map";
import { Map as MapTypes } from "#types/openlayers"; // Error: Cannot import type declaration files.
export type Props = {
mapInstance: MapTypes;
}
export const OlMap: FunctionComponent<Props> = (props): JSX.Element => {
const currentMap = props.mapInstance;
...
}
Use it from the actual package that is being typed, not the #types/* module. For example, with react-router, you add #types/react-router but to pull out the RouteProps interface, you use import {RouteProps} from "react-router";.
Additionally, from your edits, it seems that you may be using the wrong #types/* package. You mention using #types/openlayers but then mention that you are using the package ol, which should probably use the #types/ol package for types.
Note declaration file consumption:
For the most part, type declaration packages should always have the
same name as the package name on npm, but prefixed with #types/
After running yarn add ol #types/ol (or npm i ol #types/ol)
This seems to be using the right types:
import MapTypes from "ol/Map"; // Note that I don't need "{}" or "as" here
// import {Map as MapTypes} from "ol"; or pull it out of the root export
export type Props = {
mapInstance: MapTypes;
}
export const OlMap: FunctionComponent<Props> = (props): JSX.Element => {
const currentMap = props.mapInstance;
...
}
How to allow *.js files to import *.ts files in react-native but without rename of any of two files?
we want to import below src/lib/setGlobalStyle.ts file from the src/App.js
//MIT LICENSE from: https://github.com/Ajackster/react-native-global-props
import React from 'react'
import { StyleProp, ViewStyle } from 'react-native'
export function setGlobalStyle(obj, customProps) {
const oldRender = obj.prototype.render;
const initialDefaultProps = obj.prototype.constructor.defaultProps;
obj.prototype.constructor.defaultProps = {
...initialDefaultProps,
...customProps,
}
obj.prototype.render = function render() {
let oldProps = this.props;
this.props = { ...this.props, style: [customProps.style, this.props.style] };
try {
return oldRender.apply(this, arguments);
} finally {
this.props = oldProps;
}
};
}
but below import which is inside App.js only works when we rename the setGlobalStyle.ts file to setGlobalStyle.js:
import * as Utils from './lib/setGlobalStyle'
and of course the setGlobalStyle.ts currently does not contain any TypeScript types, that is because we had to remove all and rename it to .js so we can continue on the project until this gets an answer.
note: the reason why we need TypeScript is to allow IDE autocomplete of the parameters (i.e. the customProps argument).
JSDoc-support in Javascript allows you to import type definitions directly. I know that's not the same as importing the actual module (but I think that is madness if the type definitions exist):
/**
* #param {import("./mymodule").customProps } customProps
*/
export function setGlobalStyle(obj, customProps) {
customProps. // has full auto-completion