Skip to content
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

Initial implementation of the module unification feature flag #182

Merged
merged 4 commits into from Mar 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -15,6 +15,7 @@ env:
- EMBER_TRY_SCENARIO=ember-release
- EMBER_TRY_SCENARIO=ember-beta
- EMBER_TRY_SCENARIO=ember-canary
- EMBER_TRY_SCENARIO=module-unification

matrix:
fast_finish: true
Expand Down
29 changes: 26 additions & 3 deletions README.md
Expand Up @@ -45,9 +45,32 @@ In the `ember-resolver` codebase, you can import these flags:
import { EMBER_RESOLVER_MODULE_UNIFICATION } from 'ember-resolver/features';
```

#### Current feature flags
### Current feature flags

* None at this time.
#### `EMBER_RESOLVER_MODULE_UNIFICATION`

Ember [RFC #154](https://github.com/emberjs/rfcs/blob/master/text/0143-module-unification.md)
describes an improved resolution strategy and filename-on-disk
layout for Ember applications. To experiment with this feature
it must be enabled as described above, then use the `src/`
directory on disk. You can generate a new app that uses
this layout by using the following commands:

```
# Install Ember-CLI canary globally:
npm install -g ember-cli/ember-cli
# Create a new app with the module unification blueprint
ember new my-app -b ember-module-unification-blueprint
```

This will create an app running a module unification layout from
the
[ember-module-unification-blueprint](https://github.com/emberjs/ember-module-unification-blueprint)
package. By default, this app will be correctly configured.

* It uses the `glimmer-wrapper` resolver.
* It builds an glimmer resolver config and passes it to the resolver.
* It starts with a `src/` based layout on disk.

## Upgrading

Expand All @@ -58,7 +81,7 @@ version use `yarn` or `npm`. For example:
yarn upgrade ember-resolver
```

#### Migrating from bower
### Migrating from bower

