如何通过npm将自定义库导入Ember

Nic*_*ick 1 javascript npm ember.js ecmascript-6

尽管似乎有很多关于它的博客文章,我仍然在努力通过npm完善使用自定义库作为我的ember应用程序的依赖项.

我编写了一个WebGL库,目前通过npm从私有存储库安装它导入我的Ember应用程序.这当前有效并且正在生产中,但工作流程有点笨重.该库是使用NodeJS模块编写的(require - > export.modules).目前我只是在我的src文件上使用babel,这导致一个构建文件夹,文件的ES5版本仍然分开.

然后我有一个如下所示的索引文件:

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
};
Run Code Online (Sandbox Code Playgroud)

使用npm我可以将此构建目录安装到我的Ember应用程序中,并使用以下语法导入我需要的模块:

import webglRenderLibrary from 'npm:webglRenderLibrary';
const { Module5 } = webglRenderLibrary;
Run Code Online (Sandbox Code Playgroud)

其中Module5是库中导出的类,如下所示:

class Module5 {
  //Magic rendering code
}
module.exports = Module5;
Run Code Online (Sandbox Code Playgroud)

我没有必要安装任何其他插件或将库文件导入到ember供应商文件中,因为很多博客文章都说你必须这样才能让它工作.我真的不明白为什么这种方法有效,但确实如此.

¯\ _(ツ)_ /¯

我从来没有真正喜欢这个设置/工作流程(并且不知道它为什么会起作用)所以我正在努力改进它,但我在Ember,JS模块等方面的许多知识差距使得它变得困难.

我想做的第一件事是将库移到ES6模块(导入 - >导出).从我的理解ES6模块更精简和未来所以我宁愿将它们用于我的库.更改所有源代码有点劳动密集,但它工作得很好,并允许我为我的库开发创建一个很好的工作流程.

Module5现在看起来像这样:

export default class Module5 {
  //Magic rendering code
}
Run Code Online (Sandbox Code Playgroud)

package.json库中我有很多npm脚本现在调用watchify,所以我可以单独在demo文件中测试我的模块.

{
  "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 compressed",
    "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
}
Run Code Online (Sandbox Code Playgroud)

我把这一切都指出我转换到ES6模块的过程非常顺利.我的各个模块的测试Js文件编译和babelify很好; 我可以在测试HTML文件中运行它们,并且WebGL正确呈现.

现在,这里是我的知识变得模糊的地方.

该库包含我想要公开的7个模块,以便Ember App可以使用它们.所以,我index.js在库中有一个文件,只需导入每个模块然后导出它们.(如果不是这样做,请告诉我)

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

export { Module1, Module2, Module3, Module4, Module5, Module6, Module7 };
Run Code Online (Sandbox Code Playgroud)

根据我的理解,这允许我将browserify/babelify将我的ES6模块库转换为Ember可以使用的东西.在库中package.json我有一个'build'脚本,它运行browserify,babel和uglify将我的整个库混合成一个缩小的文件,dist/js/app.js其中是库中main下的入口点package.json.

我当时认为这应该与目前在我的Ember应用程序中运行的代码完全相同.唯一的区别应该是它已经被浏览器放入一个文件中并用Uglify缩小(如果我错了,请纠正我,我认为我是这样).我以为我不需要对我的Ember应用程序进行任何更改,但现在我得到了:

Uncaught TypeError: Module5 is not a constructor
Run Code Online (Sandbox Code Playgroud)

我按照以前的方式导入它:

import webglRenderLibrary from 'npm:webglRenderLibrary';
const { Module5 } = webglRenderLibrary;
Run Code Online (Sandbox Code Playgroud)

所有这些都说明了几个问题:

  1. 为什么我的原始策略只是简单地保存我的src代码并按照描述导入它,而如此多的博客文章谈论使用brocolli并将其导入供应商文件.
  2. 如果原始策略有效,为什么我对新库结构的更改也不起作用.我猜它是因为browserify改变了一些关于NodeJS模块输出的东西,但我的知识是模糊的.
  3. 要创建一个包含多个要导出的ES6模块的库,是否正确/必须从我单独的索引文件中导出它们?如果没有,那么其他库如何将其模块暴露给Ember(示例lodash)
  4. 我见过许多插件/包,声称允许将ES6模块导入到ember中.例如,ember-cli-es6-transform.我无法让这个与我的图书馆合作.类似于我的设置有没有人有这样的成功?你是怎么做到的?
  5. 如果您要创建要导入到ember应用程序的自定义库,您将如何进行?

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

