Skip to content
This repository has been archived by the owner on Dec 31, 2022. It is now read-only.

feat: Update Component Generator for Colocation #161

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions blueprints/component/files/__root__/__path__/__name__.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import Component from '@ember/component';
<%= importComponent %>
<%= importTemplate %>
export default class <%= classifiedModuleName %> extends Component.extend({
// anything which *must* be merged to prototype here
}) {<%= contents %>
// normal class body definition here
};
interface <%= classifiedModuleName %>ComponentArgs {}

export default class <%= classifiedModuleName %>Component extends Component<<%= classifiedModuleName %>ComponentArgs> {}
259 changes: 225 additions & 34 deletions blueprints/component/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
'use strict';

const chalk = require('chalk');
const path = require('path');
const SilentError = require('silent-error');
const stringUtil = require('ember-cli-string-utils');
const pathUtil = require('ember-cli-path-utils');
const getPathOption = require('ember-cli-get-component-path-option');
const normalizeEntityName = require('ember-cli-normalize-entity-name');
const EOL = require('os').EOL;
const {EOL} = require('os');
const {has} = require('@ember/edition-utils');

const OCTANE = has('octane');

// TODO: this should be reading from the @ember/canary-features module
// need to refactor broccoli/features.js to be able to work more similarly
// to https://github.com/emberjs/data/pull/6231
const EMBER_GLIMMER_SET_COMPONENT_TEMPLATE = true;

// intentionally avoiding use-edition-detector
module.exports = {
description: 'Generates a component.',

Expand All @@ -15,53 +26,209 @@ module.exports = {
name: 'path',
type: String,
default: 'components',
aliases: [{ 'no-path': '' }],
aliases: [{'no-path': ''}]
},
{
name: 'component-class',
type: ['@ember/component', '@glimmer/component', '@ember/component/template-only', ''],
default: OCTANE ? '--no-component-class' : '@ember/component',
aliases: [
{cc: '@ember/component'},
{gc: '@glimmer/component'},
{tc: '@ember/component/template-only'},
{nc: ''},
{'no-component-class': ''},
{'with-component-class': OCTANE ? '@glimmer/component' : '@ember/component'}
]
},
{
name: 'component-structure',
type: OCTANE ? ['flat', 'nested', 'classic'] : ['classic'],
default: OCTANE ? 'flat' : 'classic',
aliases: OCTANE ? [{fs: 'flat'}, {ns: 'nested'}, {cs: 'classic'}] : [{cs: 'classic'}]
}
],

