jspm / jspm-cli

ES Module Package Manager
https://jspm.org
Apache License 2.0
3.77k stars 273 forks source link

Bundled TypeScript is still fetched. #912

Closed MicahZoltu closed 9 years ago

MicahZoltu commented 9 years ago

I have a TypeScript application (Aurelia framework). I bundle using the following command:

node_modules/.bin/jspm bundle source/main + source/app + source/about dist/bundle.js --minify --skip-source-maps

However, when I navigate to my application, I can see chrome fetching source/main.ts as well as the TypeScript compiler. The primary reason I am bundling is so that I am not doing runtime transpilation since that appears to be a substantial portion of my load time, plus the TypeScript compiler is 2.3MB.

Why is source/main.ts still being fetched when I have bundled it in? Is this a bug specific to bundling TypeScript or am I using jspm bundle wrong?

Note: many other TS files are also being fetched, I just used main as an example because it is the first in my case. I am also seeing my CSS and HTML files fetched, as well as a handful of dependencies (though not nearly as many as without bundling). My expectation was that if I bundled, there would only be ~4 files downloaded.

image

pauleveritt commented 9 years ago

I'm seeing something similar for Babel. It's fetching babel-core/browser.js which is a 2 MB file. That one might be a different issue, due to #535

guybedford commented 9 years ago

Did you manage to solve your issue here?

pauleveritt commented 9 years ago

@guybedford On my side (non-TS), I simply realized I was being a stupid newbie. Of course it has to send the whole enchilada over, as I was still sending all the original sources. Once I made a full bundle, all was normal.

MicahZoltu commented 9 years ago

No, the problem persists. I stopped troubleshooting it because I reached the end of my understanding of JSPM bundling (very limited) and I am not ready for launch yet so I'm not (yet) under time pressure. However, I would really like to see this issue resolved (or instructions on how to correctly bundle) so can get my page load time down from 8 seconds.

WorldMaker commented 9 years ago

When you reference modules do you use the file extension, maybe? (require('main.ts') or import * from 'main.ts' instead of require('main') or import * from 'main'?)

MicahZoltu commented 9 years ago

I use Aurelia and I'm not sure what it does under the hood exactly, though I would assume extensionless since Aurelia doesn't know whether I am using TypeScript or JavaScript and it all works.

When one of my modules references another, it is extensionless.

WorldMaker commented 9 years ago

One other thing that jumps out at me now is your paths in your bundle command. Your module calls are all likely require('main'), but here you are bundling source/main, which is a different path. I don't remember how jspm bundle handles command line paths off the top of my head, but know that other module bundling tools can be extremely literal, so maybe try from your source directory instead:

../node_modules/.bin/jspm bundle main + app + about ../dist/bundle.js --minify --skip-source-maps

Some other general tips, in case that isn't it:

