admin管理员组

文章数量:1389750

Despite what seems like a plethora of blog posts about it, I am still struggling to perfect using a custom library as a dependency for my ember application through npm.

I have written a WebGL library and currently have it importing into my Ember app by installing it via npm from a private repository. This currently works and is in production but the workflow is somewhat clunky. The library is written using NodeJS modules (require -> export.modules). Currently I am just using babel on my src files which results in a build folder with the ES5 versions of the files still separated.

I then have an index file that looks like this:

var Helper = require('.//XXXX/utils/Helper');
module.exports = {
  Module1: require('.//XXXX/media/Module1'),
  Module2: require('.//XXXX/media/Module2'),
  Module3: require(".//XXXX/media/Module3"),
  Module4: require('./Module4'),
  Module5: require('.//XXXX/media/Module5'),
  Module6: Helper.Module6,
  Module7: Helper.Module7
};

Using npm I can install this build directory into my Ember app and import the modules I need using the following syntax:

import webglRenderLibrary from 'npm:webglRenderLibrary';
const { Module5 } = webglRenderLibrary;

Where Module5 is a class in the library exported like this:

class Module5 {
  //Magic rendering code
}
module.exports = Module5;

I didn't have to install any other plugins or import the library-files to the ember vendor file as so many blog posts say you have to in order to get this to work. I don't really understand why this method works but it does.

¯\_(ツ)_/¯

I've never really liked this setup/workflow though (and not knowing why it works) so I'm trying to improve it but the many knowledge gaps I have in Ember, JS modules and so on are making it difficult.

The first thing I wanted to do was move the library over to ES6 modules (import -> export). From what I understand ES6 modules are leaner and the future so I would rather use them for my library. Changing all the source code was a little labour intensive but it works nicely and allowed me to create a nice workflow for my library development.

Module5 now looks like this:

export default class Module5 {
  //Magic rendering code
}

In the package.json of the library I have a number of npm scripts now calling watchify so I can test my modules in demo files individually.

{
  "name": "webglRenderLibrary",
  "main": "dist/js/app.js",
  "version": "2.0.5",
  "author": "JibJab Media",
  "description": "WebGL Render Library",
  "repository": "private.git",
  "scripts": {
    "watch-sass": "sass --watch src/scss/app.scss:demo/css/app.css",
    "watch-js": "watchify src/js/index.js -t babelify -o dist/js/app.js -dv",
    "watch-module1": "watchify src/js/demos/Module1Demo.js -t babelify -o demo/js/Module1Demo.js -dv",
    "watch-module2": "watchify src/js/demos/Module2Demo.js -t babelify -o demo/js/Module2Demo.js -dv",
    "watch-module3": "watchify src/js/demos/Module3Demo.js -t babelify -o demo/js/Module3Demo.js -dv",
    "watch-module4": "watchify src/js/demos/Module4Demo.js -t babelify -o demo/js/Module4Demo.js -dv",
    "watch-module5": "watchify src/js/demos/Module5Demo.js -t babelify -o demo/js/Module5Demo.js -dv",
    "watch-module6": "watchify src/js/demos/Module6Demo.js -t babelify -o demo/js/Module6Demo.js -dv",
    "watch": "npm run watch-sass & npm run watch-module1 & npm run watch-module2 & npm run watch-module3 & npm run watch-module5 & npm run watch-module5 & npm run watch-module6",
    "build-sass": "sass src/scss/app.scss:dist/css/app.css --style pressed",
    "build-js": "browserify src/js/index.js -t [ babelify --presets [ \"env\" ] ] | uglifyjs -mc > dist/js/app.js",
    "build": "npm run build-js & npm run build-sass",
    "test": "mocha --require babel-core/register",
    "test-coverage": "nyc mocha --require babel-core/register"
  },
  "browserify": {
    "transform": [
      "babelify"
    ]
  },
  "dependencies": {
    "babel-preset-env": "1.6.1",
    "babelify": "^7.2.0",
    "opentype.js": "0.8.0"
  },
  "devDependencies": {
    "babel-cli": "*",
    "mocha": "5.0.5",
    "nyc": "11.6.0",
    "watchify": "3.11.0"
  },
  "bugs": {
    "url": "private/issues"
  },
  "homepage": "private#readme",
  "private": true
}

