TypeScript Version: 3.1.0-dev.201xxxxx
Search Terms:
import, module
Code
When using the import declaration from the documentation
import { ZipCodeValidator } from "./ZipCodeValidator";
the latest Chrome will not find the 'ZipCodeValidator' file. The console will show an error "Failed to load resource: the server responded with a status of 404 (Not Found)".
This can be easily fixed by adding the ".js" suffix to the imported module.
import { ZipCodeValidator } from "./ZipCodeValidator.js";
// A *self-contained* demonstration of the problem follows...
// Test this by running `tsc` on the command-line, rather than through another build tool such as Gulp, Webpack, etc.
Expected behavior:
Module should be imported.
Actual behavior:
Latest Chrome returns "Failed to load resource: the server responded with a status of 404 (Not Found)"
Playground Link:
Related Issues:
@griii2 I think there's a couple of things going on here:
allowJs: true
or if the .js file has a corresponding .d.ts
file, TSC's preferred extension is .ts.Hi @bcherny, I am not sure I understand what you mean. Of course I compiled the TypeScript files. No, I am not running them in Chrome. What I run in Chrome is an HTML5 file that loads the compiled .js files.
My point is this:
1/ I am reading the documentation: https://www.typescriptlang.org/docs/handbook/modules.html
2/ It briefly talks about ComonJS, AMD, ES2015 modules and more.
3/ None of the examples are labeled as specific for one of the module types.
4/ I recreate one of the examples in ES6 and it doesn't work.
@griii2 Sorry, I don't know what you know or don't know - always better to check :)
See what I said above - is this your intention?
I think what you want, is to update this issue to be a proposed enhancement for the generated output for import paths when compiling to ES2015 modules, so that files that don't have extensions will be given .js extensions in compiled output.
I see, you are saying the compiler should work the way the documentation says. I will raise the feature request.
But as things stand today, the documentation does not match the implementation, so I suggest that until the feature request is released the documentation should be fixed to describe the implementation.
The exact resolution of module specification -> file is up to the host; it is likely that nothing we write will work in every possible host. The examples work when targeting CJS compilation, which is still the default and is widespread.
Hi @RyanCavanaugh:
a) If the example works only when targeting CommonJS then the documentation should not imply it works for all module targets.
b) As a consequence, module importing targeting ES6 is not documented today.
Anyway, this was my feedback to the TypeScript team because I care. If you want to be defensive instead of fixing what is broken, that is up to you.
Best
Daniel
I'm having the same problem, but succinctly, it's that when I compile
import {Foo} from "./module"
and target ES2015 or ESNext modules, it gets transpiled as:
import {Foo} from "./module"
Yes, exactly the same thing. Since I'm targeting javascript, it should be adding the .js
on the end, but since it doesn't, any browser that supports modules gets a 404 back from the server, since the transpiled output is actually module.js
.
Smells like a bug to me. I'm on Typescript 3.1.3.
As an aside, this is handled in SystemJS with a defaultExtension: 'js'
. Is this something that Chrome, Safari, Edge and other <script type=module>
compliant browsers are meant to be doing? If yes, then okay, but in the meantime, can we have a --moduleDefaultExtension
flag or tsconfig setting please?
I have the same problem: When targeting "es6" then import TransformTool from "./TransformTool";
is compiled to import TransformTool from "./TransformTool";
.
This leds to a 404 "Page not found" error when loading in the browser since it only finds the module if it is imported with import TransformTool from "./TransformTool.js";
.
I now manually fix my files each time after compiling. Fixing this problem is really urgent!
@oising It's not really about adding extension, but expanding the full relative path during compilation.
import { Foo } from './module'
Might also result to:
import { Foo } from './module/index.js'
The path cannot be determined by the entry, but require the full path of the dependency.
.js
When using jsx: preserve
, a module.tsx
file would be compiled to module.jsx
.
Whenever there's a package.json
, the path could actually be path.resolve(__dirname, packageJson.main)
. And the search path can be ./node_modules
, ../node_modules
, etc, need to be prepended.
Also needs to take compilerOptions.paths
into consideration.
What being imported may not be a .ts(x)
file, say that you have the following source file:
- module.d.ts
- module
- index.js
For TypeScript only .d.ts
is checked, the resolution path is ./module.d.ts
, but for engines or bundlers, the JavaScript file used is ./module/index.js
, which is not even in the same folder of the declaration file. For package.json
it's even more complicated as declaration is specified by types/typings
field.
To get the right path, there must be an additional kind of path resolution which ignores declaration.
@trotyl Sure, it's complicated with Node (the majority of your examples), but if you're just wanting to write JS modules that directly target browsers - without using bundlers like webpack or parceljs, it shouldn't be that hard.
@oising All of the points above are problems for not using Node.js or any bundler.
It would be great if we could just have the _option_ of being able to specify something like resolveImportFiles
so that the exact references output file is what is used, this would work for jsx
, js
, or whatever extension is used. Typescript already knows what an import is referencing, as it does type checking against it. Within typescript you use typescripts module resolution process, within esnext
, typescript should use esnext
's module resolution process, there could be a variant for node, or the browser, so would be dependent on the target engine, esnexts
module resolution process works on full URI values, or relative references.
If typescript was to be used to do this resolution ahead of time, it could be powerful for a lot of reasons.
For example given this directory:
/util/store.js
/util/index.js
/feature-a.js
/feature-b.js
store.js
is referenced by /util/index.js
as export * from "./store"
index.js
is referenced by /util/feature-a
as import { Store } from "./util"
index.js
is referenced by /util/feature-b
as import * as Util from "./util"
This could be resolved universally as when transformed into esnext
as:
export * from "./store.js"
import { Store } from "./util/store.js"
<- Notice the shortcut, which we already know about!
import * as Util from "./util/index.js"
This is going to work universally because it sill follows a resolution process that is compatible with all engines, I don't believe there is anything lost by doing so, the option on compilerOptions
could be:
type resolveImportFiles = boolean | false;
false
or not provided, retain current value, no breaking changetrue
, resolve as file if referenced file, if vendor module then retain current value (this works for the browser as people can utilise import maps, so no additional of rewriting the wheel), no breaking change, as user must change this manuallyThis would be a semver minor change as well.
This can be extended though, and this is where it gets powerful.
Lets say we defined it as:
type resolveImportFiles = "unkpg" | boolean | false;
Then when we reference preact
, we and the reference is a vendor module then we use https://unpkg.com/[email protected]/src/preact.js
, else when we're referencing the local file we get the same as if resolveImportFiles
was true
, this isn't a requirement of resolveImportFiles
though and more of an extension of the idea.
Hello, i encountered the same problem, and i SOLVED "easily", this way : URL REWRITING, what do i mean ? i'm gonna explain that from the beginning !
Here is the story, from compiled Typescript code, i wanted to obtain some ES6 code(target es2015 in tsconfig.json), in order to keep in the final code the original syntaxes: import/export that i've written in Typescript ( indeed at final, i did not want to use Module loaders for ES5 !! Because it's old school...).
The thing is that, when Typescript generates the ES6, it completely keeps intact the syntaxes : import .. from "./.../MyJSFile", so it keeps it without the ".js" extension !! And of course, in Typescript code, you can't mention the extension on an import syntax ! (it assumes it to be ".ts").
So now, i've got like you, the ES6 code generated, and containing import syntaxes that DOESN'T mention the ".js" extension of the imported files. So what is the problem ?
The problem is :
In order to execute such code in a browser, because it uses ES6 Modules
you HAVE TO run it from a WEB SERVER (no matter which) !
BUT ! any Web Server won't find the files you want to import
(import ... from "./.../MyJSFile"), because without extension, it says, well
this resource doesn't exist ! and indeed it doesn't !! BUT what we know is
that the resource "./.../MyJSFile.JS" EXISTS !
So ?? You see me coming ?
YOU JUST HAVE TO WRITE : URL REWRITING RULES FOR THE WEB SERVER, and that's
what i did to solve ! I did it with :
and then, i just created an .htaccess file at the root of my
localhost site, in which i tell him:
If the requested resource doesn't exist, then try to find
(by URL Rewriting), the same resource name BUT, with a ".js" extension
at the end of it ! AND IT WORKS FINE !! Here is my .htaccess in its
simplest working form :
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)(/[A-Za-z0-9_]+)$ $1$2.js [L]
AND THAT's really all you have to do ! with an Apache Server.
So keep in mind, whatever is the Web Server, the idea is : URL REWRITING !
I ALSO USED THE SAME PRINCIPLE WITH a NODE.JS WEB SERVER that i've created, here is its full tested code, it works absolutely fine (nodeJsAsWebServer.js) :
//In terminal, just run : node nodeJsAsWebServer
//to launch this below Web Server. (CTRL+C to stop) .
//
//And then in the browser, just invoke your ES6 code
//(via normal html call),
//on this domain : localhost:555
//------- nodeJsAsWebServer.js ---------
const oHttpTool = require('http');
const oFilesTool = require('fs');
const oPathesTool = require('path');
const oMIMETypes = {
'html': 'text/html',
'css': 'text/css',
'js': 'text/javascript',
'json': 'application/json',
'png': 'image/png',
'jpg': 'image/jpeg',
'ico': 'image/x-icon',
'wav': 'audio/wav',
'mp3': 'audio/mpeg',
'pdf': 'application/pdf',
'doc': 'application/msword'
};
let oMyWebServer = oHttpTool.createServer(function (req, res) {
console.log("\n\n=========================================================");
console.log("=========================================================\n");
let sSubUrl = req.url;
console.log("Client code asks for: "+sSubUrl);
let sFileExtension = oPathesTool.parse(sSubUrl).ext.substr(1);
let sMIMEType = oMIMETypes[sFileExtension];
console.log("Asked MIMEType="+sMIMEType);
let sFileHDDPath = __dirname; //Real path on server hard disk
let sFileToRead = sFileHDDPath+sSubUrl;
console.log("FileToRead="+sFileToRead);
oFilesTool.exists(sFileToRead, function (pbFileExists) {
if(!pbFileExists) {
console.log(` - File NOT FOUND on the server! -`);
if (sFileExtension==="") { //If the requested URL doesn't have any extension !
let sNewUrl = sSubUrl+".js"; //<<<<<<<<<<<< URL Rewriting
console.log(" REDIRECTION ...(sNewUrl='"+sNewUrl+"'; ldUrl='"+sSubUrl+"')");
res.writeHead(301, {Location:sNewUrl});//Redirect, we try to load this newURL
res.end();
} else { //if the URL already had an extension.
res.statusCode = 404;
res.end();
}
} else { //Normal case, when the requested resource exists.
oFilesTool.readFile(sFileToRead, function(poError, poData){
if(poError){
res.statusCode = 500;
console.log("ERROR getting file content !!", poError);
res.end();
} else { //We send the file whole content and with a header.
res.setHeader('Content-type', sMIMEType );
res.end(poData); //<<Also works for Binary files such as Image, PDF, etc..
}
});
}
});
});
let iPort=555; //<<< choose your port on localhost.
console.clear();
console.log("Node Web Server listening on localhost:"+iPort+" ... (CTRL+C to stop)");
oMyWebServer.listen(iPort);
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.
I'm also running into this issue; I think it's plainly almost unimaginable that a simple .ts file using modules and targeting modern browsers and ES modules cannot be compiled to a working javascript file in the browser.
I'm using the workaround suggested by others in the related thread (using an explicit .js module reference in the typescript file, like "import {Foo} from './module.js' ", but this feels rather strange, although it works well in the browser.
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.
This would make a good question for stack overflow if the feature were supported. As a workaround, @RickInfoDev's satirical answer isn't that bad especially if the typescript people will never support Web Modules
Most helpful comment
I'm having the same problem, but succinctly, it's that when I compile
and target ES2015 or ESNext modules, it gets transpiled as:
Yes, exactly the same thing. Since I'm targeting javascript, it should be adding the
.js
on the end, but since it doesn't, any browser that supports modules gets a 404 back from the server, since the transpiled output is actuallymodule.js
.Smells like a bug to me. I'm on Typescript 3.1.3.