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