尽管似乎有大量关于它的博客文章,我仍然在努力通过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
};
使用npm我可以将此构建目录安装到我的Ember应用程序中,并使用以下语法导入我需要的模块:
import webglRenderLibrary from 'npm:webglRenderLibrary';
const { Module5 } = webglRenderLibrary;
其中Module5是导出的库中的类,如下所示:
class Module5 {
//Magic rendering code
}
module.exports = Module5;
我没有必要安装任何其他插件或将库文件导入到ember供应商文件中,因为有很多博客文章说你必须这样才能让它工作。我真的不明白为什么这种方法有效,但确实如此。
¯\ _(ツ)_ /¯
我从来没有真正喜欢这个设置/工作流程(并且不知道它为什么会起作用)所以我正在努力改进它,但是我在Ember,JS模块等方面的许多知识差距使得它变得困难。
我想要做的第一件事是将库移到ES6模块(import - > export)。从我的理解ES6模块更精简和未来所以我宁愿将它们用于我的库。更改所有源代码有点劳动密集,但它工作得很好,并允许我为我的库开发创建一个很好的工作流程。
Module5现在看起来像这样:
export default class Module5 {
//Magic rendering code
}
在库的package.json
中,我有许多现在调用watchify的npm脚本,所以我可以单独在演示文件中测试我的模块。
{
"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的'build'脚本,将我的整个库混合到dist/js/app.js
中的一个缩小文件中,这是{{1}中main下的入口点。 }}
我当时认为这应该与目前在我的Ember应用程序中运行的代码完全相同。唯一的区别应该是它已经被浏览器放入一个文件中并用Uglify缩小(如果我错了,请纠正我,我认为我是这样)。我以为我不需要对我的Ember应用程序进行任何更改,但现在我得到了:
package.json
以我之前的方式导入它:
Uncaught TypeError: Module5 is not a constructor
所有这些都说明了几个问题:
import webglRenderLibrary from 'npm:webglRenderLibrary';
const { Module5 } = webglRenderLibrary;
)===========================================
修改
我找到了一个有效的解决方案,但我并不完全理解它,我不是它的忠实粉丝。
在库lodash
中,我添加了'standalone'参数,它已经神奇地工作了。
package.json
我不知道它在引擎盖下做了什么,但现在它的工作。希望很快能找到更好的解决方案。
答案 0 :(得分:8)
如果没有和你一起经历同样的情况,我觉得回答你的问题只会是猜测的一种练习。由于我不想错过代表,我将尝试描述我如何完成同样的情况。
首先,在应用中包含供应商代码与插件之间存在细微差别。我的经验是基于创建一个ember插件。但是,由于应用程序允许使用in-repo插件,因此可以轻松地在实际应用程序中复制插件的过程。我还提出这样做:孤立地做这个(作为一个单独的插件或一个in-repo插件)比作为应用程序本身的一部分更有益。
第一个障碍是确保您要使用的模块浏览器兼容,因为实际模块将在浏览器中使用。如果您要使用的NPM模块是特定于节点的,那么这将不起作用。其次,许多NPM模块将尝试使用某种形式的模块管理,无论是CommonJS,AMD,UMD还是全局名称间距。您必须了解它在浏览器中的交互方式。 Ember在浏览器中使用AMD,因此NPM模块使用的必须包装/转换为AMD(这被称为垫片)。
在我的情况下,ember-confirmed是我的NPM模块confirmed周围的余烬包装器(免责声明:我是这些模块的作者)。在已确认的NPM模块中,我使用babel将ES6源编译为UMD模块,当通过node_modules
引用时,该模块被打包到任何package.json
目录中。
使用Ember插件我必须完成以下任务:
package.json
依赖关系(不是devDependencies
)部分。这将告诉应用程序将哪个版本的模块放在其自己的node_modules
目录中。from
语句的import
部分。此时,如果我想控制出口,我可以添加第四步。意思是通过以上我将赋予
的能力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'
查看插件'addon/index.js
文件,而from 'name-of-npm-module'
使用上面第2步中的垫片。
我基本上采用了this blog post的格式。该垫片被编写为好像是经过后编译以便在浏览器中使用。通过任何方式不进行了描述。它负责使用AMD define
函数并返回对包含的NPM模块的引用。在UMD模块的情况下,我的confirmed被编译为在构建的ember app的上下文中运行时将自己添加到全局命名空间(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 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": "*"
}
然后在我们的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]);
}
};
所有这一切也可以在in-repo addon中完成。只记得你构造的代码告诉ember如何编译输出而不是如何执行JS。所有这一仪式都是为了在浏览器中设置一个格式良好的vendor.js
。