1 |
|
2 | 'use strict';
|
3 |
|
4 | const chalk = require('chalk');
|
5 | const fs = require('fs');
|
6 | const inquirer = require('inquirer');
|
7 | const glob = require('glob');
|
8 | const mkdirp = require('mkdirp');
|
9 | const p = require('util').promisify;
|
10 | const path = require('path');
|
11 | const strip = require('../utils').strip;
|
12 |
|
13 | const ComponentFile = strip`
|
14 | import Component from '@ember/component';
|
15 |
|
16 | export default Component.extend({
|
17 | });
|
18 | `;
|
19 |
|
20 |
|
21 | const INDENT_START = '';
|
22 |
|
23 | module.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 |
|
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);
|
88 | });
|
89 |
|
90 |
|
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);
|
114 | });
|
115 |
|
116 |
|
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);
|
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 | };
|