编辑

我找到了一个有效的解决方案,但我并不完全理解它,我不是它的忠实粉丝.

在库中package.json我添加了'standalone'参数,它已经神奇地工作了.

"build-js": "browserify --standalone WebGLRenderLibrary src/js/index.js -t [ babelify --presets [ \"env\" ] ] | uglifyjs -mc > dist/js/app.js",
Run Code Online (Sandbox Code Playgroud)

我不知道它在幕后做了什么,但现在它的工作.希望很快能找到更好的解决方案.

Suk*_*ima 8

如果没有和你一起经历同样的情况,我觉得回答你的问题只会是猜测的一种练习.由于我不想错过代表,我将试图描述我如何完成同样的情况.

首先,在应用程序中包含供应商代码与插件之间存在细微差别.我的经验是基于创建一个ember插件.但是,由于应用程序允许使用in-repo插件,因此可以轻松地在实际应用程序中复制插件的过程.我还建议单独执行此操作(作为单独的插件或in-repo插件)比应用程序本身的一部分更有益.

第一个障碍是确保您要使用的模块与浏览器兼容,因为实际模块将在浏览器中使用.如果您要使用的NPM模块是特定于节点的,那么这将不起作用.其次,许多NPM模块将尝试使用某种形式的模块管理,无论是CommonJS,AMD,UMD还是全局名称间距.您必须了解它在浏览器中的交互方式.Ember在浏览器中使用AMD,因此无论NPM模块使用什么,都必须包装/转换为AMD(这被称为垫片).

在我的情况烬证实的是在我的NPM模块灰烬包装证实(免责声明:我是这些模块的作者).在确认的NPM模块中,我使用babel将ES6源编译成UMD模块,node_modules当通过a引用时,该模块被打包到任何目录中package.json.

使用Ember插件我必须完成以下操作:

  1. 将模块放在插件的package.json 依赖项(非devDependencies)部分中.这将告诉应用程序将哪个版本的模块放在其自己的node_modules目录中.
  2. 对模块进行Shim,使得内置的ember应用程序的AMD模块系统可以解析(这实际上允许我命名from我的import语句的一部分).
  3. 告诉任何使用此插件的应用程序在最终构建输出中包含模块代码和填充程序代码.

此时,如果我想控制出口,我可以添加第四步.意思是,通过以上我将给予的能力

import Something from 'name-of-npm-module';
Run Code Online (Sandbox Code Playgroud)

但是,在某些情况下,人们可能会想要这样:

import { stuff, and, things } from 'name-of-my-ember-addon';
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您必须添加一个addon/index.js导出所需内容的文件.from 'name-of-ember-addon'在上面的步骤2中使用垫片时,基本上查看插件的addon/index.js文件from 'name-of-npm-module'.

构建垫片

我基本上从这篇博文中采用了这种格式.该垫片被编写为好像是经过后编译以便在浏览器中使用.它没有通过任何手段进行转换.它负责使用AMD define功能并返回对包含的NPM模块的引用.对于UMD模块的情况,我确认编译为在构建的ember应用程序的上下文中运行时将自己添加到全局命名空间(window.confirmer),因此我的垫片将定义一个确认模块并将值设置为全局引用.

(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);
})();
Run Code Online (Sandbox Code Playgroud)

如果源模块通过babel编译,则必须手动翻译.所有ES6导入都转换为具有属性的对象,其中one(default)是唯一的.在你的情况下,为了实现这一点,垫片可能看起来像这样:

(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);
})();
Run Code Online (Sandbox Code Playgroud)

在应用程序的构建中包含文件

n addon有一个根index.js文件,告诉broccoli管道如何打包东西.由于NPM模块是第三方,因为Ember.JS,jQuery,时刻等都应该vendor.js与上面的垫片一起放在文件中.要实现这一点,插件将需要两个NPM模块,这些模块也会进入dependencies(不devDependencies)部分:

"dependencies": {
  "broccoli-funnel": "^2.0.1",
  "broccoli-merge-trees": "^2.0.0",
  "ember-cli-babel": "^6.3.0",
  "my-npm-module": "*"
}
Run Code Online (Sandbox Code Playgroud)

然后在我们的index.js文件中,我们将两个文件添加到我们的treeForVendor钩子:

/* 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]);
  }
};
Run Code Online (Sandbox Code Playgroud)

所有这些也可以在in-repo插件中完成.只记得你构造的代码告诉ember如何编译输出而不是如何执行JS.所有这一仪式都是为了vendor.js在浏览器中建立一个良好的形式.