UNPKG

6.62 kBJavaScriptView Raw
1/* eslint-disable no-console */
2'use strict';
3
4const chalk = require('chalk');
5const fs = require('fs');
6const inquirer = require('inquirer');
7const glob = require('glob');
8const mkdirp = require('mkdirp');
9const p = require('util').promisify;
10const path = require('path');
11const strip = require('../utils').strip;
12
13const ComponentFile = strip`
14 import Component from '@ember/component';
15
16 export default Component.extend({
17 });
18`;
19
20/* This forces strip`` to start counting the indentaton */
21const INDENT_START = '';
22
23module.exports = {
24 description:
25 '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: async function (project, value, shouldRunCodemod) {
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(
42 chalk.yellow(
43 `${chalk.bold(
44 'Note:'
45 )} There is an automated refactor script available for this feature, but your \`podModulePrefix\` could not be processed correctly.\n`
46 )
47 );
48 return;
49 }
50
51 podsFolder = podModulePrefix.slice(modulePrefix.length + 1);
52 if (!podsFolder) {
53 console.log(
54 chalk.yellow(
55 `${chalk.bold(
56 'Note:'
57 )} There is an automated refactor script available for this feature, but your \`podModulePrefix\` could not be processed correctly.\n`
58 )
59 );
60 return;
61 }
62 }
63
64 let templates = [];
65 let components = [];
66
67 // Handle "Classic" layout
68 let templatesRoot = path.join(root, 'app/templates/components');
69 let templateCandidates = await p(glob)('**/*.hbs', { cwd: templatesRoot });
70
71 templateCandidates.forEach((template) => {
72 let templatePath = path.join('app/templates/components', template);
73
74 let jsPath = path.join(
75 'app/components',
76 template.replace(/\.hbs$/, '.js')
77 );
78 if (fs.existsSync(path.join(root, jsPath))) return;
79
80 let tsPath = path.join(
81 'app/components',
82 template.replace(/\.hbs$/, '.ts')
83 );
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 without prefix
91
92 let componentsRoot = path.join(root, 'app/components');
93 templateCandidates = await p(glob)('**/template.hbs', {
94 cwd: componentsRoot,
95 });
96
97 templateCandidates.forEach((template) => {
98 let templatePath = path.join('app/components', template);
99
100 let jsPath = path.join(
101 'app/components',
102 template.replace(/template\.hbs$/, 'component.js')
103 );
104 if (fs.existsSync(path.join(root, jsPath))) return;
105
106 let tsPath = path.join(
107 'app/components',
108 template.replace(/template\.hbs$/, 'component.ts')
109 );
110 if (fs.existsSync(path.join(root, tsPath))) return;
111
112 templates.push(templatePath);
113 components.push(jsPath); // Always offer to create JS
114 });
115
116 // Handle "Pods" layout *with* prefix
117
118 componentsRoot = path.join(root, `app/${podsFolder}/components`);
119 templateCandidates = await p(glob)('**/template.hbs', {
120 cwd: componentsRoot,
121 });
122
123 templateCandidates.forEach((template) => {
124 let templatePath = path.join(`app/${podsFolder}/components`, template);
125
126 let jsPath = path.join(
127 `app/${podsFolder}/components`,
128 template.replace(/template\.hbs$/, 'component.js')
129 );
130 if (fs.existsSync(path.join(root, jsPath))) return;
131
132 let tsPath = path.join(
133 `app/${podsFolder}/components`,
134 template.replace(/template\.hbs$/, 'component.ts')
135 );
136 if (fs.existsSync(path.join(root, tsPath))) return;
137
138 templates.push(templatePath);
139 components.push(jsPath); // Always offer to create JS
140 });
141
142 if (templates.length === 0) {
143 return;
144 }
145
146 if (shouldRunCodemod === undefined) {
147 console.log(strip`
148 Enabling ${chalk.bold('template-only-glimmer-components')}...
149
150 This will change the semantics for template-only components (components without a \`.js\` file).
151
152 Some notable differences include...
153
154 - They will not have a component instance, so statements like \`{{this}}\`, \`{{this.foo}}\` and \`{{foo}}\` will be \`null\` or \`undefined\`.
155
156 - They will not have a wrapper element: what you have in the template will be what is rendered on the screen.
157
158 - Passing classes in the invocation (i.e. \`{{my-component class="..."}}\`) will not work, since there is no wrapper element to apply the classes to.
159
160 For more information, see ${chalk.underline(
161 'https://github.com/emberjs/rfcs/pull/278'
162 )}.
163
164 While these changes may be desirable for ${chalk.italic(
165 'new components'
166 )}, they may unexpectedly break the styling or runtime behavior of your ${chalk.italic(
167 'existing components'
168 )}.
169
170 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.)
171
172 The following components are affected:`);
173
174 for (let i = 0; i < templates.length; i++) {
175 console.log(strip`
176 ${INDENT_START}
177 - ${chalk.underline(templates[i])}
178 ${chalk.gray(
179 `(Recommendation: add ${chalk.cyan.underline(components[i])})`
180 )}
181 `);
182 }
183
184 let response = await inquirer.prompt({
185 type: 'confirm',
186 name: 'shouldGenerate',
187 message: 'Would you like me to generate these component files for you?',
188 default: true,
189 });
190
191 shouldRunCodemod = response.shouldGenerate;
192
193 console.log();
194 }
195
196 if (shouldRunCodemod) {
197 for (let i = 0; i < components.length; i++) {
198 let componentPath = components[i];
199 console.log(` ${chalk.green('create')} ${componentPath}`);
200 let absolutePath = path.join(project.root, componentPath);
201 await mkdirp(path.dirname(absolutePath));
202 await p(fs.writeFile)(absolutePath, ComponentFile, {
203 encoding: 'UTF-8',
204 });
205 }
206
207 console.log();
208 }
209 },
210};