In an existing node project written in JS we're trying to enable the use of Typescript so we can use it in our ongoing refactoring.
I've added a tsConfig like so:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"sourceMap": true,
"rootDir": "./",
"outDir": "./tsOutput",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["./"]
}
I've also installed the following as dev dependencies:
"#types/express": "^4.17.14",
"#types/node": "^18.11.9",
"#types/node-fetch": "^2.6.2",
"ts-node": "^10.9.1",
"ts-node-dev": "^2.0.0",
"typescript": "^4.9.3"
And I run tsc on build, which runs and generates a js and mapping file. I've run tried this both outputting to a separate directory and removing the outDir in the tsConfig.
To test it works, I added the following class in my test project:
class ExampleClass {
logSomeStuff(stringToLog : string, numberToLog : number, booleanToLog : boolean)
{
console.log(stringToLog);
console.log(numberToLog);
console.log(booleanToLog);
}
}
export default ExampleClass;
Consume it with this JS file:
const { Example } = require("./Example.ts");
function TypescriptConsumerExample() {
const blah = new Example();
blah.logSomeStuff("String", 2, false);
}
module.exports = TypescriptConsumerExample;
And attempt to run this JS test:
const TypescriptConsumerExample = require("../TSExample/TSConsumerExample");
describe("TS test", () => {
it("Should be able to call TS code", () => {
TypescriptConsumerExample();
console.log("Built");
});
});
But I get this warning, which implies that it doesn't understand what TS is:
logSomeStuff(stringToLog : string, numberToLog : number, booleanToLog : boolean)
^
SyntaxError: Unexpected token ':'
Equally, if I run the main project and try and call this TS class from index.js:
function TSTest()
{
console.log("Success!");
}
export default TSTest;
I get this error:
TypeError: TSTest is not a function at Object.<anonymous>
Your tsconfig.json probably should have allowJs: true to allow js in the project at all.
Use tsx watch --inspect src/server for development, tsx src/server to launch.
tsx allows running whatever import/require js/ts modules at once (conpability list)
I would recomment you to convert the entire project into .ts first (by renaming), so file extensions are consistent, in one big git commit, so blame don't break on files that were renamed and edited at once
Two ways you can go about this. You can set a script in your package.json to compile the TS and then run that compiled typescript like so:
"scripts": { "start": "tsc && node dist/server.js" },
Alternatively, nodemon is a super useful tool that you may already be using..and it understands typescript. You can set another script like so:
"dev": "npx nodemon ./src/server.ts",
This article by Cole Gawin goes into the details of standing up the latter https://blog.logrocket.com/configuring-nodemon-with-typescript/
In addition to the answer provided by Dimava - adding the allowJs flag to tsconfig.json, it also turned out I missed changing this line in package.json to point to the outputs index.js file:
"main": "./tsOutput/index.js",
In combination with the following:
"scripts": {
"start": "tsc && node --unhandled-rejections=warn ./tsOutput/index.js",
"dev": "tsc && node --unhandled-rejections=warn ./tsOutput/index.js",
I have a typescript library consists of multiple folders. Each folder contains an index.ts file which exports some business logic. I am trying to bundle this with rollup to achieve this behavior on the call site:
import { Button, ButtonProps } from 'my-lib/button'
import { Input, Textarea } from 'my-lib/input'
import { Row, Column } from 'my-lib/grid'
This is the directory structure:
I have a main index.ts under src/ which contains:
export * from './button';
export * from './input';
export * from './grid';
With this style, I can do:
import { Button, Input, InputProps, Row, Column } from 'my-lib'
But I don't want this. I want to access to each module by their namespaces. If I remove exports from the index.ts file, all I can do is:
import { Button } from 'my-lib/dist/button'
which is something I didn't see before. Adding dist/ to the import statement means I am accessing the modules via a relative path. I want my-lib/Button.
I am using rollup. I tried to use alias plugin but didn't work. Below is my rollup config:
const customResolver = resolve({
extensions: ['ts'],
});
export default {
input: `src/index.ts`,
output: [
{
file: pkg.main,
format: 'cjs',
sourcemap: true,
// plugins: [terser()],
},
{
file: pkg.module,
format: 'es',
sourcemap: true,
plugins: [terser()],
},
],
// Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')
external: [],
watch: {
include: 'src/**',
},
plugins: [
// Allow json resolution
json(),
// Compile TypeScript files
typescript({ useTsconfigDeclarationDir: true }),
// Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)
commonjs(),
// Allow node_modules resolution, so you can use 'external' to control
// which external modules to include in the bundle
// https://github.com/rollup/rollup-plugin-node-resolve#usage
resolve(),
// Resolve source maps to the original source
sourceMaps(),
alias({
entries: [
{ find: 'my-lib/button', replacement: './dist/button' },
{ find: 'my-lib/input', replacement: './dist/input' },
{ find: 'my-lib/grid', replacement: './dist/grid' },
],
customResolver,
}),
],
};
And this is the tsconfig file:
{
"compilerOptions": {
"target": "es5",
"module": "ES6",
"lib": ["ES2017", "ES7", "ES6", "DOM"],
"declaration": true,
"declarationDir": "dist",
"outDir": "dist",
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"allowJs": false,
"moduleResolution": "node",
"resolveJsonModule": true,
"baseUrl": "./src",
"paths": {
"my-lib/button": ["./src/button"],
"my-lib/input": ["./src/input"],
"my-lib/grid": ["./src/grid"]
}
},
"exclude": ["node_modules", "dist", "**/*.test.ts"],
"include": ["src/**/*.ts"]
}
I don't know how to achieve the same structure as lodash/xxx or material-ui/yyy with rollup.
People suggest aliases or named exports but I couldn't make it work.
The closest thing to my problem is below question:
Import from subfolder of npm package
I want to achieve the same thing but with typescript and rollup.
I think I am missing something, thanks.
This is possible, but requires some extra steps. A mentioned above, this is the approach taken by Material-UI.
The trick is to publish a curated dist folder, rather the root folder of your repo.
Building
To begin with, let's just be clear that it doesn't matter whether your library is built using CommonJS or ESM. This is about module resolution.
Let's assume the project is called my-package.
Now most projects, after we have built src/ to dist/ will have
my-package
package.json
src/
index.js
dist/
index.js
and in package.json
"main": "dist/index.js"
or for esm
"module": "dist/index.js"
Publishing
Most projects just add .npmignore and publish the root of the project, so when installed the project ends up in node_modules like so:
node_modules
my-package/
package.json
dist/
index.js
Resolving
Once installed, consider this import:
import myProject from "my-project";
The module resolver will do this (simplifying greatly, as the full algorithm is irrelevant here):
Go to node_modules
Find my-project
Load package.json
Return the file in main or module
Which will work because we have
node_modules
my-package/
package.json
dist/
index.js
Resolving subpaths
import something from "my-project/something";
The resolution algorithm will work with
node_modules
my-project/
somthing.js
also with
node_modules
my-project/
something/
index.js
and with
node_modules
my-project/
something/
package.json
where in the latter case it will again look at main or module.
But we have:
node_modules
my-package/
package.json
dist/
index.js
The Trick
The trick is, instead of publishing your project root with its dist folder, to "frank" the dist folder and publish the dist folder using npm publish dist instead.
Frank (as in frank a letter) means you need to create a package.json in your dist folder; add README.md LICENSE etc.
A fairly short example of how this is done can be found here.
So, given we had after build:
node_modules
my-package/
package.json
dist/
index.js
something.js
Once published we get
node_modules
my-project/
package.json
index.js
something.js
Where package.json is the curated one.
First of all, the only difference between
import { Button } from 'my-lib/dist/button'
and
import { Button } from 'my-lib/button'
is just one more directory level.
Once said that, until you have "outDir": "dist", in your tsconfig.json file you need to add dist/ to your import statements.
Indeed, both the libraries you taken as example are distributed with files in the root directory: lodash directly has js files in the root, while material-ui has not outDir option in its tsconfig.json file (which means to write output files to root directory).
Hope this helps.
After numerous trials and errors, I was able to get this working by passing in a list of inputs, using the preserveModules and preserveModulesRoot options, and a simple postinstall script.
Here's my rollup.config.js
const options = {
input: [
'src/index.ts',
'src/api/index.ts',
'src/components/index.ts',
'src/contexts/index.ts',
'src/hooks/index.ts',
'src/lib/index.ts',
'src/types/index.ts',
'src/utils/index.ts',
'src/UI/index.ts',
],
output: [
{
format: 'cjs',
dir: 'dist',
exports: 'auto',
preserveModules: true,
preserveModulesRoot: 'src',
sourcemap: true,
},
],
plugins: [
// Preferably set as first plugin.
peerDepsExternal(),
typescript({
tsconfig: './tsconfig.rollup.json',
}),
postcss({
extract: false,
modules: true,
use: ['sass'],
}),
],
};
export default options;
scripts/postinstall.sh
#!/usr/bin/env bash
set -e;
# skip postinstall if npm install for development
# rollup.config.js is not included in dist
if [ -f "rollup.config.js" ]; then
echo "skipping your package's postinstall routine.";
exit 0;
fi
echo 'Copying files from dist folder into root project folder...'
cp -r dist/* ./ && rm -rf dist
echo 'Postinstall done!'
package.json
"scripts": {
"postinstall": "./scripts/postinstall.sh",
},
This will compile and output all files to dist folder. The postinstall script will copy all files from dist into the root project folder.
Note*: The postinstall script should be skipped when running npm install locally. This is done by checking if rollup.config.js exists or not.
I have some code that doesn't work in the browser unless I "ignore" two packages, I can do this fine with browserify: browserify files.js -i fs-extra -i request --standalone files > files.browserify.js, the resulting code just works, but if I try to do it with webpack the code complains about modules being missing.
...
plugins: [
new webpack.IgnorePlugin(/fs-extra$/),
new webpack.IgnorePlugin(/request$/),
new webpack.IgnorePlugin(/fs$/)
],
...
test.webpack.js:7655 Uncaught Error: Cannot find module "request"
at webpackMissingModule (test.webpack.js:7655)
at Object.exports.byteLength (test.webpack.js:7655)
at __webpack_require__ (test.webpack.js:20)
at Object.<anonymous> (test.webpack.js:17012)
at __webpack_require__ (test.webpack.js:20)
at test.webpack.js:66
at test.webpack.js:69
I suspect that maybe webpack doesn't create an "empty stub" like browserify does: --ignore, -i Replace a file with an empty stub. Files can be globs..
What can I do to fix this?
Resources
https://webpack.github.io/docs/webpack-for-browserify-users.html#ignore
https://webpack.github.io/docs/list-of-plugins.html#ignoreplugin
What you're looking for is null-loader which returns an empty module:
module: {
loaders: [
{
test: /^(fs-extra|fs|request)$/,
loader: "null"
},
...
]
To install:
$ npm i -D null-loader
So I am trying to add the Airbnb style guide to my company's code base using ESLint and Gulp. I'm running into the following error:
Error: Failed to load plugin import: Cannot find module 'eslint-plugin-import'
Referenced from: airbnb-base
Referenced from:
at Function.Module._resolveFilename (module.js:339:15)
at Function.Module._load (module.js:290:25)
at Module.require (module.js:367:17)
Here is my current configuration:
eslint.gulp.js
export default (gulp, plugins, config) => {
return gulp.src([
`${config.SRC}/**/*.js`,
`!${config.SRC}/**/__generated/*.js`,
`!${config.SRC}/libraries/**`,
`!**/node_modules/**`
])
.pipe(plugins.errorHandler())
.pipe(plugins.eslint({
"extends": "airbnb-base"
})
)
.pipe(plugins.eslint.format())
.pipe(plugins.eslint.failAfterError());
}
.eslintrc file
{
"env": {
"es6": true,
"browser": true
},
"extends": "airbnb-base",
"rules": {}
}
Any ideas what I might be doing wrong?
i suggest
npm install npm#latest -g
rm -rf node_modules
npm install
I try to create an npm package, which can be started as a command from shell. I have package.json
{
"name": "myapp",
"version": "0.0.6",
"dependencies": {
"async": "",
"watch": "",
"node-promise": "",
"rmdir": "",
"should": "",
"websocket": ""
},
"bin": "myapp"
}
and myapp
#!/bin/bash
path=`dirname "$0"`
file="/myapp.js"
node $path$file $1 &
But I get an error:
module.js:340
throw err;
^
Error: Cannot find module '/usr/local/bin/myapp.js'
at Function.Module._resolveFilename (module.js:338:15)
at Function.Module._load (module.js:280:25)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:902:3
The problem is that myapp.js is in another directory. How can I get this directory name from my script? Or maybe there is better way to do this?
Actually, you can put your myapp.js file into bin.
So, the bin key in package.json file should be like this :
"bin": { "myapp" : "<relative_path_to_myapp.js>/lib/myapp.js" }
At the first line in myapp.js, you must add this shebang line :
#!/usr/bin/env node
It tells the system to use node to run myapp.js.
... Or if you don't want to call myapp.js directly, you can create a script like this to be your executable file :
#!/usr/bin/env node
var myapp = require('<relative_path_to_myapp.js>/myapp.js');
myapp.doSth();
and in package.json :
"bin" : { "myapp" : "<relative_path_to_the_script>/script.js" }
By doing this either way, you can avoid finding the path to your nodemodule.
But... if you insist to use your old myapp bash script, then you can find the path to the module with this :
myapp_path=$( npm explore -g myapp -- "pwd" )
Hope these help :D
https://docs.npmjs.com/files/package.json#bin
From the above link:
...
To use this, supply a bin field in your package.json which is a map of command name to local file name. On install, npm will symlink that file into prefix/bin for global installs, or ./node_modules/.bin/ for local installs.
For example, myapp could have this:
{ "bin" : { "myapp" : "./cli.js" } }
So, when you install myapp, it’ll create a symlink from the cli.js script to /usr/local/bin/myapp.
...