Skip to content
This repository has been archived by the owner on Oct 9, 2020. It is now read-only.

build a consumable npm module #475

Closed
johnpapa opened this issue Jan 18, 2016 · 24 comments
Closed

build a consumable npm module #475

johnpapa opened this issue Jan 18, 2016 · 24 comments

Comments

@johnpapa
Copy link

Guy - thanks for your help in the past. @wardbell and I are trying to create am npm package that will be consumable by others. It relies on angular2 and rxjs. We tried using the builder to make the file, but it was 684kb :)

Is there a simple example of how to get only what is needed?

@johnpapa
Copy link
Author

update: we used meta to trim it down.

But now we are getting errors about the browser not being able to find our imports. it is 404-ing on them. Do you have an example we can peruse?

@johnpapa
Copy link
Author

Here is out build file ... it currently is 404-ing , but we figured out why. the output file contains registered modules named foo.js and it should be my-package-name/foo. When we manually rename it, the app loads things perfectly.

How do we get the systemjs-builder to do this for us?

Here is our build file

var path = require("path");
var Builder = require('systemjs-builder');

// optional constructor options
// sets the baseURL and loads the configuration file
//var builder = new Builder('path/to/baseURL', 'path/to/system/config-file.js');
var builder = new Builder({
    baseUrl: './',
    map: {
        './': {
            format: 'register',
            defaultExtension: 'js'
        },
        angular2: 'node_modules/angular2',
        rxjs: 'node_modules/rxjs'
    },
    meta: {
        'angular2/*': {
            build: false
        },
        'rxjs/*': {
            build: false
        }
    },
    packages: {
        'angular2': {
            defaultExtension: 'js',
            main: 'core.js'
        },
        'rxjs': {
            defaultExtension: 'js',
            main: 'rx.js'
        }
    }
});
builder.bundle('./core.js', 'dev.js')
    .then(function() {
    console.log('Build complete');
})
    .
catch (function(err) {
    console.log('Build error');
    console.log(err);
});

@fictitious
Copy link
Contributor

Most likely you need to pass {normalize: true} option to builder.bundle(), as mentioned in the first item in 0.15.0 release notes here https://github.com/systemjs/builder/releases/tag/0.15.0

@johnpapa
Copy link
Author

Thanks, tried that and no luck.

We are trying to remove the .js from the names

@wardbell
Copy link

And we want to control the prefix.

Riffing on @johnpapa example code above, ...

Suppose my desired module prefix is "xxx". Imagine a module file called foo.js in the root directory. We want to see