I personally would turn source maps back on in the bundle and let the browser tell me which modules were actually in the bundled file (at least in IE11 it trees things based on your source map). (I like having source maps around even for Production applications, but then I also don't think minification is a security tool; I see it only as a way to save download time...) 404s returned by source map lookups can be useful indicators that you have path issues, too.

There's also the eyeball your bundle in a color source view and look for obvious accidents in minification, particularly the end of file line comment that accidentally when minified comments out a whole bunch of other modules. That one is the most annoying and most common of minification issues I've seen.

Ultimately, if you have to, do a search in the file for the System.define('main', ...) for your main module and verify it is there and syntactically legible and that the module name matches what other modules require.

MicahZoltu commented 9 years ago

@WorldMaker The chrome view in the screenshot is the collapsed view, which doesn't show paths. The full view does show that it is being loaded from source/main.ts. image

The only reason I had sourcemaps disabled was to improve load speed. I am a huge fan of shipping everything to the client and for the purpose of this test I was trying to get the numbers as low as possible for a baseline. Once I get everything sorted out I'll likely turn source maps back on unless they negatively effect load times too much.

Looking at bundle.js in the browser, I see a call to System.register("source/main.ts", ["aurelia-templating"], function (e) { ... }); Something that is possibly worth noting is that the dependency, aurelia-templating, isn't fetched from the server, indicating that it was found in the bundle.

I glanced at the bundled JS file and everything appears to be in order. It is 12,662 lines (pretty printed) so it is entirely possible that something is subtly wrong with it but I wouldn't even begin to know where to start looking. The end of the file, at least (which happens to be where main.ts is) does look good.

There is no System.define in my bundle, though there is System.register all over the place.

Perhaps for starters, is it safe for me to assume that the expected behavior is that my .ts files won't be fetched from the server if I bundle them and the TypeScript compiler won't be fetched if all of my .ts files are bundled? If so, should the command I ran do that? If so, then I assume this is a bug in JSPM's bundler.

WorldMaker commented 9 years ago

Sorry, yes when I said System.define I meant System.register; old habits from AMD work.

Expected behavior is indeed that your TS files shouldn't be fetched from the server and the TS compiler shouldn't be required if it isn't fetching a TS file directly.

I don't think this is a bug in JSPM's bundler. Did you try my proposed change to your bundle command in my last post? I don't think the command you used is correct, and here is why:

The issue is that the module names you register in your bundle (ie, "source/main.ts" in the case you just quoted) need to match the module names that your application imports or requires from System (ie, I'm assuming an import from just main because you've set a base path to your 'source' directory). To use the bundled version instead of trying to look the module up in the webserver's file system, there has to be an agreement between what you've registered with System and your expected module name. (You could fix up issues post-facto by further complicating your System mapping config, but I don't think that would be addressing your root problem here...) I think your base path may still be confusing you after you create your bundle: maybe try a "Production" config.js that has a different base path, such as your dist directory or no base path at all, or try jspm bundle --inject and let JSPM entirely replace your config.js with just the bundle's needs.

TL;DR: I've got a feeling you need to run jspm bundle from within your "source" directory and that you might want to make some changes to your config.js and/or let jspm inject your bundle for you at least until you debug your build script.

MicahZoltu commented 9 years ago

TL;DR: Skip to the end for the bug details.

The module reference in my source code matches the bundle command line. In particular, I bundle source/main and my index.html looks like

<!doctype html>
<html>
    <head>
        <title>Aurelia</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>

    <body aurelia-app="source/main">
        <div style="text-align: center">Loading...</div>

        <script src="jspm_packages/system.js"></script>
        <script src="jspm-config.js"></script>
        <script src="bundle.js"></script>
        <script>
            System.import('aurelia-bootstrapper');
        </script>
    </body>
</html>

Aurelia takes whatever string I give aurelia-app and passes it on to SystemJS to load as seen here https://github.com/aurelia/bootstrapper/blob/master/src/index.js#L181-L193 which calls into https://github.com/aurelia/loader-default/blob/master/src/index.js#L125-L137.

If you remove what appears to be caching then it looks like it does something like:

let id = 'source/main';
let newId = await System.normalize(id);
let module = await System.import(newId);
// do something with the module

Dropping into a debugger, I can see that newIdis http://localhost:8080/source/main.ts and id is source/main.

I attempted a brief foray into debugging SystemJS (chrome/IE debuggers don't like SystemJS very much) and I did find something interesting. In some object on the stack I found an object at the key defined that contains a bunch of stuff in it and low and behold it has http://localhost:8080/source/main.ts.js! The same is true for all of my TS files that I bundled.

BUG

From this I surmise that when bundled items are loaded into this object they don't follow the same set of normalization rules as the call to System.normalize follows at runtime. In particular, the bundled source/main.ts (as seen in the bundle.js file) should resolve to the id http://localhost:8080/source/main.ts. Instead it resolves to http://localhost:8080/source/main.ts.js. Later when I normalize source/main after the bundle has been loaded it normalizes to http://localhost:8080/source/main.ts which can't be found already loaded by the bundle so it fetches it from the server.

For reference, my jspm-config.js looks like:

System.config({
  "baseURL": "/",
  "defaultJSExtensions": true,
  "transpiler": "typescript",
  "paths": {
    "github:*": "jspm_packages/github/*",
    "npm:*": "jspm_packages/npm/*",
    "bower:*": "jspm_packages/bower/*"
  },
  "packages": {
    "source": {
      "defaultExtension": "ts"
    }
  },
  "buildCSS": false
});
// ...

Of particular note is the fact that defaultJSExtensions is true and the source package has defaultExtension set to ts. I believe the default extension stuff is pretty knew, is it possible that JSPM bundler is using an old version of SystemJS's normalize method and not taking into account the extension stuff?

WorldMaker commented 9 years ago

Ah, I think I see now. That does seem to be an issue. I'm more familiar with plugin-typescript and using TypeScript as transpiler option is new to me (and seems rather recent to SystemJS as well). (I've mostly preferred to do my own tsc compilation step to date. That said, TypeScript as transpiler is a compelling option moving forward to skip the Babel/Traceur step; I'll definitely look to consider it when bundle support happens... Neat.)

Anyway, I just did a brief code walk about the issue: JSPM just rehosts the systemjs-builder and there are a bunch of TypeScript issues posted over there, especially systemjs/builder#236 which is acting as an overall progress tracker for TypeScript support in the bundler.

So yes, it looks like the TypeScript transpilation option is new enough/bleeding edge enough that it doesn't bundle correctly yet. You might want to repost your particular findings in one of the issues over at systemjs/builder.

MicahZoltu commented 9 years ago

I read through the issues you linked and none of them are this issue. To be clear, TypeScript transpilation works fine with the caveat of the defaultJSExtenions issue.

I am going to cross-post this bug over on SystemJS, since I actually suspect it is an issue with SystemJS when loading bundles, not with JSPM when creating the bundles.

MicahZoltu commented 9 years ago

Closing issue as this is a SystemJS problem.

guybedford commented 9 years ago

Much appreciated for tracking down the problem.