Related
I have this import_map.json file:
{
"imports": {
"node_modules/" : "./node_modules"
}
}
at a high-level I am trying to create some compatibility for .ts files, for both Deno and Node.
My imports look like this:
import * as util from 'util';
import chalk from "node_modules/chalk";
When I run this:
deno run --import-map='import_map.json' ./src/linked-queue.ts
I get this loathsome error:
Import map diagnostics:
- Invalid target address "file:///.../linked-queue/node_modules" for package specifier "node_modules/". Package address targets must end with "/".
error: Blocked by null entry for ""node_modules/""
at file:///.../linked-queue/src/linked-queue.ts:4:19
Anyone know how to resolve this error?
"imports": {
"node_modules/" : "./node_modules/"
}
Add a trailing slash on the target specifier. See also the spec and the source.
The manual covers this scenario in the following three sections:
4.1 - Using npm packages with npm specifiers
4.3 - The std/node Library
4.4 - Using Import Maps
I'll show a reproducible example rather than copy + paste everything from the docs (because a few copied snippets aren't really enough; this is a multi-faceted issue) — however take note of the values in the import map, as they are derived by reading through all three linked sections of the documentation:
./import_map.json:
{
"imports": {
"chalk": "npm:chalk#5.2.0",
"node:util": "https://deno.land/std#0.170.0/node/util.ts"
}
}
./deno.jsonc:
{
"importMap": "./import_map.json",
"tasks": {
// I included these permissions (which are required by chalk) in advance to avoid needing to grant them one-by-one at runtime:
"dev": "deno run --allow-env=FORCE_COLOR,TF_BUILD,TERM,CI,TEAMCITY_VERSION,COLORTERM,TERM_PROGRAM,TERM_PROGRAM_VERSION src/linked-queue.ts"
}
}
./src/linked-queue.ts:
import * as util from "node:util";
import chalk from "chalk";
console.log('util:', typeof util); // util: object
console.log('chalk:', typeof chalk); // chalk: function
Running in the terminal using the defined task:
% deno --version
deno 1.29.1 (release, x86_64-apple-darwin)
v8 10.9.194.5
typescript 4.9.4
% deno task dev
Task dev deno run --allow-env=FORCE_COLOR,TF_BUILD,TERM,CI,TEAMCITY_VERSION,COLORTERM,TERM_PROGRAM,TERM_PROGRAM_VERSION src/linked-queue.ts
util: object
chalk: function
% echo $?
0
So far, everything is great in Deno.
Let's check to see that the same code works without modification in Node.js. The following files need to be added to compile and run using Node, since it doesn't include all of Deno's built-in tooling:
./package.json:
{
"name": "so-74905332",
"version": "0.1.0",
"type": "module",
"scripts": {
"compile": "tsc",
"dev": "tsc && node src/linked-queue.js"
},
"license": "MIT",
"dependencies": {
"chalk": "5.2.0"
},
"devDependencies": {
"#types/node": "^18.11.17",
"typescript": "^4.9.4"
}
}
./tsconfig.json:
Why these values? I'm just using a recommended base, linked to from the TS repo wiki:
// This file was autogenerated by a script
// Equivalent to a config of: strictest extends esm extends node18
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Node LTS + ESM + Strictest",
"_version": "18.12.1",
"compilerOptions": {
"lib": [
"es2022"
],
"module": "es2022",
"target": "es2022",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"allowUnusedLabels": false,
"allowUnreachableCode": false,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"importsNotUsedAsValues": "error",
"checkJs": true
}
}
Running in the terminal using the defined npm script:
% node --version
v18.12.1
% npm install
added 3 packages, and audited 4 packages in 1s
1 package is looking for funding
run `npm fund` for details
found 0 vulnerabilities
% npm run dev
> so-74905332#0.1.0 dev
> tsc && node src/linked-queue.js
util: object
chalk: function
% echo $?
0
The same module source code also works in Node.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 am currently working with the vss-web-extension-sdk. I am using ESLint, eslint-plugin-import, and eslint-import-resolver-typescript to validate the files.
import { WidgetSettings, WidgetStatus } from "TFS/Dashboards/WidgetContracts";
The line above, extracted from my run.ts, throws the following error.
Unable to resolve path to module 'TFS/Dashboards/WidgetContracts'. eslint(import/no-unresolved).
However, when I control-click on the import in VSCode, it navigates to the module in tfs.d.ts. I am doing something incorrectly to make the resolver not detect the module?
My tsconfig.json has
{
"compilerOptions": {
"module": "amd",
"strict": true,
"moduleResolution": "node",
"types": [
"vss-web-extension-sdk"
]
}
}
and my .eslintrc.json has
{
"settings": {
"plugins": [
"import"
],
"rules": {
"import/no-unresolved": "error"
},
"import/resolver": {
"typescript": {}
}
}
}
Install Commands
npm install --save-dev eslint-plugin-import typescript-eslint-parser eslint-import-resolver-typescript
npm install --save vss-web-extension-sdk
Try setting your baseUrl and your rootDir in your tsconfig.json to specify where that absolute path starts.
See here: https://maxisam.github.io/2017/01/03/rootDirs-in-tsconfig/
I'm using typescript v^3.4.2, in an express app (^4.14.1), using node v11.3.0.
When I run my build for typescript, I get the following error:
Could not find a declaration file for module 'vimeo'. '/Users/me/Code/MyServer/node_modules/vimeo/index.js' implicitly has an 'any' type.
Try `npm install #types/vimeo` if it exists or add a new declaration (.d.ts) file containing `declare module 'vimeo';`
1 import { Vimeo } from "vimeo";
I am using the Vimeo api client for nodejs vimeo.js, version 2.1.1.
I've attempted to run yarn add --dev #types/vimeo, but unfortunately that library is for the other vimeo library, vimeo/player.js. Installing it is of no use.
I've tried to follow this article on creating my own custom type declaration.
Here is my tsconfig.json:
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": ["node_modules/*"]
},
"typeRoots": ["./types", "./node_modules/#types"]
},
"include": ["src/**/*", "controllers/**/*", "routes/**/*", "models/**/*"],
"exclude": ["node_modules", "types"]
}
And I've created: /Users/me/Code/MyServer/types/vimeo/vimeo.d.ts, which contains:
declare module vimeo {}
When I add that the error changes to:
yarn run v1.15.2
$ yarn build
$ tslint -c tslint.json -p tsconfig.json --fix
$ tsc
error TS2688: Cannot find type definition file for 'vimeo'.
The article also suggests adding a root global.d.ts with the same definition in it, but that has no effect.
I'm stumped. Any help would be appreciated.
(This is my package.json scripts block:)
"main": "dist/app.js",
"scripts": {
"prebuild": "tslint -c tslint.json -p tsconfig.json --fix",
"build": "tsc",
"prestart": "yarn build",
"start": "babel-node dist/app.js --presets es2015",
"test": "echo \"Error: no test specified\" && exit 1"
},
And here's my tslint.json:
{
"defaultSeverity": "error",
"extends": ["tslint:recommended"],
"jsRules": {},
"rules": {
"trailing-comma": [false],
"curly": [true, "ignore-same-line"],
"ban-types": false
},
"rulesDirectory": []
}
If I recall correctly, setting "noImplicityAny": false, can solve the issue of declaration files... or you can look for some .d.ts for that module (or even make your own and do a pull request in GitHub)
I wrote some npm scripts that builds the ./lib directory before publish to npm.
1. script is responsible to convert all es6 *.js files in ./src/components/ to es5 syntax and then copy the files to ./lib (same structure).
This is the script:
"cross-env NODE_ENV=production babel ./src/components --out-dir ./lib --ignore spec.js --copy-files"
And this is the .babelrc file:
{
"presets": [
"react",
"stage-1"
],
"env": {
"development": {
"presets": [
"latest",
"react-hmre"
]
},
"production": {
"presets": [
[
"latest",
"es2015"
]
],
"plugins": [
"transform-react-constant-elements",
"transform-react-remove-prop-types",
[
"transform-imports",
{
"react-bootstrap": {
"transform": "react-bootstrap/lib/${member}",
"preventFullImport": true
},
"lodash": {
"transform": "lodash/${member}",
"preventFullImport": true
}
}
]
]
},
"test": {
"presets": [
"latest"
]
}
}
}
I have another script that responsible to convert .less files to .css and copy them to ./lib (same structure):
"lessc-glob ./src/components/**/*.less lib"
Everything works well as expected, but i have one problem now. The import that i have inside the .js files are referring to .less files, but i need it to change to .css extensions.
To make things clear,
What i have now is:
import css from './styles.less';
Converted into this:
var _styles = require('./styles.less');
But i want it to convert to this:
var _styles = require('./styles.css');
replace can be installed and utilized to find instances of .less and replace them with .css in your resultant ES5 .js file/s.
npm script
Add a replace script to your package.json as follows:
...
"scripts": {
...
"replace": "replace \".less\" \".css\" ./lib/components/ -r --include=\"*.js\""
},
...
A call to the replace script can then be chained to the end your script that is responsible for converting all es6 *.js files. E.g.
...
"scripts": {
...
"quux": "cross-env NODE_ENV=production babel ./src/components --out-dir ./lib --ignore spec.js --copy-files && npm run replace",
"replace": "replace \".less\" \".css\" ./lib/components/ -r --include=\"*.js\""
...
},
...
Note the && npm run replace part added to the end of your current quux script.
I've assumed the components folder is copied to the lib folder too. If it's not then the ./lib/components/ part in the replace script will need to be changed to ./lib/.