filesPath: function() {
let filesDirectory = 'files';
let dependencies = this.project.dependencies();
init() {
this._super && this._super.init.apply(this, arguments);
let isOctane = has('octane');

this.availableOptions.forEach(option => {
if (option.name === 'component-class') {
if (isOctane) {
option.default = '--no-component-class';
} else {
option.default = '@ember/component';
}
} else if (option.name === 'component-structure') {
if (isOctane) {
option.type = ['flat', 'nested', 'classic'];
option.default = 'flat';
option.aliases = [{fs: 'flat'}, {ns: 'nested'}, {cs: 'classic'}];
} else {
option.type = ['classic'];
option.default = 'classic';
option.aliases = [{cs: 'classic'}];
}
}
});

this.skippedJsFiles = new Set();
this.savedLocals = {};

this.EMBER_GLIMMER_SET_COMPONENT_TEMPLATE = EMBER_GLIMMER_SET_COMPONENT_TEMPLATE || isOctane;
},

if ('@glimmer/component' in dependencies) {
filesDirectory = 'glimmer-files';
install(options) {
// Normalize the `componentClass` option. This is usually handled for us,
// but we wanted to show '--no-component-class' as the default so that is
// what's passed to us literally if the user didn't override it.
if (options.componentClass === '--no-component-class') {
options.componentClass = '';
}

return path.join(this.path, filesDirectory);
if (!this.EMBER_GLIMMER_SET_COMPONENT_TEMPLATE) {
if (options.componentClass !== '@ember/component') {
throw new SilentError(
'Usage of --component-class argument to `ember generate component` is only available on canary'
);
}

if (options.componentStructure !== 'classic') {
throw new SilentError(
'Usage of --component-structure argument to `ember generate component` is only available on canary'
);
}
}

return this._super.install.apply(this, arguments);
},

fileMapTokens: function() {
return {
__path__: function(options) {
if (options.pod) {
uninstall(options) {
// Force the `componentClass` option to be non-empty. It doesn't really
// matter what it is set to. All we want is to delete the optional JS
// file if the user had created one (when using this generator, created
// manually, added later with component-class generator...).
options.componentClass = '@ember/component';

return this._super.uninstall.apply(this, arguments);
},

beforeInstall(options, locals) {
this.savedLocals = locals;
},

afterInstall(options) {
this._super.afterInstall.apply(this, arguments);

this.skippedJsFiles.forEach(file => {
let mapped = this.mapFile(file, this.savedLocals);
this.ui.writeLine(` ${chalk.yellow('skip')} ${mapped}`);
});

if (this.skippedJsFiles.size > 0) {
let command = `ember generate component-class ${options.entity.name}`;
this.ui.writeLine(` ${chalk.cyan('tip')} to add a class, run \`${command}\``);
}
},

fileMapTokens(options) {
let commandOptions = this.options;

if (commandOptions.pod) {
return {
__path__() {
return path.join(options.podPath, options.locals.path, options.dasherizedModuleName);
} else {
},
__templatepath__() {
return path.join(options.podPath, options.locals.path, options.dasherizedModuleName);
},
__templatename__() {
return 'template';
}
};
} else if (
!this.EMBER_GLIMMER_SET_COMPONENT_TEMPLATE ||
commandOptions.componentStructure === 'classic'
) {
return {
__path__() {
return 'components';
},
__templatepath__() {
return 'templates/components';
},
__templatename__() {
return options.dasherizedModuleName;
}
},
__templatepath__: function(options) {
if (options.pod) {
return path.join(options.podPath, options.locals.path, options.dasherizedModuleName);
};
} else if (
this.EMBER_GLIMMER_SET_COMPONENT_TEMPLATE &&
commandOptions.componentStructure === 'flat'
) {
return {
__path__() {
return 'components';
},
__templatepath__() {
return 'components';
},
__templatename__() {
return options.dasherizedModuleName;
}
return 'templates/components';
},
__templatename__: function(options) {
if (options.pod) {
return 'template';
};
} else if (
this.EMBER_GLIMMER_SET_COMPONENT_TEMPLATE &&
commandOptions.componentStructure === 'nested'
) {
return {
__path__() {
return `components/${options.dasherizedModuleName}`;
},
__name__() {
return 'index';
},
__templatepath__() {
return `components/${options.dasherizedModuleName}`;
},
__templatename__() {
return `index`;
}
return options.dasherizedModuleName;
},
};
};
}
},

normalizeEntityName: function(entityName) {
return normalizeEntityName(entityName);
files() {
let files = this._super.files.apply(this, arguments);

if (this.EMBER_GLIMMER_SET_COMPONENT_TEMPLATE && this.options.componentClass === '') {
files = files.filter(file => {
if (file.endsWith('.js')) {
this.skippedJsFiles.add(file);
return false;
} else {
return true;
}
});
}

return files;
},

normalizeEntityName(entityName) {
return normalizeEntityName(
entityName.replace(/\.js$/, '') //Prevent generation of ".js.js" files
);
},

locals: function(options) {
locals(options) {
let sanitizedModuleName = options.entity.name.replace(/\//g, '-');
let classifiedModuleName = stringUtil.classify(sanitizedModuleName);

let templatePath = '';
let importComponent = '';
let importTemplate = '';
let contents = '';
let defaultExport = '';

// if we're in an addon, build import statement
if (options.project.isEmberCLIAddon() || (options.inRepoAddon && !options.inDummy)) {
Expand All @@ -73,14 +240,38 @@ module.exports = {
'templates/components/' +
stringUtil.dasherize(options.entity.name);
}
importTemplate = '// @ts-ignore: Ignore import of compiled template' + EOL + 'import layout from \'' + templatePath + '\';' + EOL;
contents = EOL + ' layout = layout;';
}

let componentClass = this.EMBER_GLIMMER_SET_COMPONENT_TEMPLATE
? options.componentClass
: '@ember/component';

switch (componentClass) {
case '@ember/component':
importComponent = `import Component from '@ember/component';`;
if (templatePath) {
importTemplate = `import layout from '${templatePath}';${EOL}`;
defaultExport = `Component.extend({${EOL} layout${EOL}});`;
} else {
defaultExport = `Component.extend({${EOL}});`;
}
break;
case '@glimmer/component':
importComponent = `import Component from '@glimmer/component';`;
defaultExport = `class ${classifiedModuleName}Component extends Component {${EOL}}`;
break;
case '@ember/component/template-only':
importComponent = `import templateOnly from '@ember/component/template-only';`;
defaultExport = `templateOnly();`;
break;
}

return {
importTemplate: importTemplate,
contents: contents,
importTemplate,
importComponent,
defaultExport,
path: getPathOption(options),
componentClass: options.componentClass
};
},
}
};