Before v1.0.1 `ember-resolver` was primarially consumed bia bower. To migrate
install the addon version via `yarn` or `npm`. If you're currently using
Expand Down
4 changes: 4 additions & 0 deletions config/ember-try.js
@@ -1,6 +1,10 @@
/*jshint node:true*/
module.exports = {
scenarios: [
{
name: 'module-unification',
command: 'EMBER_RESOLVER_MODULE_UNIFICATION=true ember test'
},
{
name: 'default',
bower: {
Expand Down
18 changes: 18 additions & 0 deletions ember-cli-build.js
@@ -1,9 +1,27 @@
'use strict';
/*jshint node:true*/
/* global require, module */
var EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
var MergeTrees = require('broccoli-merge-trees');
var Funnel = require('broccoli-funnel');

module.exports = function(defaults) {
let testTrees = [new Funnel('tests', {
exclude: [/^dummy/],
})];

let config = defaults.project.config();
let resolverConfig = config['ember-resolver'] || {};

if (resolverConfig.features.EMBER_RESOLVER_MODULE_UNIFICATION) {
testTrees.push('mu-trees/tests');
}

var app = new EmberAddon(defaults, {
trees: {
tests: new MergeTrees(testTrees)
},

// Add options here
vendorFiles: {
'ember-resolver.js': null
Expand Down
57 changes: 51 additions & 6 deletions index.js
Expand Up @@ -2,20 +2,26 @@
'use strict';

var VersionChecker = require('ember-cli-version-checker');
var path = require('path');

module.exports = {
name: 'ember-resolver',

init: function() {
this._super.init.apply(this, arguments);
this.options = this.options || {};

emberResolverFeatureFlags() {
var config = this.project.config();
var resolverConfig = config['ember-resolver'] || {};

var resolverFeatureFlags = Object.assign({
return Object.assign({
/* Add default feature flags here */
EMBER_RESOLVER_MODULE_UNIFICATION: false
}, resolverConfig.features);
},

init: function() {
this._super.init.apply(this, arguments);
this.options = this.options || {};

this._emberResolverFeatureFlags = this.emberResolverFeatureFlags();

this.options.babel = {
loose: true,
Expand All @@ -31,13 +37,52 @@ module.exports = {
features: {
name: 'ember-resolver',
source: 'ember-resolver/features',
flags: resolverFeatureFlags
flags: this._emberResolverFeatureFlags
}
}]
]
};
},

treeForAddon: function() {
var MergeTrees = require('broccoli-merge-trees');
let addonTrees = [].concat(
this._super.treeForAddon.apply(this, arguments),
this._emberResolverFeatureFlags.EMBER_RESOLVER_MODULE_UNIFICATION && this._moduleUnificationTrees()
).filter(Boolean);

return new MergeTrees(addonTrees);
},

_moduleUnificationTrees() {
var resolve = require('resolve');
var Funnel = require('broccoli-funnel');

let featureTreePath = path.join(this.root, 'mu-trees/addon');
var featureTree = new Funnel(featureTreePath, {
destDir: 'ember-resolver'
});

var glimmerResolverSrc = require.resolve('@glimmer/resolver/package');
var glimmerResolverPath = path.dirname(glimmerResolverSrc);
var glimmerResolverTree = new Funnel(glimmerResolverPath, {
srcDir: 'dist/modules/es2017',
destDir: '@glimmer/resolver'
});

var glimmerDISrc = resolve.sync('@glimmer/di', { basedir: glimmerResolverPath });
var glimmerDITree = new Funnel(path.join(glimmerDISrc, '../../../..'), {
srcDir: 'dist/modules/es2017',
destDir: '@glimmer/di'
});

return [
this.preprocessJs(featureTree, { registry: this.registry }),
this.preprocessJs(glimmerResolverTree, { registry: this.registry }),
this.preprocessJs(glimmerDITree, { registry: this.registry }),
];
},

included: function() {
this._super.included.apply(this, arguments);

Expand Down
88 changes: 88 additions & 0 deletions mu-trees/addon/ember-config.js
@@ -0,0 +1,88 @@
/*
* This config describes canonical Ember, as described in the
* module unification spec:
*
* https://github.com/dgeb/rfcs/blob/module-unification/text/0000-module-unification.md
*
*/
export default function generateConfig(name) {
return {
app: {
name,
rootName: name
},
types: {
adapter: { definitiveCollection: 'models' },
application: { definitiveCollection: 'main' },
controller: { definitiveCollection: 'routes' },
component: { definitiveCollection: 'components' },
'component-lookup': { definitiveCollection: 'main' },
event_dispatcher: { definitiveCollection: 'main' },
helper: { definitiveCollection: 'components' },
initializer: { definitiveCollection: 'initializers' },
'instance-initializers': { definitiveCollection: 'instance-initializer' },
location: { definitiveCollection: 'main' },
model: { definitiveCollection: 'models' },
partial: { definitiveCollection: 'partials' },
renderer: { definitiveCollection: 'main' },
route: { definitiveCollection: 'routes' },
router: { definitiveCollection: 'main' },
serializer: { definitiveCollection: 'models' },
service: { definitiveCollection: 'services' },
template: {
definitiveCollection: 'routes',
fallbackCollectionPrefixes: {
'components': 'components'
}
},
transform: { definitiveCollection: 'transforms' },
util: { definitiveCollection: 'utils' },
view: { definitiveCollection: 'views' },
'-view-registry': { definitiveCollection: 'main' },
'-bucket-cache': { definitiveCollection: 'main' }
},
collections: {
'main': {
types: ['router', '-bucket-cache', 'component-lookup', '-view-registry', 'event_dispatcher', 'application', 'location', 'renderer']
},
components: {
group: 'ui',
types: ['component', 'helper', 'template']
},
initializers: {
group: 'init',
types: ['initializer']
},
'instance-initializers': {
group: 'init',
types: ['instance-initializers']
},
models: {
group: 'data',
types: ['model', 'adapter', 'serializer']
},
partials: {
group: 'ui',
types: ['partial']
},
routes: {
group: 'ui',
privateCollections: ['components'],
types: ['route', 'controller', 'template']
},
services: {
types: ['service']
},
utils: {
unresolvable: true
},
views: {
types: ['view']
},
transforms: {
group: 'data',
types: ['transform']
}
}
};
}
72 changes: 72 additions & 0 deletions mu-trees/addon/module-registries/requirejs.js
@@ -0,0 +1,72 @@
/* global require, requirejs */
import {
deserializeSpecifier
} from '@glimmer/di';

export default class RequireJSRegistry {

constructor(config, modulePrefix) {
this._config = config;
this._modulePrefix = modulePrefix;
}

normalize(specifier) {
let s = deserializeSpecifier(specifier);

// This is hacky solution to get around the fact that Ember
// doesn't know it is requesting a partial. It requests something like
// 'template:/my-app/routes/-author'
// Would be better to request 'template:my-app/partials/author'
let isPartial = s.type === 'template' && s.name[0] === '-';
if (isPartial) {
s.name = s.name.slice(1);
s.collection = 'partials';
}

let collectionDefinition = this._config.collections[s.collection];
let group = collectionDefinition && collectionDefinition.group;
let segments = [ s.rootName, this._modulePrefix ];

if (group) {
segments.push(group);
}

// Special case to handle definiteCollection for templates
// eventually want to find a better way to address.
// Dgeb wants to find a better way to handle these
// in config without needing definiteCollections.
let ignoreCollection = s.type === 'template' &&
s.collection === 'routes' &&
s.namespace === 'components';

if (s.collection !== 'main' && !ignoreCollection) {
segments.push(s.collection);
}

if (s.namespace) {
segments.push(s.namespace);
}

if (s.name !== 'main') {
segments.push(s.name);
}

if (!isPartial) {
segments.push(s.type);
}

let path = segments.join('/');

return path;
}

has(specifier) {
let path = this.normalize(specifier);
return path in requirejs.entries;
}

get(specifier) {
let path = this.normalize(specifier);
return require(path).default;
}
}
34 changes: 34 additions & 0 deletions mu-trees/addon/resolvers/glimmer-wrapper/index.js
@@ -0,0 +1,34 @@
import Ember from 'ember';
import GlimmerResolver from '@glimmer/resolver/resolver';
import RequireJSRegistry from '../../module-registries/requirejs';

const { DefaultResolver } = Ember;

/*
* Wrap the @glimmer/resolver in Ember's resolver API. Although
* this code extends from the DefaultResolver, it should never
* call `_super` or call into that code.
*/
const Resolver = DefaultResolver.extend({
init() {
this._super(...arguments);

if (!this.glimmerModuleRegistry) {
this.glimmerModuleRegistry = new RequireJSRegistry(this.config, 'src');
}

this._glimmerResolver = new GlimmerResolver(this.config, this.glimmerModuleRegistry);
},

normalize: null,

resolve(lookupString) {
return this._resolve(lookupString);
},

_resolve(lookupString) {
return this._glimmerResolver.resolve(lookupString);
}
});

export default Resolver;