A guide on adding a personal library to Ember using npm

Despite the abundance of blog posts discussing it, I am still facing challenges in utilizing a custom library as a dependency for my ember application through npm.

I have developed a WebGL library and successfully imported it into my Ember app by installing it via npm from a private repository. While it is currently functional and in production, the workflow feels cumbersome. The library is built using NodeJS modules (require -> export.modules). Currently, I am only using babel on my source files, which results in an ES5 version build folder with segregated files.

The index file looks like this:

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

By using npm, I can install this build directory into my Ember app and import the required modules using the following syntax:

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

After converting the library to ES6 modules (import -> export), my workflows improved significantly. However, there are still knowledge gaps in Ember, JS modules, etc., making the process challenging.

My package.json file now includes several npm scripts calling watchify to test individual modules in demo files.

{
    // Package.json content...
}

The conversion to ES6 modules has been successful, with test Js files compiling seamlessly. Despite these advancements, some fuzziness remains in my understanding.

These improvements raise several questions regarding the functionality and best practices in integrating custom libraries into Ember applications...

Answer №1

UPDATE: ember-auto-import is a convenient addon endorsed by the community that simplifies importing NPM modules into Ember without any setup or extra complexity. It's recommended to use this first. Only in rare instances (such as dealing with legacy code or incompatible implementations) will you need the manual process outlined below.


As I cannot be present to witness your specific circumstances, my answers to your queries would be purely speculative. Instead, I will endeavor to explain how I handle similar situations based on my own experiences.

There are slight distinctions between incorporating vendor code in an app versus adding it to an addon. My knowledge stems from creating an Ember addon. However, since apps allow for in-repo addons, the procedure for addons can easily be replicated within an actual app. In fact, isolating this task either as a separate addon or an in-repo addon proves to be more beneficial than integrating it directly into the app itself.

The initial challenge involves ensuring that the module you intend to utilize is browser compatible, as it will be utilized in a browser environment. If the NPM module is tailored specifically for node.js, it may not function correctly. Additionally, many NPM modules employ various forms of module management such as CommonJS, AMD, UMD, or global namespace. Understanding how these interact with the browser is crucial. Since Ember utilizes AMD in the browser, the NPM module must be wrapped or converted into AMD format (referred to as the shim).

In my scenario, ember-confirmed acts as an Ember wrapper for my NPM module confirmed (disclaimer: I am the author of these modules). I used Babel to compile the ES6 source code into a UMD module for the confirmed NPM module, which is then packaged into the node_modules directory when referenced through a package.json.

To accomplish this within the Ember addon, the following steps were necessary:

  1. Include the module in the addon's package.json dependencies section (not devDependencies). This informs an app which version of the module should be placed in its own node_modules directory.
  2. Create a shim for the module so that the built Ember app's AMD module system can resolve it (this allows specifying the from part in import statements).
  3. Instruct any apps utilizing this addon to include both the module code and the shim code in the final build output.

At this stage, an optional fourth step could involve controlling the exports. With the aforementioned steps, users have the ability to

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

However, there may be cases where one prefers:

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

This necessitates adding an addon/index.js file that exports the desired items. Essentially, from 'name-of-ember-addon' points to the addon's addon/index.js file, while from 'name-of-npm-module' uses the previously created shim.

Creating the Shim

I adopted the format from this blog post. The shim is written as if it were post-compiled for browser usage. It remains untranspiled through any means and is responsible for utilizing the AMD define function and returning a reference to the included NPM module. For instance, the UMD module compiled from my confirmed NPM module adds itself to the global namespace (window.confirmer) when executed within a built Ember app context. Therefore, my shim defines a confirmer module and assigns it the value of the global reference.

(function() {
  function vendorModule() {
    'use strict';
    var confirmer = self['confirmer'];
    return confirmer;
  }

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

In the case where the source module was not compiled via babel, manual translation of the shim is required. Every ES6 import equates to an object with properties, with one being unique (default). To achieve this, the shim might appear as follows:

(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('com/XXXX/media/Module4', [], mediaVendorModule('Module4'));
  define('com/XXXX/media/Module1', [], mediaVendorModule('Module1'));
  define('com/XXXX/media/Module2', [], mediaVendorModule('Module2'));
  define('com/XXXX/media/Module3', [], mediaVendorModule('Module3'));
  define('com/XXXX/media/Module5', [], mediaVendorModule('Module5'));
  define('com/XXXX/Helper', [], helperVendorModule);
})();

Including Files in the App's Build

An addon possesses a root index.js file guiding the Broccoli pipeline on packaging assets. As NPM modules are considered third-party like Ember.JS, jQuery, moment, etc., they should reside in the vendor.js file alongside the crowferated shim. To facilitate this, the addon requires two NPM modules specified in 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": "*"
}

Subsequently, in our index.js file, we add these files to the 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 these tasks can also be accomplished within an in-repo addon. Remember, you're crafting code to instruct Ember on compiling outputs rather than executing JavaScript. The essence of all this effort lies in producing a well-structured vendor.js primed for utilization in the browser.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for updating chromedriver on Windows 7