I bring this all up to point out that my conversion to ES6 modules has gone smoothly. My test Js files for the individual modules pile and babelify nicely; I can run them in test HTML files and the WebGL renders correctly.

Now, here is where my knowledge gets kind of fuzzy.

The library contains 7 modules I want to expose so that the Ember App can use them. So, I have an index.js file in the library which simply imports each of the modules then exports them. (Please let me know if this is not the way to do this)

import Module4              from './Module4';
import Module1              from './/XXXX/media/Module1';
import Module2              from './/XXXX/media/Module2';
import Module3              from './/XXXX/media/Module3';
import Module5              from './/XXXX/media/Module5';
import { Module6, Module7 } from ".//XXXX/utils/Helper";

export { Module1, Module2, Module3, Module4, Module5, Module6, Module7 };

From my understanding, this allows me to have browserify/babelify transpile my ES6 module library into something that Ember can consume. In the libraries package.json I have a 'build' script which runs browserify, babel and uglify to mash my whole library into one minified file in dist/js/app.js which is the entry point under main in the libraries package.json.

I was thinking that this should really be the same code which is currently working in my Ember app. The only difference should be that it has been put into a single file by browseridy and minified with Uglify (Please correct me if I'm wrong, which I think I am). I thought I wouldn't have to make any changes to my Ember app but now I'm getting:

Uncaught TypeError: Module5 is not a constructor

while importing it the way I did before:

import webglRenderLibrary from 'npm:webglRenderLibrary';
const { Module5 } = webglRenderLibrary;

All of this being said leads me to several questions:

  1. Why does my original strategy of simply babeling my src code and importing it as described work while so many blog posts talk about using brocolli and importing it to the vendor file. Example
  2. If the original strategy worked, why didn't my change to the new library structure work as well. I'm guessing it's because browserify changes something about the NodeJS module exports but my knowledge here is fuzzy.
  3. For creating a library with multiple ES6 modules to export, is it correct/necessary to export them from a single index file as I have done? If not then how do other libraries expose their modules to Ember (example lodash)
  4. I've seen a number of plugins/packages which claim to allow the import of ES6 modules into ember. For example ember-cli-es6-transform. I could not get this to work with my library. Has anyone had success with something like this for a setup similar to mine? How did you make it work?
  5. If you were creating a custom library to import to an ember app, how would you do it?

===========================================

Edit

I've found one solution which works but I don't fully understand it and I'm not a huge fan of it.

In the libraries package.json I've added the 'standalone' parameter and it has magically worked.

"build-js": "browserify --standalone WebGLRenderLibrary src/js/index.js -t [ babelify --presets [ \"env\" ] ] | uglifyjs -mc > dist/js/app.js",

I don't know what its done under the hood but for now its working. Hopefully a better solution appears soon.

Despite what seems like a plethora of blog posts about it, I am still struggling to perfect using a custom library as a dependency for my ember application through npm.

I have written a WebGL library and currently have it importing into my Ember app by installing it via npm from a private repository. This currently works and is in production but the workflow is somewhat clunky. The library is written using NodeJS modules (require -> export.modules). Currently I am just using babel on my src files which results in a build folder with the ES5 versions of the files still separated.

I then have an index file that looks like this:

var Helper = require('.//XXXX/utils/Helper');
module.exports = {
  Module1: require('.//XXXX/media/Module1'),
  Module2: require('.//XXXX/media/Module2'),
  Module3: require(".//XXXX/media/Module3"),
  Module4: require('./Module4'),
  Module5: require('.//XXXX/media/Module5'),
  Module6: Helper.Module6,
  Module7: Helper.Module7
};

Using npm I can install this build directory into my Ember app and import the modules I need using the following syntax:

import webglRenderLibrary from 'npm:webglRenderLibrary';
const { Module5 } = webglRenderLibrary;

Where Module5 is a class in the library exported like this:

class Module5 {
  //Magic rendering code
}
module.exports = Module5;

I didn't have to install any other plugins or import the library-files to the ember vendor file as so many blog posts say you have to in order to get this to work. I don't really understand why this method works but it does.

¯\_(ツ)_/¯

I've never really liked this setup/workflow though (and not knowing why it works) so I'm trying to improve it but the many knowledge gaps I have in Ember, JS modules and so on are making it difficult.

The first thing I wanted to do was move the library over to ES6 modules (import -> export). From what I understand ES6 modules are leaner and the future so I would rather use them for my library. Changing all the source code was a little labour intensive but it works nicely and allowed me to create a nice workflow for my library development.

Module5 now looks like this:

export default class Module5 {
  //Magic rendering code
}

In the package.json of the library I have a number of npm scripts now calling watchify so I can test my modules in demo files individually.

{
  "name": "webglRenderLibrary",
  "main": "dist/js/app.js",
  "version": "2.0.5",
  "author": "JibJab Media",
  "description": "WebGL Render Library",
  "repository": "private.git",
  "scripts": {
    "watch-sass": "sass --watch src/scss/app.scss:demo/css/app.css",
    "watch-js": "watchify src/js/index.js -t babelify -o dist/js/app.js -dv",
    "watch-module1": "watchify src/js/demos/Module1Demo.js -t babelify -o demo/js/Module1Demo.js -dv",
    "watch-module2": "watchify src/js/demos/Module2Demo.js -t babelify -o demo/js/Module2Demo.js -dv",
    "watch-module3": "watchify src/js/demos/Module3Demo.js -t babelify -o demo/js/Module3Demo.js -dv",
    "watch-module4": "watchify src/js/demos/Module4Demo.js -t babelify -o demo/js/Module4Demo.js -dv",
    "watch-module5": "watchify src/js/demos/Module5Demo.js -t babelify -o demo/js/Module5Demo.js -dv",
    "watch-module6": "watchify src/js/demos/Module6Demo.js -t babelify -o demo/js/Module6Demo.js -dv",
    "watch": "npm run watch-sass & npm run watch-module1 & npm run watch-module2 & npm run watch-module3 & npm run watch-module5 & npm run watch-module5 & npm run watch-module6",
    "build-sass": "sass src/scss/app.scss:dist/css/app.css --style pressed",
    "build-js": "browserify src/js/index.js -t [ babelify --presets [ \"env\" ] ] | uglifyjs -mc > dist/js/app.js",
    "build": "npm run build-js & npm run build-sass",
    "test": "mocha --require babel-core/register",
    "test-coverage": "nyc mocha --require babel-core/register"
  },
  "browserify": {
    "transform": [
      "babelify"
    ]
  },
  "dependencies": {
    "babel-preset-env": "1.6.1",
    "babelify": "^7.2.0",
    "opentype.js": "0.8.0"
  },
  "devDependencies": {
    "babel-cli": "*",
    "mocha": "5.0.5",
    "nyc": "11.6.0",
    "watchify": "3.11.0"
  },
  "bugs": {
    "url": "private/issues"
  },
  "homepage": "private#readme",
  "private": true
}

I bring this all up to point out that my conversion to ES6 modules has gone smoothly. My test Js files for the individual modules pile and babelify nicely; I can run them in test HTML files and the WebGL renders correctly.

Now, here is where my knowledge gets kind of fuzzy.

The library contains 7 modules I want to expose so that the Ember App can use them. So, I have an index.js file in the library which simply imports each of the modules then exports them. (Please let me know if this is not the way to do this)

import Module4              from './Module4';
import Module1              from './/XXXX/media/Module1';
import Module2              from './/XXXX/media/Module2';
import Module3              from './/XXXX/media/Module3';
import Module5              from './/XXXX/media/Module5';
import { Module6, Module7 } from ".//XXXX/utils/Helper";

export { Module1, Module2, Module3, Module4, Module5, Module6, Module7 };

From my understanding, this allows me to have browserify/babelify transpile my ES6 module library into something that Ember can consume. In the libraries package.json I have a 'build' script which runs browserify, babel and uglify to mash my whole library into one minified file in dist/js/app.js which is the entry point under main in the libraries package.json.

I was thinking that this should really be the same code which is currently working in my Ember app. The only difference should be that it has been put into a single file by browseridy and minified with Uglify (Please correct me if I'm wrong, which I think I am). I thought I wouldn't have to make any changes to my Ember app but now I'm getting:

Uncaught TypeError: Module5 is not a constructor

while importing it the way I did before:

import webglRenderLibrary from 'npm:webglRenderLibrary';
const { Module5 } = webglRenderLibrary;

All of this being said leads me to several questions:

  1. Why does my original strategy of simply babeling my src code and importing it as described work while so many blog posts talk about using brocolli and importing it to the vendor file. Example
  2. If the original strategy worked, why didn't my change to the new library structure work as well. I'm guessing it's because browserify changes something about the NodeJS module exports but my knowledge here is fuzzy.
  3. For creating a library with multiple ES6 modules to export, is it correct/necessary to export them from a single index file as I have done? If not then how do other libraries expose their modules to Ember (example lodash)
  4. I've seen a number of plugins/packages which claim to allow the import of ES6 modules into ember. For example ember-cli-es6-transform. I could not get this to work with my library. Has anyone had success with something like this for a setup similar to mine? How did you make it work?
  5. If you were creating a custom library to import to an ember app, how would you do it?

===========================================

Edit

I've found one solution which works but I don't fully understand it and I'm not a huge fan of it.

In the libraries package.json I've added the 'standalone' parameter and it has magically worked.

"build-js": "browserify --standalone WebGLRenderLibrary src/js/index.js -t [ babelify --presets [ \"env\" ] ] | uglifyjs -mc > dist/js/app.js",

I don't know what its done under the hood but for now its working. Hopefully a better solution appears soon.

Share Improve this question edited Apr 23, 2018 at 20:58 Nick asked Apr 23, 2018 at 18:32 NickNick 89212 silver badges38 bronze badges 2
  • Maybe you just need ember-cli-browserify? – Gennady Dogaev Commented Apr 26, 2018 at 18:53
  • We already have ember-cli-browserify installed – Nick Commented Apr 26, 2018 at 19:31
Add a ment  | 

1 Answer 1

Reset to default 11

UPDATE: ember-auto-import is a clean, small, and munity approved addon that allows NPM modules to be imported into Ember with no setup, boilerplate, or increased cognitive load. Use this first. Only in extreme cases (legacy code/inpatible usages) will you need the following manual work.


Without being there with you to experience the same situation I feel answering your questions would only be an exercise in guesswork. Since I would not wish to miss-represent I will instead attempt to describe how I acplish this same situations.

First there are slight differences between including vendor code in an app versus an addon. My experience is based on creating an ember addon. However, since apps allow in-repo addons the process for addons can be replicated in an actual app easily. I would also proposition that doing this in isolation (either as a separate addon or an in-repo addon) is far more beneficial then just as part of the app itself.

The first hurdle is to make sure that the module you wish to use is browser patible because the actual module will be used in a browser. If the NPM module you wish to use is node specific then this won't work. Secondly, many NPM modules will attempt to use some form of module management whether it be CommonJS, AMD, UMD, or Global name spacing. You will have to understand how that interacts in the browser. Ember uses AMD in the browser so whatever the NPM module uses will have to wrapped / converted to be AMD (this is referred to as the shim).

In my case ember-confirmed is an ember wrapper around my NPM module confirmed (disclaimer: I am the author of these modules). In the confirmed NPM module I used babel to pile the ES6 source into a UMD module which is what is packaged into any node_modules directory when referenced via a package.json.

With the Ember addon I had to acplish the following:

  1. Have the module in the addon's package.json dependencies (not devDependencies) section. This is what would tell an app which version of the module to place in its own node_modules directory.
  2. Shim the module such that the built ember app's AMD module system can resolve (this essentially allows me to name the from part of my import statements).
  3. Tell any apps that use this addon to include both the module code and the shim code in the final build output.

At this point I can add a forth step if I wanted to control the exports. Meaning that with the above I will have given the ability to

import Something from 'name-of-npm-module';

However, in some cases one might want this instead:

import { stuff, and, things } from 'name-of-my-ember-addon';

In which case you would have to add an addon/index.js file that exports what you want. Essentially from 'name-of-ember-addon' looks in the addons' addon/index.js file while from 'name-of-npm-module' uses the shim in step 2 above.

Constructing the shim

I basically took the format from this blog post. The shim is written as if it was post-piled for use in the browser. It is not transpiled through any means. It is responsible for using the AMD define function and returning the reference to the NPM module that was included. In the case of the UMD module that my confirmed was piled to when ran in the context of a built ember app would add itself to the global namespace (window.confirmer) and so my shim will define a confirmer module and set the value to the global reference.

(function() {
  function vendorModule() {
    'use strict';
    // self in an AMD define callback is a reference to the global
    // namespace (window)
    var confirmer = self['confirmer'];
    return confirmer;
  }

  define('confirmer', [], vendorModule);
})();

In your case where the source module was not piled through babel you will have to translate this manually. All ES6 imports translate to an object with properties on them where one (default) is unique. In your case to acplish this the shim might look like this:

(function() {
  function mediaVendorModule(moduleName) {
    'use strict';
    var MyModule = self['ModuleNamespace']; // Global
    return function() {
      return {
        'default': MyModule[moduleName]
      };
    };
  }
  function helperVendorModule() {
    'use strict';
    var MyModule = self['ModuleNamespace']; // Global
    return {
      Module6: MyModule.helper.Module6,
      Module7: MyModule.helper.Module7
    };
  }
  define('/XXXX/media/Module4', [], mediaVendorModule('Module4'));
  define('/XXXX/media/Module1', [], mediaVendorModule('Module1'));
  define('/XXXX/media/Module2', [], mediaVendorModule('Module2'));
  define('/XXXX/media/Module3', [], mediaVendorModule('Module3'));
  define('/XXXX/media/Module5', [], mediaVendorModule('Module5'));
  define('/XXXX/Helper', [], helperVendorModule);
})();

Include files in the apps' build

n addon has a root index.js file which tells the broccoli pipeline how to package things. Since the NPM modules are 3rd party in the same way Ember.JS, jQuery, moment, etc. are they should be in the vendor.js file along with the shim above. To acplish this the addon will need two NPM modules which also go into the dependencies section (not devDependencies):

"dependencies": {
  "broccoli-funnel": "^2.0.1",
  "broccoli-merge-trees": "^2.0.0",
  "ember-cli-babel": "^6.3.0",
  "my-npm-module": "*"
}

Then in our index.js file we add the two files to our treeForVendor hook:

/* eslint-env node */
'use strict';
var path = require('path');
var Funnel = require('broccoli-funnel');
var MergeTrees = require('broccoli-merge-trees');

module.exports = {
  name: 'ember-confirmed',

  included() {
    this._super.included.apply(this, arguments);
    this.import('vendor/confirmer.js');
    this.import('vendor/shims/confirmer.js');
  },

  treeForVendor(vendorTree) {
    var confirmedPath = path.join(path.dirname(require.resolve('confirmed')), 'dist');
    var confirmedTree = new Funnel(confirmedPath, {
      files: ['confirmer.js']
    });

    return new MergeTrees([vendorTree, confirmedTree]);
  }
};

All this can also be acplished in an in-repo addon. Just remember your constructing code to tell ember how to pile output not how to execute JS. All of this ceremony is to set up a well formed vendor.js ready for use in the browser.

本文标签: javascriptHow to import a custom library to Ember via npmStack Overflow