diff --git a/.gitignore b/.gitignore index 5ebab51a2..34ba5d5a9 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,7 @@ tmp/ # scenario tester debugging output tests/scenarios/output/ + +# Sys files +.DS_Store +*.swp diff --git a/packages/compat/src/synthesize-template-only-components.ts b/packages/compat/src/synthesize-template-only-components.ts index bfd0ed868..41f750eff 100644 --- a/packages/compat/src/synthesize-template-only-components.ts +++ b/packages/compat/src/synthesize-template-only-components.ts @@ -7,24 +7,26 @@ import { remove, outputFileSync, pathExistsSync } from 'fs-extra'; const source = `import templateOnlyComponent from '@ember/component/template-only'; export default templateOnlyComponent();`; -const templateExtension = '.hbs'; - const jsExtensions = ['.js', '.ts', '.mjs', '.mts']; export default class SynthesizeTemplateOnlyComponents extends Plugin { private emitted = new Set() as Set; + private allowedPaths: string[]; + private templateExtensions: string[]; - constructor(tree: Node, private allowedPaths: string[]) { + constructor(tree: Node, params: { allowedPaths: string[]; templateExtensions: string[] }) { super([tree], { - annotation: `synthesize-template-only-components:${allowedPaths.join(':')}`, + annotation: `synthesize-template-only-components:${params.allowedPaths.join(':')}`, persistentOutput: true, needsCache: false, }); + this.allowedPaths = params.allowedPaths; + this.templateExtensions = params.templateExtensions; } async build() { for (let dir of this.allowedPaths) { - let { needed, seen } = crawl(join(this.inputPaths[0], dir)); + let { needed, seen } = this.crawl(join(this.inputPaths[0], dir)); for (let file of needed) { let fullName = join(this.outputPath, dir, file); if (seen.has(file)) { @@ -53,22 +55,24 @@ export default class SynthesizeTemplateOnlyComponents extends Plugin { this.emitted.delete(filename); } } -} -function crawl(dir: string) { - const needed = new Set(); - const seen = new Set(); - if (pathExistsSync(dir)) { - for (let file of walkSync(dir, { directories: false })) { - if (file.endsWith(templateExtension)) { - needed.add(file.slice(0, -1 * templateExtension.length)); - } else { - const jsExtension = jsExtensions.find(ext => file.endsWith(ext)); - if (jsExtension) { - seen.add(file.slice(0, -1 * jsExtension.length)); + private crawl(dir: string) { + const needed = new Set(); + const seen = new Set(); + if (pathExistsSync(dir)) { + for (let file of walkSync(dir, { directories: false })) { + for (const templateExtension of this.templateExtensions) { + if (file.endsWith(templateExtension)) { + needed.add(file.slice(0, -1 * templateExtension.length)); + } else { + const jsExtension = jsExtensions.find(ext => file.endsWith(ext)); + if (jsExtension) { + seen.add(file.slice(0, -1 * jsExtension.length)); + } + } } } } + return { needed, seen }; } - return { needed, seen }; } diff --git a/packages/compat/src/v1-addon.ts b/packages/compat/src/v1-addon.ts index c385133a8..37c31f3d0 100644 --- a/packages/compat/src/v1-addon.ts +++ b/packages/compat/src/v1-addon.ts @@ -724,7 +724,13 @@ export default class V1Addon { if (!tree) { return; } - let templateOnlyComponents: Node = new SynthesizeTemplateOnlyComponents(tree, ['components']); + let templateOnlyComponents: Node = new SynthesizeTemplateOnlyComponents(tree, { + allowedPaths: ['components'], + + // if an addon has custom AST transforms, stage1 can rewrite .hbs to + // .hbs.js + templateExtensions: ['.hbs', '.hbs.js'], + }); if (!this.addonOptions.staticAddonTrees) { let filenames: string[] = []; let templateOnlyComponentNames: string[] = []; diff --git a/packages/compat/src/v1-app.ts b/packages/compat/src/v1-app.ts index eb6e86537..7458ee4ed 100644 --- a/packages/compat/src/v1-app.ts +++ b/packages/compat/src/v1-app.ts @@ -657,7 +657,9 @@ export default class V1App { let trees: Node[] = []; trees.push(appTree); - trees.push(new SynthesizeTemplateOnlyComponents(appTree, ['components'])); + trees.push( + new SynthesizeTemplateOnlyComponents(appTree, { allowedPaths: ['components'], templateExtensions: ['.hbs'] }) + ); trees.push(configReplaced); if (testsTree) { diff --git a/tests/scenarios/stage1-test.ts b/tests/scenarios/stage1-test.ts index 47144b99b..83cd9ac03 100644 --- a/tests/scenarios/stage1-test.ts +++ b/tests/scenarios/stage1-test.ts @@ -6,6 +6,8 @@ import { loadFromFixtureData } from './helpers'; import { dummyAppScenarios, baseAddon, appScenarios } from './scenarios'; import { PreparedApp } from 'scenario-tester'; import QUnit from 'qunit'; +import { expectFilesAt, ExpectFile } from '@embroider/test-support'; + const { module: Qmodule, test } = QUnit; appScenarios @@ -128,6 +130,14 @@ appScenarios merge(addon.files, { addon: { components: { + 'template-only.hbs': `
`, + 'colocated.js': ` + import Component from '@glimmer/component'; + export default class extends Component { + identifier = "i-am-colocated"; + } + `, + 'colocated.hbs': `
`, 'has-inline-template.js': ` import Component from '@ember/component'; import hbs from 'htmlbars-inline-precompile'; @@ -160,23 +170,45 @@ appScenarios workspaceDir = fs.readFileSync(join(app.dir, 'dist', '.stage1-output'), 'utf8'); }); - test('component with inline template', function (assert) { - let fileContents = fs.readFileSync( - join(workspaceDir, 'node_modules/my-addon/components/has-inline-template.js') - ); - assert.ok( - fileContents.includes('hbs`
Inline
'), + let expectFile: ExpectFile; + hooks.beforeEach(assert => { + expectFile = expectFilesAt(workspaceDir, { qunit: assert }); + }); + + test('component with inline template', function () { + let file = expectFile('node_modules/my-addon/components/has-inline-template.js'); + + file.matches( + 'hbs`
Inline
', 'tagged template is still hbs and custom transforms have run' ); - assert.ok( - /hbs\(["']
Extra<\/div>["']\)/.test(fileContents.toString()), + + file.matches( + /hbs\(["']
Extra<\/div>["']\)/, 'called template is still hbs and custom transforms have run' ); - assert.ok( - /{{macroDependencySatisfies ['"]ember-source['"] ['"]>3['"]}}<\/span>/.test(fileContents.toString()), + + file.matches( + /{{macroDependencySatisfies ['"]ember-source['"] ['"]>3['"]}}<\/span>/, 'template macros have not run' ); }); + + test('component with colocated template', function () { + // co-located pairs are left alone in stage1 because we deal with them + // in stage3 + expectFile('node_modules/my-addon/components/colocated.js').matches('i-am-colocated'); + expectFile('node_modules/my-addon/components/colocated.hbs.js').exists(); + }); + + test('template-only component', function () { + expectFile('node_modules/my-addon/components/template-only.js').matches( + 'export default templateOnlyComponent()' + ); + expectFile('node_modules/my-addon/components/template-only.hbs.js').matches( + 'export default precompileTemplate' + ); + }); }); });