When I had chromedriver 2.32.498550 installed. I cleared all cache (C:\Users\JHONE\AppData\Roaming\npm) I then decided to update to the new version (2.37): Used Command line: npm install -g chromedriver Here are the steps: ...

Personalize the start and end dates of Fullcalendar

While working with fullcalendar, I have a unique requirement. I want my days to start at 5:00 and end at 5:00 the next day. Is it feasible to achieve this customization? ...

Version 5.0.4 of Mui, when used with makeStyles and withStyles, is experiencing issues with applying styles to Mui

I've been diving into a react project using Mui v5.0.4 and encountered an unexpected issue with my custom styles. Initially, everything was functioning smoothly, but suddenly the styles I had defined were no longer appearing as intended in components ...

Ways to retrieve every span within a string variable that possesses a specific class designation

I'm having trouble listing out all spans with the class "ansspans." There may be one or more span elements with the "ansspans" class, and I need to retrieve them along with their content in order to iterate through them. Can someone explain how to do ...

Issue with datepicker functionality not operational for newly added entries in the table

@Scripts.Render("~/bundles/script/vue") <script> var vueApp = new Vue({ el: '#holiday-vue', data: { holidays: @Html.Raw(Json.Encode(Model)), tableHeader: 'Local Holidays', holidayWarning: true, dateWarning: true }, methods: ...

What is the best way to create an Office Script autofill feature that automatically fills to the last row in Excel?

Having trouble setting up an Excel script to autofill a column only down to the final row of data, without extending further. Each table I use this script on has a different number of rows, so hardcoding the row range is not helpful. Is there a way to make ...

What is the best way to retrieve a string from a URL?

Is there a way to extract only a specific string from a URL provided by an API? For instance: I'm interested in extracting only: photo_xxx-xxx-xxx.png Any suggestions on how to split the URL starting at photo and ending at png? ...

What is the best method for asynchronously injecting and providing data?

Within my page, I have implemented an asynchronous fetch method to initialize data: async fetch() { const res = await requestApi(this, '/database'); this.sliderData = res.homeSlider; this.modelData = res.model; ... } To pass thi ...

Angular.js reports that the custom HTTP response header is missing

Despite Chrome showing the correct POST response headers, my custom HTTP header X-Auth-Token is returning null in the callback function for the POST request. Angular.js seems to only be returning Cache-Control and Content-Type, with everything else showing ...

Guide to installing angular-ui-bootstrap through npm in angularjs application

My goal is to incorporate angular-ui-bootstrap into my project using the following steps: 1. npm install angular-ui-bootstrap 2. import uiBootstrap from 'angular-ui-bootstrap'; 3. angular.module('app', [      uiBootstrap    ]) I ...

What is the process for accessing and utilizing the value returned by a promise in a separate file?

I have developed a small project to demonstrate an issue. There are a total of 5 files in this project: - A container file that includes all the dependency injections. - A service file containing the necessary functions to be executed. - A controller fil ...

JavaScript - Removing an Array from an Object

Can an array be removed from an object? For instance, suppose I have an object named response with an array inside it called items: var response = { success: false, items:[] }; What is the method to remove 'items' from the response object? Tha ...

What is the best way to incorporate a subcategory within a string and separate them by commas using Vue.js?

Is it possible to post subcategories in the following format now? Here is the current result: subcategory[] : Healthcare subcategory[] : education However, I would like to have them as a string separated by commas. This is my HTML code: <div id="sub ...

How to choose multiple images in Ionic framework

I am working with the following HTML structure: <div ng-repeat="image in images"> <img ng-src="img/{{image}}_off.png" /> </div> Additionally, I have the following JavaScript code in the controller: $scope.images = ['imga',&ap ...

Enhancing a React Native application with Context Provider

I've been following a tutorial on handling authentication in a React Native app using React's Context. The tutorial includes a simple guide and provides full working source code. The tutorial uses stateful components for views and handles routin ...

Alter the style type of a Next.js element dynamically

I am currently working on dynamically changing the color of an element based on the result of a function //Sample function if ("123".includes("5")) { color = "boldOrange" } else { let color = "boldGreen" } Within my CSS, I have two clas ...

Looping through data in Vue.js with a specific condition

There is an object structured like this: groupedContacts: { 4: [ {name: 'foo'}, {name: 'bar'} ], 6: [ {name: 'foo bar'}, ] } Additionally, we have another array: companyList.models: models: [ ...

I am looking to retrieve the values entered in a textbox from an HTML document and then post them using a JSON file in Node.js when the submit button is

Here is the JavaScript code I have written: $(document).ready(function(){ $('form').on('submit', function(){ var email = $("form input[type=text][name=emails]").val(); var todo = {email: email.val(), ...

angular 2 checkbox for selecting multiple items at once

Issue I have been searching for solutions to my problem with no luck. I have a table containing multiple rows, each row having a checkbox. I am trying to implement a "select all" and "deselect all" functionality for these checkboxes. Below is an example o ...

The class 'ConsoleTVsChartsCharts' could not be located

Attempting to implement Laravel charts using the package consoletvs/charts:6.*, Utilizing service providers ConsoleTVs\Charts\ChartsServiceProvider::class, With an alias: 'Charts' => ConsoleTVs\Charts\Charts::class, ...