Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix importing of template-only components in V2 addons #1126

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 85 additions & 5 deletions packages/addon-dev/src/rollup-hbs-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,64 @@
import { createFilter } from '@rollup/pluginutils';
import type { Plugin } from 'rollup';
import { readFileSync } from 'fs';
import { hbsToJS } from '@embroider/shared-internals';
import path from 'path';
import { pathExists } from 'fs-extra';

import type { Plugin } from 'rollup';

export default function rollupHbsPlugin(): Plugin {
const filter = createFilter('**/*.hbs');

return {
name: 'rollup-hbs-plugin',
async resolveId(source: string, importer: string | undefined, options) {
const resolution = await this.resolve(source, importer, {
let resolution = await this.resolve(source, importer, {
skipSelf: true,
...options,
});

const id = resolution?.id;
let id = resolution?.id;

if (!filter(id)) return null;
// if id is undefined, it's possible we're importing a file that that rollup
// doesn't natively support such as a template-only component that the author
// doesn't want to be available on the globals resolver
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is misleading. The globals resolver isn't really relevant to this. You control global resolving by deciding whether or not to put a module into appReexports. But this code runs for your modules whether or not they're in appReexports.

//
// e.g.:
// import { default as Button } from './button';
//
// where button.hbs is the sole "button" file.
//
// if someone where to specify the `.hbs` extension themselves as in:
//
// import { default as Button } from './button';
//
// then this whole block will be skipped
if (importer && !id) {
// We can't just emit the js side of the template-only component (export default templateOnly())
// because we can't tell rollup where to put the file -- all emitted files are
// not-on-disk-area-used-at-build-time -- emitted files are for the build output
//
// https://github.com/rollup/rollup/blob/master/docs/05-plugin-development.md
//
// So, to deal with this, we need to ensure there _is no corresponding js/ts file_
// for the imported hbs, and then, add in some meta so that the load hook can
// generate the setComponentTemplate + templateOnly() combo
let fileName = path.join(path.dirname(importer), source);
let hbsExists = await pathExists(fileName + '.hbs');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is assuming that source will always be a relative path, but that's not always true. For example, if somebody tries to import from an NPM package name that isn't installed, they will end up here, and we don't want to be appending package names to the importer path.

This really should be another call to this.resolve()


if (!hbsExists) return null;

resolution = await this.resolve(source + '.hbs', importer, {
skipSelf: true,
...options,
});

id = resolution?.id;
}

if (!filter(id) || !id) return null;

let isTO = await isTemplateOnly(id);

// This creates an `*.hbs.js` that we will populate in `load()` hook.
return {
Expand All @@ -25,18 +67,28 @@ export default function rollupHbsPlugin(): Plugin {
meta: {
'rollup-hbs-plugin': {
originalId: id,
isTemplateOnly: isTO,
},
},
};
},
load(id: string) {
const meta = this.getModuleInfo(id)?.meta;
const originalId = meta?.['rollup-hbs-plugin']?.originalId;
const pluginMeta = meta?.['rollup-hbs-plugin'];
const originalId = pluginMeta?.originalId;
const isTemplateOnly = pluginMeta?.isTemplateOnly;

if (!originalId) {
return;
}

if (isTemplateOnly) {
let code = getTemplateOnly(originalId);

return { code };
}

// Co-located js + hbs
let input = readFileSync(originalId, 'utf8');
let code = hbsToJS(input);
return {
Expand All @@ -45,3 +97,31 @@ export default function rollupHbsPlugin(): Plugin {
},
};
}

const backtick = '`';

async function isTemplateOnly(hbsPath: string) {
let jsPath = hbsPath.replace(/\.hbs$/, '.js');
let tsPath = hbsPath.replace(/\.hbs$/, '.ts');

let [hasJs, hasTs] = await Promise.all([
pathExists(jsPath),
pathExists(tsPath),
]);

let hasClass = hasJs || hasTs;

return !hasClass;
}

function getTemplateOnly(hbsPath: string) {
let input = readFileSync(hbsPath, 'utf8');
let code =
`import { hbs } from 'ember-cli-htmlbars';\n` +
`import templateOnly from '@ember/component/template-only';\n` +
`import { setComponentTemplate } from '@ember/component';\n` +
`export default setComponentTemplate(\n` +
`hbs${backtick}${input}${backtick}, templateOnly());`;

return code;
}
15 changes: 9 additions & 6 deletions packages/addon-dev/src/rollup-public-entrypoints.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import walkSync from 'walk-sync';
import type { Plugin } from 'rollup';
import { join } from 'path';

import type { Plugin } from 'rollup';

function normalizeFileExt(fileName: string) {
return fileName.replace(/\.ts|\.gts|\.gjs$/, '.js');
return fileName.replace(/\.ts|\.hbs|\.gts|\.gjs$/, '.js');
}

export default function publicEntrypoints(args: {
Expand All @@ -12,10 +13,12 @@ export default function publicEntrypoints(args: {
}): Plugin {
return {
name: 'addon-modules',
buildStart() {
for (let name of walkSync(args.srcDir, {
globs: args.include,
})) {
async buildStart() {
let matches = walkSync(args.srcDir, {
globs: [...args.include],
});

for (let name of matches) {
NullVoxPopuli marked this conversation as resolved.
Show resolved Hide resolved
this.emitFile({
type: 'chunk',
id: join(args.srcDir, name),
Expand Down
22 changes: 14 additions & 8 deletions tests/scenarios/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,37 +21,43 @@
},
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.17.5",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-proposal-decorators": "^7.17.2",
"@babel/preset-env": "^7.16.11",
"@ember/string": "^1.0.0",
"@embroider/macros": "1.6.0",
"@embroider/addon-shim": "1.6.0",
"@embroider/router": "1.6.0",
"@embroider/util": "1.6.0",
"@rollup/plugin-babel": "^5.3.1",
"bootstrap": "^4.3.1",
"broccoli-funnel": "^3.0.5",
"broccoli-merge-trees": "^3.0.2",
"broccoli-persistent-filter": "^3.1.2",
"ember-bootstrap": "^5.0.0",
"ember-cli": "~3.28.0",
"ember-cli-3.16": "npm:ember-cli@~3.16.0",
"ember-cli-3.24": "npm:ember-cli@~3.24.0",
"ember-cli": "~3.28.0",
"ember-cli-beta": "npm:ember-cli@beta",
"ember-cli-htmlbars-inline-precompile": "^2.1.0",
"ember-cli-fastboot": "^3.2.0",
"ember-cli-htmlbars-3": "npm:ember-cli-htmlbars@3",
"ember-cli-htmlbars-inline-precompile": "^2.1.0",
"ember-cli-latest": "npm:ember-cli@latest",
"ember-cli-fastboot": "^3.2.0",
"ember-composable-helpers": "^4.4.1",
"ember-data": "~3.28.0",
"ember-data-3.16": "npm:ember-data@~3.16.0",
"ember-data-3.24": "npm:ember-data@~3.24.0",
"ember-data": "~3.28.0",
"ember-data-latest": "npm:ember-data@latest",
"ember-engines": "^0.8.17",
"ember-inline-svg": "^0.2.1",
"ember-source-latest": "npm:ember-source@latest",
"ember-source-beta": "npm:ember-source@beta",
"ember-source": "~3.28.0",
"ember-source-3.16": "npm:ember-source@~3.16.0",
"ember-source-3.24": "npm:ember-source@~3.24.0",
"ember-source": "~3.28.0",
"ember-truth-helpers": "^3.0.0"
"ember-source-beta": "npm:ember-source@beta",
"ember-source-latest": "npm:ember-source@latest",
"ember-truth-helpers": "^3.0.0",
"rollup": "^2.69.1"
},
"volta": {
"node": "14.16.1",
Expand Down
Loading