繁体   English   中英

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

[英]How to import a custom library to Ember via npm

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

我已经编写了一个WebGL库,当前通过从私有存储库中通过npm安装将其导入到我的Ember应用程序中。 这目前可以使用,并且正在生产中,但是工作流程有些笨拙。 该库是使用NodeJS模块编写的(要求-> 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
};

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

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

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

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

我不必安装任何其他插件或将库文件导入ember供应商文件,因为许多博客文章都说您必须这样做才能使它起作用。 我真的不明白为什么这种方法行得通,但确实可以。

¯\\ _(ツ)_ /¯

尽管我从来没有真正喜欢过此设置/工作流程(并且不知道为什么会这样),所以我正在尝试对其进行改进,但是我在Ember,JS模块等方面存在的许多知识鸿沟使得这很困难。

我要做的第一件事是将库移至ES6模块(导入->导出)。 据我了解,ES6模块更加精简和未来,因此我宁愿在我的库中使用它们。 更改所有源代码会花费一些劳力,但效果很好,并允许我为我的库开发创建一个不错的工作流。

Module5现在看起来像这样:

export default class Module5 {
  //Magic rendering code
}

在库的package.json中,我现在有许多npm脚本调用watchify,因此我可以分别在演示文件中测试模块。

{
  "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
}

我要指出的是,我向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 };

根据我的理解,这使我能够通过浏览器化/ babelify将我的ES6模块库转换为Ember可以使用的东西。 在库package.json我有一个“构建”脚本,该脚本运行browserify,babel和uglify将整个库混成dist/js/app.js一个缩小文件,这是库package.json main的入口点。

我以为这应该与我的Ember应用程序当前正在使用的代码相同。 唯一的区别应该是它已由browseridy放入单个文件中,并用Uglify缩小(如果我错了,请更正,我认为我是错的)。 我以为我不需要对Ember应用程序进行任何更改,但是现在我得到了:

Uncaught TypeError: Module5 is not a constructor

以我以前的方式导入它时:

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

所有这些都使我想到几个问题:

  1. 为什么我的原始策略只是简单地获取我的src代码并按描述的那样导入它,而为什么许多博客文章都在谈论使用brocolli并将其导入到供应商文件中。
  2. 如果原始策略可行,为什么我对新库结构所做的更改也无法正常工作。 我猜这是因为browserify改变了NodeJS模块导出的某些内容,但是我的知识很模糊。
  3. 要创建具有多个要导出的ES6模块的库,是否像我已经从单个索引文件中导出它们那样正确/必要? 如果不是,那么其他库如何将其模块公开给Ember(示例lodash
  4. 我见过许多插件/程序包,它们声称允许将ES6模块导入ember。 例如ember-cli-es6-transform 我无法将其与图书馆一起使用。 有没有人在类似我的设置上获得过类似的成功? 您是如何做到的?
  5. 如果要创建要导入到余烬应用程序的自定义库,您将如何做?

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

编辑

我找到了一个可行的解决方案,但我不太了解它,我也不是很喜欢它。

在程序package.json我添加了“ standalone”参数,它神奇地起作用了。

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

我不知道它到底是做什么的,但是目前为止它还在工作。 希望很快会有更好的解决方案。

更新: ember-auto-import是一个干净,小型且得到社区认可的插件,它可以将NPM模块导入Ember,而无需进行设置,重复样板或增加认知负荷。 首先使用此。 仅在极端情况下(旧版代码/不兼容的用法),您才需要进行以下手动操作。


如果没有和您一起经历同样的情况,我觉得回答您的问题只会是一种猜测。 由于我不想错过代表席位,因此我将尝试描述我如何完成同样的情况。

首先,在应用程序和插件中包含供应商代码之间存在细微差异。 我的经验是基于创建余烬插件。 但是,由于应用允许回购插件,因此可以在实际应用中轻松复制插件的过程。 我还建议,单独进行此操作(作为单独的插件或内部回购插件)比作为应用程序本身的一部分要有益得多。

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

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

使用Ember插件,我必须完成以下任务:

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

此时,如果要控制出口,我可以增加第四步。 意思是说,以上所述我将具有

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

但是,在某些情况下,可能需要这样做:

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

在这种情况下,您将必须添加一个addon/index.js文件来导出所需的文件。 from 'name-of-ember-addon'本质上来说, from 'name-of-ember-addon'出现在插件的addon/index.js文件中,而from 'name-of-npm-module'使用上述步骤2中的填充程序。

构造垫片

我基本上是从这篇博客文章中获取格式 填充程序的编写方式类似于将其后编译以供在浏览器中使用。 不会通过任何方式进行转译。 它负责使用AMD define功能,并将引用返回到随附的NPM模块。 在我的确认被编译为在内置ember应用程序的上下文中运行时使用的UMD模块的情况下,会将其自身添加到全局名称空间( 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);
})();

如果您的源模块不是通过babel编译的,则必须手动翻译。 所有ES6导入都会转换为具有其属性的对象,其中一个属性( 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);
})();

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

n插件具有一个根index.js文件,该文件告诉西兰花管道如何包装物品。 由于NPM模块是第三方,与Ember.JS,jQuery,moment等的方式相同,因此它们应与上面的垫片一起位于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": "*"
}

然后在我们的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]);
  }
};

所有这些还可以通过回购插件来实现。 只要记住您的构造代码即可告诉ember如何编译输出而不是如何执行JS。 所有这些仪式都是为了建立一个结构良好的vendor.js以供在浏览器中使用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM