forked from ember-cli/ember-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Micro addon support #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
f6b09ca
Add micro-addon model
begedin e9fc665
Add file existance checks
begedin b359044
Got micro-addons to work properly
begedin 0ba2c50
Merge branch 'micro-addon-support' of https://github.com/begedin/embe…
begedin 3df2b91
Added proper code commenting
begedin 0b417bc
Cleaned up uneccessary file existance checks. broccolli-funnel handle…
begedin e578fe3
Added unit tests for micro-addon
begedin c9bb8c3
Reworked treeForAddon so libraries are importable from the addon name…
begedin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| 'use strict'; | ||
|
|
||
| /** | ||
| @module ember-cli | ||
| */ | ||
|
|
||
| var path = require('path'); | ||
| var existsSync = require('exists-sync'); | ||
| var Funnel = require('broccoli-funnel'); | ||
| var assign = require('lodash/object/assign'); | ||
| var SilentError = require('silent-error'); | ||
| var mergeTrees = require('broccoli-merge-trees'); | ||
|
|
||
|
|
||
| var Addon = require('../models/addon'); | ||
|
|
||
| /** | ||
| Root class for a Micro Addon. If your Addon module exports an Object, this | ||
| will be extended with that Object. If the addon module exports a constructor, | ||
| it will not be extending this vclass. | ||
|
|
||
| MicroAddon extends the base Addon class. The custom behavior of a Micro Addon | ||
| is implemented by defining some common hooks the Addon class exposes. | ||
|
|
||
| - {{#crosslink "MicroAddon/_buildTree:method"}}_buildTree{{/crosslink}} | ||
| - {{#crosslink "MicroAddon/_mapFile:method"}}_mapFile{{/crosslink}} | ||
| - {{#crosslink "MicroAddon/treeForApp:method"}}treeForApp{{/crosslink}} | ||
| - {{#crosslink "MicroAddon/treeForAddon:method"}}treeForAddon{{/crosslink}} | ||
| - {{#crosslink "MicroAddon/treeForTemplate:method"}}treeForTemplate{{/crosslink}} | ||
|
|
||
| @class MicroAddon | ||
| @extends Addon | ||
| @param {(Project|Addon)} parent The project or addon that directly depends on this addon | ||
| @param {Project} project The current project (deprecated) | ||
| */ | ||
| var MicroAddon = Addon.extend({ | ||
|
|
||
| /** | ||
| Builds a tree out of an explicit array of files | ||
|
|
||
| @private | ||
| @method _buildTree | ||
| @param {Array} includedFiles Array of filenames to build a tree from. All files are in addon root | ||
| @return {tree} Newly built tree | ||
| */ | ||
| _buildTree: function(includedFiles) { | ||
| var addon = this; | ||
|
|
||
| return new Funnel(addon.root, { | ||
| include: includedFiles, | ||
| getDestinationPath: function(fileName) { | ||
| return addon._mapFile(fileName); | ||
| } | ||
| }); | ||
| }, | ||
|
|
||
| /** | ||
| Maps a source file (placed in addon root) to a destination file | ||
|
|
||
| Component mappings: | ||
| - component.js -> components/addon-name.js | ||
| - template.hbs -> templates/components/addon-name.hbs | ||
| - style.css -> addon/styles/addon-name.css | ||
|
|
||
| Helper mappings: | ||
| - helper.js -> helpers/addon-name.js | ||
|
|
||
| Library mappings: | ||
| - library.js -> lib/addon-name.js | ||
|
|
||
| @private | ||
| @method _mapFile | ||
| @param {String} fileName Based file name | ||
| @return {String} Mapped file path | ||
| */ | ||
| _mapFile: function(fileName) { | ||
| if (fileName === 'component.js') { | ||
| return path.join('components', this.name + '.js'); | ||
| } else if (fileName === 'template.hbs') { | ||
| return path.join('components', this.name + '.hbs'); | ||
| } else if (fileName === 'style.css') { | ||
| return path.join('addon', 'styles', this.name + '.css'); | ||
| } else if (fileName === 'helper.js') { | ||
| return path.join('helpers', this.name + '.js'); | ||
| } else if (fileName === 'library.js') { | ||
| return path.join('lib', this.name + '.js'); | ||
| } | ||
| }, | ||
|
|
||
| /** | ||
| Maps app-related files from their location in a ember-micro-addon structure | ||
| to their proper place in an ember-addon structure. | ||
|
|
||
| Used by micro-components, micro-helpers and micro-libraries | ||
|
|
||
| @public | ||
| @method treeForApp | ||
| @return {tree} A tree with properly mapped files. | ||
| */ | ||
| treeForApp: function() { | ||
| var includedFiles = ['component.js', 'helper.js']; | ||
|
|
||
| return this._buildTree(includedFiles); | ||
| }, | ||
|
|
||
| /** | ||
| Maps app-related files from their location in a ember-micro-addon structure | ||
| to their proper place in an ember-addon structure. | ||
|
|
||
| Used by micro-components | ||
|
|
||
| treeForTemplates maps to the templates subfolder automatically, so only the | ||
| components subfolder is necessary in the mapped path. | ||
|
|
||
| @public | ||
| @method treeForTemplates | ||
| @return {tree} A tree with properly mapped files. | ||
| */ | ||
| treeForTemplates: function() { | ||
| var includedFiles = ['template.hbs']; | ||
|
|
||
| return this._buildTree(includedFiles); | ||
| }, | ||
|
|
||
| /** | ||
| Maps style.css to addon/styles/[addon-name].css. At that point, treeForAddon | ||
| followed by the regular build process take over and style.css eventually | ||
| ends up being merged into the app's vendor.css. | ||
|
|
||
| @private | ||
| @method treeForAddonStyles | ||
| @return {tree} A tree with properly mapped files. | ||
| */ | ||
| _treeForAddonStyles: function() { | ||
| var includedFiles = ['style.css']; | ||
|
|
||
| return this._buildTree(includedFiles); | ||
| }, | ||
|
|
||
| /** | ||
| Maps library.js to lib/library.js and outputs it to the addon folder. At | ||
| that point, treeForAddon and the regular build process take over and | ||
| library.js eventually becomes importable from | ||
| '[addon-name]/lib/[addon-name]' | ||
|
|
||
| @private | ||
| @method treeForAddonJs | ||
| @return {tree} A tree with properly mapped files. | ||
| */ | ||
| _treeForAddonJs: function() { | ||
| var includedFiles = ['library.js']; | ||
|
|
||
| return this._buildTree(includedFiles); | ||
| }, | ||
|
|
||
| /** | ||
| Generates an addon tree and an addon style tree using treeForAddonJs and | ||
| treeForAddonStyles, then merges them and returns the result. | ||
|
|
||
| @public | ||
| @method treeForAddon | ||
| @return {tree} Merged and compiled output of _treeForAddonJs and | ||
| _treeForAddonStyles | ||
| */ | ||
| treeForAddon: function() { | ||
| var addonTree = this._treeForAddonJs(); | ||
| var compiledAddonTree = this.compileAddon(addonTree); | ||
| var addonStylesTree = this._treeForAddonStyles(); | ||
| var compiledStylesTree = this.compileStyles(addonStylesTree); | ||
|
|
||
| return mergeTrees([compiledAddonTree, compiledStylesTree]); | ||
| } | ||
| }); | ||
|
|
||
| /** | ||
| Returns the micro-addon class for a given addon name. | ||
| If the MicroAddon exports a function, that function is used | ||
| as constructor. If an Object is exported, a subclass of | ||
| `MicroAddon` is returned with the exported hash merged into it. | ||
|
|
||
| @private | ||
| @static | ||
| @method lookup | ||
| @param {String} addon MicroAddon name | ||
| @return {MicroAddon} MicroAddon class | ||
| */ | ||
| MicroAddon.lookup = function(addon) { | ||
| var Constructor, addonModule, modulePath, moduleDir; | ||
|
|
||
| modulePath = Addon.resolvePath(addon); | ||
| moduleDir = path.dirname(modulePath); | ||
|
|
||
| if (existsSync(modulePath)) { | ||
| addonModule = require(modulePath); | ||
|
|
||
| if (typeof addonModule === 'function') { | ||
| Constructor = addonModule; | ||
| Constructor.prototype.root = Constructor.prototype.root || moduleDir; | ||
| Constructor.prototype.pkg = Constructor.prototype.pkg || addon.pkg; | ||
| } else { | ||
| Constructor = MicroAddon.extend(assign({ | ||
| root: moduleDir, | ||
| pkg: addon.pkg | ||
| }, addonModule)); | ||
| } | ||
| } | ||
|
|
||
| if (!Constructor) { | ||
| throw new SilentError('The `' + addon.pkg.name + '` addon could not be found at `' + addon.path + '`.'); | ||
| } | ||
|
|
||
| return Constructor; | ||
| }; | ||
|
|
||
| module.exports = MicroAddon; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| 'use strict'; | ||
|
|
||
| var path = require('path'); | ||
| var Project = require('../../../lib/models/project'); | ||
| var MicroAddon = require('../../../lib/models/micro-addon'); | ||
| var expect = require('chai').expect; | ||
| var path = require('path'); | ||
|
|
||
| var fixturePath = path.resolve(__dirname, '../../fixtures/addon'); | ||
|
|
||
| describe('models/addon.js', function() { | ||
| var project, projectPath; | ||
|
|
||
| describe('treePaths and treeForMethods', function() { | ||
| var ExampleMicroAddon; | ||
|
|
||
| beforeEach(function() { | ||
| projectPath = path.resolve(fixturePath, 'simple'); | ||
| var packageContents = require(path.join(projectPath, 'package.json')); | ||
|
|
||
| project = new Project(projectPath, packageContents); | ||
|
|
||
| ExampleMicroAddon = MicroAddon.extend({ | ||
| name: 'example', | ||
| root: projectPath, | ||
| }); | ||
| }); | ||
|
|
||
| describe('treeForApp', function() { | ||
| it('exists even when not explicitly set', function() { | ||
| var first = new ExampleMicroAddon(project); | ||
|
|
||
| expect(first.treeForApp).to.be.a('Function'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('treeForAddon', function() { | ||
| it('exists even when not explicitly set', function() { | ||
| var first = new ExampleMicroAddon(project); | ||
|
|
||
| expect(first.treeForAddon).to.be.a('Function'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('treeForTemplates', function() { | ||
| it('exists even when not explicitly set', function() { | ||
| var first = new ExampleMicroAddon(project); | ||
|
|
||
| expect(first.treeForTemplates).to.be.a('Function'); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe('_buildTree', function() { | ||
| var ExampleMicroAddon; | ||
|
|
||
| beforeEach(function() { | ||
| projectPath = path.resolve(fixturePath, 'simple'); | ||
| var packageContents = require(path.join(projectPath, 'package.json')); | ||
|
|
||
| project = new Project(projectPath, packageContents); | ||
|
|
||
| ExampleMicroAddon = MicroAddon.extend({ | ||
| name: 'example', | ||
| root: projectPath, | ||
| }); | ||
| }); | ||
|
|
||
| it('should return a tree', function() { | ||
| var addon = new ExampleMicroAddon(); | ||
|
|
||
| var tree = addon._buildTree(['component.js']); | ||
|
|
||
| expect(tree).to.contain.all.keys('inputTree', 'include', 'destDir', 'getDestinationPath'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('_mapFile', function() { | ||
| var ExampleMicroAddon, addon; | ||
|
|
||
| beforeEach(function() { | ||
| projectPath = path.resolve(fixturePath, 'simple'); | ||
| var packageContents = require(path.join(projectPath, 'package.json')); | ||
|
|
||
| project = new Project(projectPath, packageContents); | ||
|
|
||
| ExampleMicroAddon = MicroAddon.extend({ | ||
| name: 'example', | ||
| root: projectPath, | ||
| }); | ||
|
|
||
| addon = new ExampleMicroAddon(); | ||
| }); | ||
|
|
||
| it('should perform the proper mapping', function() { | ||
| expect(addon._mapFile('component.js')).to.equal('components/example.js'); | ||
| expect(addon._mapFile('helper.js')).to.equal('helpers/example.js'); | ||
| expect(addon._mapFile('library.js')).to.equal('lib/example.js'); | ||
| expect(addon._mapFile('template.hbs')).to.equal('components/example.hbs'); | ||
| expect(addon._mapFile('style.css')).to.equal('addon/styles/example.css'); | ||
| }); | ||
|
|
||
| it('should return "undefined" for unsupported file', function() { | ||
| expect(addon._mapFile('random.ext')).to.equal(undefined); | ||
| }); | ||
| }); | ||
| }); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this should be encapsulated in the
lookupmethod itself.