Skip to content

Commit 46cb584

Browse files
committed
Opt out moving component templates used as partials or in layoutName
1 parent 4f8ae6e commit 46cb584

File tree

8 files changed

+953
-23
lines changed

8 files changed

+953
-23
lines changed

lib/migrator.js

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
1-
var fse = require("fs-extra");
2-
var path = require('path');
3-
var glob = require("glob");
4-
5-
function moveFile(sourceFilePath, targetFilePath) {
6-
let targetFileDirectory = path.dirname(targetFilePath);
7-
if (!fse.existsSync(targetFileDirectory)) {
8-
console.info(`📁 Creating ${targetFileDirectory}`);
9-
fse.mkdirSync(targetFileDirectory, { recursive: true })
10-
}
11-
12-
console.info(`👍 Moving ${sourceFilePath} -> ${targetFilePath}`);
13-
fse.renameSync(sourceFilePath, targetFilePath);
14-
}
1+
const fse = require("fs-extra");
2+
const path = require('path');
3+
const glob = require("glob");
4+
const { getLayoutNameTemplates, getPartialTemplates } = require('./utils/templates')
5+
const { moveFile, removeDirs } = require('./utils/file')
156

167
module.exports = class Migrator {
178
constructor(options) {
@@ -20,15 +11,48 @@ module.exports = class Migrator {
2011

2112
async execute() {
2213
let sourceComponentTemplatesPath = path.join(this.options.projectRoot, 'app/templates/components');
23-
var sourceTemplateFilePaths = glob.sync(`${sourceComponentTemplatesPath}/**/*.hbs`);
14+
var sourceComponentTemplateFilePaths = glob.sync(`${sourceComponentTemplatesPath}/**/*.hbs`);
15+
16+
let sourceComponentPath = path.join(this.options.projectRoot, 'app/components');
17+
let sourceComponentFilePaths = glob.sync(`${sourceComponentPath}/**/*.js`);
18+
let templatesWithLayoutName = getLayoutNameTemplates(sourceComponentFilePaths);
19+
if (templatesWithLayoutName.length) {
20+
sourceComponentTemplateFilePaths = sourceComponentTemplateFilePaths.filter(sourceTemplateFilePath => {
21+
let sourceTemplatePathInApp = sourceTemplateFilePath.slice(this.options.projectRoot.length); // '/app/templates/components/nested1/nested-component.hbs'
22+
let templatePath = sourceTemplatePathInApp.slice('app/templates/'.length); // '/nested1/nested-component.hbs'
23+
return !templatesWithLayoutName.includes(templatePath.slice(1).replace('.hbs', ''));
24+
});
25+
}
2426

25-
sourceTemplateFilePaths.forEach(sourceTemplateFilePath => {
27+
let sourceTemplatesPath = path.join(this.options.projectRoot, 'app/templates');
28+
var sourceTemplateFilePaths = glob.sync(`${sourceTemplatesPath}/**/*.hbs`);
29+
let templatesInPartials = getPartialTemplates(sourceTemplateFilePaths);
30+
if (templatesInPartials.length) {
31+
sourceComponentTemplateFilePaths = sourceComponentTemplateFilePaths.filter(sourceTemplateFilePath => {
32+
let sourceTemplatePathInApp = sourceTemplateFilePath.slice(this.options.projectRoot.length); // '/app/templates/components/nested1/nested-component.hbs'
33+
if (/\/\-[\w\-]+\.hbs/.test(sourceTemplatePathInApp)) {
34+
sourceTemplatePathInApp = sourceTemplatePathInApp.replace('/-', '/');
35+
}
36+
let templatePath = sourceTemplatePathInApp.slice('app/templates/'.length); // '/nested1/nested-component.hbs'
37+
return !templatesInPartials.includes(templatePath.slice(1).replace('.hbs', ''));
38+
});
39+
}
40+
41+
sourceComponentTemplateFilePaths.forEach(sourceTemplateFilePath => {
2642
let sourceTemplatePathInApp = sourceTemplateFilePath.slice(this.options.projectRoot.length); // '/app/templates/components/nested1/nested-component.hbs'
2743
let templatePath = sourceTemplatePathInApp.slice('app/templates/components/'.length); // '/nested1/nested-component.hbs'
2844
let targetTemplateFilePath = path.join(this.options.projectRoot, 'app/components', templatePath); // '[APP_PATH]/app/components/nested1/nested-component.hbs'
2945
moveFile(sourceTemplateFilePath, targetTemplateFilePath);
3046
});
3147

32-
await fse.remove(sourceComponentTemplatesPath);
48+
templatesWithLayoutName.sort().forEach(template => {
49+
console.info(`❌ Did not move '${template}' due to usage as "layoutName" in a component`);
50+
});
51+
templatesInPartials.sort().forEach(template => {
52+
console.info(`❌ Did not move '${template}' due to usage as a "partial"`);
53+
});
54+
55+
let onlyRemoveEmptyDirs = Boolean(templatesWithLayoutName.length);
56+
await removeDirs(sourceComponentTemplatesPath, onlyRemoveEmptyDirs);
3357
}
3458
}

lib/utils/file.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const fse = require("fs-extra");
2+
const path = require('path');
3+
const removeDirectories = require('remove-empty-directories');
4+
5+
function moveFile(sourceFilePath, targetFilePath) {
6+
let targetFileDirectory = path.dirname(targetFilePath);
7+
if (!fse.existsSync(targetFileDirectory)) {
8+
console.info(`📁 Creating ${targetFileDirectory}`);
9+
fse.mkdirSync(targetFileDirectory, { recursive: true })
10+
}
11+
12+
console.info(`👍 Moving ${sourceFilePath} -> ${targetFilePath}`);
13+
fse.renameSync(sourceFilePath, targetFilePath);
14+
}
15+
16+
async function removeDirs(dirPath, removeOnlyEmptyDirectories = false) {
17+
if (removeOnlyEmptyDirectories) {
18+
removeDirectories(dirPath);
19+
} else {
20+
await fse.remove(dirPath)
21+
}
22+
}
23+
24+
module.exports = {
25+
moveFile,
26+
removeDirs
27+
}

lib/utils/js-parser.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
3+
const babylon = require('@babel/parser');
4+
5+
// These are the options that were the default of the Babel5 parse function
6+
// see https://github.com/babel/babel/blob/5.x/packages/babel/src/api/node.js#L81
7+
const options = {
8+
sourceType: 'module',
9+
allowHashBang: true,
10+
ecmaVersion: Infinity,
11+
allowImportExportEverywhere: true,
12+
allowReturnOutsideFunction: true,
13+
startLine: 1,
14+
tokens: true,
15+
plugins: [
16+
'asyncGenerators',
17+
'classProperties',
18+
'doExpressions',
19+
'exportExtensions',
20+
'functionBind',
21+
'functionSent',
22+
'objectRestSpread',
23+
'dynamicImport',
24+
'nullishCoalescingOperator',
25+
'optionalChaining',
26+
'decorators-legacy'
27+
],
28+
};
29+
30+
module.exports = {
31+
parse(code) {
32+
return babylon.parse(code, options);
33+
},
34+
};

lib/utils/templates.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const { readFileSync } = require('fs');
2+
const JSParser = require('./js-parser');
3+
const jsTraverse = require('@babel/traverse').default;
4+
const { parse, traverse } = require('ember-template-recast');
5+
6+
function getLayoutNameTemplates(files) {
7+
console.info(`Checking if any component templates are used as templates of other components using \`layoutName\``);
8+
let names = files.map(file => {
9+
let content = readFileSync(file, 'utf8');
10+
return fileInLayoutName(content);
11+
}).filter(Boolean);
12+
return Array.from(new Set(names));
13+
}
14+
15+
function fileInLayoutName(content) {
16+
let ast = JSParser.parse(content);
17+
let layoutName;
18+
jsTraverse(ast, {
19+
ClassProperty: function(path) {
20+
if (path.node.key.name === 'layoutName') {
21+
layoutName = path.node.key.value.value;
22+
path.stop();
23+
}
24+
},
25+
Property: function(path) {
26+
if (path.node.key.name === 'layoutName') {
27+
layoutName = path.node.value.value;
28+
path.stop();
29+
}
30+
},
31+
});
32+
return layoutName;
33+
}
34+
35+
function getPartialTemplates(files) {
36+
console.info(`Checking if any component templates are used as partials`);
37+
let names = files.reduce((acc, file) => {
38+
let content = readFileSync(file, 'utf8');
39+
let partials = filesInPartials(content);
40+
return partials.length ? acc.concat(partials) : acc;
41+
}, [])
42+
.filter(Boolean)
43+
.filter(path => path.startsWith('components/'))
44+
return Array.from(new Set(names));
45+
}
46+
47+
function filesInPartials(content) {
48+
let partials = [];
49+
const ast = parse(content);
50+
traverse(ast, {
51+
MustacheStatement(node) {
52+
if (node.path.original === 'partial') {
53+
partials.push(node.params[0].value);
54+
}
55+
},
56+
});
57+
return partials;
58+
}
59+
60+
module.exports = {
61+
getLayoutNameTemplates,
62+
getPartialTemplates
63+
}

package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,17 @@
1818
"power-assert": "^1.3.1"
1919
},
2020
"dependencies": {
21+
"@babel/core": "^7.7.5",
22+
"@babel/parser": "^7.7.5",
23+
"@babel/plugin-proposal-class-properties": "^7.7.4",
24+
"@babel/plugin-proposal-decorators": "^7.7.4",
25+
"@babel/plugin-proposal-object-rest-spread": "^7.7.4",
26+
"@babel/traverse": "^7.7.4",
27+
"ember-template-recast": "^3.3.0",
2128
"fs-extra": "^7.0.1",
2229
"glob": "^7.1.4",
23-
"nopt": "^4.0.1"
30+
"nopt": "^4.0.1",
31+
"remove-empty-directories": "^0.0.1"
2432
},
2533
"repository": {
2634
"type": "git",

test/fixtures/classic-app/input.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ module.exports = {
88
nested2: {
99
'super-nested-component.js': '// nested1/nested2/super-nested-component.js',
1010
}
11+
},
12+
'layout-name': {
13+
'has-layout-name.js': `// top-level-component.js
14+
Component.extend({ layoutName: "components/layout-name/layout-name-template" });`,
1115
}
1216
},
1317
templates: {
@@ -19,6 +23,14 @@ module.exports = {
1923
nested2: {
2024
'super-nested-component.hbs': '{{!-- nested1/nested2/super-nested-component.hbs --}}'
2125
}
26+
},
27+
'layout-name': {
28+
'layout-name-template.hbs': '{{!-- layout-name-template.hbs --}}',
29+
},
30+
'partials': {
31+
'partials-template.hbs': '{{!-- partials-template.hbs --}}',
32+
'with-partial.hbs': `{{!-- with-partial.hbs --}}
33+
{{partial 'components/partials/partials-template'}}`,
2234
}
2335
}
2436
}

test/fixtures/classic-app/output.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,26 @@ module.exports = {
1111
'super-nested-component.hbs': '{{!-- nested1/nested2/super-nested-component.hbs --}}',
1212
'super-nested-component.js': '// nested1/nested2/super-nested-component.js'
1313
}
14+
},
15+
'layout-name': {
16+
'has-layout-name.js': `// top-level-component.js
17+
Component.extend({ layoutName: "components/layout-name/layout-name-template" });`,
18+
},
19+
'partials': {
20+
'with-partial.hbs': `{{!-- with-partial.hbs --}}
21+
{{partial 'components/partials/partials-template'}}`,
1422
}
1523
},
1624
templates: {
1725
'application.hbs': '{{outlet}}',
26+
components: {
27+
'layout-name': {
28+
'layout-name-template.hbs': '{{!-- layout-name-template.hbs --}}',
29+
},
30+
'partials': {
31+
'partials-template.hbs': '{{!-- partials-template.hbs --}}'
32+
}
33+
}
1834
}
1935
},
2036
};

0 commit comments

Comments
 (0)