System.register("xxx/foo", [...], function (...) {...}

But we are getting

System.register("foo.js", [...], function (...) {...}

We can trick the builder by moving our source into an xxx folder. That gets us to

System.register("xxx/foo.js", [...], function (...) {...}

Hacky but whatever. Still have the .js extension which is fatal.

FWIW, despite the generated module name xxx/foo.js in the example above, this would be referred to by its unadorned name when a dependency:

System.register("xxx/bar.js", [./foo], function (...) {...}

So how do we control the generation of registered module names?

@fictitious
Copy link
Contributor

So how do we control the generation of registered module names?

You can have full control over normalized module names if you override builder.loader.normalize()

But then you would need to override fetch() too, because builder uses normalized names when loading module files AFAIR.

@wardbell
Copy link

We finally "won" the game. The two essential tricks:

  1. Generate/copy JS files into a directory with the desired prefix (e.g, put all JS files in folder named xxx)
  2. Add the following line to the config: paths: {'*': '*.js'}

Step 1 gives us the module prefix we want (the xxx in xxx/foo) and step 2 removes the .js extension from the module name.

We wrote gulp tasks to create the (unwanted) xxx directory during the build process and then cleanup afterward.

For the curious and future reference, this is the specific gulp task that runs the builder:

function build(done) {
  var builder = new Builder({
      paths: {'*': '*.js'},
      map: {
        angular2: 'node_modules/angular2',
        rxjs: 'node_modules/rxjs'
      },
      meta: {
        'angular2/*': { build: false  },
        'rxjs/*': { build: false  }
      },
      packages: {
        'angular2': {
          defaultExtension: 'js',
          main: 'core.js'
        },
        'rxjs': {
          defaultExtension: 'js',
          main: 'rx.js'
        }
      }
    });

  builder
  // start building with the root module file in the folder with the intended module name
  .bundle('xxx/foo', 'output.js')
  .then(function(output) {
    console.log('Build complete');
  })

  .catch(function(err) {
    console.log('Build error');
    console.log(err);
  })

  .finally(done);
}

I"ll bet we could consolidate some of that configuration if we really knew what we were doing.

Hope this helps others.

@dlebedynskyi
Copy link

@wardbell can you publish whole source for this somewhere?

@wardbell
Copy link

See my a2-in-memory-web-api repo.

@guybedford
Copy link
Member

@wardbell paths configuration is exactly the system for controlling the output module names. I'm wondering if a paths: { 'xxx/*': '*.js' } might give you the right output without needing the dummy folder. Note that excluding the js extensions is highly discouraged though as the defaultJSExtensions flag is being deprecated in the SystemJS deprecation release (which will still be a while though). Would be interesting to hear why that was such a critical constraint.

@johnpapa
Copy link
Author

@guybedford Thanks. I had forgotten that defaultJSExtensions was being deprecated. The requirement we have is to be able to reference the files without an extension. Paths should be able to do this, though we couldn't find the right combination. Here is what we have right now. Notice the paths ...

function build(done) {
  var builder = new Builder({
      paths: {'*': '*.js'},
      map: {
        angular2: 'node_modules/angular2',
        rxjs: 'node_modules/rxjs'
      },
      meta: {
        'angular2/*': { build: false  },
        'rxjs/*': { build: false  }
      },
      packages: {
        'angular2': {
          defaultExtension: 'js',
          main: 'core.js'
        },
        'rxjs': {
          defaultExtension: 'js',
          main: 'rx.js'
        }
      }
    });

  builder
   // start building with the root module file in the folder with the intended module name
  .bundle('a2-in-memory-web-api/core', 'web-api.js')
  .then(function(output) {
    console.log('Build complete');
  })

  .catch(function(err) {
    console.log('Build error');
    console.log(err);
  })

  .finally(done);
}

@guybedford
Copy link
Member

@johnpapa thanks for clarifying. Yes the ability to ensure bundles contain no extension in the outputted names is not currently something that will currently continue to be supported in SystemJS with the planned deprecations but it will be a while yet before this happens.

@johnpapa
Copy link
Author

More specifically, we want to be able to do imports without extension specification such as import {Foo} from "./foo"; which is consistent with the current conventions such as import {Component} from "angular2/core";

Are you saying that you won;t support that at all or that we'll have to use an alternative means, such as some configuration of paths?

@guybedford
Copy link
Member

Those kinds of imports can work if the package uses a configuration - defaultExtension: true.

Bundles are now designed to allow configuration to be included with the bundle via:

// ensure that angular is treated as a package,
// so that this config module is loaded before any module in angular
// I just noticed this technique won't work yet pending release of
// https://github.com/systemjs/systemjs/commit/32554417a73ce4440fd3d1d28f28612f2a6356ef
System.packageConfigPaths.push('angular/config.json');

System.registerDynamic('angular/config.json', [], false, function(require, exports, module) {
  // package config here:
  module.exports = {
    defaultExtension: 'js'
  };
});
System.register('angular/x.ext', [], function(_export) {
  // ...
});

In this way the extension handling is always through the normalization layer, while the registration layer works completely in terms of file extensions.

The power of the config build approach is that we then get all the flexibility of package configuration in a way that can be bundled up.

@guybedford
Copy link
Member

Anyway, the point being that there are ways around this in the long term despite the extensions in the register being deprecated.

@valorkin
Copy link

@guybedford

I'm wondering if a paths: { 'xxx/': '.js' }

nope, it gives (in my case):

Error [Error: ENOENT: no such file or directory, open '/Users/valorkin/work/open-source/ng2-bootstrap/ng2-bootstrap']

while without xxx works fine

@valorkin
Copy link

@johnpapa @wardbell
lazy dev tip, I was to lazy to copy whole project, so I found simpler solution, may be it will be useful
https://github.com/valor-software/ng2-bootstrap/blob/master/make.js
see baseURL: '..', and builder.bundle([name, name].join('/')
:)

@bitbay
Copy link

bitbay commented Jan 29, 2016

@valorkin
i had some more time to play with the build process of ng2-bootstrap, and found another bug with the lazy dev approach.
Please see this pull request for details.

@guybedford
Copy link
Member

If there is anything further I can do to help here please just let me know.

@SonofNun15
Copy link

SonofNun15 commented May 2, 2016

I have a config almost identical to the one posted by @johnpapa above. When running the bundler, all I get is an empty file. If I remove the paths: { '*': '*.js' } line and add defaultJSExtensions: true I get a full output, but every file is emitted as System.register('source/library.js', ["angular/core.js"]
Config:

System.config({
    baseUrl: './',
    package: {
        'uuid': {
            format: 'cjs',
            defaultExtension: 'js',
        },
    },
    meta: {
        'angular2/*': {
            build: false
        },
        'rxjs': {
            build: false
        },
        'rxjs/*': {
            build: false
        },
        'lodash': {
            build: false
        },
        'moment': {
            build: false
        },
        'moment-timezone': {
            build: false
        },
        'angular': {
            build: false
        },
        'angular-mocks': {
            build: false
        },
    },
    map: {
        'uuid': 'node_modules/uuid/uuid.js',
        'crypto': 'moduleAdapters/crypto.js',
    },
    paths: {
        '*': '*.js',
    },
    // defaultJSExtensions: true,
});

@SonofNun15
Copy link

SonofNun15 commented May 2, 2016

Any ideas why it is generating an empty file with paths: { '*': '*.js' }?

@guybedford
Copy link
Member

@SonofNun15 perhaps if you are using a wildcard expression in your bundle command, it is being affected by the paths? Eg perhaps bundle('src/*') may not properly be globbing? Ideally using paths: { '*': '*.js' } is not recommended anymore, and will (eventually) be deprecated in future. Extensions should really be included in the bundled file.

@SonofNun15
Copy link

Figured it out. The problem was that we were trying to pass "source/**/*.js into the builder.

@SonofNun15
Copy link

Yes @guybedford, that exactly. Good guess!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants