I have non-AMD backbone, non-AMD underscore and non-AMD jquery in my project. I also have a lot of third party libraries that are not AMD compatible.
What is the best way of having them compile alongside AMD modules using r.js? Should I start wrapping all of them into AMD modules?
I ran r.js on a module I had which was using non-AMD libraries and underscore and backbone, it generated an output but in the output wherever there is a require("backbone") call, it returns undefined which I'm suspecting is because backbone is not registered as an AMD module.
At the same time something very strange to me is that if I do not run r.js and just run the website regularly using require.js loading, the following lines returns correct values even though they are not AMD modules:
var _ = require("underscore")
var Backbone = require("backbone")
I have those paths configured as aliases in my require.config.
Why do you think, that backbone, underscore or jquery are not AMD compatible? They do! Just a bit not out of box. You just need to add a bit more configuration to your main.js. For example like this:
require.config({
paths : {
jquery : 'jquery-2.0.3',
backbone: 'backbone',
underscore: 'underscore'
},
shim : {
underscore: {exports: '_'},
backbone: {deps: ['underscore'], exports: 'Backbone'}
}
});
require(['jquery', 'backbone'], function($, Backbone) {
console.log('Type of $: ' + typeof $);
console.log('Type of Backbone: ' + typeof Backbone);
});
There aren't very much libraries, which are fully not AMD compatible (i. e. can't be used via RequireJs). Every library, which exposes itself to global scope via some property may be used via RequireJs.
But if you would like to use CommonJs modules (which are exposed via module.exports), then the way I see, you have 2 options:
Wrap your CommonJs modules with this.
Use this construction:
define(function (require) {
var $ = require('jquery');
})
Both options work nice with RequireJs optimizer, as I tested.
Still, I would prefer use shim option in your main.js file.
Related
Have a TS 4.7 library using ESM modules.
tsconfig.json:
"target": "ES2020",
"module": "ES2020",
"lib": ["ES2020"],
"moduleResolution": "node",
package.json
"type": "module",
I have a main file with only a one silly export:
index.ts
export { Spig } from './spig';
which is compiled to:
index.js
export { Spig } from './spig';
//# sourceMappingURL=index.js.map
Problem
When I use this library from a Node CLI program (with ESM modules enabled as well), I get the following error:
Cannot find module <path>/lib/spig imported from <path>/lib/index.js
When I manually add .js in the generated index.js, the issue is gone:
export { Spig } from './spig.js';
How can I force TypeScript compiler to generate the extension, too? What am I missing here?
You cannot omit the file extension anymore in ESM module imports. The extension should be always .js/.jsx, not .ts/.tsx for a typescript file. So, in the index.ts you should add the extension to spig export like the following and every other file imported/exported if using ESM modules.:
index.ts
export { Spig } from './spig.js';
Also, moduleResolution should be set to Node16 or NodeNext so ESM modules work as expected.
As stated in the docs (enphasis by me):
Relative import paths need full extensions (e.g we have to write import "./foo.js" instead of import "./foo").
When a .ts file is compiled as an ES module, ECMAScript import/export syntax is left alone in the .js output; when it’s compiled as a CommonJS module, it will produce the same output you get today under module: commonjs.
This also means paths resolve differently between .ts files that are ES modules and ones that are CJS modules. For example, let’s say you have the following code today:
// ./foo.ts
export function helper() {
// ...
}
// ./bar.ts
import { helper } from "./foo"; // only works in CJS
helper();
This code works in CommonJS modules, but will fail in ES modules because relative import paths need to use extensions. As a result, it will have to be rewritten to use the extension of the output of foo.ts - so bar.ts will instead have to import from ./foo.js.
// ./bar.ts
import { helper } from "./foo.js"; // works in ESM & CJS
helper();
This might feel a bit cumbersome at first, but TypeScript tooling like auto-imports and path completion will typically just do this for you.
I have a NPM package written in Typescript (let's call it libd) that offers some classes and types. They are exported in a index.ts file like this:
export { ClassA } from "./ClassA";
export { TypeB } from "./TypeB";
The ClassA is using an external dependency (let's call it depc) that is set to peerDependencies in the package.json. The TypeB is just a type without any external dependencies.
In the package.json the main field is set to dist/index.js (where it is transpiled to) and types are set to dist/index.d.ts.
The tsconfig.json of the libd package looks like this:
{
"compilerOptions": {
"target": "ES2019",
"module": "commonjs",
"declaration": true,
"outDir": "dist",
"rootDir": "src",
"inlineSources": true,
"inlineSourceMap": true
}
}
Now I im consume the TypeB in another library (let's call it libe) without importing ClassA and without having the depc dependency installed. The libd library is installed via NPM and the type is imported like this:
import { TypeB } from "libd";
The tsconfig.json of libe is similar to the one from libd.
Transpiling this gives me the error TS2307: Cannot find module 'depc'.
Now I understand, if I would use ClassA I must install depc since it imports it. But I'm only using TypeB that does not require any dependencies and sometimes types come with the original package.
I could fix this problem by installing #types/depc and Typescript would be happy. Code should work at runtime, since no actual usage of depc comes from libe. But I would rather not install useless packages.
Setting "skipLibCheck": true in the tsconfig.json of libe would also fix the tsc error but I am not sure what negative impacts this would have.
So my question is: Why does typescript check a class from dependency that I am not importing? Is it, because I have bundled all exports of the libd package in an index.ts file and all imports of those are going through this index file?
Can I resolve this without separating TypeB and ClassA into different packages? That would make them very small packages and for ~80% of the time they would be installed both.
Is it, because I have bundled all exports of the libd package in an index.ts file […]?
Yes.
TypeScript compiler sees that you are using the file libd/dist/index.{js|d.ts} and starts evaluating its exports. While doing so, it finds out that ClassA is using depc, and goes for evaluating depc, – but it is nowhere to be found (since it isn't installed). That's a definitive error.
TypeScript compiler doesn't check whether you are using ClassA or not, it is not its job to perform tree shaking.
Using "skipLibCheck": true is actually a good way of solving this problem, since its description states explicitly:
Rather than doing a full check of all d.ts files, TypeScript will type check the code you specifically refer to in your app’s source code.
… which is ultimately what you want, right?
I'm trying to rollup my library's code into a dist folder.
Right now I have a problem with the built in crypto library.
terminal output:
$ yarn run build
...
lib/helpers/security.js
createHmac is not exported by node_modules/rollup-plugin-node-builtins/src/es6/empty.js
...
Rollup config
...
plugins: [
builtins(),
resolve(),
json(),
babel({
exclude: ['node_modules/**','**/*.json']
})
]
...
Source code
A snippet from my source code:
// lib/helpers/security.js
import * as crypto from 'crypto'
crypto.createHmac('sha256',nonce).update(text).digest('base64');
Result
From the rolled-up, bundled js output:
undefined('sha256', nonce).update(text).digest('base64');
Crypto.js source code
For reference the relevant export statement in node/crypto.js on github shows that createHmac is being exported.
node/crypto.js L147
Update 1 ( Solution? )
It seems that removing the import line from security.js resolves the issue. I understand that crypto is a built in node module.
I want to understand why I should not import in this case while examples in the documentation do import the module.
So this is the solution I came up with which works fine for me.
Install rollup-plugin-node-builtins in your project as a dev dependency. And import it in your rollup.config.js:
import builtins from 'rollup-plugin-node-builtins'
Set crypto to false when using builtins(). It defaults to the commonjs version from browserify. That's not I wanted or needed.
// set crypto: false when using builtins()
...
builtins({crypto: false}),
...
Make sure to add crypto to your external option:
// add `crypto` to the `external` option
// you probably already have
// one or more libs defined in there
let external = ['crypto']
Now I can use crypto in my library without the previous problems when using my built files.
import { createHmac } from "crypto";
The result is a module, 4KB in size, which depends on several external dependencies, without having them included in the built files.
For context: my source code is written in ES6 and I am building three versions of my module: cjs, umd and es.
I would avoid to babel server-side files, it can be done, but I prefer not doing it.
So you will probably not have the errors if you use require() instead of import (import is not built-in in Node.js v8):
var crypto = require("crypto");
I am working on first part of this answer
When I compile main.ts to js
require("amd-loader");
import someModule = require('../mymodule')
var someClass = new someModule.MyNamespace.MyClass();
it becomes:
define(["require", "exports", '../mymodule'], function (require, exports, someModule) {
require("amd-loader");
var someClass = new someModule.MyNamespace.MyClass();
});
then it gives me define is not defined error
When I modify it as below, error goes away.
require("amd-loader");
define(["require", "exports", '../mymodule'], function (require, exports, someModule) {
var someClass = new someModule.MyNamespace.MyClass();
});
Then I get Cannot read property 'MyClass' of undefined error
How can I get fix these error and get it work as expected as mentioned in that Q&A?
My environment is Visual Studio 2015 and I compile with AMD options as module system(obviously I tried each options). I am doing angular protractor e2e tests
As said by basarat at this answer, CommonJS should be used in this case.
In Visual Studio 2015 version 14.0.23.107.0, it seems TypeScript Module System options does not work, and it compiles always with whatever any one selected
So following this post CommonJS can be selected.
And voila it compile in with CommonJS and works as expected
test.ts
export class Test {
whatever(): Promise<any> {
return undefined;
}
}
Trying to compile with old version:
$ tsc --version
message TS6029: Version 1.4.1.0
$ tsc --target es6 --module commonjs test.ts
$ cat test.js
var Test = (function () {
function Test() {
}
Test.prototype.whatever = function () {
return undefined;
};
return Test;
})();
exports.Test = Test;
This is fine. Now with new version:
$ ./node_modules/.bin/tsc --version
message TS6029: Version 1.5.0-beta
$ ./node_modules/.bin/tsc --target es6 --module commonjs test.ts
error TS1204: Cannot compile external modules into amd or commonjs when targeting es6 or higher.
Why is that? I am developing NodeJS application, so I have to use commonjs. Also, I need native promises, hence es6 target.
$ ./node_modules/.bin/tsc --target es5 --module commonjs test.ts
test.ts(2,14): error TS2304: Cannot find name 'Promise'.
If you are compiling to a target that supports ES6, you should use ES6 module imports, rather than commonjs or AMD.
import * as Promise from 'Promise';
Remove the --module flag when you compile if you supply --target ES6.