UNPKG

6.07 kBJavaScriptView Raw
1/* eslint-disable no-console */
2'use strict';
3
4const chalk = require('chalk');
5const co = require('co');
6const fs = require('fs');
7const inquirer = require('inquirer');
8const glob = require('glob');
9const mkdirp = require('mkdirp');
10const p = require('util.promisify');
11const path = require('path');
12const strip = require('../utils').strip;
13
14const ComponentFile = strip`
15 import Component from '@ember/component';
16
17 export default Component.extend({
18 });
19`;
20
21/* This forces strip`` to start counting the indentaiton */
22const INDENT_START = '';
23
24module.exports = {
25 description: 'Use Glimmer Components semantics for template-only components (component templates with no corresponding .js file).',
26 url: 'https://github.com/emberjs/rfcs/pull/278',
27 default: false,
28 since: '3.1.0',
29 callback: co.wrap(function *(project, value) {
30 if (value !== true) {
31 return;
32 }
33
34 let root = project.root;
35 let projectConfig = project.config();
36
37 let { modulePrefix, podModulePrefix } = projectConfig;
38 let podsFolder;
39 if (podModulePrefix) {
40 if (!modulePrefix || !podModulePrefix.startsWith(`${modulePrefix}/`)) {
41 console.log(chalk.yellow(`${chalk.bold('Note:')} There is an automated refactor script available for this feature, but your \`podModulePrefix\` could not be processed correctly.\n`));
42 return;
43 }
44
45 podsFolder = podModulePrefix.slice(modulePrefix.length + 1);
46 if (!podsFolder) {
47 console.log(chalk.yellow(`${chalk.bold('Note:')} There is an automated refactor script available for this feature, but your \`podModulePrefix\` could not be processed correctly.\n`));
48 return;
49 }
50 }
51
52 let templates = [];
53 let components = [];
54
55 // Handle "Classic" layout
56 let templatesRoot = path.join(root, 'app/templates/components');
57 let templateCandidates = yield p(glob)('**/*.hbs', { cwd: templatesRoot });
58
59 templateCandidates.forEach(template => {
60 let templatePath = path.join('app/templates/components', template);
61
62 let jsPath = path.join('app/components', template.replace(/\.hbs$/, '.js'));
63 if (fs.existsSync(path.join(root, jsPath))) return;
64
65 let tsPath = path.join('app/components', template.replace(/\.hbs$/, '.ts'));
66 if (fs.existsSync(path.join(root, tsPath))) return;
67
68 templates.push(templatePath);
69 components.push(jsPath); // Always offer to create JS
70 });
71
72 // Handle "Pods" layout without prefix
73
74 let componentsRoot = path.join(root, 'app/components');
75 templateCandidates = yield p(glob)('**/template.hbs', { cwd: componentsRoot });
76
77 templateCandidates.forEach(template => {
78 let templatePath = path.join('app/components', template);
79
80 let jsPath = path.join('app/components', template.replace(/template\.hbs$/, 'component.js'));
81 if (fs.existsSync(path.join(root, jsPath))) return;
82
83 let tsPath = path.join('app/components', template.replace(/template\.hbs$/, 'component.ts'));
84 if (fs.existsSync(path.join(root, tsPath))) return;
85
86 templates.push(templatePath);
87 components.push(jsPath); // Always offer to create JS
88 });
89
90 // Handle "Pods" layout *with* prefix
91
92 componentsRoot = path.join(root, `app/${podsFolder}/components`);
93 templateCandidates = yield p(glob)('**/template.hbs', { cwd: componentsRoot });
94
95 templateCandidates.forEach(template => {
96 let templatePath = path.join(`app/${podsFolder}/components`, template);
97
98 let jsPath = path.join(`app/${podsFolder}/components`, template.replace(/template\.hbs$/, 'component.js'));
99 if (fs.existsSync(path.join(root, jsPath))) return;
100
101 let tsPath = path.join(`app/${podsFolder}/components`, template.replace(/template\.hbs$/, 'component.ts'));
102 if (fs.existsSync(path.join(root, tsPath))) return;
103
104 templates.push(templatePath);
105 components.push(jsPath); // Always offer to create JS
106 });
107
108 if (templates.length === 0) {
109 return;
110 }
111
112 console.log(strip`
113 Enabling ${chalk.bold('template-only-glimmer-components')}...
114
115 This will change the semantics for template-only components (components without a \`.js\` file).
116
117 Some notable differences include...
118
119 - They will not have a component instance, so statements like \`{{this}}\`, \`{{this.foo}}\` and \`{{foo}}\` will be \`null\` or \`undefined\`.
120
121 - They will not have a wrapper element: what you have in the template will be what is rendered on the screen.
122
123 - Passing classes in the invocation (i.e. \`{{my-component class="..."}}\`) will not work, since there is no wrapper element to apply the classes to.
124
125 For more information, see ${chalk.underline('https://github.com/emberjs/rfcs/pull/278')}.
126
127 While these changes may be desirable for ${chalk.italic('new components')}, they may unexpectedly break the styling or runtime behavior of your ${chalk.italic('existing components')}.
128
129 To be conservative, it is recommended that you add a \`.js\` file for existing template-only components. (You can always delete them later if you aren't relying on the differences.)
130
131 The following components are affected:`);
132
133 for (let i=0; i<templates.length; i++) {
134 console.log(strip`
135 ${INDENT_START}
136 - ${chalk.underline(templates[i])}
137 ${chalk.gray(`(Recommendation: add ${chalk.cyan.underline(components[i])})`)}
138 `);
139 }
140
141 let response = yield inquirer.prompt({
142 type: 'confirm',
143 name: 'shouldGenerate',
144 message: 'Would you like me to generate these component files for you?',
145 default: true
146 });
147
148 console.log();
149
150 if (response.shouldGenerate) {
151 for (let i=0; i<components.length; i++) {
152 let componentPath = components[i];
153 console.log(` ${chalk.green('create')} ${componentPath}`);
154 let absolutePath = path.join(project.root, componentPath);
155 yield p(mkdirp)(path.dirname(absolutePath));
156 yield p(fs.writeFile)(absolutePath, ComponentFile, { encoding: 'UTF-8' });
157 }
158
159 console.log();
160 }
161 })
162};